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