1<?php
2
3/**
4 * @file
5 * Install, update and uninstall functions for the system module.
6 */
7
8use Drupal\Component\FileSystem\FileSystem as FileSystemComponent;
9use Drupal\Component\Utility\Bytes;
10use Drupal\Component\Utility\Crypt;
11use Drupal\Component\Utility\Environment;
12use Drupal\Component\Utility\OpCodeCache;
13use Drupal\Component\Utility\Unicode;
14use Drupal\Core\Cache\Cache;
15use Drupal\Core\Database\Database;
16use Drupal\Core\DrupalKernel;
17use Drupal\Core\Entity\ContentEntityTypeInterface;
18use Drupal\Core\Entity\EntityTypeInterface;
19use Drupal\Core\Entity\FieldableEntityInterface;
20use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
21use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
22use Drupal\Core\Extension\Extension;
23use Drupal\Core\Field\BaseFieldDefinition;
24use Drupal\Core\File\FileSystemInterface;
25use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
26use Drupal\path_alias\Entity\PathAlias;
27use Drupal\path_alias\PathAliasStorage;
28use Drupal\Core\Site\Settings;
29use Drupal\Core\StreamWrapper\PrivateStream;
30use Drupal\Core\StreamWrapper\PublicStream;
31use Drupal\Core\StringTranslation\TranslatableMarkup;
32use Drupal\Core\Url;
33use Symfony\Component\HttpFoundation\Request;
34
35/**
36 * Implements hook_requirements().
37 */
38function system_requirements($phase) {
39  global $install_state;
40  // Reset the extension lists.
41  \Drupal::service('extension.list.module')->reset();
42  \Drupal::service('extension.list.theme')->reset();
43  $requirements = [];
44
45  // Report Drupal version
46  if ($phase == 'runtime') {
47    $requirements['drupal'] = [
48      'title' => t('Drupal'),
49      'value' => \Drupal::VERSION,
50      'severity' => REQUIREMENT_INFO,
51      'weight' => -10,
52    ];
53
54    // Display the currently active installation profile, if the site
55    // is not running the default installation profile.
56    $profile = \Drupal::installProfile();
57    if ($profile != 'standard') {
58      $info = \Drupal::service('extension.list.module')->getExtensionInfo($profile);
59      $requirements['install_profile'] = [
60        'title' => t('Installation profile'),
61        'value' => t('%profile_name (%profile-%version)', [
62          '%profile_name' => $info['name'],
63          '%profile' => $profile,
64          '%version' => $info['version'],
65        ]),
66        'severity' => REQUIREMENT_INFO,
67        'weight' => -9,
68      ];
69    }
70
71    // Warn if any experimental modules are installed.
72    $experimental_modules = [];
73    $enabled_modules = \Drupal::moduleHandler()->getModuleList();
74    foreach ($enabled_modules as $module => $data) {
75      $info = \Drupal::service('extension.list.module')->getExtensionInfo($module);
76      if (isset($info['package']) && $info['package'] === 'Core (Experimental)') {
77        $experimental_modules[$module] = $info['name'];
78      }
79    }
80    if (!empty($experimental_modules)) {
81      $requirements['experimental_modules'] = [
82        'title' => t('Experimental modules enabled'),
83        'value' => t('Experimental modules found: %module_list. <a href=":url">Experimental modules</a> are provided for testing purposes only. Use at your own risk.', ['%module_list' => implode(', ', $experimental_modules), ':url' => 'https://www.drupal.org/core/experimental']),
84        'severity' => REQUIREMENT_WARNING,
85      ];
86    }
87    // Warn if any experimental themes are installed.
88    $experimental_themes = [];
89    $installed_themes = \Drupal::service('theme_handler')->listInfo();
90    foreach ($installed_themes as $theme => $data) {
91      if (isset($data->info['experimental']) && $data->info['experimental']) {
92        $experimental_themes[$theme] = $data->info['name'];
93      }
94    }
95    if (!empty($experimental_themes)) {
96      $requirements['experimental_themes'] = [
97        'title' => t('Experimental themes enabled'),
98        'value' => t('Experimental themes found: %theme_list. Experimental themes are provided for testing purposes only. Use at your own risk.', ['%theme_list' => implode(', ', $experimental_themes)]),
99        'severity' => REQUIREMENT_WARNING,
100      ];
101    }
102  }
103
104  // Web server information.
105  $request_object = \Drupal::request();
106  $software = $request_object->server->get('SERVER_SOFTWARE');
107  $requirements['webserver'] = [
108    'title' => t('Web server'),
109    'value' => $software,
110  ];
111
112  // Tests clean URL support.
113  if ($phase == 'install' && $install_state['interactive'] && !$request_object->query->has('rewrite') && strpos($software, 'Apache') !== FALSE) {
114    // If the Apache rewrite module is not enabled, Apache version must be >=
115    // 2.2.16 because of the FallbackResource directive in the root .htaccess
116    // file. Since the Apache version reported by the server is dependent on the
117    // ServerTokens setting in httpd.conf, we may not be able to determine if a
118    // given config is valid. Thus we are unable to use version_compare() as we
119    // need have three possible outcomes: the version of Apache is greater than
120    // 2.2.16, is less than 2.2.16, or cannot be determined accurately. In the
121    // first case, we encourage the use of mod_rewrite; in the second case, we
122    // raise an error regarding the minimum Apache version; in the third case,
123    // we raise a warning that the current version of Apache may not be
124    // supported.
125    $rewrite_warning = FALSE;
126    $rewrite_error = FALSE;
127    $apache_version_string = 'Apache';
128
129    // Determine the Apache version number: major, minor and revision.
130    if (preg_match('/Apache\/(\d+)\.?(\d+)?\.?(\d+)?/', $software, $matches)) {
131      $apache_version_string = $matches[0];
132
133      // Major version number
134      if ($matches[1] < 2) {
135        $rewrite_error = TRUE;
136      }
137      elseif ($matches[1] == 2) {
138        if (!isset($matches[2])) {
139          $rewrite_warning = TRUE;
140        }
141        elseif ($matches[2] < 2) {
142          $rewrite_error = TRUE;
143        }
144        elseif ($matches[2] == 2) {
145          if (!isset($matches[3])) {
146            $rewrite_warning = TRUE;
147          }
148          elseif ($matches[3] < 16) {
149            $rewrite_error = TRUE;
150          }
151        }
152      }
153    }
154    else {
155      $rewrite_warning = TRUE;
156    }
157
158    if ($rewrite_warning) {
159      $requirements['apache_version'] = [
160        'title' => t('Apache version'),
161        'value' => $apache_version_string,
162        'severity' => REQUIREMENT_WARNING,
163        'description' => t('Due to the settings for ServerTokens in httpd.conf, it is impossible to accurately determine the version of Apache running on this server. The reported value is @reported, to run Drupal without mod_rewrite, a minimum version of 2.2.16 is needed.', ['@reported' => $apache_version_string]),
164      ];
165    }
166
167    if ($rewrite_error) {
168      $requirements['Apache version'] = [
169        'title' => t('Apache version'),
170        'value' => $apache_version_string,
171        'severity' => REQUIREMENT_ERROR,
172        'description' => t('The minimum version of Apache needed to run Drupal without mod_rewrite enabled is 2.2.16. See the <a href=":link">enabling clean URLs</a> page for more information on mod_rewrite.', [':link' => 'http://drupal.org/docs/8/clean-urls-in-drupal-8']),
173      ];
174    }
175
176    if (!$rewrite_error && !$rewrite_warning) {
177      $requirements['rewrite_module'] = [
178        'title' => t('Clean URLs'),
179        'value' => t('Disabled'),
180        'severity' => REQUIREMENT_WARNING,
181        'description' => t('Your server is capable of using clean URLs, but it is not enabled. Using clean URLs gives an improved user experience and is recommended. <a href=":link">Enable clean URLs</a>', [':link' => 'http://drupal.org/docs/8/clean-urls-in-drupal-8']),
182      ];
183    }
184  }
185
186  // Verify the user is running a supported PHP version.
187  // If the site is running a recommended version of PHP, just display it
188  // as an informational message on the status report. This will be overridden
189  // with an error or warning if the site is running older PHP versions for
190  // which Drupal has already or will soon drop support.
191  $phpversion = $phpversion_label = phpversion();
192  if (function_exists('phpinfo')) {
193    if ($phase === 'runtime') {
194      $phpversion_label = t('@phpversion (<a href=":url">more information</a>)', ['@phpversion' => $phpversion, ':url' => (new Url('system.php'))->toString()]);
195    }
196    $requirements['php'] = [
197      'title' => t('PHP'),
198      'value' => $phpversion_label,
199    ];
200  }
201  else {
202    // @todo Revisit whether this description makes sense in
203    //   https://www.drupal.org/project/drupal/issues/2927318.
204    $requirements['php'] = [
205      'title' => t('PHP'),
206      'value' => $phpversion_label,
207      'description' => t('The phpinfo() function has been disabled for security reasons. To see your server\'s phpinfo() information, change your PHP settings or contact your server administrator. For more information, <a href=":phpinfo">Enabling and disabling phpinfo()</a> handbook page.', [':phpinfo' => 'https://www.drupal.org/node/243993']),
208      'severity' => REQUIREMENT_INFO,
209    ];
210  }
211
212  // Check if the PHP version is below what Drupal supports.
213  if (version_compare($phpversion, DRUPAL_MINIMUM_SUPPORTED_PHP) < 0) {
214    $requirements['php']['description'] = t('Your PHP installation is too old. Drupal requires at least PHP %version. It is recommended to upgrade to PHP version %recommended or higher for the best ongoing support. See <a href="http://php.net/supported-versions.php">PHP\'s version support documentation</a> and the <a href=":php_requirements">Drupal 8 PHP requirements handbook page</a> for more information.',
215      [
216        '%version' => DRUPAL_MINIMUM_SUPPORTED_PHP,
217        '%recommended' => DRUPAL_RECOMMENDED_PHP,
218        ':php_requirements' => 'https://www.drupal.org/docs/8/system-requirements/php',
219      ]
220    );
221    $requirements['php']['severity'] = REQUIREMENT_ERROR;
222
223    // If the PHP version is also below the absolute minimum allowed, it's not
224    // safe to continue with the requirements check.
225    if (version_compare($phpversion, DRUPAL_MINIMUM_PHP) < 0) {
226      return $requirements;
227    }
228    // Otherwise downgrade the error to a warning during updates. Even if there
229    // are some problems with the site's PHP version, it's still better for the
230    // site to keep its Drupal codebase up to date.
231    elseif ($phase === 'update') {
232      $requirements['php']['severity'] = REQUIREMENT_WARNING;
233    }
234    // Since we allow sites with unsupported PHP versions to still run Drupal
235    // updates, we also need to be able to run tests with those PHP versions,
236    // which requires the ability to install test sites. Not all tests are
237    // required to pass on these PHP versions, but we want to monitor which
238    // ones do and don't.
239    elseif ($phase === 'install' && drupal_valid_test_ua()) {
240      $requirements['php']['severity'] = REQUIREMENT_INFO;
241    }
242  }
243  // For PHP versions that are still supported but no longer recommended,
244  // inform users of what's recommended, allowing them to take action before it
245  // becomes urgent.
246  elseif ($phase === 'runtime' && version_compare($phpversion, DRUPAL_RECOMMENDED_PHP) < 0) {
247    $requirements['php']['description'] = t('It is recommended to upgrade to PHP version %recommended or higher for the best ongoing support.  See <a href="http://php.net/supported-versions.php">PHP\'s version support documentation</a> and the <a href=":php_requirements">Drupal 8 PHP requirements handbook page</a> for more information.', ['%recommended' => DRUPAL_RECOMMENDED_PHP, ':php_requirements' => 'https://www.drupal.org/docs/8/system-requirements/php']);
248    $requirements['php']['severity'] = REQUIREMENT_INFO;
249  }
250
251  // Test for PHP extensions.
252  $requirements['php_extensions'] = [
253    'title' => t('PHP extensions'),
254  ];
255
256  $missing_extensions = [];
257  $required_extensions = [
258    'date',
259    'dom',
260    'filter',
261    'gd',
262    'hash',
263    'json',
264    'pcre',
265    'pdo',
266    'session',
267    'SimpleXML',
268    'SPL',
269    'tokenizer',
270    'xml',
271  ];
272  foreach ($required_extensions as $extension) {
273    if (!extension_loaded($extension)) {
274      $missing_extensions[] = $extension;
275    }
276  }
277
278  if (!empty($missing_extensions)) {
279    $description = t('Drupal requires you to enable the PHP extensions in the following list (see the <a href=":system_requirements">system requirements page</a> for more information):', [
280      ':system_requirements' => 'https://www.drupal.org/requirements',
281    ]);
282
283    // We use twig inline_template to avoid twig's autoescape.
284    $description = [
285      '#type' => 'inline_template',
286      '#template' => '{{ description }}{{ missing_extensions }}',
287      '#context' => [
288        'description' => $description,
289        'missing_extensions' => [
290          '#theme' => 'item_list',
291          '#items' => $missing_extensions,
292        ],
293      ],
294    ];
295
296    $requirements['php_extensions']['value'] = t('Disabled');
297    $requirements['php_extensions']['severity'] = REQUIREMENT_ERROR;
298    $requirements['php_extensions']['description'] = $description;
299  }
300  else {
301    $requirements['php_extensions']['value'] = t('Enabled');
302  }
303
304  if ($phase == 'install' || $phase == 'runtime') {
305    // Check to see if OPcache is installed.
306    if (!OpCodeCache::isEnabled()) {
307      $requirements['php_opcache'] = [
308        'value' => t('Not enabled'),
309        'severity' => REQUIREMENT_WARNING,
310        'description' => t('PHP OPcode caching can improve your site\'s performance considerably. It is <strong>highly recommended</strong> to have <a href="http://php.net/manual/opcache.installation.php" target="_blank">OPcache</a> installed on your server.'),
311      ];
312    }
313    else {
314      $requirements['php_opcache']['value'] = t('Enabled');
315    }
316    $requirements['php_opcache']['title'] = t('PHP OPcode caching');
317  }
318
319  // Check to see if APCu is installed and configured correctly.
320  if ($phase == 'runtime' && PHP_SAPI != 'cli') {
321    $requirements['php_apcu']['title'] = t('PHP APCu caching');
322    if (extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN)) {
323      $memory_info = apcu_sma_info(TRUE);
324      $apcu_actual_size = format_size($memory_info['seg_size']);
325      $apcu_recommended_size = '32 MB';
326      $requirements['php_apcu']['value'] = t('Enabled (@size)', ['@size' => $apcu_actual_size]);
327      if (Bytes::toInt($apcu_actual_size) < Bytes::toInt($apcu_recommended_size)) {
328        $requirements['php_apcu']['severity'] = REQUIREMENT_WARNING;
329        $requirements['php_apcu']['description'] = t('Depending on your configuration, Drupal can run with a @apcu_size APCu limit. However, a @apcu_default_size APCu limit (the default) or above is recommended, especially if your site uses additional custom or contributed modules.', [
330          '@apcu_size' => $apcu_actual_size,
331          '@apcu_default_size' => $apcu_recommended_size,
332        ]);
333      }
334      else {
335        $memory_available = $memory_info['avail_mem'] / $memory_info['seg_size'];
336        if ($memory_available < 0.1) {
337          $requirements['php_apcu']['severity'] = REQUIREMENT_ERROR;
338        }
339        elseif ($memory_available < 0.25) {
340          $requirements['php_apcu']['severity'] = REQUIREMENT_WARNING;
341        }
342        else {
343          $requirements['php_apcu']['severity'] = REQUIREMENT_OK;
344        }
345        $requirements['php_apcu']['description'] = t('Memory available: @available.', [
346          '@available' => format_size($memory_info['avail_mem']),
347        ]);
348      }
349    }
350    else {
351      $requirements['php_apcu'] += [
352        'value' => t('Not enabled'),
353        'severity' => REQUIREMENT_INFO,
354        'description' => t('PHP APCu caching can improve your site\'s performance considerably. It is <strong>highly recommended</strong> to have <a href="https://www.php.net/manual/apcu.installation.php" target="_blank">APCu</a> installed on your server.'),
355      ];
356    }
357  }
358
359  if ($phase != 'update') {
360    // Test whether we have a good source of random bytes.
361    $requirements['php_random_bytes'] = [
362      'title' => t('Random number generation'),
363    ];
364    try {
365      $bytes = random_bytes(10);
366      if (strlen($bytes) != 10) {
367        throw new \Exception("Tried to generate 10 random bytes, generated '" . strlen($bytes) . "'");
368      }
369      $requirements['php_random_bytes']['value'] = t('Successful');
370    }
371    catch (\Exception $e) {
372      // If /dev/urandom is not available on a UNIX-like system, check whether
373      // open_basedir restrictions are the cause.
374      $open_basedir_blocks_urandom = FALSE;
375      if (DIRECTORY_SEPARATOR === '/' && !@is_readable('/dev/urandom')) {
376        $open_basedir = ini_get('open_basedir');
377        if ($open_basedir) {
378          $open_basedir_paths = explode(PATH_SEPARATOR, $open_basedir);
379          $open_basedir_blocks_urandom = !array_intersect(['/dev', '/dev/', '/dev/urandom'], $open_basedir_paths);
380        }
381      }
382      $args = [
383        ':drupal-php' => 'https://www.drupal.org/docs/8/system-requirements/php-requirements',
384        '%exception_message' => $e->getMessage(),
385      ];
386      if ($open_basedir_blocks_urandom) {
387        $requirements['php_random_bytes']['description'] = t('Drupal is unable to generate highly randomized numbers, which means certain security features like password reset URLs are not as secure as they should be. Instead, only a slow, less-secure fallback generator is available. The most likely cause is that open_basedir restrictions are in effect and /dev/urandom is not on the whitelist. See the <a href=":drupal-php">system requirements</a> page for more information. %exception_message', $args);
388      }
389      else {
390        $requirements['php_random_bytes']['description'] = t('Drupal is unable to generate highly randomized numbers, which means certain security features like password reset URLs are not as secure as they should be. Instead, only a slow, less-secure fallback generator is available. See the <a href=":drupal-php">system requirements</a> page for more information. %exception_message', $args);
391      }
392      $requirements['php_random_bytes']['value'] = t('Less secure');
393      $requirements['php_random_bytes']['severity'] = REQUIREMENT_ERROR;
394    }
395  }
396
397  if ($phase == 'install' || $phase == 'update') {
398    // Test for PDO (database).
399    $requirements['database_extensions'] = [
400      'title' => t('Database support'),
401    ];
402
403    // Make sure PDO is available.
404    $database_ok = extension_loaded('pdo');
405    if (!$database_ok) {
406      $pdo_message = t('Your web server does not appear to support PDO (PHP Data Objects). Ask your hosting provider if they support the native PDO extension. See the <a href=":link">system requirements</a> page for more information.', [
407        ':link' => 'https://www.drupal.org/requirements/pdo',
408      ]);
409    }
410    else {
411      // Make sure at least one supported database driver exists.
412      $drivers = drupal_detect_database_types();
413      if (empty($drivers)) {
414        $database_ok = FALSE;
415        $pdo_message = t('Your web server does not appear to support any common PDO database extensions. Check with your hosting provider to see if they support PDO (PHP Data Objects) and offer any databases that <a href=":drupal-databases">Drupal supports</a>.', [
416          ':drupal-databases' => 'https://www.drupal.org/requirements/database',
417        ]);
418      }
419      // Make sure the native PDO extension is available, not the older PEAR
420      // version. (See install_verify_pdo() for details.)
421      if (!defined('PDO::ATTR_DEFAULT_FETCH_MODE')) {
422        $database_ok = FALSE;
423        $pdo_message = t('Your web server seems to have the wrong version of PDO installed. Drupal requires the PDO extension from PHP core. This system has the older PECL version. See the <a href=":link">system requirements</a> page for more information.', [
424          ':link' => 'https://www.drupal.org/requirements/pdo#pecl',
425        ]);
426      }
427    }
428
429    if (!$database_ok) {
430      $requirements['database_extensions']['value'] = t('Disabled');
431      $requirements['database_extensions']['severity'] = REQUIREMENT_ERROR;
432      $requirements['database_extensions']['description'] = $pdo_message;
433    }
434    else {
435      $requirements['database_extensions']['value'] = t('Enabled');
436    }
437  }
438
439  if ($phase === 'runtime' || $phase === 'update') {
440    // Database information.
441    $class = Database::getConnection()->getDriverClass('Install\\Tasks');
442    /** @var \Drupal\Core\Database\Install\Tasks $tasks */
443    $tasks = new $class();
444    $requirements['database_system'] = [
445      'title' => t('Database system'),
446      'value' => $tasks->name(),
447    ];
448    $requirements['database_system_version'] = [
449      'title' => t('Database system version'),
450      'value' => Database::getConnection()->version(),
451    ];
452
453    $errors = $tasks->engineVersionRequirementsCheck();
454    $error_count = count($errors);
455    if ($error_count > 0) {
456      $error_message = [
457        '#theme' => 'item_list',
458        '#items' => $errors,
459        // Use the comma-list style to display a single error without bullets.
460        '#context' => ['list_style' => $error_count === 1 ? 'comma-list' : ''],
461      ];
462      $requirements['database_system_version']['severity'] = REQUIREMENT_ERROR;
463      $requirements['database_system_version']['description'] = $error_message;
464    }
465  }
466
467  // Test PHP memory_limit
468  $memory_limit = ini_get('memory_limit');
469  $requirements['php_memory_limit'] = [
470    'title' => t('PHP memory limit'),
471    'value' => $memory_limit == -1 ? t('-1 (Unlimited)') : $memory_limit,
472  ];
473
474  if (!Environment::checkMemoryLimit(DRUPAL_MINIMUM_PHP_MEMORY_LIMIT, $memory_limit)) {
475    $description = [];
476    if ($phase == 'install') {
477      $description['phase'] = t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the installation process.', ['%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT]);
478    }
479    elseif ($phase == 'update') {
480      $description['phase'] = t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the update process.', ['%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT]);
481    }
482    elseif ($phase == 'runtime') {
483      $description['phase'] = t('Depending on your configuration, Drupal can run with a %memory_limit PHP memory limit. However, a %memory_minimum_limit PHP memory limit or above is recommended, especially if your site uses additional custom or contributed modules.', ['%memory_limit' => $memory_limit, '%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT]);
484    }
485
486    if (!empty($description['phase'])) {
487      if ($php_ini_path = get_cfg_var('cfg_file_path')) {
488        $description['memory'] = t('Increase the memory limit by editing the memory_limit parameter in the file %configuration-file and then restart your web server (or contact your system administrator or hosting provider for assistance).', ['%configuration-file' => $php_ini_path]);
489      }
490      else {
491        $description['memory'] = t('Contact your system administrator or hosting provider for assistance with increasing your PHP memory limit.');
492      }
493
494      $handbook_link = t('For more information, see the online handbook entry for <a href=":memory-limit">increasing the PHP memory limit</a>.', [':memory-limit' => 'https://www.drupal.org/node/207036']);
495
496      $description = [
497        '#type' => 'inline_template',
498        '#template' => '{{ description_phase }} {{ description_memory }} {{ handbook }}',
499        '#context' => [
500          'description_phase' => $description['phase'],
501          'description_memory' => $description['memory'],
502          'handbook' => $handbook_link,
503        ],
504      ];
505
506      $requirements['php_memory_limit']['description'] = $description;
507      $requirements['php_memory_limit']['severity'] = REQUIREMENT_WARNING;
508    }
509  }
510
511  // Test configuration files and directory for writability.
512  if ($phase == 'runtime') {
513    $conf_errors = [];
514    // Find the site path. Kernel service is not always available at this point,
515    // but is preferred, when available.
516    if (\Drupal::hasService('kernel')) {
517      $site_path = \Drupal::service('site.path');
518    }
519    else {
520      $site_path = DrupalKernel::findSitePath(Request::createFromGlobals());
521    }
522    // Allow system administrators to disable permissions hardening for the site
523    // directory. This allows additional files in the site directory to be
524    // updated when they are managed in a version control system.
525    if (Settings::get('skip_permissions_hardening')) {
526      $error_value = t('Protection disabled');
527      // If permissions hardening is disabled, then only show a warning for a
528      // writable file, as a reminder, rather than an error.
529      $file_protection_severity = REQUIREMENT_WARNING;
530    }
531    else {
532      $error_value = t('Not protected');
533      // In normal operation, writable files or directories are an error.
534      $file_protection_severity = REQUIREMENT_ERROR;
535      if (!drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir')) {
536        $conf_errors[] = t("The directory %file is not protected from modifications and poses a security risk. You must change the directory's permissions to be non-writable.", ['%file' => $site_path]);
537      }
538    }
539    foreach (['settings.php', 'settings.local.php', 'services.yml'] as $conf_file) {
540      $full_path = $site_path . '/' . $conf_file;
541      if (file_exists($full_path) && !drupal_verify_install_file($full_path, FILE_EXIST | FILE_READABLE | FILE_NOT_WRITABLE, 'file', !Settings::get('skip_permissions_hardening'))) {
542        $conf_errors[] = t("The file %file is not protected from modifications and poses a security risk. You must change the file's permissions to be non-writable.", ['%file' => $full_path]);
543      }
544    }
545    if (!empty($conf_errors)) {
546      if (count($conf_errors) == 1) {
547        $description = $conf_errors[0];
548      }
549      else {
550        // We use twig inline_template to avoid double escaping.
551        $description = [
552          '#type' => 'inline_template',
553          '#template' => '{{ configuration_error_list }}',
554          '#context' => [
555            'configuration_error_list' => [
556              '#theme' => 'item_list',
557              '#items' => $conf_errors,
558            ],
559          ],
560        ];
561      }
562      $requirements['configuration_files'] = [
563        'value' => $error_value,
564        'severity' => $file_protection_severity,
565        'description' => $description,
566      ];
567    }
568    else {
569      $requirements['configuration_files'] = [
570        'value' => t('Protected'),
571      ];
572    }
573    $requirements['configuration_files']['title'] = t('Configuration files');
574  }
575
576  // Test the contents of the .htaccess files.
577  if ($phase == 'runtime') {
578    // Try to write the .htaccess files first, to prevent false alarms in case
579    // (for example) the /tmp directory was wiped.
580    /** @var \Drupal\Core\File\HtaccessWriterInterface $htaccessWriter */
581    $htaccessWriter = \Drupal::service("file.htaccess_writer");
582    $htaccessWriter->ensure();
583    foreach ($htaccessWriter->defaultProtectedDirs() as $protected_dir) {
584      $htaccess_file = $protected_dir->getPath() . '/.htaccess';
585      // Check for the string which was added to the recommended .htaccess file
586      // in the latest security update.
587      if (!file_exists($htaccess_file) || !($contents = @file_get_contents($htaccess_file)) || strpos($contents, 'Drupal_Security_Do_Not_Remove_See_SA_2013_003') === FALSE) {
588        $url = 'https://www.drupal.org/SA-CORE-2013-003';
589        $requirements[$htaccess_file] = [
590          'title' => new TranslatableMarkup($protected_dir->getTitle()),
591          'value' => t('Not fully protected'),
592          'severity' => REQUIREMENT_ERROR,
593          'description' => t('See <a href=":url">@url</a> for information about the recommended .htaccess file which should be added to the %directory directory to help protect against arbitrary code execution.', [':url' => $url, '@url' => $url, '%directory' => $protected_dir->getPath()]),
594        ];
595      }
596    }
597  }
598
599  // Test that path.temporary config is not set.
600  if ($phase == 'runtime') {
601    if (!Settings::get('file_temp_path')) {
602      $filesystem_config = \Drupal::config('system.file');
603      if ($temp_path = $filesystem_config->get('path.temporary')) {
604        $requirements['temp_directory'] = [
605          'title' => t('Temporary Directory'),
606          'severity' => REQUIREMENT_WARNING,
607          'value' => 'Deprecated configuration',
608          'description' => [
609            [
610              '#markup' => t('You are using deprecated configuration for the temporary files path.'),
611              '#suffix' => ' ',
612            ],
613          ],
614        ];
615        if ($temp_path === FileSystemComponent::getOsTemporaryDirectory()) {
616          $requirements['temp_directory']['description'][] = [
617            '#markup' => t('Your temporary directory configuration matches the OS default and can be safely removed.'),
618            '#suffix' => ' ',
619          ];
620        }
621        else {
622          $requirements['temp_directory']['description'][] = [
623            '#markup' => t('Remove the configuration and add the following to <code>settings.php</code>. <code>$settings["file_temp_path"] = "%temp_path"</code>', ['%temp_path' => $temp_path]),
624            '#suffix' => ' ',
625          ];
626        }
627      }
628    }
629  }
630
631  // Report cron status.
632  if ($phase == 'runtime') {
633    $cron_config = \Drupal::config('system.cron');
634    // Cron warning threshold defaults to two days.
635    $threshold_warning = $cron_config->get('threshold.requirements_warning');
636    // Cron error threshold defaults to two weeks.
637    $threshold_error = $cron_config->get('threshold.requirements_error');
638
639    // Determine when cron last ran.
640    $cron_last = \Drupal::state()->get('system.cron_last');
641    if (!is_numeric($cron_last)) {
642      $cron_last = \Drupal::state()->get('install_time', 0);
643    }
644
645    // Determine severity based on time since cron last ran.
646    $severity = REQUIREMENT_INFO;
647    if (REQUEST_TIME - $cron_last > $threshold_error) {
648      $severity = REQUIREMENT_ERROR;
649    }
650    elseif (REQUEST_TIME - $cron_last > $threshold_warning) {
651      $severity = REQUIREMENT_WARNING;
652    }
653
654    // Set summary and description based on values determined above.
655    $summary = t('Last run @time ago', ['@time' => \Drupal::service('date.formatter')->formatTimeDiffSince($cron_last)]);
656
657    $requirements['cron'] = [
658      'title' => t('Cron maintenance tasks'),
659      'severity' => $severity,
660      'value' => $summary,
661    ];
662    if ($severity != REQUIREMENT_INFO) {
663      $requirements['cron']['description'][] = [
664        [
665          '#markup' => t('Cron has not run recently.'),
666          '#suffix' => ' ',
667        ],
668        [
669          '#markup' => t('For more information, see the online handbook entry for <a href=":cron-handbook">configuring cron jobs</a>.', [':cron-handbook' => 'https://www.drupal.org/cron']),
670          '#suffix' => ' ',
671        ],
672      ];
673    }
674    $requirements['cron']['description'][] = [
675      [
676        '#type' => 'link',
677        '#prefix' => '(',
678        '#title' => t('more information'),
679        '#suffix' => ')',
680        '#url' => Url::fromRoute('system.cron_settings'),
681      ],
682      [
683        '#prefix' => '<span class="cron-description__run-cron">',
684        '#suffix' => '</span>',
685        '#type' => 'link',
686        '#title' => t('Run cron'),
687        '#url' => Url::fromRoute('system.run_cron'),
688      ],
689    ];
690  }
691  if ($phase != 'install') {
692    $filesystem_config = \Drupal::config('system.file');
693    $directories = [
694      PublicStream::basePath(),
695      // By default no private files directory is configured. For private files
696      // to be secure the admin needs to provide a path outside the webroot.
697      PrivateStream::basePath(),
698      \Drupal::service('file_system')->getTempDirectory(),
699    ];
700  }
701
702  // During an install we need to make assumptions about the file system
703  // unless overrides are provided in settings.php.
704  if ($phase == 'install') {
705    $directories = [];
706    if ($file_public_path = Settings::get('file_public_path')) {
707      $directories[] = $file_public_path;
708    }
709    else {
710      // If we are installing Drupal, the settings.php file might not exist yet
711      // in the intended site directory, so don't require it.
712      $request = Request::createFromGlobals();
713      $site_path = DrupalKernel::findSitePath($request);
714      $directories[] = $site_path . '/files';
715    }
716    if ($file_private_path = Settings::get('file_private_path')) {
717      $directories[] = $file_private_path;
718    }
719    if (Settings::get('file_temp_path')) {
720      $directories[] = Settings::get('file_temp_path');
721    }
722    else {
723      // If the temporary directory is not overridden use an appropriate
724      // temporary path for the system.
725      $directories[] = FileSystemComponent::getOsTemporaryDirectory();
726    }
727  }
728
729  // Check the config directory if it is defined in settings.php. If it isn't
730  // defined, the installer will create a valid config directory later, but
731  // during runtime we must always display an error.
732  $config_sync_directory = Settings::get('config_sync_directory');
733  if (!empty($config_sync_directory)) {
734    // If we're installing Drupal try and create the config sync directory.
735    if (!is_dir($config_sync_directory) && $phase == 'install') {
736      \Drupal::service('file_system')->prepareDirectory($config_sync_directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
737    }
738    if (!is_dir($config_sync_directory)) {
739      if ($phase == 'install') {
740        $description = t('An automated attempt to create the directory %directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', ['%directory' => $config_sync_directory, ':handbook_url' => 'https://www.drupal.org/server-permissions']);
741      }
742      else {
743        $description = t('The directory %directory does not exist.', ['%directory' => $config_sync_directory]);
744      }
745      $requirements['config sync directory'] = [
746        'title' => t('Configuration sync directory'),
747        'description' => $description,
748        'severity' => REQUIREMENT_ERROR,
749      ];
750    }
751  }
752  if ($phase != 'install' && empty($config_sync_directory)) {
753    $requirements['config sync directory'] = [
754      'title' => t('Configuration sync directory'),
755      'value' => t('Not present'),
756      'description' => t("Your %file file must define the %setting setting as a string containing the directory in which configuration files can be found.", ['%file' => $site_path . '/settings.php', '%setting' => "\$settings['config_sync_directory']"]),
757      'severity' => REQUIREMENT_ERROR,
758    ];
759  }
760
761  // Handle other configuration directories. This will be removed in Drupal 9.
762  // See https://www.drupal.org/node/3018145.
763  $bc_config_directories = isset($GLOBALS['config_directories']) ? $GLOBALS['config_directories'] : [];
764  unset($bc_config_directories['sync']);
765  foreach (array_keys(array_filter($bc_config_directories)) as $type) {
766    @trigger_error("Automatic creation of '$type' configuration directory will be removed from drupal:9.0.0. See https://www.drupal.org/node/3018145.", E_USER_DEPRECATED);
767    $directory = config_get_config_directory($type);
768    // If we're installing Drupal try and create the config sync directory.
769    if (!is_dir($directory) && $phase == 'install') {
770      \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
771    }
772    if (!is_dir($directory)) {
773      if ($phase == 'install') {
774        $description = t('An automated attempt to create the directory %directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', ['%directory' => $directory, ':handbook_url' => 'https://www.drupal.org/server-permissions']);
775      }
776      else {
777        $description = t('The directory %directory does not exist.', ['%directory' => $directory]);
778      }
779      $requirements['config directory ' . $type] = [
780        'title' => t('Configuration directory: %type', ['%type' => $type]),
781        'description' => $description,
782        'severity' => REQUIREMENT_ERROR,
783      ];
784    }
785  }
786
787  $requirements['file system'] = [
788    'title' => t('File system'),
789  ];
790
791  $error = '';
792  // For installer, create the directories if possible.
793  foreach ($directories as $directory) {
794    if (!$directory) {
795      continue;
796    }
797    if ($phase == 'install') {
798      \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
799    }
800    $is_writable = is_writable($directory);
801    $is_directory = is_dir($directory);
802    if (!$is_writable || !$is_directory) {
803      $description = '';
804      $requirements['file system']['value'] = t('Not writable');
805      if (!$is_directory) {
806        $error = t('The directory %directory does not exist.', ['%directory' => $directory]);
807      }
808      else {
809        $error = t('The directory %directory is not writable.', ['%directory' => $directory]);
810      }
811      // The files directory requirement check is done only during install and runtime.
812      if ($phase == 'runtime') {
813        $description = t('You may need to set the correct directory at the <a href=":admin-file-system">file system settings page</a> or change the current directory\'s permissions so that it is writable.', [':admin-file-system' => Url::fromRoute('system.file_system_settings')->toString()]);
814      }
815      elseif ($phase == 'install') {
816        // For the installer UI, we need different wording. 'value' will
817        // be treated as version, so provide none there.
818        $description = t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', [':handbook_url' => 'https://www.drupal.org/server-permissions']);
819        $requirements['file system']['value'] = '';
820      }
821      if (!empty($description)) {
822        $description = [
823          '#type' => 'inline_template',
824          '#template' => '{{ error }} {{ description }}',
825          '#context' => [
826            'error' => $error,
827            'description' => $description,
828          ],
829        ];
830        $requirements['file system']['description'] = $description;
831        $requirements['file system']['severity'] = REQUIREMENT_ERROR;
832      }
833    }
834    else {
835      // This function can be called before the config_cache table has been
836      // created.
837      if ($phase == 'install' || \Drupal::config('system.file')->get('default_scheme') == 'public') {
838        $requirements['file system']['value'] = t('Writable (<em>public</em> download method)');
839      }
840      else {
841        $requirements['file system']['value'] = t('Writable (<em>private</em> download method)');
842      }
843    }
844  }
845
846  // See if updates are available in update.php.
847  if ($phase == 'runtime') {
848    $requirements['update'] = [
849      'title' => t('Database updates'),
850      'value' => t('Up to date'),
851    ];
852
853    // Check installed modules.
854    $has_pending_updates = FALSE;
855    foreach (\Drupal::moduleHandler()->getModuleList() as $module => $filename) {
856      $updates = drupal_get_schema_versions($module);
857      if ($updates !== FALSE) {
858        $default = drupal_get_installed_schema_version($module);
859        if (max($updates) > $default) {
860          $has_pending_updates = TRUE;
861          break;
862        }
863      }
864    }
865    if (!$has_pending_updates) {
866      /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
867      $post_update_registry = \Drupal::service('update.post_update_registry');
868      $missing_post_update_functions = $post_update_registry->getPendingUpdateFunctions();
869      if (!empty($missing_post_update_functions)) {
870        $has_pending_updates = TRUE;
871      }
872    }
873
874    if ($has_pending_updates) {
875      $requirements['update']['severity'] = REQUIREMENT_ERROR;
876      $requirements['update']['value'] = t('Out of date');
877      $requirements['update']['description'] = t('Some modules have database schema updates to install. You should run the <a href=":update">database update script</a> immediately.', [':update' => Url::fromRoute('system.db_update')->toString()]);
878    }
879
880    $requirements['entity_update'] = [
881      'title' => t('Entity/field definitions'),
882      'value' => t('Up to date'),
883    ];
884    // Verify that no entity updates are pending.
885    if ($change_list = \Drupal::entityDefinitionUpdateManager()->getChangeSummary()) {
886      $build = [];
887      foreach ($change_list as $entity_type_id => $changes) {
888        $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
889        $build[] = [
890          '#theme' => 'item_list',
891          '#title' => $entity_type->getLabel(),
892          '#items' => $changes,
893        ];
894      }
895
896      $entity_update_issues = \Drupal::service('renderer')->renderPlain($build);
897      $requirements['entity_update']['severity'] = REQUIREMENT_ERROR;
898      $requirements['entity_update']['value'] = t('Mismatched entity and/or field definitions');
899      $requirements['entity_update']['description'] = t('The following changes were detected in the entity type and field definitions. @updates', ['@updates' => $entity_update_issues]);
900    }
901  }
902
903  // Verify the update.php access setting
904  if ($phase == 'runtime') {
905    if (Settings::get('update_free_access')) {
906      $requirements['update access'] = [
907        'value' => t('Not protected'),
908        'severity' => REQUIREMENT_ERROR,
909        'description' => t('The update.php script is accessible to everyone without authentication check, which is a security risk. You must change the @settings_name value in your settings.php back to FALSE.', ['@settings_name' => '$settings[\'update_free_access\']']),
910      ];
911    }
912    else {
913      $requirements['update access'] = [
914        'value' => t('Protected'),
915      ];
916    }
917    $requirements['update access']['title'] = t('Access to update.php');
918  }
919
920  // Display an error if a newly introduced dependency in a module is not resolved.
921  if ($phase === 'update' || $phase === 'runtime') {
922    $create_extension_incompatibility_list = function ($extension_names, $description, $title) {
923      // Use an inline twig template to:
924      // - Concatenate two MarkupInterface objects and preserve safeness.
925      // - Use the item_list theme for the extension list.
926      $template = [
927        '#type' => 'inline_template',
928        '#template' => '{{ description }}{{ extensions }}',
929        '#context' => [
930          'extensions' => [
931            '#theme' => 'item_list',
932          ],
933        ],
934      ];
935      $template['#context']['extensions']['#items'] = $extension_names;
936      $template['#context']['description'] = $description;
937      return [
938        'title' => $title,
939        'value' => [
940          'list' => $template,
941          'handbook_link' => [
942            '#markup' => t(
943              'Review the <a href=":url"> suggestions for resolving this incompatibility</a> to repair your installation, and then re-run update.php.',
944              [':url' => 'https://www.drupal.org/docs/8/update/troubleshooting-database-updates']
945            ),
946          ],
947        ],
948        'severity' => REQUIREMENT_ERROR,
949      ];
950    };
951    $profile = \Drupal::installProfile();
952    $files = \Drupal::service('extension.list.module')->getList();
953    $files += \Drupal::service('extension.list.theme')->getList();
954    $core_incompatible_extensions = [];
955    $php_incompatible_extensions = [];
956    foreach ($files as $extension_name => $file) {
957      // Ignore uninstalled extensions and installation profiles.
958      if (!$file->status || $extension_name == $profile) {
959        continue;
960      }
961
962      $name = $file->info['name'];
963      if (!empty($file->info['core_incompatible'])) {
964        $core_incompatible_extensions[$file->info['type']][] = $name;
965      }
966
967      // Check the extension's PHP version.
968      $php = $file->info['php'];
969      if (version_compare($php, PHP_VERSION, '>')) {
970        $php_incompatible_extensions[$file->info['type']][] = $name;
971      }
972
973      // Check the module's required modules.
974      /** @var \Drupal\Core\Extension\Dependency $requirement */
975      foreach ($file->requires as $requirement) {
976        $required_module = $requirement->getName();
977        // Check if the module exists.
978        if (!isset($files[$required_module])) {
979          $requirements["$extension_name-$required_module"] = [
980            'title' => t('Unresolved dependency'),
981            'description' => t('@name requires this module.', ['@name' => $name]),
982            'value' => t('@required_name (Missing)', ['@required_name' => $required_module]),
983            'severity' => REQUIREMENT_ERROR,
984          ];
985          continue;
986        }
987        // Check for an incompatible version.
988        $required_file = $files[$required_module];
989        $required_name = $required_file->info['name'];
990        $version = str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $required_file->info['version']);
991        if (!$requirement->isCompatible($version)) {
992          $requirements["$extension_name-$required_module"] = [
993            'title' => t('Unresolved dependency'),
994            'description' => t('@name requires this module and version. Currently using @required_name version @version', ['@name' => $name, '@required_name' => $required_name, '@version' => $version]),
995            'value' => t('@required_name (Version @compatibility required)', ['@required_name' => $required_name, '@compatibility' => $requirement->getConstraintString()]),
996            'severity' => REQUIREMENT_ERROR,
997          ];
998          continue;
999        }
1000      }
1001    }
1002    if (!empty($core_incompatible_extensions['module'])) {
1003      $requirements['module_core_incompatible'] = $create_extension_incompatibility_list(
1004        $core_incompatible_extensions['module'],
1005        new PluralTranslatableMarkup(
1006          count($core_incompatible_extensions['module']),
1007        'The following module is installed, but it is incompatible with Drupal @version:',
1008        'The following modules are installed, but they are incompatible with Drupal @version:',
1009        ['@version' => \Drupal::VERSION]
1010        ),
1011        new PluralTranslatableMarkup(
1012          count($core_incompatible_extensions['module']),
1013          'Incompatible module',
1014          'Incompatible modules'
1015        )
1016      );
1017    }
1018    if (!empty($core_incompatible_extensions['theme'])) {
1019      $requirements['theme_core_incompatible'] = $create_extension_incompatibility_list(
1020        $core_incompatible_extensions['theme'],
1021        new PluralTranslatableMarkup(
1022          count($core_incompatible_extensions['theme']),
1023          'The following theme is installed, but it is incompatible with Drupal @version:',
1024          'The following themes are installed, but they are incompatible with Drupal @version:',
1025          ['@version' => \Drupal::VERSION]
1026        ),
1027        new PluralTranslatableMarkup(
1028          count($core_incompatible_extensions['theme']),
1029          'Incompatible theme',
1030          'Incompatible themes'
1031        )
1032      );
1033    }
1034    if (!empty($php_incompatible_extensions['module'])) {
1035      $requirements['module_php_incompatible'] = $create_extension_incompatibility_list(
1036        $php_incompatible_extensions['module'],
1037        new PluralTranslatableMarkup(
1038          count($php_incompatible_extensions['module']),
1039          'The following module is installed, but it is incompatible with PHP @version:',
1040          'The following modules are installed, but they are incompatible with PHP @version:',
1041          ['@version' => phpversion()]
1042        ),
1043        new PluralTranslatableMarkup(
1044          count($php_incompatible_extensions['module']),
1045          'Incompatible module',
1046          'Incompatible modules'
1047        )
1048      );
1049    }
1050    if (!empty($php_incompatible_extensions['theme'])) {
1051      $requirements['theme_php_incompatible'] = $create_extension_incompatibility_list(
1052        $php_incompatible_extensions['theme'],
1053        new PluralTranslatableMarkup(
1054          count($php_incompatible_extensions['theme']),
1055          'The following theme is installed, but it is incompatible with PHP @version:',
1056          'The following themes are installed, but they are incompatible with PHP @version:',
1057          ['@version' => phpversion()]
1058        ),
1059        new PluralTranslatableMarkup(
1060          count($php_incompatible_extensions['theme']),
1061          'Incompatible theme',
1062          'Incompatible themes'
1063        )
1064      );
1065    }
1066
1067    // Look for invalid modules.
1068    $extension_config = \Drupal::configFactory()->get('core.extension');
1069    /** @var \Drupal\Core\Extension\ExtensionList $extension_list */
1070    $extension_list = \Drupal::service('extension.list.module');
1071    $is_missing_extension = function ($extension_name) use (&$extension_list) {
1072      return !$extension_list->exists($extension_name);
1073    };
1074
1075    $invalid_modules = array_filter(array_keys($extension_config->get('module')), $is_missing_extension);
1076
1077    if (!empty($invalid_modules)) {
1078      $requirements['invalid_module'] = $create_extension_incompatibility_list(
1079        $invalid_modules,
1080        new PluralTranslatableMarkup(
1081          count($invalid_modules),
1082          'The following module is marked as installed in the core.extension configuration, but it is missing:',
1083          'The following modules are marked as installed in the core.extension configuration, but they are missing:'
1084        ),
1085        new PluralTranslatableMarkup(
1086          count($invalid_modules),
1087          'Missing or invalid module',
1088          'Missing or invalid modules'
1089        )
1090      );
1091    }
1092
1093    // Look for invalid themes.
1094    $extension_list = \Drupal::service('extension.list.theme');
1095    $invalid_themes = array_filter(array_keys($extension_config->get('theme')), $is_missing_extension);
1096    if (!empty($invalid_themes)) {
1097      $requirements['invalid_theme'] = $create_extension_incompatibility_list(
1098        $invalid_themes,
1099        new PluralTranslatableMarkup(
1100          count($invalid_themes),
1101          'The following theme is marked as installed in the core.extension configuration, but it is missing:',
1102          'The following themes are marked as installed in the core.extension configuration, but they are missing:'
1103        ),
1104        new PluralTranslatableMarkup(
1105          count($invalid_themes),
1106          'Missing or invalid theme',
1107          'Missing or invalid themes'
1108        )
1109      );
1110    }
1111  }
1112
1113  // Returns Unicode library status and errors.
1114  $libraries = [
1115    Unicode::STATUS_SINGLEBYTE => t('Standard PHP'),
1116    Unicode::STATUS_MULTIBYTE => t('PHP Mbstring Extension'),
1117    Unicode::STATUS_ERROR => t('Error'),
1118  ];
1119  $severities = [
1120    Unicode::STATUS_SINGLEBYTE => REQUIREMENT_WARNING,
1121    Unicode::STATUS_MULTIBYTE => NULL,
1122    Unicode::STATUS_ERROR => REQUIREMENT_ERROR,
1123  ];
1124  $failed_check = Unicode::check();
1125  $library = Unicode::getStatus();
1126
1127  $requirements['unicode'] = [
1128    'title' => t('Unicode library'),
1129    'value' => $libraries[$library],
1130    'severity' => $severities[$library],
1131  ];
1132  switch ($failed_check) {
1133    case 'mb_strlen':
1134      $requirements['unicode']['description'] = t('Operations on Unicode strings are emulated on a best-effort basis. Install the <a href="http://php.net/mbstring">PHP mbstring extension</a> for improved Unicode support.');
1135      break;
1136
1137    case 'mbstring.func_overload':
1138      $requirements['unicode']['description'] = t('Multibyte string function overloading in PHP is active and must be disabled. Check the php.ini <em>mbstring.func_overload</em> setting. Please refer to the <a href="http://php.net/mbstring">PHP mbstring documentation</a> for more information.');
1139      break;
1140
1141    case 'mbstring.encoding_translation':
1142      $requirements['unicode']['description'] = t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.encoding_translation</em> setting. Please refer to the <a href="http://php.net/mbstring">PHP mbstring documentation</a> for more information.');
1143      break;
1144
1145    case 'mbstring.http_input':
1146      $requirements['unicode']['description'] = t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_input</em> setting. Please refer to the <a href="http://php.net/mbstring">PHP mbstring documentation</a> for more information.');
1147      break;
1148
1149    case 'mbstring.http_output':
1150      $requirements['unicode']['description'] = t('Multibyte string output conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_output</em> setting. Please refer to the <a href="http://php.net/mbstring">PHP mbstring documentation</a> for more information.');
1151      break;
1152  }
1153
1154  if ($phase == 'runtime') {
1155    // Check for update status module.
1156    if (!\Drupal::moduleHandler()->moduleExists('update')) {
1157      $requirements['update status'] = [
1158        'value' => t('Not enabled'),
1159        'severity' => REQUIREMENT_WARNING,
1160        'description' => t('Update notifications are not enabled. It is <strong>highly recommended</strong> that you enable the Update Manager module from the <a href=":module">module administration page</a> in order to stay up-to-date on new releases. For more information, <a href=":update">Update status handbook page</a>.', [
1161          ':update' => 'https://www.drupal.org/documentation/modules/update',
1162          ':module' => Url::fromRoute('system.modules_list')->toString(),
1163        ]),
1164      ];
1165    }
1166    else {
1167      $requirements['update status'] = [
1168        'value' => t('Enabled'),
1169      ];
1170    }
1171    $requirements['update status']['title'] = t('Update notifications');
1172
1173    if (Settings::get('rebuild_access')) {
1174      $requirements['rebuild access'] = [
1175        'title' => t('Rebuild access'),
1176        'value' => t('Enabled'),
1177        'severity' => REQUIREMENT_ERROR,
1178        'description' => t('The rebuild_access setting is enabled in settings.php. It is recommended to have this setting disabled unless you are performing a rebuild.'),
1179      ];
1180    }
1181  }
1182
1183  // See if trusted hostnames have been configured, and warn the user if they
1184  // are not set.
1185  if ($phase == 'runtime') {
1186    $trusted_host_patterns = Settings::get('trusted_host_patterns');
1187    if (empty($trusted_host_patterns)) {
1188      $requirements['trusted_host_patterns'] = [
1189        'title' => t('Trusted Host Settings'),
1190        'value' => t('Not enabled'),
1191        'description' => t('The trusted_host_patterns setting is not configured in settings.php. This can lead to security vulnerabilities. It is <strong>highly recommended</strong> that you configure this. See <a href=":url">Protecting against HTTP HOST Header attacks</a> for more information.', [':url' => 'https://www.drupal.org/docs/8/install/trusted-host-settings']),
1192        'severity' => REQUIREMENT_ERROR,
1193      ];
1194    }
1195    else {
1196      $requirements['trusted_host_patterns'] = [
1197        'title' => t('Trusted Host Settings'),
1198        'value' => t('Enabled'),
1199        'description' => t('The trusted_host_patterns setting is set to allow %trusted_host_patterns', ['%trusted_host_patterns' => implode(', ', $trusted_host_patterns)]),
1200      ];
1201    }
1202  }
1203
1204  // Check xdebug.max_nesting_level, as some pages will not work if it is too
1205  // low.
1206  if (extension_loaded('xdebug')) {
1207    // Setting this value to 256 was considered adequate on Xdebug 2.3
1208    // (see http://bugs.xdebug.org/bug_view_page.php?bug_id=00001100)
1209    $minimum_nesting_level = 256;
1210    $current_nesting_level = ini_get('xdebug.max_nesting_level');
1211
1212    if ($current_nesting_level < $minimum_nesting_level) {
1213      $requirements['xdebug_max_nesting_level'] = [
1214        'title' => t('Xdebug settings'),
1215        'value' => t('xdebug.max_nesting_level is set to %value.', ['%value' => $current_nesting_level]),
1216        'description' => t('Set <code>xdebug.max_nesting_level=@level</code> in your PHP configuration as some pages in your Drupal site will not work when this setting is too low.', ['@level' => $minimum_nesting_level]),
1217        'severity' => REQUIREMENT_ERROR,
1218      ];
1219    }
1220  }
1221
1222  // Warning for httpoxy on IIS with affected PHP versions
1223  // @see https://www.drupal.org/node/2783079
1224  if (strpos($software, 'Microsoft-IIS') !== FALSE && version_compare(PHP_VERSION, '7.0.9', '<')) {
1225    $dom = new \DOMDocument('1.0', 'UTF-8');
1226    $webconfig = file_get_contents('web.config');
1227    // If you are here the web.config file must - of course - be well formed.
1228    // But the PHP DOM component will throw warnings on some XML compliant
1229    // stuff, so silently parse the configuration file.
1230    @$dom->loadHTML($webconfig);
1231    $httpoxy_rewrite = FALSE;
1232    foreach ($dom->getElementsByTagName('rule') as $rule) {
1233      foreach ($rule->attributes as $attr) {
1234        if (@$attr->name == 'name' && @$attr->nodeValue == 'Erase HTTP_PROXY') {
1235          $httpoxy_rewrite = TRUE;
1236          break 2;
1237        }
1238      }
1239    }
1240    if (!$httpoxy_rewrite) {
1241      $requirements['iis_httpoxy_protection'] = [
1242        'title' => t('IIS httpoxy protection'),
1243        'value' => t('Your PHP runtime version is affected by the httpoxy vulnerability.'),
1244        'description' => t('Either update your PHP runtime version or uncomment the "Erase HTTP_PROXY" rule in your web.config file and add HTTP_PROXY to the allowed headers list. See more details in the <a href=":link">security advisory</a>.', [':link' => 'https://www.drupal.org/SA-CORE-2016-003']),
1245        'severity' => REQUIREMENT_ERROR,
1246      ];
1247    }
1248  }
1249
1250  // Installations on Windows can run into limitations with MAX_PATH if the
1251  // Drupal root directory is too deep in the filesystem. Generally this shows
1252  // up in cached Twig templates and other public files with long directory or
1253  // file names. There is no definite root directory depth below which Drupal is
1254  // guaranteed to function correctly on Windows. Since problems are likely
1255  // with more than 100 characters in the Drupal root path, show an error.
1256  if (substr(PHP_OS, 0, 3) == 'WIN') {
1257    $depth = strlen(realpath(DRUPAL_ROOT . '/' . PublicStream::basePath()));
1258    if ($depth > 120) {
1259      $requirements['max_path_on_windows'] = [
1260        'title' => t('Windows installation depth'),
1261        'description' => t('The public files directory path is %depth characters. Paths longer than 120 characters will cause problems on Windows.', ['%depth' => $depth]),
1262        'severity' => REQUIREMENT_ERROR,
1263      ];
1264    }
1265  }
1266  // Check to see if dates will be limited to 1901-2038.
1267  if (PHP_INT_SIZE <= 4) {
1268    $requirements['limited_date_range'] = [
1269      'title' => t('Limited date range'),
1270      'value' => t('Your PHP installation has a limited date range.'),
1271      'description' => t('You are running on a system where PHP is compiled or limited to using 32-bit integers. This will limit the range of dates and timestamps to the years 1901-2038. Read about the <a href=":url">limitations of 32-bit PHP</a>.', [':url' => 'https://www.drupal.org/docs/8/system-requirements/limitations-of-32-bit-php']),
1272      'severity' => REQUIREMENT_WARNING,
1273    ];
1274  }
1275
1276  // During installs from configuration don't support install profiles that
1277  // implement hook_install.
1278  if ($phase == 'install' && !empty($install_state['config_install_path'])) {
1279    $install_hook = $install_state['parameters']['profile'] . '_install';
1280    if (function_exists($install_hook)) {
1281      $requirements['config_install'] = [
1282        'title' => t('Configuration install'),
1283        'value' => $install_state['parameters']['profile'],
1284        'description' => t('The selected profile has a hook_install() implementation and therefore can not be installed from configuration.'),
1285        'severity' => REQUIREMENT_ERROR,
1286      ];
1287    }
1288  }
1289
1290  if ($phase === 'runtime') {
1291    $settings = Settings::getAll();
1292    if (array_key_exists('install_profile', $settings)) {
1293      // The following message is only informational because not all site owners
1294      // have access to edit their settings.php as it may be controlled by their
1295      // hosting provider.
1296      $requirements['install_profile_in_settings'] = [
1297        'title' => t('Install profile in settings'),
1298        'value' => t("Drupal 8 no longer uses the \$settings['install_profile'] value in settings.php and it can be removed."),
1299        'severity' => REQUIREMENT_INFO,
1300      ];
1301    }
1302  }
1303
1304  // Prevent installation or update if the Pathauto module is installed and its
1305  // version is less than 1.6.
1306  if ($phase === 'install' || $phase === 'update') {
1307    if (\Drupal::moduleHandler()->moduleExists('pathauto')) {
1308      $info = \Drupal::service('extension.list.module')->getExtensionInfo('pathauto');
1309      if ($info['version'] && version_compare($info['version'], '8.x-1.5') <= 0) {
1310        $requirements['pathauto_module_incompatibility'] = [
1311          'title' => t('Pathauto'),
1312          'description' => t('The Pathauto module is not compatible with the current version of Drupal core. Update the <a href=":url">Pathauto</a> module to 8.x-1.6 or later.', [
1313            ':url' => 'https://drupal.org/project/pathauto',
1314          ]),
1315          'severity' => REQUIREMENT_ERROR,
1316        ];
1317      }
1318    }
1319  }
1320
1321  // Ensure that no module has a current schema version that is lower than the
1322  // one that was last removed.
1323  if ($phase == 'update') {
1324    $module_handler = \Drupal::moduleHandler();
1325    $module_list = [];
1326    foreach ($module_handler->getImplementations('update_last_removed') as $module) {
1327      $last_removed = $module_handler->invoke($module, 'update_last_removed');
1328      if ($last_removed && $last_removed > drupal_get_installed_schema_version($module)) {
1329
1330        /** @var \Drupal\Core\Extension\Extension $module_info */
1331        $module_info = \Drupal::service('extension.list.module')->get($module);
1332        $module_list[$module_info->info['package']][$module] = [
1333          'info' => $module_info,
1334          'last_removed' => $last_removed,
1335          'installed_version' => drupal_get_installed_schema_version($module),
1336        ];
1337      }
1338    }
1339
1340    foreach ($module_list as $package => $package_modules) {
1341      foreach ($package_modules as $module => $data) {
1342        $requirements[$module . '_update_last_removed'] = [
1343          'title' => t('Unsupported schema version: @module', ['@module' => $data['info']->info['name']]),
1344          'description' => t('The installed version of the %module module is too old to update. Update to an intermediate version first (last removed version: @last_removed_version, installed version: @installed_version).', [
1345            '%module' => $data['info']->info['name'],
1346            '@last_removed_version' => $data['last_removed'],
1347            '@installed_version' => $data['installed_version'],
1348          ]),
1349          'severity' => REQUIREMENT_ERROR,
1350        ];
1351      }
1352    }
1353    // Also check post-updates. Only do this if we're not already showing an
1354    // error for hook_update_N().
1355    if (empty($module_list)) {
1356      $existing_updates = \Drupal::service('keyvalue')->get('post_update')->get('existing_updates', []);
1357      $post_update_registry = \Drupal::service('update.post_update_registry');
1358      $modules = \Drupal::moduleHandler()->getModuleList();
1359      $module_extension_list = \Drupal::service('extension.list.module');
1360      foreach ($modules as $module => $extension) {
1361        $module_info = $module_extension_list->get($module);
1362        $removed_post_updates = $post_update_registry->getRemovedPostUpdates($module);
1363        if ($missing_updates = array_diff(array_keys($removed_post_updates), $existing_updates)) {
1364          $versions = array_unique(array_intersect_key($removed_post_updates, array_flip($missing_updates)));
1365          $description = new PluralTranslatableMarkup(count($versions),
1366            'The installed version of the %module module is too old to update. Update to a version prior to @versions first (missing updates: @missing_updates).',
1367            'The installed version of the %module module is too old to update. Update first to a version prior to all of the following: @versions (missing updates: @missing_updates).',
1368            [
1369              '%module' => $module_info->info['name'],
1370              '@missing_updates' => implode(', ', $missing_updates),
1371              '@versions' => implode(', ', $versions),
1372            ]
1373          );
1374          $requirements[$module . '_post_update_removed'] = [
1375            'title' => t('Missing updates for: @module', ['@module' => $module_info->info['name']]),
1376            'description' => $description,
1377            'severity' => REQUIREMENT_ERROR,
1378          ];
1379        }
1380      }
1381    }
1382  }
1383
1384  return $requirements;
1385}
1386
1387/**
1388 * Implements hook_install().
1389 */
1390function system_install() {
1391  // Populate the cron key state variable.
1392  $cron_key = Crypt::randomBytesBase64(55);
1393  \Drupal::state()->set('system.cron_key', $cron_key);
1394
1395  // Populate the site UUID and default name (if not set).
1396  $site = \Drupal::configFactory()->getEditable('system.site');
1397  $site->set('uuid', \Drupal::service('uuid')->generate());
1398  if (!$site->get('name')) {
1399    $site->set('name', 'Drupal');
1400  }
1401  $site->save(TRUE);
1402}
1403
1404/**
1405 * Implements hook_schema().
1406 */
1407function system_schema() {
1408  $schema['key_value'] = [
1409    'description' => 'Generic key-value storage table. See the state system for an example.',
1410    'fields' => [
1411      'collection' => [
1412        'description' => 'A named collection of key and value pairs.',
1413        'type' => 'varchar_ascii',
1414        'length' => 128,
1415        'not null' => TRUE,
1416        'default' => '',
1417      ],
1418      'name' => [
1419        'description' => 'The key of the key-value pair. As KEY is a SQL reserved keyword, name was chosen instead.',
1420        'type' => 'varchar_ascii',
1421        'length' => 128,
1422        'not null' => TRUE,
1423        'default' => '',
1424      ],
1425      'value' => [
1426        'description' => 'The value.',
1427        'type' => 'blob',
1428        'not null' => TRUE,
1429        'size' => 'big',
1430      ],
1431    ],
1432    'primary key' => ['collection', 'name'],
1433  ];
1434
1435  $schema['key_value_expire'] = [
1436    'description' => 'Generic key/value storage table with an expiration.',
1437    'fields' => [
1438      'collection' => [
1439        'description' => 'A named collection of key and value pairs.',
1440        'type' => 'varchar_ascii',
1441        'length' => 128,
1442        'not null' => TRUE,
1443        'default' => '',
1444      ],
1445      'name' => [
1446        // KEY is an SQL reserved word, so use 'name' as the key's field name.
1447        'description' => 'The key of the key/value pair.',
1448        'type' => 'varchar_ascii',
1449        'length' => 128,
1450        'not null' => TRUE,
1451        'default' => '',
1452      ],
1453      'value' => [
1454        'description' => 'The value of the key/value pair.',
1455        'type' => 'blob',
1456        'not null' => TRUE,
1457        'size' => 'big',
1458      ],
1459      'expire' => [
1460        'description' => 'The time since Unix epoch in seconds when this item expires. Defaults to the maximum possible time.',
1461        'type' => 'int',
1462        'not null' => TRUE,
1463        'default' => 2147483647,
1464      ],
1465    ],
1466    'primary key' => ['collection', 'name'],
1467    'indexes' => [
1468      'all' => ['name', 'collection', 'expire'],
1469      'expire' => ['expire'],
1470    ],
1471  ];
1472
1473  $schema['sequences'] = [
1474    'description' => 'Stores IDs.',
1475    'fields' => [
1476      'value' => [
1477        'description' => 'The value of the sequence.',
1478        'type' => 'serial',
1479        'unsigned' => TRUE,
1480        'not null' => TRUE,
1481      ],
1482     ],
1483    'primary key' => ['value'],
1484  ];
1485
1486  $schema['sessions'] = [
1487    'description' => "Drupal's session handlers read and write into the sessions table. Each record represents a user session, either anonymous or authenticated.",
1488    'fields' => [
1489      'uid' => [
1490        'description' => 'The {users}.uid corresponding to a session, or 0 for anonymous user.',
1491        'type' => 'int',
1492        'unsigned' => TRUE,
1493        'not null' => TRUE,
1494      ],
1495      'sid' => [
1496        'description' => "A session ID (hashed). The value is generated by Drupal's session handlers.",
1497        'type' => 'varchar_ascii',
1498        'length' => 128,
1499        'not null' => TRUE,
1500      ],
1501      'hostname' => [
1502        'description' => 'The IP address that last used this session ID (sid).',
1503        'type' => 'varchar_ascii',
1504        'length' => 128,
1505        'not null' => TRUE,
1506        'default' => '',
1507      ],
1508      'timestamp' => [
1509        'description' => 'The Unix timestamp when this session last requested a page. Old records are purged by PHP automatically.',
1510        'type' => 'int',
1511        'not null' => TRUE,
1512        'default' => 0,
1513      ],
1514      'session' => [
1515        'description' => 'The serialized contents of $_SESSION, an array of name/value pairs that persists across page requests by this session ID. Drupal loads $_SESSION from here at the start of each request and saves it at the end.',
1516        'type' => 'blob',
1517        'not null' => FALSE,
1518        'size' => 'big',
1519      ],
1520    ],
1521    'primary key' => [
1522      'sid',
1523    ],
1524    'indexes' => [
1525      'timestamp' => ['timestamp'],
1526      'uid' => ['uid'],
1527    ],
1528    'foreign keys' => [
1529      'session_user' => [
1530        'table' => 'users',
1531        'columns' => ['uid' => 'uid'],
1532      ],
1533    ],
1534  ];
1535
1536  return $schema;
1537}
1538
1539/**
1540 * Change two fields on the default menu link storage to be serialized data.
1541 */
1542function system_update_8001(&$sandbox = NULL) {
1543  $database = \Drupal::database();
1544  $schema = $database->schema();
1545  if ($schema->tableExists('menu_tree')) {
1546
1547    if (!isset($sandbox['current'])) {
1548      // Converting directly to blob can cause problems with reading out and
1549      // serializing the string data later on postgres, so rename the existing
1550      // columns and create replacement ones to hold the serialized objects.
1551      $old_fields = [
1552        'title' => [
1553          'description' => 'The text displayed for the link.',
1554          'type' => 'varchar',
1555          'length' => 255,
1556          'not null' => TRUE,
1557          'default' => '',
1558        ],
1559        'description' => [
1560          'description' => 'The description of this link - used for admin pages and title attribute.',
1561          'type' => 'text',
1562          'not null' => FALSE,
1563        ],
1564      ];
1565      foreach ($old_fields as $name => $spec) {
1566        $schema->changeField('menu_tree', $name, 'system_update_8001_' . $name, $spec);
1567      }
1568      $spec = [
1569        'description' => 'The title for the link. May be a serialized TranslatableMarkup.',
1570        'type' => 'blob',
1571        'size' => 'big',
1572        'not null' => FALSE,
1573        'serialize' => TRUE,
1574      ];
1575      $schema->addField('menu_tree', 'title', $spec);
1576      $spec = [
1577        'description' => 'The description of this link - used for admin pages and title attribute.',
1578        'type' => 'blob',
1579        'size' => 'big',
1580        'not null' => FALSE,
1581        'serialize' => TRUE,
1582      ];
1583      $schema->addField('menu_tree', 'description', $spec);
1584
1585      $sandbox['current'] = 0;
1586      $sandbox['max'] = $database->query('SELECT COUNT(mlid) FROM {menu_tree}')
1587        ->fetchField();
1588    }
1589
1590    $menu_links = $database->queryRange('SELECT mlid, system_update_8001_title AS title, system_update_8001_description AS description FROM {menu_tree} ORDER BY mlid ASC', $sandbox['current'], $sandbox['current'] + 50)
1591      ->fetchAllAssoc('mlid');
1592
1593    foreach ($menu_links as $menu_link) {
1594      $menu_link = (array) $menu_link;
1595      // Convert title and description to serialized strings.
1596      $menu_link['title'] = serialize($menu_link['title']);
1597      $menu_link['description'] = serialize($menu_link['description']);
1598
1599      $database->update('menu_tree')
1600        ->fields($menu_link)
1601        ->condition('mlid', $menu_link['mlid'])
1602        ->execute();
1603
1604      $sandbox['current']++;
1605    }
1606
1607    $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['current'] / $sandbox['max']);
1608
1609    if ($sandbox['#finished'] >= 1) {
1610      // Drop unnecessary fields from {menu_tree}.
1611      $schema->dropField('menu_tree', 'system_update_8001_title');
1612      $schema->dropField('menu_tree', 'title_arguments');
1613      $schema->dropField('menu_tree', 'title_context');
1614      $schema->dropField('menu_tree', 'system_update_8001_description');
1615    }
1616    return t('Menu links converted');
1617  }
1618  else {
1619    return t('Menu link conversion skipped, because the {menu_tree} table did not exist yet.');
1620  }
1621}
1622
1623/**
1624 * Removes the system.filter configuration.
1625 */
1626function system_update_8002() {
1627  \Drupal::configFactory()->getEditable('system.filter')->delete();
1628  return t('The system.filter configuration has been moved to a container parameter, see default.services.yml for more information.');
1629}
1630
1631/**
1632 * Change the index on the {router} table.
1633 */
1634function system_update_8003() {
1635  $database = \Drupal::database();
1636  $database->schema()->dropIndex('router', 'pattern_outline_fit');
1637  $database->schema()->addIndex(
1638    'router',
1639    'pattern_outline_parts',
1640    ['pattern_outline', 'number_parts'],
1641    [
1642      'fields' => [
1643        'pattern_outline' => [
1644          'description' => 'The pattern',
1645          'type' => 'varchar',
1646          'length' => 255,
1647          'not null' => TRUE,
1648          'default' => '',
1649        ],
1650        'number_parts' => [
1651          'description' => 'Number of parts in this router path.',
1652          'type' => 'int',
1653          'not null' => TRUE,
1654          'default' => 0,
1655          'size' => 'small',
1656        ],
1657      ],
1658    ]
1659  );
1660}
1661
1662/**
1663 * Add a (id, default_langcode, langcode) composite index to entities.
1664 */
1665function system_update_8004() {
1666  // \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema was changed in
1667  // https://www.drupal.org/node/2261669 to include a (id, default_langcode,
1668  // langcode) compound index, but this update function wasn't added until
1669  // https://www.drupal.org/node/2542748. Regenerate the related schemas to
1670  // ensure they match the currently expected status.
1671  $manager = \Drupal::entityDefinitionUpdateManager();
1672  foreach (array_keys(\Drupal::entityTypeManager()
1673    ->getDefinitions()) as $entity_type_id) {
1674    // Only update the entity type if it already exists. This condition is
1675    // needed in case new entity types are introduced after this update.
1676    if ($entity_type = $manager->getEntityType($entity_type_id)) {
1677      $manager->updateEntityType($entity_type);
1678    }
1679  }
1680}
1681
1682/**
1683 * Place local actions and tasks blocks in every theme.
1684 */
1685function system_update_8005() {
1686  // When block module is not installed, there is nothing that could be done
1687  // except showing a warning.
1688  if (!\Drupal::moduleHandler()->moduleExists('block')) {
1689    return t('Block module is not enabled so local actions and tasks which have been converted to blocks, are not visible anymore.');
1690  }
1691  $config_factory = \Drupal::configFactory();
1692  /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
1693  $theme_handler = \Drupal::service('theme_handler');
1694  $custom_themes_installed = FALSE;
1695  $message = NULL;
1696  $langcode = \Drupal::service('language_manager')->getCurrentLanguage()->getId();
1697
1698  $local_actions_default_settings = [
1699    'plugin' => 'local_actions_block',
1700    'region' => 'content',
1701    'settings.label' => 'Primary admin actions',
1702    'settings.label_display' => 0,
1703    'settings.cache.max_age' => 0,
1704    'visibility' => [],
1705    'weight' => 0,
1706    'langcode' => $langcode,
1707  ];
1708  $tabs_default_settings = [
1709    'plugin' => 'local_tasks_block',
1710    'region' => 'content',
1711    'settings.label' => 'Tabs',
1712    'settings.label_display' => 0,
1713    'settings.cache.max_age' => 0,
1714    'visibility' => [],
1715    'weight' => 0,
1716    'langcode' => $langcode,
1717  ];
1718  foreach ($theme_handler->listInfo() as $theme) {
1719    $theme_name = $theme->getName();
1720    switch ($theme_name) {
1721      case 'bartik':
1722        $name = 'block.block.bartik_local_actions';
1723        $values = [
1724          'id' => 'bartik_local_actions',
1725          'weight' => -1,
1726        ] + $local_actions_default_settings;
1727        _system_update_create_block($name, $theme_name, $values);
1728
1729        $name = 'block.block.bartik_local_tasks';
1730        $values = [
1731          'id' => 'bartik_local_tasks',
1732          'weight' => -7,
1733        ] + $tabs_default_settings;
1734        _system_update_create_block($name, $theme_name, $values);
1735
1736        // Help region has been removed so all the blocks inside has to be moved
1737        // to content region.
1738        $weight = -6;
1739        $blocks = [];
1740        foreach ($config_factory->listAll('block.block.') as $block_config) {
1741          $block = $config_factory->getEditable($block_config);
1742          if ($block->get('theme') == 'bartik' && $block->get('region') == 'help') {
1743            $blocks[] = $block;
1744          }
1745        }
1746        // Sort blocks by block weight.
1747        uasort($blocks, function ($a, $b) {
1748          return $a->get('weight') - $b->get('weight');
1749        });
1750        // Move blocks to content region and set them in right order by their
1751        // weight.
1752        foreach ($blocks as $block) {
1753          $block->set('region', 'content');
1754          $block->set('weight', $weight++);
1755          $block->save();
1756        }
1757        break;
1758
1759      case 'seven':
1760        $name = 'block.block.seven_local_actions';
1761        $values = [
1762          'id' => 'seven_local_actions',
1763          'weight' => -10,
1764        ] + $local_actions_default_settings;
1765        _system_update_create_block($name, $theme_name, $values);
1766
1767        $name = 'block.block.seven_primary_local_tasks';
1768        $values = [
1769          'region' => 'header',
1770          'id' => 'seven_primary_local_tasks',
1771          'settings.label' => 'Primary tabs',
1772          'settings.primary' => TRUE,
1773          'settings.secondary' => FALSE,
1774        ] + $tabs_default_settings;
1775        _system_update_create_block($name, $theme_name, $values);
1776
1777        $name = 'block.block.seven_secondary_local_tasks';
1778        $values = [
1779          'region' => 'pre_content',
1780          'id' => 'seven_secondary_local_tasks',
1781          'settings.label' => 'Secondary tabs',
1782          'settings.primary' => FALSE,
1783          'settings.secondary' => TRUE,
1784        ] + $tabs_default_settings;
1785        _system_update_create_block($name, $theme_name, $values);
1786        break;
1787
1788      case 'stark':
1789        $name = 'block.block.stark_local_actions';
1790        $values = [
1791          'id' => 'stark_local_actions',
1792        ] + $local_actions_default_settings;
1793        _system_update_create_block($name, $theme_name, $values);
1794
1795        $name = 'block.block.stark_local_tasks';
1796        $values = [
1797          'id' => 'stark_local_tasks',
1798        ] + $tabs_default_settings;
1799        _system_update_create_block($name, $theme_name, $values);
1800        break;
1801
1802      case 'classy':
1803      case 'stable':
1804        // Don't place any blocks or trigger custom themes installed warning.
1805        break;
1806
1807      default:
1808        $custom_themes_installed = TRUE;
1809        $name = 'block.block.' . $theme_name . '_local_actions';
1810        $values = [
1811          'id' => $theme_name . '_local_actions',
1812          'weight' => -10,
1813        ] + $local_actions_default_settings;
1814        _system_update_create_block($name, $theme_name, $values);
1815
1816        $name = sprintf('block.block.%s_local_tasks', $theme_name);
1817        $values = [
1818          'id' => $theme_name . '_local_tasks',
1819          'weight' => -20,
1820        ] + $tabs_default_settings;
1821        _system_update_create_block($name, $theme_name, $values);
1822        break;
1823    }
1824  }
1825
1826  if ($custom_themes_installed) {
1827    $message = t('Because your site has custom theme(s) installed, we had to set local actions and tasks blocks into the content region. Please manually review the block configurations and remove the removed variables from your templates.');
1828  }
1829
1830  return $message;
1831}
1832
1833/**
1834 * Place branding blocks in every theme.
1835 */
1836function system_update_8006() {
1837  // When block module is not installed, there is nothing that could be done
1838  // except showing a warning.
1839  if (!\Drupal::moduleHandler()->moduleExists('block')) {
1840    return t('Block module is not enabled so site branding elements, which have been converted to a block, are not visible anymore.');
1841  }
1842
1843  /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
1844  $theme_handler = \Drupal::service('theme_handler');
1845  $custom_themes_installed = FALSE;
1846  $message = NULL;
1847  $langcode = \Drupal::service('language_manager')->getCurrentLanguage()->getId();
1848
1849  $site_branding_default_settings = [
1850    'plugin' => 'system_branding_block',
1851    'region' => 'content',
1852    'settings.label' => 'Site branding',
1853    'settings.label_display' => 0,
1854    'visibility' => [],
1855    'weight' => 0,
1856    'langcode' => $langcode,
1857  ];
1858  foreach ($theme_handler->listInfo() as $theme) {
1859    $theme_name = $theme->getName();
1860    switch ($theme_name) {
1861      case 'bartik':
1862        $name = 'block.block.bartik_branding';
1863        $values = [
1864            'id' => 'bartik_branding',
1865            'region' => 'header',
1866          ] + $site_branding_default_settings;
1867        _system_update_create_block($name, $theme_name, $values);
1868        break;
1869
1870      case 'stark':
1871        $name = 'block.block.stark_branding';
1872        $values = [
1873            'id' => 'stark_branding',
1874            'region' => 'header',
1875          ] + $site_branding_default_settings;
1876        _system_update_create_block($name, $theme_name, $values);
1877        break;
1878
1879      case 'seven':
1880      case 'classy':
1881      case 'stable':
1882        // Don't place any blocks or trigger custom themes installed warning.
1883        break;
1884      default:
1885        $custom_themes_installed = TRUE;
1886        $name = sprintf('block.block.%s_branding', $theme_name);
1887        $values = [
1888            'id' => sprintf('%s_branding', $theme_name),
1889            'region' => 'content',
1890            'weight' => '-50',
1891          ] + $site_branding_default_settings;
1892        _system_update_create_block($name, $theme_name, $values);
1893        break;
1894    }
1895  }
1896
1897  if ($custom_themes_installed) {
1898    $message = t('Because your site has custom theme(s) installed, we had to set the branding block into the content region. Please manually review the block configuration and remove the site name, slogan, and logo variables from your templates.');
1899  }
1900
1901  return $message;
1902}
1903
1904/**
1905 * Helper function to create block configuration objects for an update.
1906 *
1907 * @param string $name
1908 *   The name of the config object.
1909 * @param string $theme_name
1910 *   The name of the theme the block is associated with.
1911 * @param array $values
1912 *   The block config values.
1913 */
1914function _system_update_create_block($name, $theme_name, array $values) {
1915  if (!\Drupal::service('config.storage')->exists($name)) {
1916    $block = \Drupal::configFactory()->getEditable($name);
1917    $values['uuid'] = \Drupal::service('uuid')->generate();
1918    $values['theme'] = $theme_name;
1919    $values['dependencies.theme'] = [$theme_name];
1920    foreach ($values as $key => $value) {
1921      $block->set($key, $value);
1922    }
1923    $block->save();
1924  }
1925}
1926
1927/**
1928 * Set langcode fields to be ASCII-only.
1929 */
1930function system_update_8007() {
1931  $database = \Drupal::database();
1932  $database_schema = $database->schema();
1933  $entity_types = \Drupal::entityTypeManager()->getDefinitions();
1934
1935  $schema = \Drupal::keyValue('entity.storage_schema.sql')->getAll();
1936  $schema_copy = $schema;
1937  foreach ($schema as $item_name => $item) {
1938    list($entity_type_id, ,) = explode('.', $item_name);
1939    if (!isset($entity_types[$entity_type_id])) {
1940      continue;
1941    }
1942    foreach ($item as $table_name => $table_schema) {
1943      foreach ($table_schema as $schema_key => $schema_data) {
1944        if ($schema_key == 'fields') {
1945          foreach ($schema_data as $field_name => $field_data) {
1946            foreach ($field_data as $field_data_property => $field_data_value) {
1947              // Langcode fields have the property 'is_ascii' set, instead
1948              // they should have set the type to 'varchar_ascii'.
1949              if ($field_data_property == 'is_ascii') {
1950                unset($schema_copy[$item_name][$table_name]['fields'][$field_name]['is_ascii']);
1951                $schema_copy[$item_name][$table_name]['fields'][$field_name]['type'] = 'varchar_ascii';
1952                if ($database->driver() == 'mysql') {
1953                  $database_schema->changeField($table_name, $field_name, $field_name, $schema_copy[$item_name][$table_name]['fields'][$field_name]);
1954                }
1955              }
1956            }
1957          }
1958        }
1959      }
1960    }
1961  }
1962  \Drupal::keyValue('entity.storage_schema.sql')->setMultiple($schema_copy);
1963
1964  $definitions = \Drupal::keyValue('entity.definitions.installed')->getAll();
1965  $definitions_copy = $definitions;
1966  foreach ($definitions as $item_name => $item_value) {
1967    $suffix = '.field_storage_definitions';
1968    if (substr($item_name, -strlen($suffix)) == $suffix) {
1969      foreach ($item_value as $field_name => $field_definition) {
1970        $reflection = new \ReflectionObject($field_definition);
1971        $schema_property = $reflection->getProperty('schema');
1972        $schema_property->setAccessible(TRUE);
1973        $schema = $schema_property->getValue($field_definition);
1974        if (isset($schema['columns']['value']['is_ascii'])) {
1975          $schema['columns']['value']['type'] = 'varchar_ascii';
1976          unset($schema['columns']['value']['is_ascii']);
1977        }
1978        $schema_property->setValue($field_definition, $schema);
1979        $schema_property->setAccessible(FALSE);
1980        $definitions_copy[$item_name][$field_name] = $field_definition;
1981      }
1982    }
1983  }
1984  \Drupal::keyValue('entity.definitions.installed')->setMultiple($definitions_copy);
1985}
1986
1987/**
1988 * Purge field schema data for uninstalled entity types.
1989 */
1990function system_update_8008() {
1991  $entity_types = \Drupal::entityTypeManager()->getDefinitions();
1992  /** @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface $schema */
1993  $schema = \Drupal::keyValue('entity.storage_schema.sql');
1994  foreach ($schema->getAll() as $key => $item) {
1995    list($entity_type_id, ,) = explode('.', $key);
1996    if (!isset($entity_types[$entity_type_id])) {
1997      $schema->delete($key);
1998    }
1999  }
2000}
2001
2002/**
2003 * Add allowed attributes to existing html filters.
2004 */
2005function system_update_8009() {
2006  $default_mapping = [
2007    '<a>' => '<a href hreflang>',
2008    '<blockquote>' => '<blockquote cite>',
2009    '<ol>' => '<ol start type>',
2010    '<ul>' => '<ul type>',
2011    '<img>' => '<img src alt height width>',
2012    '<h2>' => '<h2 id>',
2013    '<h3>' => '<h3 id>',
2014    '<h4>' => '<h4 id>',
2015    '<h5>' => '<h5 id>',
2016    '<h6>' => '<h6 id>',
2017  ];
2018  $config_factory = \Drupal::configFactory();
2019  foreach ($config_factory->listAll('filter.format') as $name) {
2020    $allowed_html_mapping = $default_mapping;
2021    $config = $config_factory->getEditable($name);
2022    // The image alignment filter needs the data-align attribute.
2023    $align_enabled = $config->get('filters.filter_align.status');
2024    if ($align_enabled) {
2025      $allowed_html_mapping['<img>'] = str_replace('>', ' data-align>', $allowed_html_mapping['<img>']);
2026    }
2027    // The image caption filter needs the data-caption attribute.
2028    $caption_enabled = $config->get('filters.filter_caption.status');
2029    if ($caption_enabled) {
2030      $allowed_html_mapping['<img>'] = str_replace('>', ' data-caption>', $allowed_html_mapping['<img>']);
2031    }
2032    $allowed_html = $config->get('filters.filter_html.settings.allowed_html');
2033    if (!empty($allowed_html)) {
2034      $allowed_html = strtr($allowed_html, $allowed_html_mapping);
2035      $config->set('filters.filter_html.settings.allowed_html', $allowed_html);
2036      $config->save();
2037    }
2038  }
2039}
2040
2041/**
2042 * Place page title blocks in every theme.
2043 */
2044function system_update_8010() {
2045  // When block module is not installed, there is nothing that could be done
2046  // except showing a warning.
2047  if (!\Drupal::moduleHandler()->moduleExists('block')) {
2048    return t('Block module is not enabled. The page title has been converted to a block, but default page title markup will still display at the top of the main content area.');
2049  }
2050
2051  /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
2052  $theme_handler = \Drupal::service('theme_handler');
2053  $custom_themes_installed = FALSE;
2054  $message = NULL;
2055  $langcode = \Drupal::service('language_manager')->getCurrentLanguage()->getId();
2056
2057  $page_title_default_settings = [
2058    'plugin' => 'page_title_block',
2059    'region' => 'content',
2060    'settings.label' => 'Page title',
2061    'settings.label_display' => 0,
2062    'visibility' => [],
2063    'weight' => -50,
2064    'langcode' => $langcode,
2065  ];
2066  foreach ($theme_handler->listInfo() as $theme) {
2067    $theme_name = $theme->getName();
2068    switch ($theme_name) {
2069      case 'bartik':
2070        $name = 'block.block.bartik_page_title';
2071        $values = [
2072          'id' => 'bartik_page_title',
2073        ] + $page_title_default_settings;
2074        _system_update_create_block($name, $theme_name, $values);
2075        break;
2076
2077      case 'stark':
2078        $name = 'block.block.stark_page_title';
2079        $values = [
2080          'id' => 'stark_page_title',
2081          'region' => 'content',
2082        ] + $page_title_default_settings;
2083        _system_update_create_block($name, $theme_name, $values);
2084        break;
2085
2086      case 'seven':
2087        $name = 'block.block.seven_page_title';
2088        $values = [
2089          'id' => 'seven_page_title',
2090          'region' => 'header',
2091        ] + $page_title_default_settings;
2092        _system_update_create_block($name, $theme_name, $values);
2093        break;
2094
2095      case 'classy':
2096        $name = 'block.block.classy_page_title';
2097        $values = [
2098          'id' => 'classy_page_title',
2099          'region' => 'content',
2100        ] + $page_title_default_settings;
2101        _system_update_create_block($name, $theme_name, $values);
2102        break;
2103
2104      default:
2105        $custom_themes_installed = TRUE;
2106        $name = sprintf('block.block.%s_page_title', $theme_name);
2107        $values = [
2108          'id' => sprintf('%s_page_title', $theme_name),
2109          'region' => 'content',
2110          'weight' => '-50',
2111        ] + $page_title_default_settings;
2112        _system_update_create_block($name, $theme_name, $values);
2113        break;
2114    }
2115  }
2116
2117  if ($custom_themes_installed) {
2118    $message = t('Because your site has custom theme(s) installed, we have placed the page title block in the content region. Please manually review the block configuration and remove the page title variables from your page templates.');
2119  }
2120
2121  return $message;
2122}
2123
2124/**
2125 * Add secondary local tasks block to Seven (fixes system_update_8005).
2126 */
2127function system_update_8011() {
2128  $langcode = \Drupal::service('language_manager')->getCurrentLanguage()->getId();
2129  $theme_name = 'seven';
2130  $name = 'block.block.seven_secondary_local_tasks';
2131  $values = [
2132      'plugin' => 'local_tasks_block',
2133      'region' => 'pre_content',
2134      'id' => 'seven_secondary_local_tasks',
2135      'settings.label' => 'Secondary tabs',
2136      'settings.label_display' => 0,
2137      'settings.primary' => FALSE,
2138      'settings.secondary' => TRUE,
2139      'visibility' => [],
2140      'weight' => 0,
2141      'langcode' => $langcode,
2142    ];
2143  _system_update_create_block($name, $theme_name, $values);
2144}
2145
2146/**
2147 * Enable automated cron module and move the config into it.
2148 */
2149function system_update_8013() {
2150  $autorun = \Drupal::configFactory()->getEditable('system.cron')->get('threshold.autorun');
2151  if ($autorun) {
2152    // Install 'automated_cron' module.
2153    \Drupal::service('module_installer')->install(['automated_cron'], FALSE);
2154
2155    // Copy 'autorun' value into the new module's 'interval' setting.
2156    \Drupal::configFactory()->getEditable('automated_cron.settings')
2157      ->set('interval', $autorun)
2158      ->save(TRUE);
2159  }
2160
2161  // Remove the 'autorun' key in system module config.
2162  \Drupal::configFactory()
2163    ->getEditable('system.cron')
2164    ->clear('threshold.autorun')
2165    ->save(TRUE);
2166}
2167
2168/**
2169 * Install the Stable base theme if needed.
2170 */
2171function system_update_8014() {
2172  $theme_handler = \Drupal::service('theme_handler');
2173  if ($theme_handler->themeExists('stable')) {
2174    return;
2175  }
2176  $theme_handler->refreshInfo();
2177  $theme_installer = \Drupal::service('theme_installer');
2178  foreach ($theme_handler->listInfo() as $theme) {
2179    // We first check that a base theme is set because if it's set to false then
2180    // it's unset in
2181    // \Drupal\Core\Extension\ThemeExtensionList::createExtensionInfo().
2182    if (isset($theme->info['base theme']) && $theme->info['base theme'] == 'stable') {
2183      $theme_installer->install(['stable']);
2184      return;
2185    }
2186  }
2187}
2188
2189/**
2190 * Fix configuration overrides to not override non existing keys.
2191 */
2192function system_update_8200(&$sandbox) {
2193  $config_factory = \Drupal::configFactory();
2194  if (!array_key_exists('config_names', $sandbox)) {
2195    $sandbox['config_names'] = $config_factory->listAll();
2196    $sandbox['max'] = count($sandbox['config_names']);
2197  }
2198
2199  // Get a list of 50 to work on at a time.
2200  $config_names_to_process = array_slice($sandbox['config_names'], 0, 50);
2201  // Preload in a single query.
2202  $config_factory->loadMultiple($config_names_to_process);
2203  foreach ($config_names_to_process as $config_name) {
2204    $config_factory->getEditable($config_name)->save();
2205  }
2206
2207  // Update the list of names to process.
2208  $sandbox['config_names'] = array_diff($sandbox['config_names'], $config_names_to_process);
2209  $sandbox['#finished'] = empty($sandbox['config_names']) ? 1 : ($sandbox['max'] - count($sandbox['config_names'])) / $sandbox['max'];
2210}
2211
2212/**
2213 * Clear caches due to behavior change in DefaultPluginManager.
2214 */
2215function system_update_8201() {
2216  // Empty update to cause a cache rebuild.
2217
2218  // Use hook_post_update_NAME() instead to clear the cache.
2219  // The use of hook_update_N() to clear the cache has been deprecated
2220  // see https://www.drupal.org/node/2960601 for more details.
2221}
2222
2223/**
2224 * Clear caches due to behavior change in MachineName element.
2225 */
2226function system_update_8202() {
2227  // Empty update to cause a cache rebuild.
2228
2229  // Use hook_post_update_NAME() instead to clear the cache.The use
2230  // of hook_update_N to clear the cache has been deprecated see
2231  // https://www.drupal.org/node/2960601 for more details.
2232}
2233
2234/**
2235 * Add detailed cron logging configuration.
2236 */
2237function system_update_8300() {
2238  \Drupal::configFactory()->getEditable('system.cron')
2239    ->set('logging', 1)
2240    ->save(TRUE);
2241}
2242
2243/**
2244 * Add install profile to core.extension configuration.
2245 */
2246function system_update_8301() {
2247  \Drupal::configFactory()->getEditable('core.extension')
2248    ->set('profile', \Drupal::installProfile())
2249    ->save();
2250}
2251
2252/**
2253 * Move revision metadata fields to the revision table.
2254 */
2255function system_update_8400(&$sandbox) {
2256  // Due to the fields from RevisionLogEntityTrait not being explicitly
2257  // mentioned in the storage they might have been installed wrongly in the base
2258  // table for revisionable untranslatable entities and in the data and revision
2259  // data tables for revisionable and translatable entities.
2260  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
2261  $database = \Drupal::database();
2262  $database_schema = $database->schema();
2263
2264  if (!isset($sandbox['current'])) {
2265    // This must be the first run. Initialize the sandbox.
2266    $sandbox['current'] = 0;
2267
2268    $definitions = array_filter(\Drupal::entityTypeManager()->getDefinitions(), function (EntityTypeInterface $entity_type) use ($entity_definition_update_manager) {
2269      if ($entity_type = $entity_definition_update_manager->getEntityType($entity_type->id())) {
2270        return is_subclass_of($entity_type->getClass(), FieldableEntityInterface::class) && ($entity_type instanceof ContentEntityTypeInterface) && $entity_type->isRevisionable();
2271      }
2272      return FALSE;
2273    });
2274    $sandbox['entity_type_ids'] = array_keys($definitions);
2275    $sandbox['max'] = count($sandbox['entity_type_ids']);
2276  }
2277
2278  $current_entity_type_key = $sandbox['current'];
2279  for ($i = $current_entity_type_key; ($i < $current_entity_type_key + 1) && ($i < $sandbox['max']); $i++) {
2280    $entity_type_id = $sandbox['entity_type_ids'][$i];
2281    /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
2282    $entity_type = $entity_definition_update_manager->getEntityType($entity_type_id);
2283
2284    $base_fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($entity_type_id);
2285    $revision_metadata_fields = $entity_type->getRevisionMetadataKeys();
2286    $fields_to_update = array_intersect_key($base_fields, array_flip($revision_metadata_fields));
2287
2288    if (!empty($fields_to_update)) {
2289      // Initialize the entity table names.
2290      // @see \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout()
2291      $base_table = $entity_type->getBaseTable() ?: $entity_type_id;
2292      $data_table = $entity_type->getDataTable() ?: $entity_type_id . '_field_data';
2293      $revision_table = $entity_type->getRevisionTable() ?: $entity_type_id . '_revision';
2294      $revision_data_table = $entity_type->getRevisionDataTable() ?: $entity_type_id . '_field_revision';
2295      $revision_field = $entity_type->getKey('revision');
2296
2297      // No data needs to be migrated if the entity type is not translatable.
2298      if ($entity_type->isTranslatable()) {
2299        if (!isset($sandbox[$entity_type_id])) {
2300          // This must be the first run for this entity type. Initialize the
2301          // sub-sandbox for it.
2302
2303          // Calculate the number of revisions to process.
2304          $count = \Drupal::entityQuery($entity_type_id)
2305            ->allRevisions()
2306            ->count()
2307            ->accessCheck(FALSE)
2308            ->execute();
2309
2310          $sandbox[$entity_type_id]['current'] = 0;
2311          $sandbox[$entity_type_id]['max'] = $count;
2312        }
2313        // Define the step size.
2314        $steps = Settings::get('entity_update_batch_size', 50);
2315
2316        // Collect the revision IDs to process.
2317        $revisions = \Drupal::entityQuery($entity_type_id)
2318          ->allRevisions()
2319          ->range($sandbox[$entity_type_id]['current'], $sandbox[$entity_type_id]['current'] + $steps)
2320          ->sort($revision_field, 'ASC')
2321          ->accessCheck(FALSE)
2322          ->execute();
2323        $revisions = array_keys($revisions);
2324
2325        foreach ($fields_to_update as $revision_metadata_field_name => $definition) {
2326          // If the revision metadata field is present in the data and the
2327          // revision data table, install its definition again with the updated
2328          // storage code in order for the field to be installed in the
2329          // revision table. Afterwards, copy over the field values and remove
2330          // the field from the data and the revision data tables.
2331          if ($database_schema->fieldExists($data_table, $revision_metadata_field_name) && $database_schema->fieldExists($revision_data_table, $revision_metadata_field_name)) {
2332            // Install the field in the revision table.
2333            if (!isset($sandbox[$entity_type_id]['storage_definition_installed'][$revision_metadata_field_name])) {
2334              $entity_definition_update_manager->installFieldStorageDefinition($revision_metadata_field_name, $entity_type_id, $entity_type->getProvider(), $definition);
2335              $sandbox[$entity_type_id]['storage_definition_installed'][$revision_metadata_field_name] = TRUE;
2336            }
2337
2338            // Apply the field value from the revision data table to the
2339            // revision table.
2340            foreach ($revisions as $rev_id) {
2341              $field_value = $database->select($revision_data_table, 't')
2342                ->fields('t', [$revision_metadata_field_name])
2343                ->condition($revision_field, $rev_id)
2344                ->execute()
2345                ->fetchField();
2346              $database->update($revision_table)
2347                ->condition($revision_field, $rev_id)
2348                ->fields([$revision_metadata_field_name => $field_value])
2349                ->execute();
2350            }
2351          }
2352        }
2353
2354        $sandbox[$entity_type_id]['current'] += count($revisions);
2355        $sandbox[$entity_type_id]['finished'] = ($sandbox[$entity_type_id]['current'] == $sandbox[$entity_type_id]['max']) || empty($revisions);
2356
2357        if ($sandbox[$entity_type_id]['finished']) {
2358          foreach ($fields_to_update as $revision_metadata_field_name => $definition) {
2359            // Drop the field from the data and revision data tables.
2360            $database_schema->dropField($data_table, $revision_metadata_field_name);
2361            $database_schema->dropField($revision_data_table, $revision_metadata_field_name);
2362          }
2363          $sandbox['current']++;
2364        }
2365      }
2366      else {
2367        foreach ($fields_to_update as $revision_metadata_field_name => $definition) {
2368          if ($database_schema->fieldExists($base_table, $revision_metadata_field_name)) {
2369            // Install the field in the revision table.
2370            $entity_definition_update_manager->installFieldStorageDefinition($revision_metadata_field_name, $entity_type_id, $entity_type->getProvider(), $definition);
2371            // Drop the field from the base table.
2372            $database_schema->dropField($base_table, $revision_metadata_field_name);
2373          }
2374        }
2375        $sandbox['current']++;
2376      }
2377    }
2378    else {
2379      $sandbox['current']++;
2380    }
2381
2382  }
2383
2384  $sandbox['#finished'] = $sandbox['current'] == $sandbox['max'];
2385}
2386
2387/**
2388 * Remove response.gzip (and response) from system module configuration.
2389 */
2390function system_update_8401() {
2391  \Drupal::configFactory()->getEditable('system.performance')
2392    ->clear('response.gzip')
2393    ->clear('response')
2394    ->save();
2395}
2396
2397/**
2398 * Add the 'revision_translation_affected' field to all entity types.
2399 */
2400function system_update_8402() {
2401  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
2402
2403  // Clear the cached entity type definitions so we get the new
2404  // 'revision_translation_affected' entity key.
2405  \Drupal::entityTypeManager()->clearCachedDefinitions();
2406
2407  // Get a list of revisionable and translatable entity types.
2408  /** @var \Drupal\Core\Entity\ContentEntityTypeInterface[] $definitions */
2409  $definitions = array_filter(\Drupal::entityTypeManager()->getDefinitions(), function (EntityTypeInterface $entity_type) use ($definition_update_manager) {
2410    if ($entity_type = $definition_update_manager->getEntityType($entity_type->id())) {
2411      return $entity_type->isRevisionable() && $entity_type->isTranslatable();
2412    }
2413    return FALSE;
2414  });
2415
2416  foreach ($definitions as $entity_type_id => $entity_type) {
2417    $field_name = $entity_type->getKey('revision_translation_affected');
2418    // Install the 'revision_translation_affected' field if needed.
2419    if (!$definition_update_manager->getFieldStorageDefinition($field_name, $entity_type_id)) {
2420      $storage_definition = BaseFieldDefinition::create('boolean')
2421        ->setLabel(t('Revision translation affected'))
2422        ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.'))
2423        ->setReadOnly(TRUE)
2424        ->setRevisionable(TRUE)
2425        ->setTranslatable(TRUE)
2426        // Mark all pre-existing revisions as affected in order to be consistent
2427        // with the previous API return value: if the field was not defined the
2428        // value returned was always TRUE.
2429        ->setInitialValue(TRUE);
2430
2431      $definition_update_manager
2432        ->installFieldStorageDefinition($field_name, $entity_type_id, $entity_type_id, $storage_definition);
2433    }
2434  }
2435}
2436
2437/**
2438 * Delete all cache_* tables. They are recreated on demand with the new schema.
2439 */
2440function system_update_8403() {
2441  foreach (Cache::getBins() as $bin => $cache_backend) {
2442    // Try to delete the table regardless of which cache backend is handling it.
2443    // This is to ensure the new schema is used if the configuration for the
2444    // backend class is changed after the update hook runs.
2445    $table_name = "cache_$bin";
2446    $schema = Database::getConnection()->schema();
2447    if ($schema->tableExists($table_name)) {
2448      $schema->dropTable($table_name);
2449    }
2450  }
2451}
2452
2453/**
2454 * Add the 'revision_default' field to all relevant entity types.
2455 */
2456function system_update_8501() {
2457  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
2458
2459  // Clear the cached entity type definitions so we get the new
2460  // 'revision_default' revision metadata key.
2461  \Drupal::entityTypeManager()->clearCachedDefinitions();
2462
2463  // Get a list of revisionable entity types.
2464  /** @var \Drupal\Core\Entity\ContentEntityTypeInterface[] $definitions */
2465  $definitions = array_filter(\Drupal::entityTypeManager()->getDefinitions(), function (EntityTypeInterface $entity_type) use ($definition_update_manager) {
2466    if ($entity_type = $definition_update_manager->getEntityType($entity_type->id())) {
2467      return $entity_type->isRevisionable();
2468    }
2469    return FALSE;
2470  });
2471
2472  // Install the 'revision_default' field.
2473  foreach ($definitions as $entity_type_id => $entity_type) {
2474    $field_name = $entity_type->getRevisionMetadataKey('revision_default');
2475    // Install the 'revision_default' field if needed.
2476    if (!$definition_update_manager->getFieldStorageDefinition($field_name, $entity_type_id)) {
2477      // Make sure the new "revision_default" revision metadata key is available
2478      // also to code using the latest installed definition.
2479      $installed_entity_type = $definition_update_manager->getEntityType($entity_type_id);
2480      $revision_metadata_keys = $installed_entity_type->get('revision_metadata_keys');
2481
2482      if (!isset($revision_metadata_keys['revision_default'])) {
2483        // Update the property holding the required revision metadata keys,
2484        // which is used by the BC layer for retrieving the revision metadata
2485        // keys.
2486        // @see \Drupal\Core\Entity\ContentEntityType::getRevisionMetadataKeys().
2487        $required_revision_metadata_keys = $installed_entity_type->get('requiredRevisionMetadataKeys');
2488        $required_revision_metadata_keys['revision_default'] = $field_name;
2489        $installed_entity_type->set('requiredRevisionMetadataKeys', $required_revision_metadata_keys);
2490
2491        // Update the revision metadata keys to add the new required revision
2492        // metadata key "revision_default".
2493        $revision_metadata_keys['revision_default'] = $required_revision_metadata_keys['revision_default'];
2494        $installed_entity_type->set('revision_metadata_keys', $revision_metadata_keys);
2495
2496        $definition_update_manager->updateEntityType($installed_entity_type);
2497      }
2498
2499      $storage_definition = BaseFieldDefinition::create('boolean')
2500        ->setLabel(t('Default revision'))
2501        ->setDescription(t('A flag indicating whether this was a default revision when it was saved.'))
2502        ->setStorageRequired(TRUE)
2503        ->setTranslatable(FALSE)
2504        ->setRevisionable(TRUE)
2505        // We cannot tell whether existing revisions were default or not when
2506        // they were created, but since we did not support creating non-default
2507        // revisions in any core stable UI so far, we default to TRUE.
2508        ->setInitialValue(TRUE);
2509
2510      $definition_update_manager
2511        ->installFieldStorageDefinition($field_name, $entity_type_id, $entity_type_id, $storage_definition);
2512    }
2513    else {
2514      $variables = ['@entity_type_label' => $entity_type->getLabel()];
2515      if ($field_name === 'revision_default') {
2516        \Drupal::logger('system')->error('An existing "Default revision" field was found for the @entity_type_label entity type, but no "revision_default" revision metadata key was found in its definition.', $variables);
2517      }
2518      else {
2519        \Drupal::logger('system')->error('An existing "Default revision" field was found for the @entity_type_label entity type.', $variables);
2520      }
2521    }
2522  }
2523}
2524
2525/**
2526* Fix missing install profile after updating to Drupal 8.6.9 with Drush 8.
2527*/
2528function system_update_8601() {
2529  $extension_config = \Drupal::configFactory()->getEditable('core.extension');
2530  $install_profile = $extension_config->get('profile');
2531  if (!$install_profile) {
2532    // There's no install profile configured.
2533    return;
2534  }
2535  $modules = $extension_config->get('module');
2536  if (isset($modules[$install_profile])) {
2537    // The install profile is already in the installed module list.
2538    return;
2539  }
2540
2541  // Ensure the install profile is available.
2542  if (!\Drupal::service('extension.list.module')->exists($install_profile)) {
2543    return t('The %install_profile install profile configured in core.extension is not available.', ['%install_profile' => $install_profile]);
2544  }
2545
2546  // Add the install profile to the list of enabled modules.
2547  $modules[$install_profile] = 1000;
2548  $modules = module_config_sort($modules);
2549  $extension_config
2550    ->set('module', $modules)
2551    ->save(TRUE);
2552
2553  // Build a module list from the updated extension configuration.
2554  $current_module_filenames = \Drupal::moduleHandler()->getModuleList();
2555  $current_modules = array_fill_keys(array_keys($current_module_filenames), 0);
2556  $current_modules = module_config_sort(array_merge($current_modules, $extension_config->get('module')));
2557  $module_filenames = [];
2558  foreach ($current_modules as $name => $weight) {
2559    if (isset($current_module_filenames[$name])) {
2560      $module_filenames[$name] = $current_module_filenames[$name];
2561    }
2562    else {
2563      $module_path = \Drupal::service('extension.list.module')->getPath($name);
2564      $pathname = "$module_path/$name.info.yml";
2565      $filename = file_exists($module_path . "/$name.module") ? "$name.module" : NULL;
2566      $module_filenames[$name] = new Extension(\Drupal::root(), 'module', $pathname, $filename);
2567    }
2568  }
2569
2570  // Update the module handler list to contain the missing install profile.
2571  \Drupal::moduleHandler()->setModuleList($module_filenames);
2572  \Drupal::moduleHandler()->load($install_profile);
2573
2574  // Clear the static cache of the "extension.list.module" service to pick
2575  // up the new install profile correctly.
2576  \Drupal::service('extension.list.profile')->reset();
2577
2578  // Clear the static cache of the "extension.list.module" service to pick
2579  // up the new module, since it merges the installation status of modules
2580  // into its statically cached list.
2581  \Drupal::service('extension.list.module')->reset();
2582
2583  // Update the kernel to include the missing profile.
2584  \Drupal::service('kernel')->updateModules($module_filenames, $module_filenames);
2585
2586  return t('The %install_profile install profile has been added to the installed module list.', ['%install_profile' => $install_profile]);
2587}
2588
2589/**
2590 * Remove the unused 'system.theme.data' from state.
2591 */
2592function system_update_8701() {
2593  // The system.theme.data key is no longer used in Drupal 8.7.x.
2594  \Drupal::state()->delete('system.theme.data');
2595}
2596
2597/**
2598 * Add the 'revision_translation_affected' entity key.
2599 */
2600function system_update_8702() {
2601  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
2602
2603  // Get a list of revisionable and translatable entity types.
2604  /** @var \Drupal\Core\Entity\EntityTypeInterface[] $last_installed_definitions */
2605  $last_installed_definitions = array_filter($entity_definition_update_manager->getEntityTypes(), function (EntityTypeInterface $entity_type) {
2606    return $entity_type->isRevisionable() && $entity_type->isTranslatable();
2607  });
2608
2609  // Ensure that we don't use the cached in-code definitions to support sites
2610  // that might be updating from 8.3.x straight to 8.7.x.
2611  \Drupal::entityTypeManager()->useCaches(FALSE);
2612  $live_definitions = \Drupal::entityTypeManager()->getDefinitions();
2613
2614  // Update the 'revision_translation_affected' entity key of the last installed
2615  // definitions to use the value of the live (in-code) entity type definitions
2616  // in cases when the key has not been populated yet.
2617  foreach ($last_installed_definitions as $entity_type_id => $entity_type) {
2618    // The live (in-code) entity type definition might not exist anymore, while
2619    // an update function that would remove its last installed definition didn't
2620    // run yet. We don't need to update it in that case.
2621    if (!isset($live_definitions[$entity_type_id])) {
2622      continue;
2623    }
2624
2625    $revision_translation_affected_key = $live_definitions[$entity_type_id]->getKey('revision_translation_affected');
2626    if (!$entity_type->hasKey('revision_translation_affected') && !empty($revision_translation_affected_key) && $entity_definition_update_manager->getFieldStorageDefinition($revision_translation_affected_key, $entity_type_id)) {
2627      $entity_keys = $entity_type->getKeys();
2628      $entity_keys['revision_translation_affected'] = $revision_translation_affected_key;
2629      $entity_type->set('entity_keys', $entity_keys);
2630      $entity_definition_update_manager->updateEntityType($entity_type);
2631    }
2632  }
2633  \Drupal::entityTypeManager()->useCaches(TRUE);
2634}
2635
2636/**
2637 * Remove 'path.temporary' config if redundant.
2638 */
2639function system_update_8801() {
2640  // If settings is already being used, or the config is set to the OS default,
2641  // clear the config value.
2642  $config = Drupal::configFactory()->getEditable('system.file');
2643  if (Settings::get('file_temp_path') || $config->get('path.temporary') === FileSystemComponent::getOsTemporaryDirectory()) {
2644    $config->clear('path.temporary')
2645      ->save(TRUE);
2646  }
2647}
2648
2649/**
2650 * Fix system.theme:admin when the default theme is used as the admin theme.
2651 */
2652function system_update_8802() {
2653  $config = Drupal::configFactory()->getEditable('system.theme');
2654  // Replace '0' with an empty string as '0' is not a valid value.
2655  if ($config->get('admin') == '0') {
2656    $config
2657      ->set('admin', '')
2658      ->save(TRUE);
2659  }
2660}
2661
2662/**
2663 * Install the 'path_alias' entity type.
2664 */
2665function system_update_8803() {
2666  // Enable the Path Alias module if needed.
2667  if (!\Drupal::moduleHandler()->moduleExists('path_alias')) {
2668    \Drupal::service('module_installer')->install(['path_alias'], FALSE);
2669    return t('The "path_alias" entity type has been installed.');
2670  }
2671}
2672
2673/**
2674 * Convert path aliases to entities.
2675 */
2676function system_update_8804(&$sandbox = NULL) {
2677  // Bail out early if the entity type is not using the default storage class.
2678  $storage = \Drupal::entityTypeManager()->getStorage('path_alias');
2679  if (!$storage instanceof PathAliasStorage) {
2680    return;
2681  }
2682
2683  if (!isset($sandbox['current_id'])) {
2684    // This must be the first run. Initialize the sandbox.
2685    $sandbox['progress'] = 0;
2686    $sandbox['current_id'] = 0;
2687  }
2688
2689  $database = \Drupal::database();
2690  $step_size = 200;
2691  $url_aliases = $database->select('url_alias', 't')
2692    ->condition('t.pid', $sandbox['current_id'], '>')
2693    ->fields('t')
2694    ->orderBy('pid', 'ASC')
2695    ->range(0, $step_size)
2696    ->execute()
2697    ->fetchAll();
2698
2699  if ($url_aliases) {
2700    /** @var \Drupal\Component\Uuid\UuidInterface $uuid */
2701    $uuid = \Drupal::service('uuid');
2702
2703    $base_table_insert = $database->insert('path_alias');
2704    $base_table_insert->fields(['id', 'revision_id', 'uuid', 'path', 'alias', 'langcode', 'status']);
2705    $revision_table_insert = $database->insert('path_alias_revision');
2706    $revision_table_insert->fields(['id', 'revision_id', 'path', 'alias', 'langcode', 'status', 'revision_default']);
2707    foreach ($url_aliases as $url_alias) {
2708      $values = [
2709        'id' => $url_alias->pid,
2710        'revision_id' => $url_alias->pid,
2711        'uuid' => $uuid->generate(),
2712        'path' => $url_alias->source,
2713        'alias' => $url_alias->alias,
2714        'langcode' => $url_alias->langcode,
2715        'status' => 1,
2716      ];
2717      $base_table_insert->values($values);
2718
2719      unset($values['uuid']);
2720      $values['revision_default'] = 1;
2721      $revision_table_insert->values($values);
2722    }
2723    $base_table_insert->execute();
2724    $revision_table_insert->execute();
2725
2726    $sandbox['progress'] += count($url_aliases);
2727    $last_url_alias = end($url_aliases);
2728    $sandbox['current_id'] = $last_url_alias->pid;
2729
2730    // If we're not in maintenance mode, the number of path aliases could change
2731    // at any time so make sure that we always use the latest record count.
2732    $missing = $database->select('url_alias', 't')
2733      ->condition('t.pid', $sandbox['current_id'], '>')
2734      ->orderBy('pid', 'ASC')
2735      ->countQuery()
2736      ->execute()
2737      ->fetchField();
2738    $sandbox['#finished'] = $missing ? $sandbox['progress'] / ($sandbox['progress'] + (int) $missing) : 1;
2739  }
2740  else {
2741    $sandbox['#finished'] = 1;
2742  }
2743
2744  if ($sandbox['#finished'] >= 1) {
2745    // Keep a backup of the old 'url_alias' table if requested.
2746    if (Settings::get('entity_update_backup', TRUE)) {
2747      $old_table_name = 'old_' . substr(uniqid(), 0, 6) . '_url_alias';
2748      if (!$database->schema()->tableExists($old_table_name)) {
2749        $database->schema()->renameTable('url_alias', $old_table_name);
2750      }
2751    }
2752    else {
2753      $database->schema()->dropTable('url_alias');
2754    }
2755
2756    return t('Path aliases have been converted to entities.');
2757  }
2758}
2759
2760/**
2761 * Change the provider of the 'path_alias' entity type and its base fields.
2762 */
2763function system_update_8805() {
2764  // If the path alias module is not installed, it means that
2765  // system_update_8803() ran as part of Drupal 8.8.0-alpha1, in which case we
2766  // need to enable the module and change the provider of the 'path_alias'
2767  // entity type.
2768  if (!\Drupal::moduleHandler()->moduleExists('path_alias')) {
2769    \Drupal::service('module_installer')->install(['path_alias'], FALSE);
2770
2771    /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
2772    $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
2773    $entity_type = $last_installed_schema_repository->getLastInstalledDefinition('path_alias');
2774
2775    // Set the new class for the entity type.
2776    $entity_type->setClass(PathAlias::class);
2777
2778    // Set the new provider for the entity type.
2779    $reflection = new ReflectionClass($entity_type);
2780    $property = $reflection->getProperty('provider');
2781    $property->setAccessible(TRUE);
2782    $property->setValue($entity_type, 'path_alias');
2783
2784    $last_installed_schema_repository->setLastInstalledDefinition($entity_type);
2785
2786    $field_storage_definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions('path_alias');
2787    foreach ($field_storage_definitions as $field_storage_definition) {
2788      if ($field_storage_definition->isBaseField()) {
2789        $field_storage_definition->setProvider('path_alias');
2790        $last_installed_schema_repository->setLastInstalledFieldStorageDefinition($field_storage_definition);
2791      }
2792    }
2793  }
2794}
2795
2796/**
2797 * Update the stored schema data for entity identifier fields.
2798 */
2799function system_update_8901() {
2800  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
2801  $entity_type_manager = \Drupal::entityTypeManager();
2802  $installed_storage_schema = \Drupal::keyValue('entity.storage_schema.sql');
2803
2804  foreach ($definition_update_manager->getEntityTypes() as $entity_type_id => $entity_type) {
2805    // Ensure that we are dealing with a non-deleted entity type that uses the
2806    // default SQL storage.
2807    if (!$entity_type_manager->hasDefinition($entity_type_id)) {
2808      continue;
2809    }
2810
2811    $storage = $entity_type_manager->getStorage($entity_type_id);
2812    if (!$storage instanceof SqlContentEntityStorage) {
2813      continue;
2814    }
2815
2816    foreach (['id', 'revision'] as $key) {
2817      if (!$entity_type->hasKey($key)) {
2818        continue;
2819      }
2820
2821      $field_name = $entity_type->getKey($key);
2822      $field_storage_definition = $definition_update_manager->getFieldStorageDefinition($field_name, $entity_type_id);
2823      if (!$field_storage_definition) {
2824        continue;
2825      }
2826      if ($field_storage_definition->getType() !== 'integer') {
2827        continue;
2828      }
2829
2830      // Retrieve the storage schema in order to use its own method for updating
2831      // the identifier schema - ::processIdentifierSchema(). This is needed
2832      // because some storage schemas might not use serial identifiers.
2833      // @see \Drupal\user\UserStorageSchema::processIdentifierSchema()
2834      $ref_get_storage_schema = new \ReflectionMethod($storage, 'getStorageSchema');
2835      $ref_get_storage_schema->setAccessible(TRUE);
2836      $storage_schema = $ref_get_storage_schema->invoke($storage);
2837
2838      if ($storage_schema instanceof SqlContentEntityStorageSchema) {
2839        $field_schema_data = $installed_storage_schema->get($entity_type_id . '.field_schema_data.' . $field_storage_definition->getName(), []);
2840        $table = $key === 'id' ? $entity_type->getBaseTable() : $entity_type->getRevisionTable();
2841
2842        $ref_process_identifier_schema = new \ReflectionMethod($storage_schema, 'processIdentifierSchema');
2843        $ref_process_identifier_schema->setAccessible(TRUE);
2844        $ref_process_identifier_schema->invokeArgs($storage_schema, [&$field_schema_data[$table], $field_name]);
2845
2846        $installed_storage_schema->set($entity_type_id . '.field_schema_data.' . $field_storage_definition->getName(), $field_schema_data);
2847      }
2848    }
2849  }
2850}
2851