5 * API functions for installing modules and themes.
8 use Drupal\Component\Utility\Unicode;
9 use Symfony\Component\HttpFoundation\RedirectResponse;
10 use Drupal\Component\Utility\Crypt;
11 use Drupal\Component\Utility\OpCodeCache;
12 use Drupal\Component\Utility\UrlHelper;
13 use Drupal\Core\Extension\ExtensionDiscovery;
14 use Drupal\Core\Site\Settings;
17 * Requirement severity -- Informational message only.
19 const REQUIREMENT_INFO = -1;
22 * Requirement severity -- Requirement successfully met.
24 const REQUIREMENT_OK = 0;
27 * Requirement severity -- Warning condition; proceed but flag warning.
29 const REQUIREMENT_WARNING = 1;
32 * Requirement severity -- Error condition; abort installation.
34 const REQUIREMENT_ERROR = 2;
37 * File permission check -- File exists.
42 * File permission check -- File is readable.
44 const FILE_READABLE = 2;
47 * File permission check -- File is writable.
49 const FILE_WRITABLE = 4;
52 * File permission check -- File is executable.
54 const FILE_EXECUTABLE = 8;
57 * File permission check -- File does not exist.
59 const FILE_NOT_EXIST = 16;
62 * File permission check -- File is not readable.
64 const FILE_NOT_READABLE = 32;
67 * File permission check -- File is not writable.
69 const FILE_NOT_WRITABLE = 64;
72 * File permission check -- File is not executable.
74 const FILE_NOT_EXECUTABLE = 128;
77 * Loads .install files for installed modules to initialize the update system.
79 function drupal_load_updates() {
80 foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) {
81 if ($schema_version > -1) {
82 module_load_install($module);
88 * Loads the installation profile, extracting its defined distribution name.
91 * The distribution name defined in the profile's .info.yml file. Defaults to
92 * "Drupal" if none is explicitly provided by the installation profile.
94 * @see install_profile_info()
96 function drupal_install_profile_distribution_name() {
97 // During installation, the profile information is stored in the global
98 // installation state (it might not be saved anywhere yet).
100 if (drupal_installation_attempted()) {
101 global $install_state;
102 if (isset($install_state['profile_info'])) {
103 $info = $install_state['profile_info'];
106 // At all other times, we load the profile via standard methods.
108 $profile = drupal_get_profile();
109 $info = system_get_info('module', $profile);
111 return isset($info['distribution']['name']) ? $info['distribution']['name'] : 'Drupal';
115 * Loads the installation profile, extracting its defined version.
117 * @return string Distribution version defined in the profile's .info.yml file.
118 * Defaults to \Drupal::VERSION if no version is explicitly provided
119 * by the installation profile.
121 * @see install_profile_info()
123 function drupal_install_profile_distribution_version() {
124 // During installation, the profile information is stored in the global
125 // installation state (it might not be saved anywhere yet).
126 if (drupal_installation_attempted()) {
127 global $install_state;
128 return isset($install_state['profile_info']['version']) ? $install_state['profile_info']['version'] : \Drupal::VERSION;
130 // At all other times, we load the profile via standard methods.
132 $profile = drupal_get_profile();
133 $info = system_get_info('module', $profile);
134 return $info['version'];
139 * Detects all supported databases that are compiled into PHP.
142 * An array of database types compiled into PHP.
144 function drupal_detect_database_types() {
145 $databases = drupal_get_database_types();
147 foreach ($databases as $driver => $installer) {
148 $databases[$driver] = $installer->name();
155 * Returns all supported database driver installer objects.
157 * @return \Drupal\Core\Database\Install\Tasks[]
158 * An array of available database driver installer objects.
160 function drupal_get_database_types() {
164 // The internal database driver name is any valid PHP identifier.
165 $mask = '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '$/';
166 $files = file_scan_directory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, ['recurse' => FALSE]);
167 if (is_dir(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database')) {
168 $files += file_scan_directory(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database/', $mask, ['recurse' => FALSE]);
170 foreach ($files as $file) {
171 if (file_exists($file->uri . '/Install/Tasks.php')) {
172 $drivers[$file->filename] = $file->uri;
175 foreach ($drivers as $driver => $file) {
176 $installer = db_installer_object($driver);
177 if ($installer->installable()) {
178 $databases[$driver] = $installer;
182 // Usability: unconditionally put the MySQL driver on top.
183 if (isset($databases['mysql'])) {
184 $mysql_database = $databases['mysql'];
185 unset($databases['mysql']);
186 $databases = ['mysql' => $mysql_database] + $databases;
193 * Replaces values in settings.php with values in the submitted array.
195 * This function replaces values in place if possible, even for
196 * multidimensional arrays. This way the old settings do not linger,
197 * overridden and also the doxygen on a value remains where it should be.
200 * An array of settings that need to be updated. Multidimensional arrays
201 * are dumped up to a stdClass object. The object can have value, required
202 * and comment properties.
204 * $settings['config_directories'] = array(
205 * CONFIG_SYNC_DIRECTORY => (object) array(
206 * 'value' => 'config_hash/sync',
207 * 'required' => TRUE,
213 * $config_directories['sync'] = 'config_hash/sync'
216 function drupal_rewrite_settings($settings = [], $settings_file = NULL) {
217 if (!isset($settings_file)) {
218 $settings_file = \Drupal::service('site.path') . '/settings.php';
220 // Build list of setting names and insert the values into the global namespace.
221 $variable_names = [];
222 $settings_settings = [];
223 foreach ($settings as $setting => $data) {
224 if ($setting != 'settings') {
225 _drupal_rewrite_settings_global($GLOBALS[$setting], $data);
228 _drupal_rewrite_settings_global($settings_settings, $data);
230 $variable_names['$' . $setting] = $setting;
232 $contents = file_get_contents($settings_file);
233 if ($contents !== FALSE) {
234 // Initialize the contents for the settings.php file if it is empty.
235 if (trim($contents) === '') {
236 $contents = "<?php\n";
238 // Step through each token in settings.php and replace any variables that
239 // are in the passed-in array.
242 foreach (token_get_all($contents) as $token) {
243 if (is_array($token)) {
244 list($type, $value) = $token;
250 // Do not operate on whitespace.
251 if (!in_array($type, [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
254 if ($type === T_VARIABLE && isset($variable_names[$value])) {
255 // This will be necessary to unset the dumped variable.
256 $parent = &$settings;
257 // This is the current index in parent.
258 $index = $variable_names[$value];
259 // This will be necessary for descending into the array.
260 $current = &$parent[$index];
261 $state = 'candidate_left';
264 case 'candidate_left':
266 $state = 'array_index';
269 $state = 'candidate_right';
273 if (_drupal_rewrite_settings_is_array_index($type, $value)) {
274 $index = trim($value, '\'"');
275 $state = 'right_bracket';
278 // $a[foo()] or $a[$bar] or something like that.
279 throw new Exception('invalid array index');
282 case 'right_bracket':
284 if (isset($current[$index])) {
285 // If the new settings has this index, descend into it.
287 $current = &$parent[$index];
288 $state = 'candidate_left';
291 // Otherwise, jump back to the default state.
292 $state = 'wait_for_semicolon';
297 throw new Exception('] expected');
300 case 'candidate_right':
301 if (_drupal_rewrite_settings_is_simple($type, $value)) {
302 $value = _drupal_rewrite_settings_dump_one($current);
303 // Unsetting $current would not affect $settings at all.
304 unset($parent[$index]);
305 // Skip the semicolon because _drupal_rewrite_settings_dump_one() added one.
306 $state = 'semicolon_skip';
309 $state = 'wait_for_semicolon';
312 case 'wait_for_semicolon':
317 case 'semicolon_skip':
323 // If the expression was $a = 1 + 2; then we replaced 1 and
324 // the + is unexpected.
325 throw new Exception('Unexpected token after replacing value.');
332 foreach ($settings as $name => $setting) {
333 $buffer .= _drupal_rewrite_settings_dump($setting, '$' . $name);
336 // Write the new settings file.
337 if (file_put_contents($settings_file, $buffer) === FALSE) {
338 throw new Exception(t('Failed to modify %settings. Verify the file permissions.', ['%settings' => $settings_file]));
341 // In case any $settings variables were written, import them into the
342 // Settings singleton.
343 if (!empty($settings_settings)) {
344 $old_settings = Settings::getAll();
345 new Settings($settings_settings + $old_settings);
347 // The existing settings.php file might have been included already. In
348 // case an opcode cache is enabled, the rewritten contents of the file
349 // will not be reflected in this process. Ensure to invalidate the file
350 // in case an opcode cache is enabled.
351 OpCodeCache::invalidate(DRUPAL_ROOT . '/' . $settings_file);
355 throw new Exception(t('Failed to open %settings. Verify the file permissions.', ['%settings' => $settings_file]));
360 * Helper for drupal_rewrite_settings().
362 * Checks whether this token represents a scalar or NULL.
366 * @param string $value
367 * The value of the token.
370 * TRUE if this token represents a scalar or NULL.
374 function _drupal_rewrite_settings_is_simple($type, $value) {
375 $is_integer = $type == T_LNUMBER;
376 $is_float = $type == T_DNUMBER;
377 $is_string = $type == T_CONSTANT_ENCAPSED_STRING;
378 $is_boolean_or_null = $type == T_STRING && in_array(strtoupper($value), ['TRUE', 'FALSE', 'NULL']);
379 return $is_integer || $is_float || $is_string || $is_boolean_or_null;
384 * Helper for drupal_rewrite_settings().
386 * Checks whether this token represents a valid array index: a number or a
393 * TRUE if this token represents a number or a string.
397 function _drupal_rewrite_settings_is_array_index($type) {
398 $is_integer = $type == T_LNUMBER;
399 $is_float = $type == T_DNUMBER;
400 $is_string = $type == T_CONSTANT_ENCAPSED_STRING;
401 return $is_integer || $is_float || $is_string;
405 * Helper for drupal_rewrite_settings().
407 * Makes the new settings global.
409 * @param array|null $ref
410 * A reference to a nested index in $GLOBALS.
411 * @param array|object $variable
412 * The nested value of the setting being copied.
414 function _drupal_rewrite_settings_global(&$ref, $variable) {
415 if (is_object($variable)) {
416 $ref = $variable->value;
419 foreach ($variable as $k => $v) {
420 _drupal_rewrite_settings_global($ref[$k], $v);
426 * Helper for drupal_rewrite_settings().
428 * Dump the relevant value properties.
430 * @param array|object $variable
431 * The container for variable values.
432 * @param string $variable_name
435 * A string containing valid PHP code of the variable suitable for placing
438 function _drupal_rewrite_settings_dump($variable, $variable_name) {
440 if (is_object($variable)) {
441 if (!empty($variable->required)) {
442 $return .= _drupal_rewrite_settings_dump_one($variable, "$variable_name = ", "\n");
446 foreach ($variable as $k => $v) {
447 $return .= _drupal_rewrite_settings_dump($v, $variable_name . "['" . $k . "']");
455 * Helper for drupal_rewrite_settings().
457 * Dump the value of a value property and adds the comment if it exists.
459 * @param object $variable
460 * A stdClass object with at least a value property.
461 * @param string $prefix
462 * A string to prepend to the variable's value.
463 * @param string $suffix
464 * A string to append to the variable's value.
466 * A string containing valid PHP code of the variable suitable for placing
469 function _drupal_rewrite_settings_dump_one(\stdClass $variable, $prefix = '', $suffix = '') {
470 $return = $prefix . var_export($variable->value, TRUE) . ';';
471 if (!empty($variable->comment)) {
472 $return .= ' // ' . $variable->comment;
479 * Creates the config directory and ensures it is operational.
481 * @see install_settings_form_submit()
482 * @see update_prepare_d8_bootstrap()
484 function drupal_install_config_directories() {
485 global $config_directories;
487 // Add a randomized config directory name to settings.php, unless it was
488 // manually defined in the existing already.
489 if (empty($config_directories[CONFIG_SYNC_DIRECTORY])) {
490 $config_directories[CONFIG_SYNC_DIRECTORY] = \Drupal::service('site.path') . '/files/config_' . Crypt::randomBytesBase64(55) . '/sync';
491 $settings['config_directories'][CONFIG_SYNC_DIRECTORY] = (object) [
492 'value' => $config_directories[CONFIG_SYNC_DIRECTORY],
495 // Rewrite settings.php, which also sets the value as global variable.
496 drupal_rewrite_settings($settings);
499 // This should never fail, since if the config directory was specified in
500 // settings.php it will have already been created and verified earlier, and
501 // if it wasn't specified in settings.php, it is created here inside the
502 // public files directory, which has already been verified to be writable
503 // itself. But if it somehow fails anyway, the installation cannot proceed.
504 // Bail out using a similar error message as in system_requirements().
505 if (!file_prepare_directory($config_directories[CONFIG_SYNC_DIRECTORY], FILE_CREATE_DIRECTORY)
506 && !file_exists($config_directories[CONFIG_SYNC_DIRECTORY])) {
507 throw new Exception(t('The directory %directory could not be created. To proceed with the installation, either create the directory or ensure that the installer has the permissions to create it automatically. For more information, see the <a href=":handbook_url">online handbook</a>.', [
508 '%directory' => config_get_config_directory(CONFIG_SYNC_DIRECTORY),
509 ':handbook_url' => 'https://www.drupal.org/server-permissions',
512 elseif (is_writable($config_directories[CONFIG_SYNC_DIRECTORY])) {
513 // Put a README.txt into the sync config directory. This is required so that
514 // they can later be added to git. Since this directory is auto-created, we
515 // have to write out the README rather than just adding it to the drupal core
517 $text = 'This directory contains configuration to be imported into your Drupal site. To make this configuration active, visit admin/config/development/configuration/sync.' . ' For information about deploying configuration between servers, see https://www.drupal.org/documentation/administer/config';
518 file_put_contents(config_get_config_directory(CONFIG_SYNC_DIRECTORY) . '/README.txt', $text);
523 * Ensures that the config directory exists and is writable, or can be made so.
525 * @param string $type
526 * Type of config directory to return. Drupal core provides 'sync'.
529 * TRUE if the config directory exists and is writable.
531 * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use
532 * config_get_config_directory() and file_prepare_directory() instead.
534 function install_ensure_config_directory($type) {
535 // The config directory must be defined in settings.php.
536 global $config_directories;
537 if (!isset($config_directories[$type])) {
540 // The logic here is similar to that used by system_requirements() for other
541 // directories that the installer creates.
543 $config_directory = config_get_config_directory($type);
544 return file_prepare_directory($config_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
549 * Verifies that all dependencies are met for a given installation profile.
551 * @param $install_state
552 * An array of information about the current installation state.
555 * The list of modules to install.
557 function drupal_verify_profile($install_state) {
558 $profile = $install_state['parameters']['profile'];
559 $info = $install_state['profile_info'];
561 // Get the list of available modules for the selected installation profile.
562 $listing = new ExtensionDiscovery(\Drupal::root());
563 $present_modules = [];
564 foreach ($listing->scan('module') as $present_module) {
565 $present_modules[] = $present_module->getName();
568 // The installation profile is also a module, which needs to be installed
569 // after all the other dependencies have been installed.
570 $present_modules[] = $profile;
572 // Verify that all of the profile's required modules are present.
573 $missing_modules = array_diff($info['dependencies'], $present_modules);
577 if ($missing_modules) {
579 '#theme' => 'item_list',
580 '#context' => ['list_style' => 'comma-list'],
583 foreach ($missing_modules as $module) {
584 $build['#items'][] = ['#markup' => '<span class="admin-missing">' . Unicode::ucfirst($module) . '</span>'];
587 $modules_list = \Drupal::service('renderer')->renderPlain($build);
588 $requirements['required_modules'] = [
589 'title' => t('Required modules'),
590 'value' => t('Required modules not found.'),
591 'severity' => REQUIREMENT_ERROR,
592 'description' => t('The following modules are required but were not found. Move them into the appropriate modules subdirectory, such as <em>/modules</em>. Missing modules: @modules', ['@modules' => $modules_list]),
595 return $requirements;
599 * Installs the system module.
601 * Separated from the installation of other modules so core system
602 * functions can be made available while other modules are installed.
604 * @param array $install_state
605 * An array of information about the current installation state. This is used
606 * to set the default language.
608 function drupal_install_system($install_state) {
609 // Remove the service provider of the early installer.
610 unset($GLOBALS['conf']['container_service_providers']['InstallerServiceProvider']);
612 $request = \Drupal::request();
613 // Reboot into a full production environment to continue the installation.
614 /** @var \Drupal\Core\Installer\InstallerKernel $kernel */
615 $kernel = \Drupal::service('kernel');
617 // Have installer rebuild from the disk, rather then building from scratch.
618 $kernel->rebuildContainer(FALSE);
619 $kernel->prepareLegacyRequest($request);
621 // Install base system configuration.
622 \Drupal::service('config.installer')->installDefaultConfig('core', 'core');
624 // Store the installation profile in configuration to populate the
625 // 'install_profile' container parameter.
626 \Drupal::configFactory()->getEditable('core.extension')
627 ->set('profile', $install_state['parameters']['profile'])
630 // Install System module and rebuild the newly available routes.
631 $kernel->getContainer()->get('module_installer')->install(['system'], FALSE);
632 \Drupal::service('router.builder')->rebuild();
634 // Ensure default language is saved.
635 if (isset($install_state['parameters']['langcode'])) {
636 \Drupal::configFactory()->getEditable('system.site')
637 ->set('langcode', (string) $install_state['parameters']['langcode'])
638 ->set('default_langcode', (string) $install_state['parameters']['langcode'])
644 * Verifies the state of the specified file.
647 * The file to check for.
649 * An optional bitmask created from various FILE_* constants.
651 * The type of file. Can be file (default), dir, or link.
654 * TRUE on success or FALSE on failure. A message is set for the latter.
656 function drupal_verify_install_file($file, $mask = NULL, $type = 'file') {
658 // Check for files that shouldn't be there.
659 if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) {
662 // Verify that the file is the type of file it is supposed to be.
663 if (isset($type) && file_exists($file)) {
664 $check = 'is_' . $type;
665 if (!function_exists($check) || !$check($file)) {
670 // Verify file permissions.
672 $masks = [FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
673 foreach ($masks as $current_mask) {
674 if ($mask & $current_mask) {
675 switch ($current_mask) {
677 if (!file_exists($file)) {
678 if ($type == 'dir') {
679 drupal_install_mkdir($file, $mask);
681 if (!file_exists($file)) {
687 if (!is_readable($file) && !drupal_install_fix_file($file, $mask)) {
692 if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) {
696 case FILE_EXECUTABLE:
697 if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) {
701 case FILE_NOT_READABLE:
702 if (is_readable($file) && !drupal_install_fix_file($file, $mask)) {
706 case FILE_NOT_WRITABLE:
707 if (is_writable($file) && !drupal_install_fix_file($file, $mask)) {
711 case FILE_NOT_EXECUTABLE:
712 if (is_executable($file) && !drupal_install_fix_file($file, $mask)) {
724 * Creates a directory with the specified permissions.
727 * The name of the directory to create;
729 * The permissions of the directory to create.
731 * (optional) Whether to output messages. Defaults to TRUE.
734 * TRUE/FALSE whether or not the directory was successfully created.
736 function drupal_install_mkdir($file, $mask, $message = TRUE) {
738 $masks = [FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
739 foreach ($masks as $m) {
748 case FILE_EXECUTABLE:
755 if (@drupal_mkdir($file, $mod)) {
764 * Attempts to fix file permissions.
766 * The general approach here is that, because we do not know the security
767 * setup of the webserver, we apply our permission changes to all three
768 * digits of the file permission (i.e. user, group and all).
770 * To ensure that the values behave as expected (and numbers don't carry
771 * from one digit to the next) we do the calculation on the octal value
772 * using bitwise operations. This lets us remove, for example, 0222 from
773 * 0700 and get the correct value of 0500.
776 * The name of the file with permissions to fix.
778 * The desired permissions for the file.
780 * (optional) Whether to output messages. Defaults to TRUE.
783 * TRUE/FALSE whether or not we were able to fix the file's permissions.
785 function drupal_install_fix_file($file, $mask, $message = TRUE) {
786 // If $file does not exist, fileperms() issues a PHP warning.
787 if (!file_exists($file)) {
791 $mod = fileperms($file) & 0777;
792 $masks = [FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
794 // FILE_READABLE, FILE_WRITABLE, and FILE_EXECUTABLE permission strings
795 // can theoretically be 0400, 0200, and 0100 respectively, but to be safe
796 // we set all three access types in case the administrator intends to
797 // change the owner of settings.php after installation.
798 foreach ($masks as $m) {
802 if (!is_readable($file)) {
807 if (!is_writable($file)) {
811 case FILE_EXECUTABLE:
812 if (!is_executable($file)) {
816 case FILE_NOT_READABLE:
817 if (is_readable($file)) {
821 case FILE_NOT_WRITABLE:
822 if (is_writable($file)) {
826 case FILE_NOT_EXECUTABLE:
827 if (is_executable($file)) {
835 // chmod() will work if the web server is running as owner of the file.
836 if (@chmod($file, $mod)) {
845 * Sends the user to a different installer page.
847 * This issues an on-site HTTP redirect. Messages (and errors) are erased.
852 function install_goto($path) {
855 // Not a permanent redirect.
856 'Cache-Control' => 'no-cache',
858 $response = new RedirectResponse($base_url . '/' . $path, 302, $headers);
863 * Returns the URL of the current script, with modified query parameters.
865 * This function can be called by low-level scripts (such as install.php and
866 * update.php) and returns the URL of the current script. Existing query
867 * parameters are preserved by default, but new ones can optionally be merged
870 * This function is used when the script must maintain certain query parameters
871 * over multiple page requests in order to work correctly. In such cases (for
872 * example, update.php, which requires the 'continue=1' parameter to remain in
873 * the URL throughout the update process if there are any requirement warnings
874 * that need to be bypassed), using this function to generate the URL for links
875 * to the next steps of the script ensures that the links will work correctly.
878 * (optional) An array of query parameters to merge in to the existing ones.
881 * The URL of the current script, with query parameters modified by the
882 * passed-in $query. The URL is not sanitized, so it still needs to be run
883 * through \Drupal\Component\Utility\UrlHelper::filterBadProtocol() if it will be
884 * used as an HTML attribute value.
886 * @see drupal_requirements_url()
887 * @see Drupal\Component\Utility\UrlHelper::filterBadProtocol()
889 function drupal_current_script_url($query = []) {
890 $uri = $_SERVER['SCRIPT_NAME'];
891 $query = array_merge(UrlHelper::filterQueryParameters(\Drupal::request()->query->all()), $query);
892 if (!empty($query)) {
893 $uri .= '?' . UrlHelper::buildQuery($query);
899 * Returns a URL for proceeding to the next page after a requirements problem.
901 * This function can be called by low-level scripts (such as install.php and
902 * update.php) and returns a URL that can be used to attempt to proceed to the
903 * next step of the script.
906 * The severity of the requirements problem, as returned by
907 * drupal_requirements_severity().
910 * A URL for attempting to proceed to the next step of the script. The URL is
911 * not sanitized, so it still needs to be run through
912 * \Drupal\Component\Utility\UrlHelper::filterBadProtocol() if it will be used
913 * as an HTML attribute value.
915 * @see drupal_current_script_url()
916 * @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol()
918 function drupal_requirements_url($severity) {
920 // If there are no errors, only warnings, append 'continue=1' to the URL so
921 // the user can bypass this screen on the next page load.
922 if ($severity == REQUIREMENT_WARNING) {
923 $query['continue'] = 1;
925 return drupal_current_script_url($query);
929 * Checks an installation profile's requirements.
931 * @param string $profile
932 * Name of installation profile to check.
935 * Array of the installation profile's requirements.
937 function drupal_check_profile($profile) {
938 $info = install_profile_info($profile);
939 // Collect requirement testing results.
941 // Performs an ExtensionDiscovery scan as the system module is unavailable and
942 // we don't yet know where all the modules are located.
943 // @todo Remove as part of https://www.drupal.org/node/2186491
944 $drupal_root = \Drupal::root();
945 $module_list = (new ExtensionDiscovery($drupal_root))->scan('module');
947 foreach ($info['dependencies'] as $module) {
948 // If the module is in the module list we know it exists and we can continue
949 // including and registering it.
950 // @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory()
951 if (isset($module_list[$module])) {
952 $function = $module . '_requirements';
953 $module_path = $module_list[$module]->getPath();
954 $install_file = "$drupal_root/$module_path/$module.install";
956 if (is_file($install_file)) {
957 require_once $install_file;
960 drupal_classloader_register($module, $module_path);
962 if (function_exists($function)) {
963 $requirements = array_merge($requirements, $function('install'));
968 return $requirements;
972 * Extracts the highest severity from the requirements array.
974 * @param $requirements
975 * An array of requirements, in the same format as is returned by
976 * hook_requirements().
979 * The highest severity in the array.
981 function drupal_requirements_severity(&$requirements) {
982 $severity = REQUIREMENT_OK;
983 foreach ($requirements as $requirement) {
984 if (isset($requirement['severity'])) {
985 $severity = max($severity, $requirement['severity']);
992 * Checks a module's requirements.
995 * Machine name of module to check.
998 * TRUE or FALSE, depending on whether the requirements are met.
1000 function drupal_check_module($module) {
1001 module_load_install($module);
1002 // Check requirements
1003 $requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', ['install']);
1004 if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
1005 // Print any error messages
1006 foreach ($requirements as $requirement) {
1007 if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
1008 $message = $requirement['description'];
1009 if (isset($requirement['value']) && $requirement['value']) {
1010 $message = t('@requirements_message (Currently using @item version @version)', ['@requirements_message' => $requirement['description'], '@item' => $requirement['title'], '@version' => $requirement['value']]);
1012 drupal_set_message($message, 'error');
1021 * Retrieves information about an installation profile from its .info.yml file.
1023 * The information stored in a profile .info.yml file is similar to that stored
1024 * in a normal Drupal module .info.yml file. For example:
1025 * - name: The real name of the installation profile for display purposes.
1026 * - description: A brief description of the profile.
1027 * - dependencies: An array of shortnames of other modules that this install
1030 * Additional, less commonly-used information that can appear in a
1031 * profile.info.yml file but not in a normal Drupal module .info.yml file
1034 * - distribution: Existence of this key denotes that the installation profile
1035 * is intended to be the only eligible choice in a distribution and will be
1036 * auto-selected during installation, whereas the installation profile
1037 * selection screen will be skipped. If more than one distribution profile is
1038 * found then the first one discovered will be selected.
1039 * The following subproperties may be set:
1040 * - name: The name of the distribution that is being installed, to be shown
1041 * throughout the installation process. If omitted,
1042 * drupal_install_profile_distribution_name() defaults to 'Drupal'.
1043 * - install: Optional parameters to override the installer:
1044 * - theme: The machine name of a theme to use in the installer instead of
1045 * Drupal's default installer theme.
1047 * Note that this function does an expensive file system scan to get info file
1048 * information for dependencies. If you only need information from the info
1049 * file itself, use system_get_info().
1051 * Example of .info.yml file:
1054 * description: Start fresh, with only a few modules enabled.
1063 * Language code (if any).
1068 function install_profile_info($profile, $langcode = 'en') {
1069 $cache = &drupal_static(__FUNCTION__, []);
1071 if (!isset($cache[$profile][$langcode])) {
1072 // Set defaults for module info.
1074 'dependencies' => [],
1075 'themes' => ['stark'],
1076 'description' => '',
1079 'php' => DRUPAL_MINIMUM_PHP,
1081 $profile_file = drupal_get_path('profile', $profile) . "/$profile.info.yml";
1082 $info = \Drupal::service('info_parser')->parse($profile_file);
1085 // drupal_required_modules() includes the current profile as a dependency.
1086 // Remove that dependency, since a module cannot depend on itself.
1087 $required = array_diff(drupal_required_modules(), [$profile]);
1089 $locale = !empty($langcode) && $langcode != 'en' ? ['locale'] : [];
1091 $info['dependencies'] = array_unique(array_merge($required, $info['dependencies'], $locale));
1093 $cache[$profile][$langcode] = $info;
1095 return $cache[$profile][$langcode];
1099 * Returns a database installer object.
1102 * The name of the driver.
1104 * @return \Drupal\Core\Database\Install\Tasks
1105 * A class defining the requirements and tasks for installing the database.
1107 function db_installer_object($driver) {
1108 // We cannot use Database::getConnection->getDriverClass() here, because
1109 // the connection object is not yet functional.
1110 $task_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Install\\Tasks";
1111 if (class_exists($task_class)) {
1112 return new $task_class();
1115 $task_class = "Drupal\\Driver\\Database\\{$driver}\\Install\\Tasks";
1116 return new $task_class();