c5b93b58a652d69d23d8b4f4e4bb2e4e87c50297
[yaffs-website] / web / core / includes / install.inc
1 <?php
2
3 /**
4  * @file
5  * API functions for installing modules and themes.
6  */
7
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;
15
16 /**
17  * Requirement severity -- Informational message only.
18  */
19 const REQUIREMENT_INFO = -1;
20
21 /**
22  * Requirement severity -- Requirement successfully met.
23  */
24 const REQUIREMENT_OK = 0;
25
26 /**
27  * Requirement severity -- Warning condition; proceed but flag warning.
28  */
29 const REQUIREMENT_WARNING = 1;
30
31 /**
32  * Requirement severity -- Error condition; abort installation.
33  */
34 const REQUIREMENT_ERROR = 2;
35
36 /**
37  * File permission check -- File exists.
38  */
39 const FILE_EXIST = 1;
40
41 /**
42  * File permission check -- File is readable.
43  */
44 const FILE_READABLE = 2;
45
46 /**
47  * File permission check -- File is writable.
48  */
49 const FILE_WRITABLE = 4;
50
51 /**
52  * File permission check -- File is executable.
53  */
54 const FILE_EXECUTABLE = 8;
55
56 /**
57  * File permission check -- File does not exist.
58  */
59 const FILE_NOT_EXIST = 16;
60
61 /**
62  * File permission check -- File is not readable.
63  */
64 const FILE_NOT_READABLE = 32;
65
66 /**
67  * File permission check -- File is not writable.
68  */
69 const FILE_NOT_WRITABLE = 64;
70
71 /**
72  * File permission check -- File is not executable.
73  */
74 const FILE_NOT_EXECUTABLE = 128;
75
76 /**
77  * Loads .install files for installed modules to initialize the update system.
78  */
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);
83     }
84   }
85 }
86
87 /**
88  * Loads the installation profile, extracting its defined distribution name.
89  *
90  * @return
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.
93  *
94  * @see install_profile_info()
95  */
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).
99   $info = [];
100   if (drupal_installation_attempted()) {
101     global $install_state;
102     if (isset($install_state['profile_info'])) {
103       $info = $install_state['profile_info'];
104     }
105   }
106   // At all other times, we load the profile via standard methods.
107   else {
108     $profile = drupal_get_profile();
109     $info = system_get_info('module', $profile);
110   }
111   return isset($info['distribution']['name']) ? $info['distribution']['name'] : 'Drupal';
112 }
113
114 /**
115  * Loads the installation profile, extracting its defined version.
116  *
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.
120  *
121  * @see install_profile_info()
122  */
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;
129   }
130   // At all other times, we load the profile via standard methods.
131   else {
132     $profile = drupal_get_profile();
133     $info = system_get_info('module', $profile);
134     return $info['version'];
135   }
136 }
137
138 /**
139  * Detects all supported databases that are compiled into PHP.
140  *
141  * @return
142  *   An array of database types compiled into PHP.
143  */
144 function drupal_detect_database_types() {
145   $databases = drupal_get_database_types();
146
147   foreach ($databases as $driver => $installer) {
148     $databases[$driver] = $installer->name();
149   }
150
151   return $databases;
152 }
153
154 /**
155  * Returns all supported database driver installer objects.
156  *
157  * @return \Drupal\Core\Database\Install\Tasks[]
158  *   An array of available database driver installer objects.
159  */
160 function drupal_get_database_types() {
161   $databases = [];
162   $drivers = [];
163
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]);
169   }
170   foreach ($files as $file) {
171     if (file_exists($file->uri . '/Install/Tasks.php')) {
172       $drivers[$file->filename] = $file->uri;
173     }
174   }
175   foreach ($drivers as $driver => $file) {
176     $installer = db_installer_object($driver);
177     if ($installer->installable()) {
178       $databases[$driver] = $installer;
179     }
180   }
181
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;
187   }
188
189   return $databases;
190 }
191
192 /**
193  * Replaces values in settings.php with values in the submitted array.
194  *
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.
198  *
199  * @param $settings
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.
203  *   @code
204  *   $settings['config_directories'] = array(
205  *     CONFIG_SYNC_DIRECTORY => (object) array(
206  *       'value' => 'config_hash/sync',
207  *       'required' => TRUE,
208  *     ),
209  *   );
210  *   @endcode
211  *   gets dumped as:
212  *   @code
213  *   $config_directories['sync'] = 'config_hash/sync'
214  *   @endcode
215  */
216 function drupal_rewrite_settings($settings = [], $settings_file = NULL) {
217   if (!isset($settings_file)) {
218     $settings_file = \Drupal::service('site.path') . '/settings.php';
219   }
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);
226     }
227     else {
228       _drupal_rewrite_settings_global($settings_settings, $data);
229     }
230     $variable_names['$' . $setting] = $setting;
231   }
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";
237     }
238     // Step through each token in settings.php and replace any variables that
239     // are in the passed-in array.
240     $buffer = '';
241     $state = 'default';
242     foreach (token_get_all($contents) as $token) {
243       if (is_array($token)) {
244         list($type, $value) = $token;
245       }
246       else {
247         $type = -1;
248         $value = $token;
249       }
250       // Do not operate on whitespace.
251       if (!in_array($type, [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
252         switch ($state) {
253           case 'default':
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';
262             }
263             break;
264           case 'candidate_left':
265             if ($value == '[') {
266               $state = 'array_index';
267             }
268             if ($value == '=') {
269               $state = 'candidate_right';
270             }
271             break;
272           case 'array_index':
273             if (_drupal_rewrite_settings_is_array_index($type, $value)) {
274               $index = trim($value, '\'"');
275               $state = 'right_bracket';
276             }
277             else {
278               // $a[foo()] or $a[$bar] or something like that.
279               throw new Exception('invalid array index');
280             }
281             break;
282           case 'right_bracket':
283             if ($value == ']') {
284               if (isset($current[$index])) {
285                 // If the new settings has this index, descend into it.
286                 $parent = &$current;
287                 $current = &$parent[$index];
288                 $state = 'candidate_left';
289               }
290               else {
291                 // Otherwise, jump back to the default state.
292                 $state = 'wait_for_semicolon';
293               }
294             }
295             else {
296               // $a[1 + 2].
297               throw new Exception('] expected');
298             }
299             break;
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';
307             }
308             else {
309               $state = 'wait_for_semicolon';
310             }
311             break;
312           case 'wait_for_semicolon':
313             if ($value == ';') {
314               $state = 'default';
315             }
316             break;
317           case 'semicolon_skip':
318             if ($value == ';') {
319               $value = '';
320               $state = 'default';
321             }
322             else {
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.');
326             }
327             break;
328         }
329       }
330       $buffer .= $value;
331     }
332     foreach ($settings as $name => $setting) {
333       $buffer .= _drupal_rewrite_settings_dump($setting, '$' . $name);
334     }
335
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]));
339     }
340     else {
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);
346       }
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);
352     }
353   }
354   else {
355     throw new Exception(t('Failed to open %settings. Verify the file permissions.', ['%settings' => $settings_file]));
356   }
357 }
358
359 /**
360  * Helper for drupal_rewrite_settings().
361  *
362  * Checks whether this token represents a scalar or NULL.
363  *
364  * @param int $type
365  *   The token type.
366  * @param string $value
367  *   The value of the token.
368  *
369  * @return bool
370  *   TRUE if this token represents a scalar or NULL.
371  *
372  * @see token_name()
373  */
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;
380 }
381
382
383 /**
384  * Helper for drupal_rewrite_settings().
385  *
386  * Checks whether this token represents a valid array index: a number or a
387  * string.
388  *
389  * @param int $type
390  *   The token type.
391  *
392  * @return bool
393  *   TRUE if this token represents a number or a string.
394  *
395  * @see token_name()
396  */
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;
402 }
403
404 /**
405  * Helper for drupal_rewrite_settings().
406  *
407  * Makes the new settings global.
408  *
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.
413  */
414 function _drupal_rewrite_settings_global(&$ref, $variable) {
415   if (is_object($variable)) {
416     $ref = $variable->value;
417   }
418   else {
419     foreach ($variable as $k => $v) {
420       _drupal_rewrite_settings_global($ref[$k], $v);
421     }
422   }
423 }
424
425 /**
426  * Helper for drupal_rewrite_settings().
427  *
428  * Dump the relevant value properties.
429  *
430  * @param array|object $variable
431  *   The container for variable values.
432  * @param string $variable_name
433  *   Name of variable.
434  * @return string
435  *   A string containing valid PHP code of the variable suitable for placing
436  *   into settings.php.
437  */
438 function _drupal_rewrite_settings_dump($variable, $variable_name) {
439   $return = '';
440   if (is_object($variable)) {
441     if (!empty($variable->required)) {
442       $return .= _drupal_rewrite_settings_dump_one($variable, "$variable_name = ", "\n");
443     }
444   }
445   else {
446     foreach ($variable as $k => $v) {
447       $return .= _drupal_rewrite_settings_dump($v, $variable_name . "['" . $k . "']");
448     }
449   }
450   return $return;
451 }
452
453
454 /**
455  * Helper for drupal_rewrite_settings().
456  *
457  * Dump the value of a value property and adds the comment if it exists.
458  *
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.
465  * @return string
466  *   A string containing valid PHP code of the variable suitable for placing
467  *   into settings.php.
468  */
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;
473   }
474   $return .= $suffix;
475   return $return;
476 }
477
478 /**
479  * Creates the config directory and ensures it is operational.
480  *
481  * @see install_settings_form_submit()
482  * @see update_prepare_d8_bootstrap()
483  */
484 function drupal_install_config_directories() {
485   global $config_directories;
486
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],
493       'required' => TRUE,
494     ];
495     // Rewrite settings.php, which also sets the value as global variable.
496     drupal_rewrite_settings($settings);
497   }
498
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',
510     ]));
511   }
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
516     // repo.
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);
519   }
520 }
521
522 /**
523  * Ensures that the config directory exists and is writable, or can be made so.
524  *
525  * @param string $type
526  *   Type of config directory to return. Drupal core provides 'sync'.
527  *
528  * @return bool
529  *   TRUE if the config directory exists and is writable.
530  *
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.
533  */
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])) {
538     return FALSE;
539   }
540   // The logic here is similar to that used by system_requirements() for other
541   // directories that the installer creates.
542   else {
543     $config_directory = config_get_config_directory($type);
544     return file_prepare_directory($config_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
545   }
546 }
547
548 /**
549  * Verifies that all dependencies are met for a given installation profile.
550  *
551  * @param $install_state
552  *   An array of information about the current installation state.
553  *
554  * @return
555  *   The list of modules to install.
556  */
557 function drupal_verify_profile($install_state) {
558   $profile = $install_state['parameters']['profile'];
559   $info = $install_state['profile_info'];
560
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();
566   }
567
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;
571
572   // Verify that all of the profile's required modules are present.
573   $missing_modules = array_diff($info['dependencies'], $present_modules);
574
575   $requirements = [];
576
577   if ($missing_modules) {
578     $build = [
579       '#theme' => 'item_list',
580       '#context' => ['list_style' => 'comma-list'],
581     ];
582
583     foreach ($missing_modules as $module) {
584       $build['#items'][] = ['#markup' => '<span class="admin-missing">' . Unicode::ucfirst($module) . '</span>'];
585     }
586
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]),
593     ];
594   }
595   return $requirements;
596 }
597
598 /**
599  * Installs the system module.
600  *
601  * Separated from the installation of other modules so core system
602  * functions can be made available while other modules are installed.
603  *
604  * @param array $install_state
605  *   An array of information about the current installation state. This is used
606  *   to set the default language.
607  */
608 function drupal_install_system($install_state) {
609   // Remove the service provider of the early installer.
610   unset($GLOBALS['conf']['container_service_providers']['InstallerServiceProvider']);
611
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');
616   $kernel->shutdown();
617   // Have installer rebuild from the disk, rather then building from scratch.
618   $kernel->rebuildContainer(FALSE);
619   $kernel->prepareLegacyRequest($request);
620
621   // Install base system configuration.
622   \Drupal::service('config.installer')->installDefaultConfig('core', 'core');
623
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'])
628     ->save();
629
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();
633
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'])
639       ->save(TRUE);
640   }
641 }
642
643 /**
644  * Verifies the state of the specified file.
645  *
646  * @param $file
647  *   The file to check for.
648  * @param $mask
649  *   An optional bitmask created from various FILE_* constants.
650  * @param $type
651  *   The type of file. Can be file (default), dir, or link.
652  *
653  * @return
654  *   TRUE on success or FALSE on failure. A message is set for the latter.
655  */
656 function drupal_verify_install_file($file, $mask = NULL, $type = 'file') {
657   $return = TRUE;
658   // Check for files that shouldn't be there.
659   if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) {
660     return FALSE;
661   }
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)) {
666       $return = FALSE;
667     }
668   }
669
670   // Verify file permissions.
671   if (isset($mask)) {
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) {
676           case FILE_EXIST:
677             if (!file_exists($file)) {
678               if ($type == 'dir') {
679                 drupal_install_mkdir($file, $mask);
680               }
681               if (!file_exists($file)) {
682                 $return = FALSE;
683               }
684             }
685             break;
686           case FILE_READABLE:
687             if (!is_readable($file) && !drupal_install_fix_file($file, $mask)) {
688               $return = FALSE;
689             }
690             break;
691           case FILE_WRITABLE:
692             if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) {
693               $return = FALSE;
694             }
695             break;
696           case FILE_EXECUTABLE:
697             if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) {
698               $return = FALSE;
699             }
700             break;
701           case FILE_NOT_READABLE:
702             if (is_readable($file) && !drupal_install_fix_file($file, $mask)) {
703               $return = FALSE;
704             }
705             break;
706           case FILE_NOT_WRITABLE:
707             if (is_writable($file) && !drupal_install_fix_file($file, $mask)) {
708               $return = FALSE;
709             }
710             break;
711           case FILE_NOT_EXECUTABLE:
712             if (is_executable($file) && !drupal_install_fix_file($file, $mask)) {
713               $return = FALSE;
714             }
715             break;
716         }
717       }
718     }
719   }
720   return $return;
721 }
722
723 /**
724  * Creates a directory with the specified permissions.
725  *
726  * @param $file
727  *   The name of the directory to create;
728  * @param $mask
729  *   The permissions of the directory to create.
730  * @param $message
731  *   (optional) Whether to output messages. Defaults to TRUE.
732  *
733  * @return
734  *   TRUE/FALSE whether or not the directory was successfully created.
735  */
736 function drupal_install_mkdir($file, $mask, $message = TRUE) {
737   $mod = 0;
738   $masks = [FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
739   foreach ($masks as $m) {
740     if ($mask & $m) {
741       switch ($m) {
742         case FILE_READABLE:
743           $mod |= 0444;
744           break;
745         case FILE_WRITABLE:
746           $mod |= 0222;
747           break;
748         case FILE_EXECUTABLE:
749           $mod |= 0111;
750           break;
751       }
752     }
753   }
754
755   if (@drupal_mkdir($file, $mod)) {
756     return TRUE;
757   }
758   else {
759     return FALSE;
760   }
761 }
762
763 /**
764  * Attempts to fix file permissions.
765  *
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).
769  *
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.
774  *
775  * @param $file
776  *   The name of the file with permissions to fix.
777  * @param $mask
778  *   The desired permissions for the file.
779  * @param $message
780  *   (optional) Whether to output messages. Defaults to TRUE.
781  *
782  * @return
783  *   TRUE/FALSE whether or not we were able to fix the file's permissions.
784  */
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)) {
788     return FALSE;
789   }
790
791   $mod = fileperms($file) & 0777;
792   $masks = [FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
793
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) {
799     if ($mask & $m) {
800       switch ($m) {
801         case FILE_READABLE:
802           if (!is_readable($file)) {
803             $mod |= 0444;
804           }
805           break;
806         case FILE_WRITABLE:
807           if (!is_writable($file)) {
808             $mod |= 0222;
809           }
810           break;
811         case FILE_EXECUTABLE:
812           if (!is_executable($file)) {
813             $mod |= 0111;
814           }
815           break;
816         case FILE_NOT_READABLE:
817           if (is_readable($file)) {
818             $mod &= ~0444;
819           }
820           break;
821         case FILE_NOT_WRITABLE:
822           if (is_writable($file)) {
823             $mod &= ~0222;
824           }
825           break;
826         case FILE_NOT_EXECUTABLE:
827           if (is_executable($file)) {
828             $mod &= ~0111;
829           }
830           break;
831       }
832     }
833   }
834
835   // chmod() will work if the web server is running as owner of the file.
836   if (@chmod($file, $mod)) {
837     return TRUE;
838   }
839   else {
840     return FALSE;
841   }
842 }
843
844 /**
845  * Sends the user to a different installer page.
846  *
847  * This issues an on-site HTTP redirect. Messages (and errors) are erased.
848  *
849  * @param $path
850  *   An installer path.
851  */
852 function install_goto($path) {
853   global $base_url;
854   $headers = [
855     // Not a permanent redirect.
856     'Cache-Control' => 'no-cache',
857   ];
858   $response = new RedirectResponse($base_url . '/' . $path, 302, $headers);
859   $response->send();
860 }
861
862 /**
863  * Returns the URL of the current script, with modified query parameters.
864  *
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
868  * in.
869  *
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.
876  *
877  * @param $query
878  *   (optional) An array of query parameters to merge in to the existing ones.
879  *
880  * @return
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.
885  *
886  * @see drupal_requirements_url()
887  * @see Drupal\Component\Utility\UrlHelper::filterBadProtocol()
888  */
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);
894   }
895   return $uri;
896 }
897
898 /**
899  * Returns a URL for proceeding to the next page after a requirements problem.
900  *
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.
904  *
905  * @param $severity
906  *   The severity of the requirements problem, as returned by
907  *   drupal_requirements_severity().
908  *
909  * @return
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.
914  *
915  * @see drupal_current_script_url()
916  * @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol()
917  */
918 function drupal_requirements_url($severity) {
919   $query = [];
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;
924   }
925   return drupal_current_script_url($query);
926 }
927
928 /**
929  * Checks an installation profile's requirements.
930  *
931  * @param string $profile
932  *   Name of installation profile to check.
933  *
934  * @return array
935  *   Array of the installation profile's requirements.
936  */
937 function drupal_check_profile($profile) {
938   $info = install_profile_info($profile);
939   // Collect requirement testing results.
940   $requirements = [];
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');
946
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";
955
956       if (is_file($install_file)) {
957         require_once $install_file;
958       }
959
960       drupal_classloader_register($module, $module_path);
961
962       if (function_exists($function)) {
963         $requirements = array_merge($requirements, $function('install'));
964       }
965     }
966   }
967
968   return $requirements;
969 }
970
971 /**
972  * Extracts the highest severity from the requirements array.
973  *
974  * @param $requirements
975  *   An array of requirements, in the same format as is returned by
976  *   hook_requirements().
977  *
978  * @return
979  *   The highest severity in the array.
980  */
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']);
986     }
987   }
988   return $severity;
989 }
990
991 /**
992  * Checks a module's requirements.
993  *
994  * @param $module
995  *   Machine name of module to check.
996  *
997  * @return
998  *   TRUE or FALSE, depending on whether the requirements are met.
999  */
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']]);
1011         }
1012         drupal_set_message($message, 'error');
1013       }
1014     }
1015     return FALSE;
1016   }
1017   return TRUE;
1018 }
1019
1020 /**
1021  * Retrieves information about an installation profile from its .info.yml file.
1022  *
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
1028  *   profile requires.
1029  *
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
1032  * includes:
1033  *
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.
1046  *
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().
1050  *
1051  * Example of .info.yml file:
1052  * @code
1053  *    name: Minimal
1054  *    description: Start fresh, with only a few modules enabled.
1055  *    dependencies:
1056  *      - block
1057  *      - dblog
1058  * @endcode
1059  *
1060  * @param $profile
1061  *   Name of profile.
1062  * @param $langcode
1063  *   Language code (if any).
1064  *
1065  * @return
1066  *   The info array.
1067  */
1068 function install_profile_info($profile, $langcode = 'en') {
1069   $cache = &drupal_static(__FUNCTION__, []);
1070
1071   if (!isset($cache[$profile][$langcode])) {
1072     // Set defaults for module info.
1073     $defaults = [
1074       'dependencies' => [],
1075       'themes' => ['stark'],
1076       'description' => '',
1077       'version' => NULL,
1078       'hidden' => FALSE,
1079       'php' => DRUPAL_MINIMUM_PHP,
1080     ];
1081     $profile_file = drupal_get_path('profile', $profile) . "/$profile.info.yml";
1082     $info = \Drupal::service('info_parser')->parse($profile_file);
1083     $info += $defaults;
1084
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]);
1088
1089     $locale = !empty($langcode) && $langcode != 'en' ? ['locale'] : [];
1090
1091     $info['dependencies'] = array_unique(array_merge($required, $info['dependencies'], $locale));
1092
1093     $cache[$profile][$langcode] = $info;
1094   }
1095   return $cache[$profile][$langcode];
1096 }
1097
1098 /**
1099  * Returns a database installer object.
1100  *
1101  * @param $driver
1102  *   The name of the driver.
1103  *
1104  * @return \Drupal\Core\Database\Install\Tasks
1105  *   A class defining the requirements and tasks for installing the database.
1106  */
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();
1113   }
1114   else {
1115     $task_class = "Drupal\\Driver\\Database\\{$driver}\\Install\\Tasks";
1116     return new $task_class();
1117   }
1118 }