summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/system
diff options
context:
space:
mode:
Diffstat (limited to 'core/modules/system')
-rw-r--r--core/modules/system/config/schema/system.schema.yml4
-rw-r--r--core/modules/system/css/components/item-list.module.css19
-rw-r--r--core/modules/system/css/components/system-status-report-counters.css2
-rw-r--r--core/modules/system/src/Controller/DbUpdateController.php7
-rw-r--r--core/modules/system/src/Controller/SystemController.php2
-rw-r--r--core/modules/system/src/Element/StatusReportPage.php23
-rw-r--r--core/modules/system/src/EventSubscriber/SecurityFileUploadEventSubscriber.php17
-rw-r--r--core/modules/system/src/Form/ModulesListForm.php2
-rw-r--r--core/modules/system/src/Form/ModulesUninstallForm.php11
-rw-r--r--core/modules/system/src/Hook/PageAttachmentsHook.php2
-rw-r--r--core/modules/system/src/Hook/SystemHooks.php12
-rw-r--r--core/modules/system/src/Hook/SystemRequirementsHooks.php29
-rw-r--r--core/modules/system/src/Install/Requirements/SystemRequirements.php1665
-rw-r--r--core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php7
-rw-r--r--core/modules/system/src/SystemManager.php37
-rw-r--r--core/modules/system/system.install1631
-rw-r--r--core/modules/system/system.libraries.yml1
-rw-r--r--core/modules/system/system.module4
-rw-r--r--core/modules/system/templates/details.html.twig6
-rw-r--r--core/modules/system/templates/field-multiple-value-form.html.twig2
-rw-r--r--core/modules/system/templates/field.html.twig2
-rw-r--r--core/modules/system/templates/image.html.twig2
-rw-r--r--core/modules/system/templates/install-page.html.twig2
-rw-r--r--core/modules/system/templates/item-list.html.twig2
-rw-r--r--core/modules/system/templates/maintenance-page.html.twig2
-rw-r--r--core/modules/system/templates/menu-local-action.html.twig2
-rw-r--r--core/modules/system/templates/menu-local-task.html.twig2
-rw-r--r--core/modules/system/templates/pager.html.twig2
-rw-r--r--core/modules/system/templates/region.html.twig2
-rw-r--r--core/modules/system/templates/table.html.twig2
-rw-r--r--core/modules/system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gzbin162581 -> 162168 bytes
-rw-r--r--core/modules/system/tests/fixtures/update/drupal-10.3.0.filled.standard.php.gzbin596855 -> 596593 bytes
-rw-r--r--core/modules/system/tests/modules/common_test/common_test.module29
-rw-r--r--core/modules/system/tests/modules/common_test/src/Hook/CommonTestHooks.php125
-rw-r--r--core/modules/system/tests/modules/common_test/src/Hook/CommonTestThemeHooks.php165
-rw-r--r--core/modules/system/tests/modules/container_initialize/container_initialize.info.yml5
-rw-r--r--core/modules/system/tests/modules/container_initialize/container_initialize.module10
-rw-r--r--core/modules/system/tests/modules/delay_cache_tags_invalidation/src/Hook/DelayCacheTagsInvalidationHooks.php12
-rw-r--r--core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php3
-rw-r--r--core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php6
-rw-r--r--core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php57
-rw-r--r--core/modules/system/tests/modules/entity_crud_hook_test/src/Hook/EntityCrudHookTestHooks.php114
-rw-r--r--core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install22
-rw-r--r--core/modules/system/tests/modules/experimental_module_requirements_test/src/Install/Requirements/ExperimentalModuleRequirementsTestRequirements.php29
-rw-r--r--core/modules/system/tests/modules/form_test/src/Form/FormTestClickedButtonForm.php38
-rw-r--r--core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php5
-rw-r--r--core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php26
-rw-r--r--core/modules/system/tests/modules/js_displace/js_displace.module15
-rw-r--r--core/modules/system/tests/modules/js_displace/src/Hook/JsDisplaceThemeHooks.php22
-rw-r--r--core/modules/system/tests/modules/module_install_unmet_requirements/src/Install/Requirements/ModuleInstallUnmetRequirementsRequirements.php3
-rw-r--r--core/modules/system/tests/modules/module_runtime_requirements/src/Hook/ModuleRuntimeRequirementsHooks.php7
-rw-r--r--core/modules/system/tests/modules/module_test/module_test.file.inc14
-rw-r--r--core/modules/system/tests/modules/module_test_oop_preprocess/src/Hook/ModuleTestOopPreprocessThemeHooks.php6
-rw-r--r--core/modules/system/tests/modules/module_update_requirements/src/Hook/ModuleUpdateRequirementsHooks.php7
-rw-r--r--core/modules/system/tests/modules/requirements1_test/requirements1_test.install8
-rw-r--r--core/modules/system/tests/modules/requirements1_test/src/Hook/Requirements1TestHooks.php3
-rw-r--r--core/modules/system/tests/modules/sdc_test/components/my-banner/my-banner.component.yml4
-rw-r--r--core/modules/system/tests/modules/sdc_test/components/my-button/my-button.component.yml4
-rw-r--r--core/modules/system/tests/modules/sdc_test/components/my-cta/my-cta.component.yml13
-rw-r--r--core/modules/system/tests/modules/sdc_test_replacements/components/my-button/my-button.component.yml4
-rw-r--r--core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php21
-rw-r--r--core/modules/system/tests/modules/test_htmx/css/style.css3
-rw-r--r--core/modules/system/tests/modules/test_htmx/js/behavior.js14
-rw-r--r--core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php83
-rw-r--r--core/modules/system/tests/modules/test_htmx/src/Form/HtmxTestAjaxForm.php51
-rw-r--r--core/modules/system/tests/modules/test_htmx/test_htmx.info.yml4
-rw-r--r--core/modules/system/tests/modules/test_htmx/test_htmx.libraries.yml10
-rw-r--r--core/modules/system/tests/modules/test_htmx/test_htmx.routing.yml23
-rw-r--r--core/modules/system/tests/modules/theme_suggestions_test/src/Hook/ThemeSuggestionsTestHooks.php4
-rw-r--r--core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php20
-rw-r--r--core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestHooks.php4
-rw-r--r--core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestThemeHooks.php4
-rw-r--r--core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php2
-rw-r--r--core/modules/system/tests/modules/update_script_test/src/Hook/UpdateScriptTestRequirements.php11
-rw-r--r--core/modules/system/tests/modules/update_test_schema/src/Hook/UpdateTestSchemaRequirements.php3
-rw-r--r--core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php108
-rw-r--r--core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php15
-rw-r--r--core/modules/system/tests/src/Functional/Entity/Traits/EntityDefinitionTestTrait.php56
-rw-r--r--core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php29
-rw-r--r--core/modules/system/tests/src/Functional/Form/ElementTest.php10
-rw-r--r--core/modules/system/tests/src/Functional/Form/FormTest.php32
-rw-r--r--core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php61
-rw-r--r--core/modules/system/tests/src/Functional/Menu/LinksetControllerMultiLingualTest.php6
-rw-r--r--core/modules/system/tests/src/Functional/Module/GenericModuleTestBase.php10
-rw-r--r--core/modules/system/tests/src/Functional/Module/UninstallTest.php17
-rw-r--r--core/modules/system/tests/src/Functional/Module/VersionTest.php1
-rw-r--r--core/modules/system/tests/src/Functional/SecurityAdvisories/SecurityAdvisoryTest.php35
-rw-r--r--core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php6
-rw-r--r--core/modules/system/tests/src/Functional/Theme/FastTest.php52
-rw-r--r--core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathTestJavaScriptTest.php2
-rw-r--r--core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php7
-rw-r--r--core/modules/system/tests/src/Kernel/DateFormatAccessControlHandlerTest.php11
-rw-r--r--core/modules/system/tests/src/Kernel/Element/StatusReportPageTest.php58
-rw-r--r--core/modules/system/tests/src/Kernel/Module/RequirementsTest.php3
-rw-r--r--core/modules/system/tests/src/Kernel/System/CronQueueTest.php3
-rw-r--r--core/modules/system/tests/src/Kernel/System/RunTimeRequirementsTest.php5
-rw-r--r--core/modules/system/tests/src/Unit/Event/SecurityFileUploadEventSubscriberTest.php17
-rw-r--r--core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php55
-rw-r--r--core/modules/system/tests/themes/test_theme/test_theme.theme4
99 files changed, 2758 insertions, 2361 deletions
diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml
index 88f34652b986..cfd2f82952cc 100644
--- a/core/modules/system/config/schema/system.schema.yml
+++ b/core/modules/system/config/schema/system.schema.yml
@@ -69,14 +69,14 @@ system.cron:
type: integer
label: 'Requirements warning period'
constraints:
- # @see system_requirements()
+ # @see \Drupal\system\Hook\SystemRequirementsHooks
Range:
min: 60
requirements_error:
type: integer
label: 'Requirements error period'
constraints:
- # @see system_requirements()
+ # @see \Drupal\system\Hook\SystemRequirementsHooks
Range:
min: 300
logging:
diff --git a/core/modules/system/css/components/item-list.module.css b/core/modules/system/css/components/item-list.module.css
deleted file mode 100644
index 2d23ee5bd335..000000000000
--- a/core/modules/system/css/components/item-list.module.css
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * @file
- * Styles for item list.
- */
-
-.item-list__comma-list,
-.item-list__comma-list li {
- display: inline;
-}
-.item-list__comma-list {
- margin: 0;
- padding: 0;
-}
-.item-list__comma-list li::after {
- content: ", ";
-}
-.item-list__comma-list li:last-child::after {
- content: "";
-}
diff --git a/core/modules/system/css/components/system-status-report-counters.css b/core/modules/system/css/components/system-status-report-counters.css
index 7040c257a0f2..54ffabe5fc1c 100644
--- a/core/modules/system/css/components/system-status-report-counters.css
+++ b/core/modules/system/css/components/system-status-report-counters.css
@@ -9,7 +9,7 @@
padding: 0.5em 0;
text-align: center;
white-space: nowrap;
- background-color: rgba(0, 0, 0, 0.063);
+ background-color: rgb(0, 0, 0, 0.063);
}
@media screen and (min-width: 60em) {
diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php
index 131b6a075d5b..2f5ae0512040 100644
--- a/core/modules/system/src/Controller/DbUpdateController.php
+++ b/core/modules/system/src/Controller/DbUpdateController.php
@@ -7,6 +7,7 @@ use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
use Drupal\Core\Render\BareHtmlPageRendererInterface;
use Drupal\Core\Session\AccountInterface;
@@ -166,8 +167,8 @@ class DbUpdateController extends ControllerBase {
$regions = [];
$requirements = update_check_requirements();
- $severity = drupal_requirements_severity($requirements);
- if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && !$request->getSession()->has('update_ignore_warnings'))) {
+ $severity = RequirementSeverity::maxSeverityFromRequirements($requirements);
+ if ($severity === RequirementSeverity::Error || ($severity === RequirementSeverity::Warning && !$request->getSession()->has('update_ignore_warnings'))) {
$regions['sidebar_first'] = $this->updateTasksList('requirements');
$output = $this->requirements($severity, $requirements, $request);
}
@@ -543,7 +544,7 @@ class DbUpdateController extends ControllerBase {
* A render array.
*/
public function requirements($severity, array $requirements, Request $request) {
- $options = $severity == REQUIREMENT_WARNING ? ['continue' => 1] : [];
+ $options = $severity === RequirementSeverity::Warning ? ['continue' => 1] : [];
// @todo Revisit once https://www.drupal.org/node/2548095 is in. Something
// like Url::fromRoute('system.db_update')->setOptions() should then be
// possible.
diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php
index b340555c6289..fc253ac2f7cf 100644
--- a/core/modules/system/src/Controller/SystemController.php
+++ b/core/modules/system/src/Controller/SystemController.php
@@ -280,8 +280,6 @@ class SystemController extends ControllerBase {
continue;
}
- // @todo Add logic for not displaying hidden modules in
- // https://drupal.org/node/3117829.
$module_name = $modules[$dependency]->info['name'];
$theme->module_dependencies_list[$dependency] = $modules[$dependency]->status ? $this->t('@module_name', ['@module_name' => $module_name]) : $this->t('@module_name (<span class="admin-disabled">disabled</span>)', ['@module_name' => $module_name]);
diff --git a/core/modules/system/src/Element/StatusReportPage.php b/core/modules/system/src/Element/StatusReportPage.php
index 90a878831ead..2d6494f2fe39 100644
--- a/core/modules/system/src/Element/StatusReportPage.php
+++ b/core/modules/system/src/Element/StatusReportPage.php
@@ -2,9 +2,9 @@
namespace Drupal\system\Element;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Render\Attribute\RenderElement;
use Drupal\Core\Render\Element\RenderElementBase;
-use Drupal\Core\Render\Element\StatusReport;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
/**
@@ -37,6 +37,7 @@ class StatusReportPage extends RenderElementBase {
'#theme' => 'status_report_general_info',
];
// Loop through requirements and pull out items.
+ RequirementSeverity::convertLegacyIntSeveritiesToEnums($element['#requirements'], __METHOD__);
foreach ($element['#requirements'] as $key => $requirement) {
switch ($key) {
case 'cron':
@@ -59,10 +60,10 @@ class StatusReportPage extends RenderElementBase {
case 'php':
case 'php_memory_limit':
$element['#general_info']['#' . $key] = $requirement;
- if (isset($requirement['severity']) && $requirement['severity'] < REQUIREMENT_WARNING) {
- if (empty($requirement['severity']) || $requirement['severity'] == REQUIREMENT_OK) {
- unset($element['#requirements'][$key]);
- }
+ if (isset($requirement['severity']) &&
+ in_array($requirement['severity'], [RequirementSeverity::Info, RequirementSeverity::OK], TRUE)
+ ) {
+ unset($element['#requirements'][$key]);
}
break;
}
@@ -94,18 +95,18 @@ class StatusReportPage extends RenderElementBase {
],
];
- $severities = StatusReport::getSeverities();
+ RequirementSeverity::convertLegacyIntSeveritiesToEnums($element['#requirements'], __METHOD__);
foreach ($element['#requirements'] as $key => &$requirement) {
- $severity = $severities[REQUIREMENT_INFO];
+ $severity = RequirementSeverity::Info;
if (isset($requirement['severity'])) {
- $severity = $severities[(int) $requirement['severity']];
+ $severity = $requirement['severity'];
}
elseif (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install') {
- $severity = $severities[REQUIREMENT_OK];
+ $severity = RequirementSeverity::OK;
}
- if (isset($counters[$severity['status']])) {
- $counters[$severity['status']]['amount']++;
+ if (isset($counters[$severity->status()])) {
+ $counters[$severity->status()]['amount']++;
}
}
diff --git a/core/modules/system/src/EventSubscriber/SecurityFileUploadEventSubscriber.php b/core/modules/system/src/EventSubscriber/SecurityFileUploadEventSubscriber.php
index 737b9a7a53e8..c9722a5e4e12 100644
--- a/core/modules/system/src/EventSubscriber/SecurityFileUploadEventSubscriber.php
+++ b/core/modules/system/src/EventSubscriber/SecurityFileUploadEventSubscriber.php
@@ -52,22 +52,17 @@ class SecurityFileUploadEventSubscriber implements EventSubscriberInterface {
// http://php.net/manual/security.filesystem.nullbytes.php
$filename = str_replace(chr(0), '', $filename);
+ if ($filename !== $event->getFilename()) {
+ $event->setFilename($filename)->setSecurityRename();
+ }
+
// Split up the filename by periods. The first part becomes the basename,
// the last part the final extension.
$filename_parts = explode('.', $filename);
// Remove file basename.
$filename = array_shift($filename_parts);
- // Remove final extension.
+ // Remove final extension. In the case of dot filenames this will be empty.
$final_extension = (string) array_pop($filename_parts);
- // Check if we're dealing with a dot file that is also an insecure extension
- // e.g. .htaccess. In this scenario there is only one 'part' and the
- // extension becomes the filename. We use the original filename from the
- // event rather than the trimmed version above.
- $insecure_uploads = $this->configFactory->get('system.file')->get('allow_insecure_uploads');
- if (!$insecure_uploads && $final_extension === '' && str_contains($event->getFilename(), '.') && in_array(strtolower($filename), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) {
- $final_extension = $filename;
- $filename = '';
- }
$extensions = $event->getAllowedExtensions();
if (!empty($extensions) && !in_array(strtolower($final_extension), $extensions, TRUE)) {
@@ -81,7 +76,7 @@ class SecurityFileUploadEventSubscriber implements EventSubscriberInterface {
return;
}
- if (!$insecure_uploads && in_array(strtolower($final_extension), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) {
+ if (!$this->configFactory->get('system.file')->get('allow_insecure_uploads') && in_array(strtolower($final_extension), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) {
if (empty($extensions) || in_array('txt', $extensions, TRUE)) {
// Add .txt to potentially executable files prior to munging to help
// prevent exploits. This results in a filenames like filename.php being
diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php
index 90dc9ead38b7..13297f3578c3 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -375,8 +375,6 @@ class ModulesListForm extends FormBase {
// If this module requires other modules, add them to the array.
/** @var \Drupal\Core\Extension\Dependency $dependency_object */
foreach ($module->requires as $dependency => $dependency_object) {
- // @todo Add logic for not displaying hidden modules in
- // https://drupal.org/node/3117829.
if ($incompatible = $this->checkDependencyMessage($modules, $dependency, $dependency_object)) {
$row['#requires'][$dependency] = $incompatible;
$row['enable']['#disabled'] = TRUE;
diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php
index c999f37fe23f..97f76abe40e3 100644
--- a/core/modules/system/src/Form/ModulesUninstallForm.php
+++ b/core/modules/system/src/Form/ModulesUninstallForm.php
@@ -106,7 +106,8 @@ class ModulesUninstallForm extends FormBase {
include_once DRUPAL_ROOT . '/core/includes/install.inc';
// Get a list of all available modules that can be uninstalled.
- $uninstallable = array_filter($this->moduleExtensionList->getList(), function ($module) {
+ $modules = $this->moduleExtensionList->getList();
+ $uninstallable = array_filter($modules, function ($module) {
return empty($module->info['required']) && $module->status;
});
@@ -199,7 +200,13 @@ class ModulesUninstallForm extends FormBase {
// we can allow this module to be uninstalled.
foreach (array_keys($module->required_by) as $dependent) {
if ($this->updateRegistry->getInstalledVersion($dependent) !== $this->updateRegistry::SCHEMA_UNINSTALLED) {
- $form['modules'][$module->getName()]['#required_by'][] = $dependent;
+ $module_name = $modules[$dependent]->info['name'];
+ if ($dependent != strtolower(str_replace(' ', '_', $module_name))) {
+ $form['modules'][$module->getName()]['#required_by'][] = $module_name . " (" . $dependent . ")";
+ }
+ else {
+ $form['modules'][$module->getName()]['#required_by'][] = $module_name;
+ }
$form['uninstall'][$module->getName()]['#disabled'] = TRUE;
}
}
diff --git a/core/modules/system/src/Hook/PageAttachmentsHook.php b/core/modules/system/src/Hook/PageAttachmentsHook.php
index fb6335f90c31..3f271571ede1 100644
--- a/core/modules/system/src/Hook/PageAttachmentsHook.php
+++ b/core/modules/system/src/Hook/PageAttachmentsHook.php
@@ -19,7 +19,7 @@ final class PageAttachmentsHook {
/**
* Implements hook_page_attachments().
*
- * @see template_preprocess_maintenance_page()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage()
* @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
*/
#[Hook('page_attachments')]
diff --git a/core/modules/system/src/Hook/SystemHooks.php b/core/modules/system/src/Hook/SystemHooks.php
index ae3e8d710747..e1be05077e29 100644
--- a/core/modules/system/src/Hook/SystemHooks.php
+++ b/core/modules/system/src/Hook/SystemHooks.php
@@ -159,10 +159,6 @@ class SystemHooks {
}
/**
- * @} End of "defgroup authorize".
- */
-
- /**
* Implements hook_updater_info().
*/
#[Hook('updater_info')]
@@ -276,7 +272,11 @@ class SystemHooks {
// before doing so. Also add the loaded libraries to ajaxPageState.
/** @var \Drupal\Core\Asset\LibraryDependencyResolver $library_dependency_resolver */
$library_dependency_resolver = \Drupal::service('library.dependency_resolver');
- if (isset($settings['ajaxPageState']) || in_array('core/drupal.ajax', $library_dependency_resolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()))) {
+ $loaded_libraries = [];
+ if (!isset($settings['ajaxPageState'])) {
+ $loaded_libraries = $library_dependency_resolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries());
+ }
+ if (isset($settings['ajaxPageState']) || in_array('core/drupal.ajax', $loaded_libraries) || in_array('core/drupal.htmx', $loaded_libraries)) {
if (!defined('MAINTENANCE_MODE')) {
// The theme token is only validated when the theme requested is not the
// default, so don't generate it unless necessary.
@@ -339,7 +339,7 @@ class SystemHooks {
\Drupal::service('file.htaccess_writer')->ensure();
if (\Drupal::config('system.advisories')->get('enabled')) {
// Fetch the security advisories so that they will be pre-fetched during
- // _system_advisories_requirements() and system_page_top().
+ // systemAdvisoriesRequirements() and system_page_top().
/** @var \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher $fetcher */
$fetcher = \Drupal::service('system.sa_fetcher');
$fetcher->getSecurityAdvisories();
diff --git a/core/modules/system/src/Hook/SystemRequirementsHooks.php b/core/modules/system/src/Hook/SystemRequirementsHooks.php
new file mode 100644
index 000000000000..49d318eb9bff
--- /dev/null
+++ b/core/modules/system/src/Hook/SystemRequirementsHooks.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\system\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\system\Install\Requirements\SystemRequirements;
+
+/**
+ * Requirements hook implementations for system module.
+ */
+class SystemRequirementsHooks {
+
+ /**
+ * Implements hook_update_requirements().
+ */
+ #[Hook('update_requirements')]
+ public function updateRequirements(): array {
+ return SystemRequirements::checkRequirements('update');
+ }
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtimeRequirements(): array {
+ return SystemRequirements::checkRequirements('runtime');
+ }
+
+}
diff --git a/core/modules/system/src/Install/Requirements/SystemRequirements.php b/core/modules/system/src/Install/Requirements/SystemRequirements.php
new file mode 100644
index 000000000000..ee6a1d7a8028
--- /dev/null
+++ b/core/modules/system/src/Install/Requirements/SystemRequirements.php
@@ -0,0 +1,1665 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\system\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+use Drupal\Component\FileSystem\FileSystem as FileSystemComponent;
+use Drupal\Component\Utility\Bytes;
+use Drupal\Component\Utility\Environment;
+use Drupal\Component\Utility\OpCodeCache;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Database\Database;
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Extension\ExtensionLifecycle;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Render\Markup;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\StreamWrapper\PrivateStream;
+use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Url;
+use Drupal\Core\Utility\Error;
+use Drupal\Core\Utility\PhpRequirements;
+use Psr\Http\Client\ClientExceptionInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Install time requirements for the system module.
+ */
+class SystemRequirements implements InstallRequirementsInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getRequirements(): array {
+ return self::checkRequirements('install');
+ }
+
+ /**
+ * Check requirements for a given phase.
+ *
+ * @param string $phase
+ * The phase in which requirements are checked, as documented in
+ * hook_runtime_requirements() and hook_update_requirements().
+ *
+ * @return array
+ * An associative array of requirements, as documented in
+ * hook_runtime_requirements() and hook_update_requirements().
+ */
+ public static function checkRequirements(string $phase): array {
+ global $install_state;
+
+ // Get the current default PHP requirements for this version of Drupal.
+ $minimum_supported_php = PhpRequirements::getMinimumSupportedPhp();
+
+ // Reset the extension lists.
+ /** @var \Drupal\Core\Extension\ModuleExtensionList $module_extension_list */
+ $module_extension_list = \Drupal::service('extension.list.module');
+ $module_extension_list->reset();
+ /** @var \Drupal\Core\Extension\ThemeExtensionList $theme_extension_list */
+ $theme_extension_list = \Drupal::service('extension.list.theme');
+ $theme_extension_list->reset();
+ $requirements = [];
+
+ // Report Drupal version
+ if ($phase == 'runtime') {
+ $requirements['drupal'] = [
+ 'title' => t('Drupal'),
+ 'value' => \Drupal::VERSION,
+ 'severity' => RequirementSeverity::Info,
+ 'weight' => -10,
+ ];
+
+ // Display the currently active installation profile, if the site
+ // is not running the default installation profile.
+ $profile = \Drupal::installProfile();
+ if ($profile != 'standard' && !empty($profile)) {
+ $info = $module_extension_list->getExtensionInfo($profile);
+ $requirements['install_profile'] = [
+ 'title' => t('Installation profile'),
+ 'value' => t('%profile_name (%profile%version)', [
+ '%profile_name' => $info['name'],
+ '%profile' => $profile,
+ '%version' => !empty($info['version']) ? '-' . $info['version'] : '',
+ ]),
+ 'severity' => RequirementSeverity::Info,
+ 'weight' => -9,
+ ];
+ }
+
+ // Gather all obsolete and experimental modules being enabled.
+ $obsolete_extensions = [];
+ $deprecated_modules = [];
+ $experimental_modules = [];
+ $enabled_modules = \Drupal::moduleHandler()->getModuleList();
+ foreach ($enabled_modules as $module => $data) {
+ $info = $module_extension_list->getExtensionInfo($module);
+ if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])) {
+ if ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
+ $experimental_modules[$module] = $info['name'];
+ }
+ elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
+ $deprecated_modules[] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']];
+ }
+ elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) {
+ $obsolete_extensions[$module] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']];
+ }
+ }
+ }
+
+ // Warn if any experimental modules are installed.
+ if (!empty($experimental_modules)) {
+ $requirements['experimental_modules'] = [
+ 'title' => t('Experimental modules installed'),
+ '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']),
+ 'severity' => RequirementSeverity::Warning,
+ ];
+ }
+ // Warn if any deprecated modules are installed.
+ if (!empty($deprecated_modules)) {
+ foreach ($deprecated_modules as $deprecated_module) {
+ $deprecated_modules_link_list[] = (string) Link::fromTextAndUrl($deprecated_module['name'], Url::fromUri($deprecated_module['lifecycle_link']))->toString();
+ }
+ $requirements['deprecated_modules'] = [
+ 'title' => t('Deprecated modules installed'),
+ 'value' => t('Deprecated modules found: %module_list.', [
+ '%module_list' => Markup::create(implode(', ', $deprecated_modules_link_list)),
+ ]),
+ 'severity' => RequirementSeverity::Warning,
+ ];
+ }
+
+ // Gather all obsolete and experimental themes being installed.
+ $experimental_themes = [];
+ $deprecated_themes = [];
+ $installed_themes = \Drupal::service('theme_handler')->listInfo();
+ foreach ($installed_themes as $theme => $data) {
+ $info = $theme_extension_list->getExtensionInfo($theme);
+ if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])) {
+ if ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
+ $experimental_themes[$theme] = $info['name'];
+ }
+ elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
+ $deprecated_themes[] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']];
+ }
+ elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) {
+ $obsolete_extensions[$theme] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']];
+ }
+ }
+ }
+
+ // Warn if any experimental themes are installed.
+ if (!empty($experimental_themes)) {
+ $requirements['experimental_themes'] = [
+ 'title' => t('Experimental themes installed'),
+ '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)]),
+ 'severity' => RequirementSeverity::Warning,
+ ];
+ }
+
+ // Warn if any deprecated themes are installed.
+ if (!empty($deprecated_themes)) {
+ foreach ($deprecated_themes as $deprecated_theme) {
+ $deprecated_themes_link_list[] = (string) Link::fromTextAndUrl($deprecated_theme['name'], Url::fromUri($deprecated_theme['lifecycle_link']))->toString();
+
+ }
+ $requirements['deprecated_themes'] = [
+ 'title' => t('Deprecated themes installed'),
+ 'value' => t('Deprecated themes found: %theme_list.', [
+ '%theme_list' => Markup::create(implode(', ', $deprecated_themes_link_list)),
+ ]),
+ 'severity' => RequirementSeverity::Warning,
+ ];
+ }
+
+ // Warn if any obsolete extensions (themes or modules) are installed.
+ if (!empty($obsolete_extensions)) {
+ foreach ($obsolete_extensions as $obsolete_extension) {
+ $obsolete_extensions_link_list[] = (string) Link::fromTextAndUrl($obsolete_extension['name'], Url::fromUri($obsolete_extension['lifecycle_link']))->toString();
+ }
+ $requirements['obsolete_extensions'] = [
+ 'title' => t('Obsolete extensions installed'),
+ 'value' => t('Obsolete extensions found: %extensions. Obsolete extensions are provided only so that they can be uninstalled cleanly. You should immediately <a href=":uninstall_url">uninstall these extensions</a> since they may be removed in a future release.', [
+ '%extensions' => Markup::create(implode(', ', $obsolete_extensions_link_list)),
+ ':uninstall_url' => Url::fromRoute('system.modules_uninstall')->toString(),
+ ]),
+ 'severity' => RequirementSeverity::Warning,
+ ];
+ }
+ self::systemAdvisoriesRequirements($requirements);
+ }
+
+ // Web server information.
+ $request_object = \Drupal::request();
+ $software = $request_object->server->get('SERVER_SOFTWARE');
+ $requirements['webserver'] = [
+ 'title' => t('Web server'),
+ 'value' => $software,
+ ];
+
+ // Tests clean URL support.
+ if ($phase == 'install' && $install_state['interactive'] && !$request_object->query->has('rewrite') && str_contains($software, 'Apache')) {
+ // If the Apache rewrite module is not enabled, Apache version must be >=
+ // 2.2.16 because of the FallbackResource directive in the root .htaccess
+ // file. Since the Apache version reported by the server is dependent on
+ // the ServerTokens setting in httpd.conf, we may not be able to
+ // determine if a given config is valid. Thus we are unable to use
+ // version_compare() as we need have three possible outcomes: the version
+ // of Apache is greater than 2.2.16, is less than 2.2.16, or cannot be
+ // determined accurately. In the first case, we encourage the use of
+ // mod_rewrite; in the second case, we raise an error regarding the
+ // minimum Apache version; in the third case, we raise a warning that the
+ // current version of Apache may not be supported.
+ $rewrite_warning = FALSE;
+ $rewrite_error = FALSE;
+ $apache_version_string = 'Apache';
+
+ // Determine the Apache version number: major, minor and revision.
+ if (preg_match('/Apache\/(\d+)\.?(\d+)?\.?(\d+)?/', $software, $matches)) {
+ $apache_version_string = $matches[0];
+
+ // Major version number
+ if ($matches[1] < 2) {
+ $rewrite_error = TRUE;
+ }
+ elseif ($matches[1] == 2) {
+ if (!isset($matches[2])) {
+ $rewrite_warning = TRUE;
+ }
+ elseif ($matches[2] < 2) {
+ $rewrite_error = TRUE;
+ }
+ elseif ($matches[2] == 2) {
+ if (!isset($matches[3])) {
+ $rewrite_warning = TRUE;
+ }
+ elseif ($matches[3] < 16) {
+ $rewrite_error = TRUE;
+ }
+ }
+ }
+ }
+ else {
+ $rewrite_warning = TRUE;
+ }
+
+ if ($rewrite_warning) {
+ $requirements['apache_version'] = [
+ 'title' => t('Apache version'),
+ 'value' => $apache_version_string,
+ 'severity' => RequirementSeverity::Warning,
+ '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]),
+ ];
+ }
+
+ if ($rewrite_error) {
+ $requirements['Apache version'] = [
+ 'title' => t('Apache version'),
+ 'value' => $apache_version_string,
+ 'severity' => RequirementSeverity::Error,
+ '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' => 'https://www.drupal.org/docs/8/clean-urls-in-drupal-8']),
+ ];
+ }
+
+ if (!$rewrite_error && !$rewrite_warning) {
+ $requirements['rewrite_module'] = [
+ 'title' => t('Clean URLs'),
+ 'value' => t('Disabled'),
+ 'severity' => RequirementSeverity::Warning,
+ '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' => 'https://www.drupal.org/docs/8/clean-urls-in-drupal-8']),
+ ];
+ }
+ }
+
+ // Verify the user is running a supported PHP version.
+ // If the site is running a recommended version of PHP, just display it
+ // as an informational message on the status report. This will be overridden
+ // with an error or warning if the site is running older PHP versions for
+ // which Drupal has already or will soon drop support.
+ $phpversion = $phpversion_label = phpversion();
+ if ($phase === 'runtime') {
+ $phpversion_label = t('@phpversion (<a href=":url">more information</a>)', [
+ '@phpversion' => $phpversion,
+ ':url' => (new Url('system.php'))->toString(),
+ ]);
+ }
+ $requirements['php'] = [
+ 'title' => t('PHP'),
+ 'value' => $phpversion_label,
+ ];
+
+ // Check if the PHP version is below what Drupal supports.
+ if (version_compare($phpversion, $minimum_supported_php) < 0) {
+ $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 PHP requirements</a> page for more information.',
+ [
+ '%version' => $minimum_supported_php,
+ '%recommended' => \Drupal::RECOMMENDED_PHP,
+ ':php_requirements' => 'https://www.drupal.org/docs/system-requirements/php-requirements',
+ ]
+ );
+
+ // If the PHP version is also below the absolute minimum allowed, it's not
+ // safe to continue with the requirements check, and should always be an
+ // error.
+ if (version_compare($phpversion, \Drupal::MINIMUM_PHP) < 0) {
+ $requirements['php']['severity'] = RequirementSeverity::Error;
+ return $requirements;
+ }
+ // Otherwise, the message should be an error at runtime, and a warning
+ // during installation or update.
+ $requirements['php']['severity'] = ($phase === 'runtime') ? RequirementSeverity::Error : RequirementSeverity::Warning;
+ }
+ // For PHP versions that are still supported but no longer recommended,
+ // inform users of what's recommended, allowing them to take action before
+ // it becomes urgent.
+ elseif ($phase === 'runtime' && version_compare($phpversion, \Drupal::RECOMMENDED_PHP) < 0) {
+ $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 PHP requirements</a> page for more information.', ['%recommended' => \Drupal::RECOMMENDED_PHP, ':php_requirements' => 'https://www.drupal.org/docs/system-requirements/php-requirements']);
+ $requirements['php']['severity'] = RequirementSeverity::Info;
+ }
+
+ // Test for PHP extensions.
+ $requirements['php_extensions'] = [
+ 'title' => t('PHP extensions'),
+ ];
+
+ $missing_extensions = [];
+ $required_extensions = [
+ 'date',
+ 'dom',
+ 'filter',
+ 'gd',
+ 'hash',
+ 'json',
+ 'pcre',
+ 'pdo',
+ 'session',
+ 'SimpleXML',
+ 'SPL',
+ 'tokenizer',
+ 'xml',
+ 'zlib',
+ ];
+ foreach ($required_extensions as $extension) {
+ if (!extension_loaded($extension)) {
+ $missing_extensions[] = $extension;
+ }
+ }
+
+ if (!empty($missing_extensions)) {
+ $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):', [
+ ':system_requirements' => 'https://www.drupal.org/docs/system-requirements',
+ ]);
+
+ // We use twig inline_template to avoid twig's autoescape.
+ $description = [
+ '#type' => 'inline_template',
+ '#template' => '{{ description }}{{ missing_extensions }}',
+ '#context' => [
+ 'description' => $description,
+ 'missing_extensions' => [
+ '#theme' => 'item_list',
+ '#items' => $missing_extensions,
+ ],
+ ],
+ ];
+
+ $requirements['php_extensions']['value'] = t('Disabled');
+ $requirements['php_extensions']['severity'] = RequirementSeverity::Error;
+ $requirements['php_extensions']['description'] = $description;
+ }
+ else {
+ $requirements['php_extensions']['value'] = t('Enabled');
+ }
+
+ if ($phase == 'install' || $phase == 'runtime') {
+ // Check to see if OPcache is installed.
+ if (!OpCodeCache::isEnabled()) {
+ $requirements['php_opcache'] = [
+ 'value' => t('Not enabled'),
+ 'severity' => RequirementSeverity::Warning,
+ '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.'),
+ ];
+ }
+ else {
+ $requirements['php_opcache']['value'] = t('Enabled');
+ }
+ $requirements['php_opcache']['title'] = t('PHP OPcode caching');
+ }
+
+ // Check to see if APCu is installed and configured correctly.
+ if ($phase == 'runtime' && PHP_SAPI != 'cli') {
+ $requirements['php_apcu_enabled']['title'] = t('PHP APCu caching');
+ $requirements['php_apcu_available']['title'] = t('PHP APCu available caching');
+ if (extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN)) {
+ $memory_info = apcu_sma_info(TRUE);
+ $apcu_actual_size = ByteSizeMarkup::create($memory_info['seg_size'] * $memory_info['num_seg']);
+ $apcu_recommended_size = '32 MB';
+ $requirements['php_apcu_enabled']['value'] = t('Enabled (@size)', ['@size' => $apcu_actual_size]);
+ if (Bytes::toNumber(ini_get('apc.shm_size')) * ini_get('apc.shm_segments') < Bytes::toNumber($apcu_recommended_size)) {
+ $requirements['php_apcu_enabled']['severity'] = RequirementSeverity::Warning;
+ $requirements['php_apcu_enabled']['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.', [
+ '@apcu_size' => $apcu_actual_size,
+ '@apcu_default_size' => $apcu_recommended_size,
+ ]);
+ }
+ else {
+ $memory_available = $memory_info['avail_mem'] / ($memory_info['seg_size'] * $memory_info['num_seg']);
+ if ($memory_available < 0.1) {
+ $requirements['php_apcu_available']['severity'] = RequirementSeverity::Error;
+ $requirements['php_apcu_available']['description'] = t('APCu is using over 90% of its allotted memory (@apcu_actual_size). To improve APCu performance, consider increasing this limit.', [
+ '@apcu_actual_size' => $apcu_actual_size,
+ ]);
+ }
+ elseif ($memory_available < 0.25) {
+ $requirements['php_apcu_available']['severity'] = RequirementSeverity::Warning;
+ $requirements['php_apcu_available']['description'] = t('APCu is using over 75% of its allotted memory (@apcu_actual_size). To improve APCu performance, consider increasing this limit.', [
+ '@apcu_actual_size' => $apcu_actual_size,
+ ]);
+ }
+ else {
+ $requirements['php_apcu_available']['severity'] = RequirementSeverity::OK;
+ }
+ $requirements['php_apcu_available']['value'] = t('Memory available: @available.', [
+ '@available' => ByteSizeMarkup::create($memory_info['avail_mem']),
+ ]);
+ }
+ }
+ else {
+ $requirements['php_apcu_enabled'] += [
+ 'value' => t('Not enabled'),
+ 'severity' => RequirementSeverity::Info,
+ '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.'),
+ ];
+ }
+ }
+
+ if ($phase != 'update') {
+ // Test whether we have a good source of random bytes.
+ $requirements['php_random_bytes'] = [
+ 'title' => t('Random number generation'),
+ ];
+ try {
+ $bytes = random_bytes(10);
+ if (strlen($bytes) != 10) {
+ throw new \Exception("Tried to generate 10 random bytes, generated '" . strlen($bytes) . "'");
+ }
+ $requirements['php_random_bytes']['value'] = t('Successful');
+ }
+ catch (\Exception $e) {
+ // If /dev/urandom is not available on a UNIX-like system, check whether
+ // open_basedir restrictions are the cause.
+ $open_basedir_blocks_urandom = FALSE;
+ if (DIRECTORY_SEPARATOR === '/' && !@is_readable('/dev/urandom')) {
+ $open_basedir = ini_get('open_basedir');
+ if ($open_basedir) {
+ $open_basedir_paths = explode(PATH_SEPARATOR, $open_basedir);
+ $open_basedir_blocks_urandom = !array_intersect(['/dev', '/dev/', '/dev/urandom'], $open_basedir_paths);
+ }
+ }
+ $args = [
+ ':drupal-php' => 'https://www.drupal.org/docs/system-requirements/php-requirements',
+ '%exception_message' => $e->getMessage(),
+ ];
+ if ($open_basedir_blocks_urandom) {
+ $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 allowed list. See the <a href=":drupal-php">system requirements</a> page for more information. %exception_message', $args);
+ }
+ else {
+ $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);
+ }
+ $requirements['php_random_bytes']['value'] = t('Less secure');
+ $requirements['php_random_bytes']['severity'] = RequirementSeverity::Error;
+ }
+ }
+
+ if ($phase === 'runtime' && PHP_SAPI !== 'cli') {
+ if (!function_exists('fastcgi_finish_request') && !function_exists('litespeed_finish_request') && !ob_get_status()) {
+ $requirements['output_buffering'] = [
+ 'title' => t('Output Buffering'),
+ 'error_value' => t('Not enabled'),
+ 'severity' => RequirementSeverity::Warning,
+ 'description' => t('<a href="https://www.php.net/manual/en/function.ob-start.php">Output buffering</a> is not enabled. This may degrade Drupal\'s performance. You can enable output buffering by default <a href="https://www.php.net/manual/en/outcontrol.configuration.php#ini.output-buffering">in your PHP settings</a>.'),
+ ];
+ }
+ }
+
+ if ($phase == 'install' || $phase == 'update') {
+ // Test for PDO (database).
+ $requirements['database_extensions'] = [
+ 'title' => t('Database support'),
+ ];
+
+ // Make sure PDO is available.
+ $database_ok = extension_loaded('pdo');
+ if (!$database_ok) {
+ $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.', [
+ ':link' => 'https://www.drupal.org/docs/system-requirements/php-requirements#database',
+ ]);
+ }
+ else {
+ // Make sure at least one supported database driver exists.
+ if (empty(Database::getDriverList()->getInstallableList())) {
+ $database_ok = FALSE;
+ $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>.', [
+ ':drupal-databases' => 'https://www.drupal.org/docs/system-requirements/database-server-requirements',
+ ]);
+ }
+ // Make sure the native PDO extension is available, not the older PEAR
+ // version. (See install_verify_pdo() for details.)
+ if (!defined('PDO::ATTR_DEFAULT_FETCH_MODE')) {
+ $database_ok = FALSE;
+ $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.', [
+ ':link' => 'https://www.drupal.org/docs/system-requirements/php-requirements#database',
+ ]);
+ }
+ }
+
+ if (!$database_ok) {
+ $requirements['database_extensions']['value'] = t('Disabled');
+ $requirements['database_extensions']['severity'] = RequirementSeverity::Error;
+ $requirements['database_extensions']['description'] = $pdo_message;
+ }
+ else {
+ $requirements['database_extensions']['value'] = t('Enabled');
+ }
+ }
+
+ if ($phase === 'runtime' || $phase === 'update') {
+ // Database information.
+ $class = Database::getConnection()->getConnectionOptions()['namespace'] . '\\Install\\Tasks';
+ /** @var \Drupal\Core\Database\Install\Tasks $tasks */
+ $tasks = new $class();
+ $requirements['database_system'] = [
+ 'title' => t('Database system'),
+ 'value' => $tasks->name(),
+ ];
+ $requirements['database_system_version'] = [
+ 'title' => t('Database system version'),
+ 'value' => Database::getConnection()->version(),
+ ];
+
+ $errors = $tasks->engineVersionRequirementsCheck();
+ $error_count = count($errors);
+ if ($error_count > 0) {
+ $error_message = [
+ '#theme' => 'item_list',
+ '#items' => $errors,
+ // Use the comma-list style to display a single error without bullets.
+ '#context' => ['list_style' => $error_count === 1 ? 'comma-list' : ''],
+ ];
+ $requirements['database_system_version']['severity'] = RequirementSeverity::Error;
+ $requirements['database_system_version']['description'] = $error_message;
+ }
+ }
+
+ if ($phase === 'runtime' || $phase === 'update') {
+ // Test database JSON support.
+ $requirements['database_support_json'] = [
+ 'title' => t('Database support for JSON'),
+ 'severity' => RequirementSeverity::OK,
+ 'value' => t('Available'),
+ 'description' => t('Drupal requires databases that support JSON storage.'),
+ ];
+
+ if (!Database::getConnection()->hasJson()) {
+ $requirements['database_support_json']['value'] = t('Not available');
+ $requirements['database_support_json']['severity'] = RequirementSeverity::Error;
+ }
+ }
+
+ // Test PHP memory_limit
+ $memory_limit = ini_get('memory_limit');
+ $requirements['php_memory_limit'] = [
+ 'title' => t('PHP memory limit'),
+ 'value' => $memory_limit == -1 ? t('-1 (Unlimited)') : $memory_limit,
+ ];
+
+ if (!Environment::checkMemoryLimit(\Drupal::MINIMUM_PHP_MEMORY_LIMIT, $memory_limit)) {
+ $description = [];
+ if ($phase == 'install') {
+ $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]);
+ }
+ elseif ($phase == 'update') {
+ $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]);
+ }
+ elseif ($phase == 'runtime') {
+ $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]);
+ }
+
+ if (!empty($description['phase'])) {
+ if ($php_ini_path = get_cfg_var('cfg_file_path')) {
+ $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]);
+ }
+ else {
+ $description['memory'] = t('Contact your system administrator or hosting provider for assistance with increasing your PHP memory limit.');
+ }
+
+ $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']);
+
+ $description = [
+ '#type' => 'inline_template',
+ '#template' => '{{ description_phase }} {{ description_memory }} {{ handbook }}',
+ '#context' => [
+ 'description_phase' => $description['phase'],
+ 'description_memory' => $description['memory'],
+ 'handbook' => $handbook_link,
+ ],
+ ];
+
+ $requirements['php_memory_limit']['description'] = $description;
+ $requirements['php_memory_limit']['severity'] = RequirementSeverity::Warning;
+ }
+ }
+
+ // Test if configuration files and directory are writable.
+ if ($phase == 'runtime') {
+ $conf_errors = [];
+ // Find the site path. Kernel service is not always available at this
+ // point, but is preferred, when available.
+ if (\Drupal::hasService('kernel')) {
+ $site_path = \Drupal::getContainer()->getParameter('site.path');
+ }
+ else {
+ $site_path = DrupalKernel::findSitePath(Request::createFromGlobals());
+ }
+ // Allow system administrators to disable permissions hardening for the
+ // site directory. This allows additional files in the site directory to
+ // be updated when they are managed in a version control system.
+ if (Settings::get('skip_permissions_hardening')) {
+ $error_value = t('Protection disabled');
+ // If permissions hardening is disabled, then only show a warning for a
+ // writable file, as a reminder, rather than an error.
+ $file_protection_severity = RequirementSeverity::Warning;
+ }
+ else {
+ $error_value = t('Not protected');
+ // In normal operation, writable files or directories are an error.
+ $file_protection_severity = RequirementSeverity::Error;
+ if (!drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir')) {
+ $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]);
+ }
+ }
+ foreach (['settings.php', 'settings.local.php', 'services.yml'] as $conf_file) {
+ $full_path = $site_path . '/' . $conf_file;
+ if (file_exists($full_path) && !drupal_verify_install_file($full_path, FILE_EXIST | FILE_READABLE | FILE_NOT_WRITABLE, 'file', !Settings::get('skip_permissions_hardening'))) {
+ $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]);
+ }
+ }
+ if (!empty($conf_errors)) {
+ if (count($conf_errors) == 1) {
+ $description = $conf_errors[0];
+ }
+ else {
+ // We use twig inline_template to avoid double escaping.
+ $description = [
+ '#type' => 'inline_template',
+ '#template' => '{{ configuration_error_list }}',
+ '#context' => [
+ 'configuration_error_list' => [
+ '#theme' => 'item_list',
+ '#items' => $conf_errors,
+ ],
+ ],
+ ];
+ }
+ $requirements['configuration_files'] = [
+ 'value' => $error_value,
+ 'severity' => $file_protection_severity,
+ 'description' => $description,
+ ];
+ }
+ else {
+ $requirements['configuration_files'] = [
+ 'value' => t('Protected'),
+ ];
+ }
+ $requirements['configuration_files']['title'] = t('Configuration files');
+ }
+
+ // Test the contents of the .htaccess files.
+ if ($phase == 'runtime' && Settings::get('auto_create_htaccess', TRUE)) {
+ // Try to write the .htaccess files first, to prevent false alarms in
+ // case (for example) the /tmp directory was wiped.
+ /** @var \Drupal\Core\File\HtaccessWriterInterface $htaccessWriter */
+ $htaccessWriter = \Drupal::service("file.htaccess_writer");
+ $htaccessWriter->ensure();
+ foreach ($htaccessWriter->defaultProtectedDirs() as $protected_dir) {
+ $htaccess_file = $protected_dir->getPath() . '/.htaccess';
+ // Check for the string which was added to the recommended .htaccess
+ // file in the latest security update.
+ if (!file_exists($htaccess_file) || !($contents = @file_get_contents($htaccess_file)) || !str_contains($contents, 'Drupal_Security_Do_Not_Remove_See_SA_2013_003')) {
+ $url = 'https://www.drupal.org/SA-CORE-2013-003';
+ $requirements[$htaccess_file] = [
+ // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
+ 'title' => new TranslatableMarkup($protected_dir->getTitle()),
+ 'value' => t('Not fully protected'),
+ 'severity' => RequirementSeverity::Error,
+ '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()]),
+ ];
+ }
+ }
+ }
+
+ // Report cron status.
+ if ($phase == 'runtime') {
+ $cron_config = \Drupal::config('system.cron');
+ // Cron warning threshold defaults to two days.
+ $threshold_warning = $cron_config->get('threshold.requirements_warning');
+ // Cron error threshold defaults to two weeks.
+ $threshold_error = $cron_config->get('threshold.requirements_error');
+
+ // Determine when cron last ran.
+ $cron_last = \Drupal::state()->get('system.cron_last');
+ if (!is_numeric($cron_last)) {
+ $cron_last = \Drupal::state()->get('install_time', 0);
+ }
+
+ // Determine severity based on time since cron last ran.
+ $severity = RequirementSeverity::Info;
+ $request_time = \Drupal::time()->getRequestTime();
+ if ($request_time - $cron_last > $threshold_error) {
+ $severity = RequirementSeverity::Error;
+ }
+ elseif ($request_time - $cron_last > $threshold_warning) {
+ $severity = RequirementSeverity::Warning;
+ }
+
+ // Set summary and description based on values determined above.
+ $summary = t('Last run @time ago', ['@time' => \Drupal::service('date.formatter')->formatTimeDiffSince($cron_last)]);
+
+ $requirements['cron'] = [
+ 'title' => t('Cron maintenance tasks'),
+ 'severity' => $severity,
+ 'value' => $summary,
+ ];
+ if ($severity != RequirementSeverity::Info) {
+ $requirements['cron']['description'][] = [
+ [
+ '#markup' => t('Cron has not run recently.'),
+ '#suffix' => ' ',
+ ],
+ [
+ '#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/docs/administering-a-drupal-site/cron-automated-tasks/cron-automated-tasks-overview']),
+ '#suffix' => ' ',
+ ],
+ ];
+ }
+ $requirements['cron']['description'][] = [
+ [
+ '#type' => 'link',
+ '#prefix' => '(',
+ '#title' => t('more information'),
+ '#suffix' => ')',
+ '#url' => Url::fromRoute('system.cron_settings'),
+ ],
+ [
+ '#prefix' => '<span class="cron-description__run-cron">',
+ '#suffix' => '</span>',
+ '#type' => 'link',
+ '#title' => t('Run cron'),
+ '#url' => Url::fromRoute('system.run_cron'),
+ ],
+ ];
+ }
+ if ($phase != 'install') {
+ $directories = [
+ PublicStream::basePath(),
+ // By default no private files directory is configured. For private
+ // files to be secure the admin needs to provide a path outside the
+ // webroot.
+ PrivateStream::basePath(),
+ \Drupal::service('file_system')->getTempDirectory(),
+ ];
+ }
+
+ // During an install we need to make assumptions about the file system
+ // unless overrides are provided in settings.php.
+ if ($phase == 'install') {
+ $directories = [];
+ if ($file_public_path = Settings::get('file_public_path')) {
+ $directories[] = $file_public_path;
+ }
+ else {
+ // If we are installing Drupal, the settings.php file might not exist
+ // yet in the intended site directory, so don't require it.
+ $request = Request::createFromGlobals();
+ $site_path = DrupalKernel::findSitePath($request);
+ $directories[] = $site_path . '/files';
+ }
+ if ($file_private_path = Settings::get('file_private_path')) {
+ $directories[] = $file_private_path;
+ }
+ if (Settings::get('file_temp_path')) {
+ $directories[] = Settings::get('file_temp_path');
+ }
+ else {
+ // If the temporary directory is not overridden use an appropriate
+ // temporary path for the system.
+ $directories[] = FileSystemComponent::getOsTemporaryDirectory();
+ }
+ }
+
+ // Check the config directory if it is defined in settings.php. If it isn't
+ // defined, the installer will create a valid config directory later, but
+ // during runtime we must always display an error.
+ $config_sync_directory = Settings::get('config_sync_directory');
+ if (!empty($config_sync_directory)) {
+ // If we're installing Drupal try and create the config sync directory.
+ if (!is_dir($config_sync_directory) && $phase == 'install') {
+ \Drupal::service('file_system')->prepareDirectory($config_sync_directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
+ }
+ if (!is_dir($config_sync_directory)) {
+ if ($phase == 'install') {
+ $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']);
+ }
+ else {
+ $description = t('The directory %directory does not exist.', ['%directory' => $config_sync_directory]);
+ }
+ $requirements['config sync directory'] = [
+ 'title' => t('Configuration sync directory'),
+ 'description' => $description,
+ 'severity' => RequirementSeverity::Error,
+ ];
+ }
+ }
+ if ($phase != 'install' && empty($config_sync_directory)) {
+ $requirements['config sync directory'] = [
+ 'title' => t('Configuration sync directory'),
+ 'value' => t('Not present'),
+ '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']"]),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ }
+
+ $requirements['file system'] = [
+ 'title' => t('File system'),
+ ];
+
+ $error = '';
+ // For installer, create the directories if possible.
+ foreach ($directories as $directory) {
+ if (!$directory) {
+ continue;
+ }
+ if ($phase == 'install') {
+ \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
+ }
+ $is_writable = is_writable($directory);
+ $is_directory = is_dir($directory);
+ if (!$is_writable || !$is_directory) {
+ $description = '';
+ $requirements['file system']['value'] = t('Not writable');
+ if (!$is_directory) {
+ $error = t('The directory %directory does not exist.', ['%directory' => $directory]);
+ }
+ else {
+ $error = t('The directory %directory is not writable.', ['%directory' => $directory]);
+ }
+ // The files directory requirement check is done only during install and
+ // runtime.
+ if ($phase == 'runtime') {
+ $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()]);
+ }
+ elseif ($phase == 'install') {
+ // For the installer UI, we need different wording. 'value' will
+ // be treated as version, so provide none there.
+ $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']);
+ $requirements['file system']['value'] = '';
+ }
+ if (!empty($description)) {
+ $description = [
+ '#type' => 'inline_template',
+ '#template' => '{{ error }} {{ description }}',
+ '#context' => [
+ 'error' => $error,
+ 'description' => $description,
+ ],
+ ];
+ $requirements['file system']['description'] = $description;
+ $requirements['file system']['severity'] = RequirementSeverity::Error;
+ }
+ }
+ else {
+ // This function can be called before the config_cache table has been
+ // created.
+ if ($phase == 'install' || \Drupal::config('system.file')->get('default_scheme') == 'public') {
+ $requirements['file system']['value'] = t('Writable (<em>public</em> download method)');
+ }
+ else {
+ $requirements['file system']['value'] = t('Writable (<em>private</em> download method)');
+ }
+ }
+ }
+
+ // See if updates are available in update.php.
+ if ($phase == 'runtime') {
+ $requirements['update'] = [
+ 'title' => t('Database updates'),
+ 'value' => t('Up to date'),
+ ];
+
+ // Check installed modules.
+ $has_pending_updates = FALSE;
+ /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */
+ $update_registry = \Drupal::service('update.update_hook_registry');
+ foreach (\Drupal::moduleHandler()->getModuleList() as $module => $filename) {
+ $updates = $update_registry->getAvailableUpdates($module);
+ if ($updates) {
+ $default = $update_registry->getInstalledVersion($module);
+ if (max($updates) > $default) {
+ $has_pending_updates = TRUE;
+ break;
+ }
+ }
+ }
+ if (!$has_pending_updates) {
+ /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
+ $post_update_registry = \Drupal::service('update.post_update_registry');
+ $missing_post_update_functions = $post_update_registry->getPendingUpdateFunctions();
+ if (!empty($missing_post_update_functions)) {
+ $has_pending_updates = TRUE;
+ }
+ }
+
+ if ($has_pending_updates) {
+ $requirements['update']['severity'] = RequirementSeverity::Error;
+ $requirements['update']['value'] = t('Out of date');
+ $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()]);
+ }
+
+ $requirements['entity_update'] = [
+ 'title' => t('Entity/field definitions'),
+ 'value' => t('Up to date'),
+ ];
+ // Verify that no entity updates are pending.
+ if ($change_list = \Drupal::entityDefinitionUpdateManager()->getChangeSummary()) {
+ $build = [];
+ foreach ($change_list as $entity_type_id => $changes) {
+ $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+ $build[] = [
+ '#theme' => 'item_list',
+ '#title' => $entity_type->getLabel(),
+ '#items' => $changes,
+ ];
+ }
+
+ $entity_update_issues = \Drupal::service('renderer')->renderInIsolation($build);
+ $requirements['entity_update']['severity'] = RequirementSeverity::Error;
+ $requirements['entity_update']['value'] = t('Mismatched entity and/or field definitions');
+ $requirements['entity_update']['description'] = t('The following changes were detected in the entity type and field definitions. @updates', ['@updates' => $entity_update_issues]);
+ }
+ }
+
+ // Display the deployment identifier if set.
+ if ($phase == 'runtime') {
+ if ($deployment_identifier = Settings::get('deployment_identifier')) {
+ $requirements['deployment identifier'] = [
+ 'title' => t('Deployment identifier'),
+ 'value' => $deployment_identifier,
+ 'severity' => RequirementSeverity::Info,
+ ];
+ }
+ }
+
+ // Verify the update.php access setting
+ if ($phase == 'runtime') {
+ if (Settings::get('update_free_access')) {
+ $requirements['update access'] = [
+ 'value' => t('Not protected'),
+ 'severity' => RequirementSeverity::Error,
+ '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\']']),
+ ];
+ }
+ else {
+ $requirements['update access'] = [
+ 'value' => t('Protected'),
+ ];
+ }
+ $requirements['update access']['title'] = t('Access to update.php');
+ }
+
+ // Display an error if a newly introduced dependency in a module is not
+ // resolved.
+ if ($phase === 'update' || $phase === 'runtime') {
+ $create_extension_incompatibility_list = function (array $extension_names, PluralTranslatableMarkup $description, PluralTranslatableMarkup $title, TranslatableMarkup|string $message = '', TranslatableMarkup|string $additional_description = '') {
+ if ($message === '') {
+ $message = new TranslatableMarkup('Review the <a href=":url"> suggestions for resolving this incompatibility</a> to repair your installation, and then re-run update.php.', [':url' => 'https://www.drupal.org/docs/updating-drupal/troubleshooting-database-updates']);
+ }
+ // Use an inline twig template to:
+ // - Concatenate MarkupInterface objects and preserve safeness.
+ // - Use the item_list theme for the extension list.
+ $template = [
+ '#type' => 'inline_template',
+ '#template' => '{{ description }}{{ extensions }}{{ additional_description }}<br>',
+ '#context' => [
+ 'extensions' => [
+ '#theme' => 'item_list',
+ ],
+ ],
+ ];
+ $template['#context']['extensions']['#items'] = $extension_names;
+ $template['#context']['description'] = $description;
+ $template['#context']['additional_description'] = $additional_description;
+ return [
+ 'title' => $title,
+ 'value' => [
+ 'list' => $template,
+ 'handbook_link' => [
+ '#markup' => $message,
+ ],
+ ],
+ 'severity' => RequirementSeverity::Error,
+ ];
+ };
+ $profile = \Drupal::installProfile();
+ $files = $module_extension_list->getList();
+ $files += $theme_extension_list->getList();
+ $core_incompatible_extensions = [];
+ $php_incompatible_extensions = [];
+ foreach ($files as $extension_name => $file) {
+ // Ignore uninstalled extensions and installation profiles.
+ if (!$file->status || $extension_name == $profile) {
+ continue;
+ }
+
+ $name = $file->info['name'];
+ if (!empty($file->info['core_incompatible'])) {
+ $core_incompatible_extensions[$file->info['type']][] = $name;
+ }
+
+ // Check the extension's PHP version.
+ $php = (string) $file->info['php'];
+ if (version_compare($php, PHP_VERSION, '>')) {
+ $php_incompatible_extensions[$file->info['type']][] = $name;
+ }
+
+ // Check the module's required modules.
+ /** @var \Drupal\Core\Extension\Dependency $requirement */
+ foreach ($file->requires as $requirement) {
+ $required_module = $requirement->getName();
+ // Check if the module exists.
+ if (!isset($files[$required_module])) {
+ $requirements["$extension_name-$required_module"] = [
+ 'title' => t('Unresolved dependency'),
+ 'description' => t('@name requires this module.', ['@name' => $name]),
+ 'value' => t('@required_name (Missing)', ['@required_name' => $required_module]),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ continue;
+ }
+ // Check for an incompatible version.
+ $required_file = $files[$required_module];
+ $required_name = $required_file->info['name'];
+ // Remove CORE_COMPATIBILITY- only from the start of the string.
+ $version = preg_replace('/^(' . \Drupal::CORE_COMPATIBILITY . '\-)/', '', $required_file->info['version'] ?? '');
+ if (!$requirement->isCompatible($version)) {
+ $requirements["$extension_name-$required_module"] = [
+ 'title' => t('Unresolved dependency'),
+ 'description' => t('@name requires this module and version. Currently using @required_name version @version', ['@name' => $name, '@required_name' => $required_name, '@version' => $version]),
+ 'value' => t('@required_name (Version @compatibility required)', ['@required_name' => $required_name, '@compatibility' => $requirement->getConstraintString()]),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ continue;
+ }
+ }
+ }
+ if (!empty($core_incompatible_extensions['module'])) {
+ $requirements['module_core_incompatible'] = $create_extension_incompatibility_list(
+ $core_incompatible_extensions['module'],
+ new PluralTranslatableMarkup(
+ count($core_incompatible_extensions['module']),
+ 'The following module is installed, but it is incompatible with Drupal @version:',
+ 'The following modules are installed, but they are incompatible with Drupal @version:',
+ ['@version' => \Drupal::VERSION]
+ ),
+ new PluralTranslatableMarkup(
+ count($core_incompatible_extensions['module']),
+ 'Incompatible module',
+ 'Incompatible modules'
+ )
+ );
+ }
+ if (!empty($core_incompatible_extensions['theme'])) {
+ $requirements['theme_core_incompatible'] = $create_extension_incompatibility_list(
+ $core_incompatible_extensions['theme'],
+ new PluralTranslatableMarkup(
+ count($core_incompatible_extensions['theme']),
+ 'The following theme is installed, but it is incompatible with Drupal @version:',
+ 'The following themes are installed, but they are incompatible with Drupal @version:',
+ ['@version' => \Drupal::VERSION]
+ ),
+ new PluralTranslatableMarkup(
+ count($core_incompatible_extensions['theme']),
+ 'Incompatible theme',
+ 'Incompatible themes'
+ )
+ );
+ }
+ if (!empty($php_incompatible_extensions['module'])) {
+ $requirements['module_php_incompatible'] = $create_extension_incompatibility_list(
+ $php_incompatible_extensions['module'],
+ new PluralTranslatableMarkup(
+ count($php_incompatible_extensions['module']),
+ 'The following module is installed, but it is incompatible with PHP @version:',
+ 'The following modules are installed, but they are incompatible with PHP @version:',
+ ['@version' => phpversion()]
+ ),
+ new PluralTranslatableMarkup(
+ count($php_incompatible_extensions['module']),
+ 'Incompatible module',
+ 'Incompatible modules'
+ )
+ );
+ }
+ if (!empty($php_incompatible_extensions['theme'])) {
+ $requirements['theme_php_incompatible'] = $create_extension_incompatibility_list(
+ $php_incompatible_extensions['theme'],
+ new PluralTranslatableMarkup(
+ count($php_incompatible_extensions['theme']),
+ 'The following theme is installed, but it is incompatible with PHP @version:',
+ 'The following themes are installed, but they are incompatible with PHP @version:',
+ ['@version' => phpversion()]
+ ),
+ new PluralTranslatableMarkup(
+ count($php_incompatible_extensions['theme']),
+ 'Incompatible theme',
+ 'Incompatible themes'
+ )
+ );
+ }
+
+ $extension_config = \Drupal::configFactory()->get('core.extension');
+
+ // Look for removed core modules.
+ $is_removed_module = function ($extension_name) use ($module_extension_list) {
+ return !$module_extension_list->exists($extension_name)
+ && array_key_exists($extension_name, DRUPAL_CORE_REMOVED_MODULE_LIST);
+ };
+ $removed_modules = array_filter(array_keys($extension_config->get('module')), $is_removed_module);
+ if (!empty($removed_modules)) {
+ $list = [];
+ foreach ($removed_modules as $removed_module) {
+ $list[] = t('<a href=":url">@module</a>', [
+ ':url' => "https://www.drupal.org/project/$removed_module",
+ '@module' => DRUPAL_CORE_REMOVED_MODULE_LIST[$removed_module],
+ ]);
+ }
+ $requirements['removed_module'] = $create_extension_incompatibility_list(
+ $list,
+ new PluralTranslatableMarkup(
+ count($removed_modules),
+ 'You must add the following contributed module and reload this page.',
+ 'You must add the following contributed modules and reload this page.'
+ ),
+ new PluralTranslatableMarkup(
+ count($removed_modules),
+ 'Removed core module',
+ 'Removed core modules'
+ ),
+ new TranslatableMarkup(
+ 'For more information read the <a href=":url">documentation on deprecated modules.</a>',
+ [':url' => 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-modules']
+ ),
+ new PluralTranslatableMarkup(
+ count($removed_modules),
+ 'This module is installed on your site but is no longer provided by Core.',
+ 'These modules are installed on your site but are no longer provided by Core.'
+ ),
+ );
+ }
+
+ // Look for removed core themes.
+ $is_removed_theme = function ($extension_name) use ($theme_extension_list) {
+ return !$theme_extension_list->exists($extension_name)
+ && array_key_exists($extension_name, DRUPAL_CORE_REMOVED_THEME_LIST);
+ };
+ $removed_themes = array_filter(array_keys($extension_config->get('theme')), $is_removed_theme);
+ if (!empty($removed_themes)) {
+ $list = [];
+ foreach ($removed_themes as $removed_theme) {
+ $list[] = t('<a href=":url">@theme</a>', [
+ ':url' => "https://www.drupal.org/project/$removed_theme",
+ '@theme' => DRUPAL_CORE_REMOVED_THEME_LIST[$removed_theme],
+ ]);
+ }
+ $requirements['removed_theme'] = $create_extension_incompatibility_list(
+ $list,
+ new PluralTranslatableMarkup(
+ count($removed_themes),
+ 'You must add the following contributed theme and reload this page.',
+ 'You must add the following contributed themes and reload this page.'
+ ),
+ new PluralTranslatableMarkup(
+ count($removed_themes),
+ 'Removed core theme',
+ 'Removed core themes'
+ ),
+ new TranslatableMarkup(
+ 'For more information read the <a href=":url">documentation on deprecated themes.</a>',
+ [':url' => 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-themes']
+ ),
+ new PluralTranslatableMarkup(
+ count($removed_themes),
+ 'This theme is installed on your site but is no longer provided by Core.',
+ 'These themes are installed on your site but are no longer provided by Core.'
+ ),
+ );
+ }
+
+ // Look for missing modules.
+ $is_missing_module = function ($extension_name) use ($module_extension_list) {
+ return !$module_extension_list->exists($extension_name) && !in_array($extension_name, array_keys(DRUPAL_CORE_REMOVED_MODULE_LIST), TRUE);
+ };
+ $invalid_modules = array_filter(array_keys($extension_config->get('module')), $is_missing_module);
+
+ if (!empty($invalid_modules)) {
+ $requirements['invalid_module'] = $create_extension_incompatibility_list(
+ $invalid_modules,
+ new PluralTranslatableMarkup(
+ count($invalid_modules),
+ 'The following module is marked as installed in the core.extension configuration, but it is missing:',
+ 'The following modules are marked as installed in the core.extension configuration, but they are missing:'
+ ),
+ new PluralTranslatableMarkup(
+ count($invalid_modules),
+ 'Missing or invalid module',
+ 'Missing or invalid modules'
+ )
+ );
+ }
+
+ // Look for invalid themes.
+ $is_missing_theme = function ($extension_name) use (&$theme_extension_list) {
+ return !$theme_extension_list->exists($extension_name) && !in_array($extension_name, array_keys(DRUPAL_CORE_REMOVED_THEME_LIST), TRUE);
+ };
+ $invalid_themes = array_filter(array_keys($extension_config->get('theme')), $is_missing_theme);
+ if (!empty($invalid_themes)) {
+ $requirements['invalid_theme'] = $create_extension_incompatibility_list(
+ $invalid_themes,
+ new PluralTranslatableMarkup(
+ count($invalid_themes),
+ 'The following theme is marked as installed in the core.extension configuration, but it is missing:',
+ 'The following themes are marked as installed in the core.extension configuration, but they are missing:'
+ ),
+ new PluralTranslatableMarkup(
+ count($invalid_themes),
+ 'Missing or invalid theme',
+ 'Missing or invalid themes'
+ )
+ );
+ }
+ }
+
+ // Returns Unicode library status and errors.
+ $libraries = [
+ Unicode::STATUS_SINGLEBYTE => t('Standard PHP'),
+ Unicode::STATUS_MULTIBYTE => t('PHP Mbstring Extension'),
+ Unicode::STATUS_ERROR => t('Error'),
+ ];
+ $severities = [
+ Unicode::STATUS_SINGLEBYTE => RequirementSeverity::Warning,
+ Unicode::STATUS_MULTIBYTE => NULL,
+ Unicode::STATUS_ERROR => RequirementSeverity::Error,
+ ];
+ $failed_check = Unicode::check();
+ $library = Unicode::getStatus();
+
+ $requirements['unicode'] = [
+ 'title' => t('Unicode library'),
+ 'value' => $libraries[$library],
+ 'severity' => $severities[$library],
+ ];
+ switch ($failed_check) {
+ case 'mb_strlen':
+ $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.');
+ break;
+
+ case 'mbstring.encoding_translation':
+ $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. Refer to the <a href="http://php.net/mbstring">PHP mbstring documentation</a> for more information.');
+ break;
+ }
+
+ if ($phase == 'runtime') {
+ // Check for update status module.
+ if (!\Drupal::moduleHandler()->moduleExists('update')) {
+ $requirements['update status'] = [
+ 'value' => t('Not enabled'),
+ 'severity' => RequirementSeverity::Warning,
+ 'description' => t('Update notifications are not enabled. It is <strong>highly recommended</strong> that you install the Update Status 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>.', [
+ ':update' => 'https://www.drupal.org/documentation/modules/update',
+ ':module' => Url::fromRoute('system.modules_list')->toString(),
+ ]),
+ ];
+ }
+ else {
+ $requirements['update status'] = [
+ 'value' => t('Enabled'),
+ ];
+ }
+ $requirements['update status']['title'] = t('Update notifications');
+
+ if (Settings::get('rebuild_access')) {
+ $requirements['rebuild access'] = [
+ 'title' => t('Rebuild access'),
+ 'value' => t('Enabled'),
+ 'severity' => RequirementSeverity::Error,
+ '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.'),
+ ];
+ }
+ }
+
+ // Check if the SameSite cookie attribute is set to a valid value. Since
+ // this involves checking whether we are using a secure connection this
+ // only makes sense inside an HTTP request, not on the command line.
+ if ($phase === 'runtime' && PHP_SAPI !== 'cli') {
+ $samesite = ini_get('session.cookie_samesite') ?: t('Not set');
+ // Check if the SameSite attribute is set to a valid value. If it is set
+ // to 'None' the request needs to be done over HTTPS.
+ $valid = match ($samesite) {
+ 'Lax', 'Strict' => TRUE,
+ 'None' => $request_object->isSecure(),
+ default => FALSE,
+ };
+ $requirements['php_session_samesite'] = [
+ 'title' => t('SameSite cookie attribute'),
+ 'value' => $samesite,
+ 'severity' => $valid ? RequirementSeverity::OK : RequirementSeverity::Warning,
+ 'description' => t('This attribute should be explicitly set to Lax, Strict or None. If set to None then the request must be made via HTTPS. See <a href=":url" target="_blank">PHP documentation</a>', [
+ ':url' => 'https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-samesite',
+ ]),
+ ];
+ }
+
+ // See if trusted host names have been configured, and warn the user if they
+ // are not set.
+ if ($phase == 'runtime') {
+ $trusted_host_patterns = Settings::get('trusted_host_patterns');
+ if (empty($trusted_host_patterns)) {
+ $requirements['trusted_host_patterns'] = [
+ 'title' => t('Trusted Host Settings'),
+ 'value' => t('Not enabled'),
+ '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/installing-drupal/trusted-host-settings']),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ }
+ else {
+ $requirements['trusted_host_patterns'] = [
+ 'title' => t('Trusted Host Settings'),
+ 'value' => t('Enabled'),
+ 'description' => t('The trusted_host_patterns setting is set to allow %trusted_host_patterns', ['%trusted_host_patterns' => implode(', ', $trusted_host_patterns)]),
+ ];
+ }
+ }
+
+ // When the database driver is provided by a module, then check that the
+ // providing module is installed.
+ if ($phase === 'runtime' || $phase === 'update') {
+ $connection = Database::getConnection();
+ $provider = $connection->getProvider();
+ if ($provider !== 'core' && !\Drupal::moduleHandler()->moduleExists($provider)) {
+ $autoload = $connection->getConnectionOptions()['autoload'] ?? '';
+ if (str_contains($autoload, 'src/Driver/Database/')) {
+ $post_update_registry = \Drupal::service('update.post_update_registry');
+ $pending_updates = $post_update_registry->getPendingUpdateInformation();
+ if (!in_array('enable_provider_database_driver', array_keys($pending_updates['system']['pending'] ?? []), TRUE)) {
+ // Only show the warning when the post update function has run and
+ // the module that is providing the database driver is not
+ // installed.
+ $requirements['database_driver_provided_by_module'] = [
+ 'title' => t('Database driver provided by module'),
+ 'value' => t('Not installed'),
+ 'description' => t('The current database driver is provided by the module: %module. The module is currently not installed. You should immediately <a href=":install">install</a> the module.', ['%module' => $provider, ':install' => Url::fromRoute('system.modules_list')->toString()]),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ }
+ }
+ }
+ }
+
+ // Check xdebug.max_nesting_level, as some pages will not work if it is too
+ // low.
+ if (extension_loaded('xdebug')) {
+ // Setting this value to 256 was considered adequate on Xdebug 2.3
+ // (see http://bugs.xdebug.org/bug_view_page.php?bug_id=00001100)
+ $minimum_nesting_level = 256;
+ $current_nesting_level = ini_get('xdebug.max_nesting_level');
+
+ if ($current_nesting_level < $minimum_nesting_level) {
+ $requirements['xdebug_max_nesting_level'] = [
+ 'title' => t('Xdebug settings'),
+ 'value' => t('xdebug.max_nesting_level is set to %value.', ['%value' => $current_nesting_level]),
+ '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]),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ }
+ }
+
+ // Installations on Windows can run into limitations with MAX_PATH if the
+ // Drupal root directory is too deep in the filesystem. Generally this
+ // shows up in cached Twig templates and other public files with long
+ // directory or file names. There is no definite root directory depth below
+ // which Drupal is guaranteed to function correctly on Windows. Since
+ // problems are likely with more than 100 characters in the Drupal root
+ // path, show an error.
+ if (str_starts_with(PHP_OS, 'WIN')) {
+ $depth = strlen(realpath(DRUPAL_ROOT . '/' . PublicStream::basePath()));
+ if ($depth > 120) {
+ $requirements['max_path_on_windows'] = [
+ 'title' => t('Windows installation depth'),
+ 'description' => t('The public files directory path is %depth characters. Paths longer than 120 characters will cause problems on Windows.', ['%depth' => $depth]),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ }
+ }
+ // Check to see if dates will be limited to 1901-2038.
+ if (PHP_INT_SIZE <= 4) {
+ $requirements['limited_date_range'] = [
+ 'title' => t('Limited date range'),
+ 'value' => t('Your PHP installation has a limited date range.'),
+ '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/system-requirements/limitations-of-32-bit-php']),
+ 'severity' => RequirementSeverity::Warning,
+ ];
+ }
+
+ // During installs from configuration don't support install profiles that
+ // implement hook_install.
+ if ($phase == 'install' && !empty($install_state['config_install_path'])) {
+ $install_hook = $install_state['parameters']['profile'] . '_install';
+ if (function_exists($install_hook)) {
+ $requirements['config_install'] = [
+ 'title' => t('Configuration install'),
+ 'value' => $install_state['parameters']['profile'],
+ 'description' => t('The selected profile has a hook_install() implementation and therefore can not be installed from configuration.'),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ }
+ }
+
+ if ($phase === 'runtime') {
+ $settings = Settings::getAll();
+ if (array_key_exists('install_profile', $settings)) {
+ // The following message is only informational because not all site
+ // owners have access to edit their settings.php as it may be
+ // controlled by their hosting provider.
+ $requirements['install_profile_in_settings'] = [
+ 'title' => t('Install profile in settings'),
+ 'value' => t("Drupal 9 no longer uses the \$settings['install_profile'] value in settings.php and it should be removed."),
+ 'severity' => RequirementSeverity::Warning,
+ ];
+ }
+ }
+
+ // Ensure that no module has a current schema version that is lower than the
+ // one that was last removed.
+ if ($phase == 'update') {
+ $module_handler = \Drupal::moduleHandler();
+ /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */
+ $update_registry = \Drupal::service('update.update_hook_registry');
+ $module_list = [];
+ // hook_update_last_removed() is a procedural hook hook because we
+ // do not have classes loaded that would be needed.
+ // Simply inlining the old hook mechanism is better than making
+ // ModuleInstaller::invoke() public.
+ foreach ($module_handler->getModuleList() as $module => $extension) {
+ $function = $module . '_update_last_removed';
+ if (function_exists($function)) {
+ $last_removed = $function();
+ if ($last_removed && $last_removed > $update_registry->getInstalledVersion($module)) {
+
+ /** @var \Drupal\Core\Extension\Extension $module_info */
+ $module_info = $module_extension_list->get($module);
+ $module_list[$module] = [
+ 'name' => $module_info->info['name'],
+ 'last_removed' => $last_removed,
+ 'installed_version' => $update_registry->getInstalledVersion($module),
+ ];
+ }
+ }
+ }
+
+ // If user module is in the list then only show a specific message for
+ // Drupal core.
+ if (isset($module_list['user'])) {
+ $requirements['user_update_last_removed'] = [
+ 'title' => t('The version of Drupal you are trying to update from is too old'),
+ 'description' => t('Updating to Drupal @current_major is only supported from Drupal version @required_min_version or higher. If you are trying to update from an older version, first update to the latest version of Drupal @previous_major. (<a href=":url">Drupal upgrade guide</a>)', [
+ '@current_major' => 10,
+ '@required_min_version' => '9.4.0',
+ '@previous_major' => 9,
+ ':url' => 'https://www.drupal.org/docs/upgrading-drupal/drupal-8-and-higher',
+ ]),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ }
+ else {
+ foreach ($module_list as $module => $data) {
+ $requirements[$module . '_update_last_removed'] = [
+ 'title' => t('Unsupported schema version: @module', ['@module' => $data['name']]),
+ '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).', [
+ '%module' => $data['name'],
+ '@last_removed_version' => $data['last_removed'],
+ '@installed_version' => $data['installed_version'],
+ ]),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ }
+ }
+ // Also check post-updates. Only do this if we're not already showing an
+ // error for hook_update_N().
+ $missing_updates = [];
+ if (empty($module_list)) {
+ $existing_updates = \Drupal::service('keyvalue')->get('post_update')->get('existing_updates', []);
+ $post_update_registry = \Drupal::service('update.post_update_registry');
+ $modules = \Drupal::moduleHandler()->getModuleList();
+ foreach ($modules as $module => $extension) {
+ $module_info = $module_extension_list->get($module);
+ $removed_post_updates = $post_update_registry->getRemovedPostUpdates($module);
+ if ($missing_updates = array_diff(array_keys($removed_post_updates), $existing_updates)) {
+ $versions = array_unique(array_intersect_key($removed_post_updates, array_flip($missing_updates)));
+ $description = new PluralTranslatableMarkup(count($versions),
+ 'The installed version of the %module module is too old to update. Update to a version prior to @versions first (missing updates: @missing_updates).',
+ '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).',
+ [
+ '%module' => $module_info->info['name'],
+ '@missing_updates' => implode(', ', $missing_updates),
+ '@versions' => implode(', ', $versions),
+ ]
+ );
+ $requirements[$module . '_post_update_removed'] = [
+ 'title' => t('Missing updates for: @module', ['@module' => $module_info->info['name']]),
+ 'description' => $description,
+ 'severity' => RequirementSeverity::Error,
+ ];
+ }
+ }
+ }
+
+ if (empty($missing_updates)) {
+ foreach ($update_registry->getAllEquivalentUpdates() as $module => $equivalent_updates) {
+ $module_info = $module_extension_list->get($module);
+ foreach ($equivalent_updates as $future_update => $data) {
+ $future_update_function_name = $module . '_update_' . $future_update;
+ $ran_update_function_name = $module . '_update_' . $data['ran_update'];
+ // If an update was marked as an equivalent by a previous update,
+ // and both the previous update and the equivalent update are not
+ // found in the current code base, prevent updating. This indicates
+ // a site attempting to go 'backwards' in terms of database schema.
+ // @see \Drupal\Core\Update\UpdateHookRegistry::markFutureUpdateEquivalent()
+ if (!function_exists($ran_update_function_name) && !function_exists($future_update_function_name)) {
+ // If the module is provided by core prepend helpful text as the
+ // module does not exist in composer or Drupal.org.
+ if (str_starts_with($module_info->getPathname(), 'core/')) {
+ $future_version_string = 'Drupal Core ' . $data['future_version_string'];
+ }
+ else {
+ $future_version_string = $data['future_version_string'];
+ }
+ $requirements[$module . '_equivalent_update_missing'] = [
+ 'title' => t('Missing updates for: @module', ['@module' => $module_info->info['name']]),
+ 'description' => t('The version of the %module module that you are attempting to update to is missing update @future_update (which was marked as an equivalent by @ran_update). Update to at least @future_version_string.', [
+ '%module' => $module_info->info['name'],
+ '@ran_update' => $data['ran_update'],
+ '@future_update' => $future_update,
+ '@future_version_string' => $future_version_string,
+ ]),
+ 'severity' => RequirementSeverity::Error,
+ ];
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Add warning when twig debug option is enabled.
+ if ($phase === 'runtime') {
+ $development_settings = \Drupal::keyValue('development_settings');
+ $twig_debug = $development_settings->get('twig_debug', FALSE);
+ $twig_cache_disable = $development_settings->get('twig_cache_disable', FALSE);
+ if ($twig_debug || $twig_cache_disable) {
+ $requirements['twig_debug_enabled'] = [
+ 'title' => t('Twig development mode'),
+ 'value' => t('Twig development mode settings are turned on. Go to @link to disable them.', [
+ '@link' => Link::createFromRoute(
+ 'development settings page',
+ 'system.development_settings',
+ )->toString(),
+ ]),
+ 'severity' => RequirementSeverity::Warning,
+ ];
+ }
+ $render_cache_disabled = $development_settings->get('disable_rendered_output_cache_bins', FALSE);
+ if ($render_cache_disabled) {
+ $requirements['render_cache_disabled'] = [
+ 'title' => t('Markup caching disabled'),
+ 'value' => t('Render cache, dynamic page cache, and page cache are bypassed. Go to @link to enable them.', [
+ '@link' => Link::createFromRoute(
+ 'development settings page',
+ 'system.development_settings',
+ )->toString(),
+ ]),
+ 'severity' => RequirementSeverity::Warning,
+ ];
+ }
+ }
+
+ return $requirements;
+ }
+
+ /**
+ * Display requirements from security advisories.
+ *
+ * @param array[] $requirements
+ * The requirements array as specified in hook_requirements().
+ */
+ public static function systemAdvisoriesRequirements(array &$requirements): void {
+ if (!\Drupal::config('system.advisories')->get('enabled')) {
+ return;
+ }
+
+ /** @var \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher $fetcher */
+ $fetcher = \Drupal::service('system.sa_fetcher');
+ try {
+ $advisories = $fetcher->getSecurityAdvisories(TRUE, 5);
+ }
+ catch (ClientExceptionInterface $exception) {
+ $requirements['system_advisories']['title'] = t('Critical security announcements');
+ $requirements['system_advisories']['severity'] = RequirementSeverity::Warning;
+ $requirements['system_advisories']['description'] = ['#theme' => 'system_security_advisories_fetch_error_message'];
+ Error::logException(\Drupal::logger('system'), $exception, 'Failed to retrieve security advisory data.');
+ return;
+ }
+
+ if (!empty($advisories)) {
+ $advisory_links = [];
+ $severity = RequirementSeverity::Warning;
+ foreach ($advisories as $advisory) {
+ if (!$advisory->isPsa()) {
+ $severity = RequirementSeverity::Error;
+ }
+ $advisory_links[] = new Link($advisory->getTitle(), Url::fromUri($advisory->getUrl()));
+ }
+ $requirements['system_advisories']['title'] = t('Critical security announcements');
+ $requirements['system_advisories']['severity'] = $severity;
+ $requirements['system_advisories']['description'] = [
+ 'list' => [
+ '#theme' => 'item_list',
+ '#items' => $advisory_links,
+ ],
+ ];
+ if (\Drupal::moduleHandler()->moduleExists('help')) {
+ $requirements['system_advisories']['description']['help_link'] = Link::createFromRoute(
+ 'What are critical security announcements?',
+ 'help.page', ['name' => 'system'],
+ ['fragment' => 'security-advisories']
+ )->toRenderable();
+ }
+ }
+ }
+
+}
diff --git a/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
index ccd9725e8215..2ce434e0fd3d 100644
--- a/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
+++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
@@ -4,6 +4,7 @@ namespace Drupal\system\Plugin\ImageToolkit;
use Drupal\Component\Utility\Color;
use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
@@ -451,7 +452,7 @@ class GDToolkit extends ImageToolkitBase {
);
}
if ($unsupported_formats) {
- $requirements['version']['severity'] = REQUIREMENT_WARNING;
+ $requirements['version']['severity'] = RequirementSeverity::Warning;
$unsupported = $this->formatPlural(
count($unsupported_formats),
'Unsupported image file format: %formats.',
@@ -472,7 +473,7 @@ class GDToolkit extends ImageToolkitBase {
// Check for filter and rotate support.
if (!function_exists('imagefilter') || !function_exists('imagerotate')) {
- $requirements['version']['severity'] = REQUIREMENT_WARNING;
+ $requirements['version']['severity'] = RequirementSeverity::Warning;
$descriptions[] = $this->t('The GD Library for PHP is enabled, but was compiled without support for functions used by the rotate and desaturate effects. It was probably compiled using the official GD libraries from the <a href="https://libgd.github.io/">gdLibrary site</a> instead of the GD library bundled with PHP. You should recompile PHP --with-gd using the bundled GD library. See <a href="https://www.php.net/manual/book.image.php">the PHP manual</a>.');
}
@@ -561,7 +562,7 @@ class GDToolkit extends ImageToolkitBase {
}
$tempFile = fopen('php://memory', 'r+');
- $supported = imageavif(imagecreatetruecolor(1, 1), $tempFile, 0, 10) && fstat($tempFile)['size'] > 0;
+ $supported = function_exists('imageavif') && imageavif(imagecreatetruecolor(1, 1), $tempFile, 0, 10) && fstat($tempFile)['size'] > 0;
fclose($tempFile);
return $supported;
diff --git a/core/modules/system/src/SystemManager.php b/core/modules/system/src/SystemManager.php
index 5534e70147b4..43a53fe05425 100644
--- a/core/modules/system/src/SystemManager.php
+++ b/core/modules/system/src/SystemManager.php
@@ -2,11 +2,12 @@
namespace Drupal\system;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Menu\MenuActiveTrailInterface;
-use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuTreeParameters;
-use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\RequestStack;
@@ -54,16 +55,31 @@ class SystemManager {
/**
* Requirement severity -- Requirement successfully met.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
+ * \Drupal\Core\Extension\Requirement\RequirementSeverity::OK instead.
+ *
+ * @see https://www.drupal.org/node/3410939
*/
const REQUIREMENT_OK = 0;
/**
* Requirement severity -- Warning condition; proceed but flag warning.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
+ * \Drupal\Core\Extension\Requirement\RequirementSeverity::Warning instead.
+ *
+ * @see https://www.drupal.org/node/3410939
*/
const REQUIREMENT_WARNING = 1;
/**
* Requirement severity -- Error condition; abort installation.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
+ * \Drupal\Core\Extension\Requirement\RequirementSeverity::Error instead.
+ *
+ * @see https://www.drupal.org/node/3410939
*/
const REQUIREMENT_ERROR = 2;
@@ -94,7 +110,7 @@ class SystemManager {
*/
public function checkRequirements() {
$requirements = $this->listRequirements();
- return $this->getMaxSeverity($requirements) == static::REQUIREMENT_ERROR;
+ return RequirementSeverity::maxSeverityFromRequirements($requirements) === RequirementSeverity::Error;
}
/**
@@ -136,15 +152,16 @@ class SystemManager {
*
* @return int
* The highest severity in the array.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
+ * \Drupal\Core\Extension\Requirement\RequirementSeverity::getMaxSeverity()
+ * instead.
+ *
+ * @see https://www.drupal.org/node/3410939
*/
public function getMaxSeverity(&$requirements) {
- $severity = static::REQUIREMENT_OK;
- foreach ($requirements as $requirement) {
- if (isset($requirement['severity'])) {
- $severity = max($severity, $requirement['severity']);
- }
- }
- return $severity;
+ @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . RequirementSeverity::class . '::maxSeverityFromRequirements() instead. See https://www.drupal.org/node/3410939', \E_USER_DEPRECATED);
+ return RequirementSeverity::maxSeverityFromRequirements($requirements)->value;
}
/**
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 9b8c25c157ed..5bbaa8a5436f 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -5,30 +5,9 @@
* Install, update and uninstall functions for the system module.
*/
-use Drupal\Component\FileSystem\FileSystem as FileSystemComponent;
-use Drupal\Component\Utility\Bytes;
use Drupal\Component\Utility\Crypt;
-use Drupal\Component\Utility\Environment;
-use Drupal\Component\Utility\OpCodeCache;
-use Drupal\Component\Utility\Unicode;
-use Drupal\Core\Database\Database;
-use Drupal\Core\DrupalKernel;
-use Drupal\Core\Extension\ExtensionLifecycle;
-use Drupal\Core\File\FileSystemInterface;
-use Drupal\Core\Link;
-use Drupal\Core\Utility\PhpRequirements;
-use Drupal\Core\Render\Markup;
-use Drupal\Core\Site\Settings;
-use Drupal\Core\StreamWrapper\PrivateStream;
-use Drupal\Core\StreamWrapper\PublicStream;
-use Drupal\Core\StringTranslation\ByteSizeMarkup;
-use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Update\EquivalentUpdate;
-use Drupal\Core\Url;
-use Drupal\Core\Utility\Error;
-use Psr\Http\Client\ClientExceptionInterface;
-use Symfony\Component\HttpFoundation\Request;
// cspell:ignore quickedit
@@ -61,1565 +40,6 @@ const DRUPAL_CORE_REMOVED_THEME_LIST = [
];
/**
- * Implements hook_requirements().
- */
-function system_requirements($phase): array {
- global $install_state;
-
- // Get the current default PHP requirements for this version of Drupal.
- $minimum_supported_php = PhpRequirements::getMinimumSupportedPhp();
-
- // Reset the extension lists.
- /** @var \Drupal\Core\Extension\ModuleExtensionList $module_extension_list */
- $module_extension_list = \Drupal::service('extension.list.module');
- $module_extension_list->reset();
- /** @var \Drupal\Core\Extension\ThemeExtensionList $theme_extension_list */
- $theme_extension_list = \Drupal::service('extension.list.theme');
- $theme_extension_list->reset();
- $requirements = [];
-
- // Report Drupal version
- if ($phase == 'runtime') {
- $requirements['drupal'] = [
- 'title' => t('Drupal'),
- 'value' => \Drupal::VERSION,
- 'severity' => REQUIREMENT_INFO,
- 'weight' => -10,
- ];
-
- // Display the currently active installation profile, if the site
- // is not running the default installation profile.
- $profile = \Drupal::installProfile();
- if ($profile != 'standard' && !empty($profile)) {
- $info = $module_extension_list->getExtensionInfo($profile);
- $requirements['install_profile'] = [
- 'title' => t('Installation profile'),
- 'value' => t('%profile_name (%profile%version)', [
- '%profile_name' => $info['name'],
- '%profile' => $profile,
- '%version' => !empty($info['version']) ? '-' . $info['version'] : '',
- ]),
- 'severity' => REQUIREMENT_INFO,
- 'weight' => -9,
- ];
- }
-
- // Gather all obsolete and experimental modules being enabled.
- $obsolete_extensions = [];
- $deprecated_modules = [];
- $experimental_modules = [];
- $enabled_modules = \Drupal::moduleHandler()->getModuleList();
- foreach ($enabled_modules as $module => $data) {
- $info = $module_extension_list->getExtensionInfo($module);
- if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])) {
- if ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
- $experimental_modules[$module] = $info['name'];
- }
- elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
- $deprecated_modules[] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']];
- }
- elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) {
- $obsolete_extensions[$module] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']];
- }
- }
- }
-
- // Warn if any experimental modules are installed.
- if (!empty($experimental_modules)) {
- $requirements['experimental_modules'] = [
- 'title' => t('Experimental modules installed'),
- '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']),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
- // Warn if any deprecated modules are installed.
- if (!empty($deprecated_modules)) {
- foreach ($deprecated_modules as $deprecated_module) {
- $deprecated_modules_link_list[] = (string) Link::fromTextAndUrl($deprecated_module['name'], Url::fromUri($deprecated_module['lifecycle_link']))->toString();
- }
- $requirements['deprecated_modules'] = [
- 'title' => t('Deprecated modules installed'),
- 'value' => t('Deprecated modules found: %module_list.', [
- '%module_list' => Markup::create(implode(', ', $deprecated_modules_link_list)),
- ]),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
-
- // Gather all obsolete and experimental themes being installed.
- $experimental_themes = [];
- $deprecated_themes = [];
- $installed_themes = \Drupal::service('theme_handler')->listInfo();
- foreach ($installed_themes as $theme => $data) {
- $info = $theme_extension_list->getExtensionInfo($theme);
- if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])) {
- if ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
- $experimental_themes[$theme] = $info['name'];
- }
- elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
- $deprecated_themes[] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']];
- }
- elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) {
- $obsolete_extensions[$theme] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']];
- }
- }
- }
-
- // Warn if any experimental themes are installed.
- if (!empty($experimental_themes)) {
- $requirements['experimental_themes'] = [
- 'title' => t('Experimental themes installed'),
- '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)]),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
-
- // Warn if any deprecated themes are installed.
- if (!empty($deprecated_themes)) {
- foreach ($deprecated_themes as $deprecated_theme) {
- $deprecated_themes_link_list[] = (string) Link::fromTextAndUrl($deprecated_theme['name'], Url::fromUri($deprecated_theme['lifecycle_link']))->toString();
-
- }
- $requirements['deprecated_themes'] = [
- 'title' => t('Deprecated themes installed'),
- 'value' => t('Deprecated themes found: %theme_list.', [
- '%theme_list' => Markup::create(implode(', ', $deprecated_themes_link_list)),
- ]),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
-
- // Warn if any obsolete extensions (themes or modules) are installed.
- if (!empty($obsolete_extensions)) {
- foreach ($obsolete_extensions as $obsolete_extension) {
- $obsolete_extensions_link_list[] = (string) Link::fromTextAndUrl($obsolete_extension['name'], Url::fromUri($obsolete_extension['lifecycle_link']))->toString();
- }
- $requirements['obsolete_extensions'] = [
- 'title' => t('Obsolete extensions installed'),
- 'value' => t('Obsolete extensions found: %extensions. Obsolete extensions are provided only so that they can be uninstalled cleanly. You should immediately <a href=":uninstall_url">uninstall these extensions</a> since they may be removed in a future release.', [
- '%extensions' => Markup::create(implode(', ', $obsolete_extensions_link_list)),
- ':uninstall_url' => Url::fromRoute('system.modules_uninstall')->toString(),
- ]),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
- _system_advisories_requirements($requirements);
- }
-
- // Web server information.
- $request_object = \Drupal::request();
- $software = $request_object->server->get('SERVER_SOFTWARE');
- $requirements['webserver'] = [
- 'title' => t('Web server'),
- 'value' => $software,
- ];
-
- // Tests clean URL support.
- if ($phase == 'install' && $install_state['interactive'] && !$request_object->query->has('rewrite') && str_contains($software, 'Apache')) {
- // If the Apache rewrite module is not enabled, Apache version must be >=
- // 2.2.16 because of the FallbackResource directive in the root .htaccess
- // file. Since the Apache version reported by the server is dependent on the
- // ServerTokens setting in httpd.conf, we may not be able to determine if a
- // given config is valid. Thus we are unable to use version_compare() as we
- // need have three possible outcomes: the version of Apache is greater than
- // 2.2.16, is less than 2.2.16, or cannot be determined accurately. In the
- // first case, we encourage the use of mod_rewrite; in the second case, we
- // raise an error regarding the minimum Apache version; in the third case,
- // we raise a warning that the current version of Apache may not be
- // supported.
- $rewrite_warning = FALSE;
- $rewrite_error = FALSE;
- $apache_version_string = 'Apache';
-
- // Determine the Apache version number: major, minor and revision.
- if (preg_match('/Apache\/(\d+)\.?(\d+)?\.?(\d+)?/', $software, $matches)) {
- $apache_version_string = $matches[0];
-
- // Major version number
- if ($matches[1] < 2) {
- $rewrite_error = TRUE;
- }
- elseif ($matches[1] == 2) {
- if (!isset($matches[2])) {
- $rewrite_warning = TRUE;
- }
- elseif ($matches[2] < 2) {
- $rewrite_error = TRUE;
- }
- elseif ($matches[2] == 2) {
- if (!isset($matches[3])) {
- $rewrite_warning = TRUE;
- }
- elseif ($matches[3] < 16) {
- $rewrite_error = TRUE;
- }
- }
- }
- }
- else {
- $rewrite_warning = TRUE;
- }
-
- if ($rewrite_warning) {
- $requirements['apache_version'] = [
- 'title' => t('Apache version'),
- 'value' => $apache_version_string,
- 'severity' => REQUIREMENT_WARNING,
- '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]),
- ];
- }
-
- if ($rewrite_error) {
- $requirements['Apache version'] = [
- 'title' => t('Apache version'),
- 'value' => $apache_version_string,
- 'severity' => REQUIREMENT_ERROR,
- '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' => 'https://www.drupal.org/docs/8/clean-urls-in-drupal-8']),
- ];
- }
-
- if (!$rewrite_error && !$rewrite_warning) {
- $requirements['rewrite_module'] = [
- 'title' => t('Clean URLs'),
- 'value' => t('Disabled'),
- 'severity' => REQUIREMENT_WARNING,
- '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' => 'https://www.drupal.org/docs/8/clean-urls-in-drupal-8']),
- ];
- }
- }
-
- // Verify the user is running a supported PHP version.
- // If the site is running a recommended version of PHP, just display it
- // as an informational message on the status report. This will be overridden
- // with an error or warning if the site is running older PHP versions for
- // which Drupal has already or will soon drop support.
- $phpversion = $phpversion_label = phpversion();
- if ($phase === 'runtime') {
- $phpversion_label = t('@phpversion (<a href=":url">more information</a>)', [
- '@phpversion' => $phpversion,
- ':url' => (new Url('system.php'))->toString(),
- ]);
- }
- $requirements['php'] = [
- 'title' => t('PHP'),
- 'value' => $phpversion_label,
- ];
-
- // Check if the PHP version is below what Drupal supports.
- if (version_compare($phpversion, $minimum_supported_php) < 0) {
- $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 PHP requirements</a> page for more information.',
- [
- '%version' => $minimum_supported_php,
- '%recommended' => \Drupal::RECOMMENDED_PHP,
- ':php_requirements' => 'https://www.drupal.org/docs/system-requirements/php-requirements',
- ]
- );
-
- // If the PHP version is also below the absolute minimum allowed, it's not
- // safe to continue with the requirements check, and should always be an
- // error.
- if (version_compare($phpversion, \Drupal::MINIMUM_PHP) < 0) {
- $requirements['php']['severity'] = REQUIREMENT_ERROR;
- return $requirements;
- }
- // Otherwise, the message should be an error at runtime, and a warning
- // during installation or update.
- $requirements['php']['severity'] = ($phase === 'runtime') ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
- }
- // For PHP versions that are still supported but no longer recommended,
- // inform users of what's recommended, allowing them to take action before it
- // becomes urgent.
- elseif ($phase === 'runtime' && version_compare($phpversion, \Drupal::RECOMMENDED_PHP) < 0) {
- $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 PHP requirements</a> page for more information.', ['%recommended' => \Drupal::RECOMMENDED_PHP, ':php_requirements' => 'https://www.drupal.org/docs/system-requirements/php-requirements']);
- $requirements['php']['severity'] = REQUIREMENT_INFO;
- }
-
- // Test for PHP extensions.
- $requirements['php_extensions'] = [
- 'title' => t('PHP extensions'),
- ];
-
- $missing_extensions = [];
- $required_extensions = [
- 'date',
- 'dom',
- 'filter',
- 'gd',
- 'hash',
- 'json',
- 'pcre',
- 'pdo',
- 'session',
- 'SimpleXML',
- 'SPL',
- 'tokenizer',
- 'xml',
- 'zlib',
- ];
- foreach ($required_extensions as $extension) {
- if (!extension_loaded($extension)) {
- $missing_extensions[] = $extension;
- }
- }
-
- if (!empty($missing_extensions)) {
- $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):', [
- ':system_requirements' => 'https://www.drupal.org/docs/system-requirements',
- ]);
-
- // We use twig inline_template to avoid twig's autoescape.
- $description = [
- '#type' => 'inline_template',
- '#template' => '{{ description }}{{ missing_extensions }}',
- '#context' => [
- 'description' => $description,
- 'missing_extensions' => [
- '#theme' => 'item_list',
- '#items' => $missing_extensions,
- ],
- ],
- ];
-
- $requirements['php_extensions']['value'] = t('Disabled');
- $requirements['php_extensions']['severity'] = REQUIREMENT_ERROR;
- $requirements['php_extensions']['description'] = $description;
- }
- else {
- $requirements['php_extensions']['value'] = t('Enabled');
- }
-
- if ($phase == 'install' || $phase == 'runtime') {
- // Check to see if OPcache is installed.
- if (!OpCodeCache::isEnabled()) {
- $requirements['php_opcache'] = [
- 'value' => t('Not enabled'),
- 'severity' => REQUIREMENT_WARNING,
- '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.'),
- ];
- }
- else {
- $requirements['php_opcache']['value'] = t('Enabled');
- }
- $requirements['php_opcache']['title'] = t('PHP OPcode caching');
- }
-
- // Check to see if APCu is installed and configured correctly.
- if ($phase == 'runtime' && PHP_SAPI != 'cli') {
- $requirements['php_apcu_enabled']['title'] = t('PHP APCu caching');
- $requirements['php_apcu_available']['title'] = t('PHP APCu available caching');
- if (extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN)) {
- $memory_info = apcu_sma_info(TRUE);
- $apcu_actual_size = ByteSizeMarkup::create($memory_info['seg_size'] * $memory_info['num_seg']);
- $apcu_recommended_size = '32 MB';
- $requirements['php_apcu_enabled']['value'] = t('Enabled (@size)', ['@size' => $apcu_actual_size]);
- if (Bytes::toNumber(ini_get('apc.shm_size')) * ini_get('apc.shm_segments') < Bytes::toNumber($apcu_recommended_size)) {
- $requirements['php_apcu_enabled']['severity'] = REQUIREMENT_WARNING;
- $requirements['php_apcu_enabled']['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.', [
- '@apcu_size' => $apcu_actual_size,
- '@apcu_default_size' => $apcu_recommended_size,
- ]);
- }
- else {
- $memory_available = $memory_info['avail_mem'] / ($memory_info['seg_size'] * $memory_info['num_seg']);
- if ($memory_available < 0.1) {
- $requirements['php_apcu_available']['severity'] = REQUIREMENT_ERROR;
- $requirements['php_apcu_available']['description'] = t('APCu is using over 90% of its allotted memory (@apcu_actual_size). To improve APCu performance, consider increasing this limit.', [
- '@apcu_actual_size' => $apcu_actual_size,
- ]);
- }
- elseif ($memory_available < 0.25) {
- $requirements['php_apcu_available']['severity'] = REQUIREMENT_WARNING;
- $requirements['php_apcu_available']['description'] = t('APCu is using over 75% of its allotted memory (@apcu_actual_size). To improve APCu performance, consider increasing this limit.', [
- '@apcu_actual_size' => $apcu_actual_size,
- ]);
- }
- else {
- $requirements['php_apcu_available']['severity'] = REQUIREMENT_OK;
- }
- $requirements['php_apcu_available']['value'] = t('Memory available: @available.', [
- '@available' => ByteSizeMarkup::create($memory_info['avail_mem']),
- ]);
- }
- }
- else {
- $requirements['php_apcu_enabled'] += [
- 'value' => t('Not enabled'),
- 'severity' => REQUIREMENT_INFO,
- '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.'),
- ];
- }
- }
-
- if ($phase != 'update') {
- // Test whether we have a good source of random bytes.
- $requirements['php_random_bytes'] = [
- 'title' => t('Random number generation'),
- ];
- try {
- $bytes = random_bytes(10);
- if (strlen($bytes) != 10) {
- throw new \Exception("Tried to generate 10 random bytes, generated '" . strlen($bytes) . "'");
- }
- $requirements['php_random_bytes']['value'] = t('Successful');
- }
- catch (\Exception $e) {
- // If /dev/urandom is not available on a UNIX-like system, check whether
- // open_basedir restrictions are the cause.
- $open_basedir_blocks_urandom = FALSE;
- if (DIRECTORY_SEPARATOR === '/' && !@is_readable('/dev/urandom')) {
- $open_basedir = ini_get('open_basedir');
- if ($open_basedir) {
- $open_basedir_paths = explode(PATH_SEPARATOR, $open_basedir);
- $open_basedir_blocks_urandom = !array_intersect(['/dev', '/dev/', '/dev/urandom'], $open_basedir_paths);
- }
- }
- $args = [
- ':drupal-php' => 'https://www.drupal.org/docs/system-requirements/php-requirements',
- '%exception_message' => $e->getMessage(),
- ];
- if ($open_basedir_blocks_urandom) {
- $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 allowed list. See the <a href=":drupal-php">system requirements</a> page for more information. %exception_message', $args);
- }
- else {
- $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);
- }
- $requirements['php_random_bytes']['value'] = t('Less secure');
- $requirements['php_random_bytes']['severity'] = REQUIREMENT_ERROR;
- }
- }
-
- if ($phase === 'runtime' && PHP_SAPI !== 'cli') {
- if (!function_exists('fastcgi_finish_request') && !function_exists('litespeed_finish_request') && !ob_get_status()) {
- $requirements['output_buffering'] = [
- 'title' => t('Output Buffering'),
- 'error_value' => t('Not enabled'),
- 'severity' => REQUIREMENT_WARNING,
- 'description' => t('<a href="https://www.php.net/manual/en/function.ob-start.php">Output buffering</a> is not enabled. This may degrade Drupal\'s performance. You can enable output buffering by default <a href="https://www.php.net/manual/en/outcontrol.configuration.php#ini.output-buffering">in your PHP settings</a>.'),
- ];
- }
- }
-
- if ($phase == 'install' || $phase == 'update') {
- // Test for PDO (database).
- $requirements['database_extensions'] = [
- 'title' => t('Database support'),
- ];
-
- // Make sure PDO is available.
- $database_ok = extension_loaded('pdo');
- if (!$database_ok) {
- $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.', [
- ':link' => 'https://www.drupal.org/docs/system-requirements/php-requirements#database',
- ]);
- }
- else {
- // Make sure at least one supported database driver exists.
- if (empty(Database::getDriverList()->getInstallableList())) {
- $database_ok = FALSE;
- $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>.', [
- ':drupal-databases' => 'https://www.drupal.org/docs/system-requirements/database-server-requirements',
- ]);
- }
- // Make sure the native PDO extension is available, not the older PEAR
- // version. (See install_verify_pdo() for details.)
- if (!defined('PDO::ATTR_DEFAULT_FETCH_MODE')) {
- $database_ok = FALSE;
- $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.', [
- ':link' => 'https://www.drupal.org/docs/system-requirements/php-requirements#database',
- ]);
- }
- }
-
- if (!$database_ok) {
- $requirements['database_extensions']['value'] = t('Disabled');
- $requirements['database_extensions']['severity'] = REQUIREMENT_ERROR;
- $requirements['database_extensions']['description'] = $pdo_message;
- }
- else {
- $requirements['database_extensions']['value'] = t('Enabled');
- }
- }
-
- if ($phase === 'runtime' || $phase === 'update') {
- // Database information.
- $class = Database::getConnection()->getConnectionOptions()['namespace'] . '\\Install\\Tasks';
- /** @var \Drupal\Core\Database\Install\Tasks $tasks */
- $tasks = new $class();
- $requirements['database_system'] = [
- 'title' => t('Database system'),
- 'value' => $tasks->name(),
- ];
- $requirements['database_system_version'] = [
- 'title' => t('Database system version'),
- 'value' => Database::getConnection()->version(),
- ];
-
- $errors = $tasks->engineVersionRequirementsCheck();
- $error_count = count($errors);
- if ($error_count > 0) {
- $error_message = [
- '#theme' => 'item_list',
- '#items' => $errors,
- // Use the comma-list style to display a single error without bullets.
- '#context' => ['list_style' => $error_count === 1 ? 'comma-list' : ''],
- ];
- $requirements['database_system_version']['severity'] = REQUIREMENT_ERROR;
- $requirements['database_system_version']['description'] = $error_message;
- }
- }
-
- if ($phase === 'runtime' || $phase === 'update') {
- // Test database JSON support.
- $requirements['database_support_json'] = [
- 'title' => t('Database support for JSON'),
- 'severity' => REQUIREMENT_OK,
- 'value' => t('Available'),
- 'description' => t('Drupal requires databases that support JSON storage.'),
- ];
-
- if (!Database::getConnection()->hasJson()) {
- $requirements['database_support_json']['value'] = t('Not available');
- $requirements['database_support_json']['severity'] = REQUIREMENT_ERROR;
- }
- }
-
- // Test PHP memory_limit
- $memory_limit = ini_get('memory_limit');
- $requirements['php_memory_limit'] = [
- 'title' => t('PHP memory limit'),
- 'value' => $memory_limit == -1 ? t('-1 (Unlimited)') : $memory_limit,
- ];
-
- if (!Environment::checkMemoryLimit(\Drupal::MINIMUM_PHP_MEMORY_LIMIT, $memory_limit)) {
- $description = [];
- if ($phase == 'install') {
- $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]);
- }
- elseif ($phase == 'update') {
- $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]);
- }
- elseif ($phase == 'runtime') {
- $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]);
- }
-
- if (!empty($description['phase'])) {
- if ($php_ini_path = get_cfg_var('cfg_file_path')) {
- $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]);
- }
- else {
- $description['memory'] = t('Contact your system administrator or hosting provider for assistance with increasing your PHP memory limit.');
- }
-
- $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']);
-
- $description = [
- '#type' => 'inline_template',
- '#template' => '{{ description_phase }} {{ description_memory }} {{ handbook }}',
- '#context' => [
- 'description_phase' => $description['phase'],
- 'description_memory' => $description['memory'],
- 'handbook' => $handbook_link,
- ],
- ];
-
- $requirements['php_memory_limit']['description'] = $description;
- $requirements['php_memory_limit']['severity'] = REQUIREMENT_WARNING;
- }
- }
-
- // Test if configuration files and directory are writable.
- if ($phase == 'runtime') {
- $conf_errors = [];
- // Find the site path. Kernel service is not always available at this point,
- // but is preferred, when available.
- if (\Drupal::hasService('kernel')) {
- $site_path = \Drupal::getContainer()->getParameter('site.path');
- }
- else {
- $site_path = DrupalKernel::findSitePath(Request::createFromGlobals());
- }
- // Allow system administrators to disable permissions hardening for the site
- // directory. This allows additional files in the site directory to be
- // updated when they are managed in a version control system.
- if (Settings::get('skip_permissions_hardening')) {
- $error_value = t('Protection disabled');
- // If permissions hardening is disabled, then only show a warning for a
- // writable file, as a reminder, rather than an error.
- $file_protection_severity = REQUIREMENT_WARNING;
- }
- else {
- $error_value = t('Not protected');
- // In normal operation, writable files or directories are an error.
- $file_protection_severity = REQUIREMENT_ERROR;
- if (!drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir')) {
- $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]);
- }
- }
- foreach (['settings.php', 'settings.local.php', 'services.yml'] as $conf_file) {
- $full_path = $site_path . '/' . $conf_file;
- if (file_exists($full_path) && !drupal_verify_install_file($full_path, FILE_EXIST | FILE_READABLE | FILE_NOT_WRITABLE, 'file', !Settings::get('skip_permissions_hardening'))) {
- $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]);
- }
- }
- if (!empty($conf_errors)) {
- if (count($conf_errors) == 1) {
- $description = $conf_errors[0];
- }
- else {
- // We use twig inline_template to avoid double escaping.
- $description = [
- '#type' => 'inline_template',
- '#template' => '{{ configuration_error_list }}',
- '#context' => [
- 'configuration_error_list' => [
- '#theme' => 'item_list',
- '#items' => $conf_errors,
- ],
- ],
- ];
- }
- $requirements['configuration_files'] = [
- 'value' => $error_value,
- 'severity' => $file_protection_severity,
- 'description' => $description,
- ];
- }
- else {
- $requirements['configuration_files'] = [
- 'value' => t('Protected'),
- ];
- }
- $requirements['configuration_files']['title'] = t('Configuration files');
- }
-
- // Test the contents of the .htaccess files.
- if ($phase == 'runtime') {
- // Try to write the .htaccess files first, to prevent false alarms in case
- // (for example) the /tmp directory was wiped.
- /** @var \Drupal\Core\File\HtaccessWriterInterface $htaccessWriter */
- $htaccessWriter = \Drupal::service("file.htaccess_writer");
- $htaccessWriter->ensure();
- foreach ($htaccessWriter->defaultProtectedDirs() as $protected_dir) {
- $htaccess_file = $protected_dir->getPath() . '/.htaccess';
- // Check for the string which was added to the recommended .htaccess file
- // in the latest security update.
- if (!file_exists($htaccess_file) || !($contents = @file_get_contents($htaccess_file)) || !str_contains($contents, 'Drupal_Security_Do_Not_Remove_See_SA_2013_003')) {
- $url = 'https://www.drupal.org/SA-CORE-2013-003';
- $requirements[$htaccess_file] = [
- // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
- 'title' => new TranslatableMarkup($protected_dir->getTitle()),
- 'value' => t('Not fully protected'),
- 'severity' => REQUIREMENT_ERROR,
- '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()]),
- ];
- }
- }
- }
-
- // Report cron status.
- if ($phase == 'runtime') {
- $cron_config = \Drupal::config('system.cron');
- // Cron warning threshold defaults to two days.
- $threshold_warning = $cron_config->get('threshold.requirements_warning');
- // Cron error threshold defaults to two weeks.
- $threshold_error = $cron_config->get('threshold.requirements_error');
-
- // Determine when cron last ran.
- $cron_last = \Drupal::state()->get('system.cron_last');
- if (!is_numeric($cron_last)) {
- $cron_last = \Drupal::state()->get('install_time', 0);
- }
-
- // Determine severity based on time since cron last ran.
- $severity = REQUIREMENT_INFO;
- $request_time = \Drupal::time()->getRequestTime();
- if ($request_time - $cron_last > $threshold_error) {
- $severity = REQUIREMENT_ERROR;
- }
- elseif ($request_time - $cron_last > $threshold_warning) {
- $severity = REQUIREMENT_WARNING;
- }
-
- // Set summary and description based on values determined above.
- $summary = t('Last run @time ago', ['@time' => \Drupal::service('date.formatter')->formatTimeDiffSince($cron_last)]);
-
- $requirements['cron'] = [
- 'title' => t('Cron maintenance tasks'),
- 'severity' => $severity,
- 'value' => $summary,
- ];
- if ($severity != REQUIREMENT_INFO) {
- $requirements['cron']['description'][] = [
- [
- '#markup' => t('Cron has not run recently.'),
- '#suffix' => ' ',
- ],
- [
- '#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/docs/administering-a-drupal-site/cron-automated-tasks/cron-automated-tasks-overview']),
- '#suffix' => ' ',
- ],
- ];
- }
- $requirements['cron']['description'][] = [
- [
- '#type' => 'link',
- '#prefix' => '(',
- '#title' => t('more information'),
- '#suffix' => ')',
- '#url' => Url::fromRoute('system.cron_settings'),
- ],
- [
- '#prefix' => '<span class="cron-description__run-cron">',
- '#suffix' => '</span>',
- '#type' => 'link',
- '#title' => t('Run cron'),
- '#url' => Url::fromRoute('system.run_cron'),
- ],
- ];
- }
- if ($phase != 'install') {
- $directories = [
- PublicStream::basePath(),
- // By default no private files directory is configured. For private files
- // to be secure the admin needs to provide a path outside the webroot.
- PrivateStream::basePath(),
- \Drupal::service('file_system')->getTempDirectory(),
- ];
- }
-
- // During an install we need to make assumptions about the file system
- // unless overrides are provided in settings.php.
- if ($phase == 'install') {
- $directories = [];
- if ($file_public_path = Settings::get('file_public_path')) {
- $directories[] = $file_public_path;
- }
- else {
- // If we are installing Drupal, the settings.php file might not exist yet
- // in the intended site directory, so don't require it.
- $request = Request::createFromGlobals();
- $site_path = DrupalKernel::findSitePath($request);
- $directories[] = $site_path . '/files';
- }
- if ($file_private_path = Settings::get('file_private_path')) {
- $directories[] = $file_private_path;
- }
- if (Settings::get('file_temp_path')) {
- $directories[] = Settings::get('file_temp_path');
- }
- else {
- // If the temporary directory is not overridden use an appropriate
- // temporary path for the system.
- $directories[] = FileSystemComponent::getOsTemporaryDirectory();
- }
- }
-
- // Check the config directory if it is defined in settings.php. If it isn't
- // defined, the installer will create a valid config directory later, but
- // during runtime we must always display an error.
- $config_sync_directory = Settings::get('config_sync_directory');
- if (!empty($config_sync_directory)) {
- // If we're installing Drupal try and create the config sync directory.
- if (!is_dir($config_sync_directory) && $phase == 'install') {
- \Drupal::service('file_system')->prepareDirectory($config_sync_directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
- }
- if (!is_dir($config_sync_directory)) {
- if ($phase == 'install') {
- $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']);
- }
- else {
- $description = t('The directory %directory does not exist.', ['%directory' => $config_sync_directory]);
- }
- $requirements['config sync directory'] = [
- 'title' => t('Configuration sync directory'),
- 'description' => $description,
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- }
- if ($phase != 'install' && empty($config_sync_directory)) {
- $requirements['config sync directory'] = [
- 'title' => t('Configuration sync directory'),
- 'value' => t('Not present'),
- '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']"]),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
-
- $requirements['file system'] = [
- 'title' => t('File system'),
- ];
-
- $error = '';
- // For installer, create the directories if possible.
- foreach ($directories as $directory) {
- if (!$directory) {
- continue;
- }
- if ($phase == 'install') {
- \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
- }
- $is_writable = is_writable($directory);
- $is_directory = is_dir($directory);
- if (!$is_writable || !$is_directory) {
- $description = '';
- $requirements['file system']['value'] = t('Not writable');
- if (!$is_directory) {
- $error = t('The directory %directory does not exist.', ['%directory' => $directory]);
- }
- else {
- $error = t('The directory %directory is not writable.', ['%directory' => $directory]);
- }
- // The files directory requirement check is done only during install and
- // runtime.
- if ($phase == 'runtime') {
- $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()]);
- }
- elseif ($phase == 'install') {
- // For the installer UI, we need different wording. 'value' will
- // be treated as version, so provide none there.
- $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']);
- $requirements['file system']['value'] = '';
- }
- if (!empty($description)) {
- $description = [
- '#type' => 'inline_template',
- '#template' => '{{ error }} {{ description }}',
- '#context' => [
- 'error' => $error,
- 'description' => $description,
- ],
- ];
- $requirements['file system']['description'] = $description;
- $requirements['file system']['severity'] = REQUIREMENT_ERROR;
- }
- }
- else {
- // This function can be called before the config_cache table has been
- // created.
- if ($phase == 'install' || \Drupal::config('system.file')->get('default_scheme') == 'public') {
- $requirements['file system']['value'] = t('Writable (<em>public</em> download method)');
- }
- else {
- $requirements['file system']['value'] = t('Writable (<em>private</em> download method)');
- }
- }
- }
-
- // See if updates are available in update.php.
- if ($phase == 'runtime') {
- $requirements['update'] = [
- 'title' => t('Database updates'),
- 'value' => t('Up to date'),
- ];
-
- // Check installed modules.
- $has_pending_updates = FALSE;
- /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */
- $update_registry = \Drupal::service('update.update_hook_registry');
- foreach (\Drupal::moduleHandler()->getModuleList() as $module => $filename) {
- $updates = $update_registry->getAvailableUpdates($module);
- if ($updates) {
- $default = $update_registry->getInstalledVersion($module);
- if (max($updates) > $default) {
- $has_pending_updates = TRUE;
- break;
- }
- }
- }
- if (!$has_pending_updates) {
- /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
- $post_update_registry = \Drupal::service('update.post_update_registry');
- $missing_post_update_functions = $post_update_registry->getPendingUpdateFunctions();
- if (!empty($missing_post_update_functions)) {
- $has_pending_updates = TRUE;
- }
- }
-
- if ($has_pending_updates) {
- $requirements['update']['severity'] = REQUIREMENT_ERROR;
- $requirements['update']['value'] = t('Out of date');
- $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()]);
- }
-
- $requirements['entity_update'] = [
- 'title' => t('Entity/field definitions'),
- 'value' => t('Up to date'),
- ];
- // Verify that no entity updates are pending.
- if ($change_list = \Drupal::entityDefinitionUpdateManager()->getChangeSummary()) {
- $build = [];
- foreach ($change_list as $entity_type_id => $changes) {
- $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
- $build[] = [
- '#theme' => 'item_list',
- '#title' => $entity_type->getLabel(),
- '#items' => $changes,
- ];
- }
-
- $entity_update_issues = \Drupal::service('renderer')->renderInIsolation($build);
- $requirements['entity_update']['severity'] = REQUIREMENT_ERROR;
- $requirements['entity_update']['value'] = t('Mismatched entity and/or field definitions');
- $requirements['entity_update']['description'] = t('The following changes were detected in the entity type and field definitions. @updates', ['@updates' => $entity_update_issues]);
- }
- }
-
- // Display the deployment identifier if set.
- if ($phase == 'runtime') {
- if ($deployment_identifier = Settings::get('deployment_identifier')) {
- $requirements['deployment identifier'] = [
- 'title' => t('Deployment identifier'),
- 'value' => $deployment_identifier,
- 'severity' => REQUIREMENT_INFO,
- ];
- }
- }
-
- // Verify the update.php access setting
- if ($phase == 'runtime') {
- if (Settings::get('update_free_access')) {
- $requirements['update access'] = [
- 'value' => t('Not protected'),
- 'severity' => REQUIREMENT_ERROR,
- '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\']']),
- ];
- }
- else {
- $requirements['update access'] = [
- 'value' => t('Protected'),
- ];
- }
- $requirements['update access']['title'] = t('Access to update.php');
- }
-
- // Display an error if a newly introduced dependency in a module is not
- // resolved.
- if ($phase === 'update' || $phase === 'runtime') {
- $create_extension_incompatibility_list = function (array $extension_names, PluralTranslatableMarkup $description, PluralTranslatableMarkup $title, TranslatableMarkup|string $message = '', TranslatableMarkup|string $additional_description = '') {
- if ($message === '') {
- $message = new TranslatableMarkup('Review the <a href=":url"> suggestions for resolving this incompatibility</a> to repair your installation, and then re-run update.php.', [':url' => 'https://www.drupal.org/docs/updating-drupal/troubleshooting-database-updates']);
- }
- // Use an inline twig template to:
- // - Concatenate MarkupInterface objects and preserve safeness.
- // - Use the item_list theme for the extension list.
- $template = [
- '#type' => 'inline_template',
- '#template' => '{{ description }}{{ extensions }}{{ additional_description }}<br>',
- '#context' => [
- 'extensions' => [
- '#theme' => 'item_list',
- ],
- ],
- ];
- $template['#context']['extensions']['#items'] = $extension_names;
- $template['#context']['description'] = $description;
- $template['#context']['additional_description'] = $additional_description;
- return [
- 'title' => $title,
- 'value' => [
- 'list' => $template,
- 'handbook_link' => [
- '#markup' => $message,
- ],
- ],
- 'severity' => REQUIREMENT_ERROR,
- ];
- };
- $profile = \Drupal::installProfile();
- $files = $module_extension_list->getList();
- $files += $theme_extension_list->getList();
- $core_incompatible_extensions = [];
- $php_incompatible_extensions = [];
- foreach ($files as $extension_name => $file) {
- // Ignore uninstalled extensions and installation profiles.
- if (!$file->status || $extension_name == $profile) {
- continue;
- }
-
- $name = $file->info['name'];
- if (!empty($file->info['core_incompatible'])) {
- $core_incompatible_extensions[$file->info['type']][] = $name;
- }
-
- // Check the extension's PHP version.
- $php = $file->info['php'];
- if (version_compare($php, PHP_VERSION, '>')) {
- $php_incompatible_extensions[$file->info['type']][] = $name;
- }
-
- // Check the module's required modules.
- /** @var \Drupal\Core\Extension\Dependency $requirement */
- foreach ($file->requires as $requirement) {
- $required_module = $requirement->getName();
- // Check if the module exists.
- if (!isset($files[$required_module])) {
- $requirements["$extension_name-$required_module"] = [
- 'title' => t('Unresolved dependency'),
- 'description' => t('@name requires this module.', ['@name' => $name]),
- 'value' => t('@required_name (Missing)', ['@required_name' => $required_module]),
- 'severity' => REQUIREMENT_ERROR,
- ];
- continue;
- }
- // Check for an incompatible version.
- $required_file = $files[$required_module];
- $required_name = $required_file->info['name'];
- // Remove CORE_COMPATIBILITY- only from the start of the string.
- $version = preg_replace('/^(' . \Drupal::CORE_COMPATIBILITY . '\-)/', '', $required_file->info['version'] ?? '');
- if (!$requirement->isCompatible($version)) {
- $requirements["$extension_name-$required_module"] = [
- 'title' => t('Unresolved dependency'),
- 'description' => t('@name requires this module and version. Currently using @required_name version @version', ['@name' => $name, '@required_name' => $required_name, '@version' => $version]),
- 'value' => t('@required_name (Version @compatibility required)', ['@required_name' => $required_name, '@compatibility' => $requirement->getConstraintString()]),
- 'severity' => REQUIREMENT_ERROR,
- ];
- continue;
- }
- }
- }
- if (!empty($core_incompatible_extensions['module'])) {
- $requirements['module_core_incompatible'] = $create_extension_incompatibility_list(
- $core_incompatible_extensions['module'],
- new PluralTranslatableMarkup(
- count($core_incompatible_extensions['module']),
- 'The following module is installed, but it is incompatible with Drupal @version:',
- 'The following modules are installed, but they are incompatible with Drupal @version:',
- ['@version' => \Drupal::VERSION]
- ),
- new PluralTranslatableMarkup(
- count($core_incompatible_extensions['module']),
- 'Incompatible module',
- 'Incompatible modules'
- )
- );
- }
- if (!empty($core_incompatible_extensions['theme'])) {
- $requirements['theme_core_incompatible'] = $create_extension_incompatibility_list(
- $core_incompatible_extensions['theme'],
- new PluralTranslatableMarkup(
- count($core_incompatible_extensions['theme']),
- 'The following theme is installed, but it is incompatible with Drupal @version:',
- 'The following themes are installed, but they are incompatible with Drupal @version:',
- ['@version' => \Drupal::VERSION]
- ),
- new PluralTranslatableMarkup(
- count($core_incompatible_extensions['theme']),
- 'Incompatible theme',
- 'Incompatible themes'
- )
- );
- }
- if (!empty($php_incompatible_extensions['module'])) {
- $requirements['module_php_incompatible'] = $create_extension_incompatibility_list(
- $php_incompatible_extensions['module'],
- new PluralTranslatableMarkup(
- count($php_incompatible_extensions['module']),
- 'The following module is installed, but it is incompatible with PHP @version:',
- 'The following modules are installed, but they are incompatible with PHP @version:',
- ['@version' => phpversion()]
- ),
- new PluralTranslatableMarkup(
- count($php_incompatible_extensions['module']),
- 'Incompatible module',
- 'Incompatible modules'
- )
- );
- }
- if (!empty($php_incompatible_extensions['theme'])) {
- $requirements['theme_php_incompatible'] = $create_extension_incompatibility_list(
- $php_incompatible_extensions['theme'],
- new PluralTranslatableMarkup(
- count($php_incompatible_extensions['theme']),
- 'The following theme is installed, but it is incompatible with PHP @version:',
- 'The following themes are installed, but they are incompatible with PHP @version:',
- ['@version' => phpversion()]
- ),
- new PluralTranslatableMarkup(
- count($php_incompatible_extensions['theme']),
- 'Incompatible theme',
- 'Incompatible themes'
- )
- );
- }
-
- $extension_config = \Drupal::configFactory()->get('core.extension');
-
- // Look for removed core modules.
- $is_removed_module = function ($extension_name) use ($module_extension_list) {
- return !$module_extension_list->exists($extension_name)
- && array_key_exists($extension_name, DRUPAL_CORE_REMOVED_MODULE_LIST);
- };
- $removed_modules = array_filter(array_keys($extension_config->get('module')), $is_removed_module);
- if (!empty($removed_modules)) {
- $list = [];
- foreach ($removed_modules as $removed_module) {
- $list[] = t('<a href=":url">@module</a>', [
- ':url' => "https://www.drupal.org/project/$removed_module",
- '@module' => DRUPAL_CORE_REMOVED_MODULE_LIST[$removed_module],
- ]);
- }
- $requirements['removed_module'] = $create_extension_incompatibility_list(
- $list,
- new PluralTranslatableMarkup(
- count($removed_modules),
- 'You must add the following contributed module and reload this page.',
- 'You must add the following contributed modules and reload this page.'
- ),
- new PluralTranslatableMarkup(
- count($removed_modules),
- 'Removed core module',
- 'Removed core modules'
- ),
- new TranslatableMarkup(
- 'For more information read the <a href=":url">documentation on deprecated modules.</a>',
- [':url' => 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-modules']
- ),
- new PluralTranslatableMarkup(
- count($removed_modules),
- 'This module is installed on your site but is no longer provided by Core.',
- 'These modules are installed on your site but are no longer provided by Core.'
- ),
- );
- }
-
- // Look for removed core themes.
- $is_removed_theme = function ($extension_name) use ($theme_extension_list) {
- return !$theme_extension_list->exists($extension_name)
- && array_key_exists($extension_name, DRUPAL_CORE_REMOVED_THEME_LIST);
- };
- $removed_themes = array_filter(array_keys($extension_config->get('theme')), $is_removed_theme);
- if (!empty($removed_themes)) {
- $list = [];
- foreach ($removed_themes as $removed_theme) {
- $list[] = t('<a href=":url">@theme</a>', [
- ':url' => "https://www.drupal.org/project/$removed_theme",
- '@theme' => DRUPAL_CORE_REMOVED_THEME_LIST[$removed_theme],
- ]);
- }
- $requirements['removed_theme'] = $create_extension_incompatibility_list(
- $list,
- new PluralTranslatableMarkup(
- count($removed_themes),
- 'You must add the following contributed theme and reload this page.',
- 'You must add the following contributed themes and reload this page.'
- ),
- new PluralTranslatableMarkup(
- count($removed_themes),
- 'Removed core theme',
- 'Removed core themes'
- ),
- new TranslatableMarkup(
- 'For more information read the <a href=":url">documentation on deprecated themes.</a>',
- [':url' => 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-themes']
- ),
- new PluralTranslatableMarkup(
- count($removed_themes),
- 'This theme is installed on your site but is no longer provided by Core.',
- 'These themes are installed on your site but are no longer provided by Core.'
- ),
- );
- }
-
- // Look for missing modules.
- $is_missing_module = function ($extension_name) use ($module_extension_list) {
- return !$module_extension_list->exists($extension_name) && !in_array($extension_name, array_keys(DRUPAL_CORE_REMOVED_MODULE_LIST), TRUE);
- };
- $invalid_modules = array_filter(array_keys($extension_config->get('module')), $is_missing_module);
-
- if (!empty($invalid_modules)) {
- $requirements['invalid_module'] = $create_extension_incompatibility_list(
- $invalid_modules,
- new PluralTranslatableMarkup(
- count($invalid_modules),
- 'The following module is marked as installed in the core.extension configuration, but it is missing:',
- 'The following modules are marked as installed in the core.extension configuration, but they are missing:'
- ),
- new PluralTranslatableMarkup(
- count($invalid_modules),
- 'Missing or invalid module',
- 'Missing or invalid modules'
- )
- );
- }
-
- // Look for invalid themes.
- $is_missing_theme = function ($extension_name) use (&$theme_extension_list) {
- return !$theme_extension_list->exists($extension_name) && !in_array($extension_name, array_keys(DRUPAL_CORE_REMOVED_THEME_LIST), TRUE);
- };
- $invalid_themes = array_filter(array_keys($extension_config->get('theme')), $is_missing_theme);
- if (!empty($invalid_themes)) {
- $requirements['invalid_theme'] = $create_extension_incompatibility_list(
- $invalid_themes,
- new PluralTranslatableMarkup(
- count($invalid_themes),
- 'The following theme is marked as installed in the core.extension configuration, but it is missing:',
- 'The following themes are marked as installed in the core.extension configuration, but they are missing:'
- ),
- new PluralTranslatableMarkup(
- count($invalid_themes),
- 'Missing or invalid theme',
- 'Missing or invalid themes'
- )
- );
- }
- }
-
- // Returns Unicode library status and errors.
- $libraries = [
- Unicode::STATUS_SINGLEBYTE => t('Standard PHP'),
- Unicode::STATUS_MULTIBYTE => t('PHP Mbstring Extension'),
- Unicode::STATUS_ERROR => t('Error'),
- ];
- $severities = [
- Unicode::STATUS_SINGLEBYTE => REQUIREMENT_WARNING,
- Unicode::STATUS_MULTIBYTE => NULL,
- Unicode::STATUS_ERROR => REQUIREMENT_ERROR,
- ];
- $failed_check = Unicode::check();
- $library = Unicode::getStatus();
-
- $requirements['unicode'] = [
- 'title' => t('Unicode library'),
- 'value' => $libraries[$library],
- 'severity' => $severities[$library],
- ];
- switch ($failed_check) {
- case 'mb_strlen':
- $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.');
- break;
-
- case 'mbstring.encoding_translation':
- $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. Refer to the <a href="http://php.net/mbstring">PHP mbstring documentation</a> for more information.');
- break;
- }
-
- if ($phase == 'runtime') {
- // Check for update status module.
- if (!\Drupal::moduleHandler()->moduleExists('update')) {
- $requirements['update status'] = [
- 'value' => t('Not enabled'),
- 'severity' => REQUIREMENT_WARNING,
- 'description' => t('Update notifications are not enabled. It is <strong>highly recommended</strong> that you install the Update Status 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>.', [
- ':update' => 'https://www.drupal.org/documentation/modules/update',
- ':module' => Url::fromRoute('system.modules_list')->toString(),
- ]),
- ];
- }
- else {
- $requirements['update status'] = [
- 'value' => t('Enabled'),
- ];
- }
- $requirements['update status']['title'] = t('Update notifications');
-
- if (Settings::get('rebuild_access')) {
- $requirements['rebuild access'] = [
- 'title' => t('Rebuild access'),
- 'value' => t('Enabled'),
- 'severity' => REQUIREMENT_ERROR,
- '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.'),
- ];
- }
- }
-
- // Check if the SameSite cookie attribute is set to a valid value. Since this
- // involves checking whether we are using a secure connection this only makes
- // sense inside an HTTP request, not on the command line.
- if ($phase === 'runtime' && PHP_SAPI !== 'cli') {
- $samesite = ini_get('session.cookie_samesite') ?: t('Not set');
- // Check if the SameSite attribute is set to a valid value. If it is set to
- // 'None' the request needs to be done over HTTPS.
- $valid = match ($samesite) {
- 'Lax', 'Strict' => TRUE,
- 'None' => $request_object->isSecure(),
- default => FALSE,
- };
- $requirements['php_session_samesite'] = [
- 'title' => t('SameSite cookie attribute'),
- 'value' => $samesite,
- 'severity' => $valid ? REQUIREMENT_OK : REQUIREMENT_WARNING,
- 'description' => t('This attribute should be explicitly set to Lax, Strict or None. If set to None then the request must be made via HTTPS. See <a href=":url" target="_blank">PHP documentation</a>', [
- ':url' => 'https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-samesite',
- ]),
- ];
- }
-
- // See if trusted host names have been configured, and warn the user if they
- // are not set.
- if ($phase == 'runtime') {
- $trusted_host_patterns = Settings::get('trusted_host_patterns');
- if (empty($trusted_host_patterns)) {
- $requirements['trusted_host_patterns'] = [
- 'title' => t('Trusted Host Settings'),
- 'value' => t('Not enabled'),
- '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/installing-drupal/trusted-host-settings']),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- else {
- $requirements['trusted_host_patterns'] = [
- 'title' => t('Trusted Host Settings'),
- 'value' => t('Enabled'),
- 'description' => t('The trusted_host_patterns setting is set to allow %trusted_host_patterns', ['%trusted_host_patterns' => implode(', ', $trusted_host_patterns)]),
- ];
- }
- }
-
- // When the database driver is provided by a module, then check that the
- // providing module is installed.
- if ($phase === 'runtime' || $phase === 'update') {
- $connection = Database::getConnection();
- $provider = $connection->getProvider();
- if ($provider !== 'core' && !\Drupal::moduleHandler()->moduleExists($provider)) {
- $autoload = $connection->getConnectionOptions()['autoload'] ?? '';
- if (str_contains($autoload, 'src/Driver/Database/')) {
- $post_update_registry = \Drupal::service('update.post_update_registry');
- $pending_updates = $post_update_registry->getPendingUpdateInformation();
- if (!in_array('enable_provider_database_driver', array_keys($pending_updates['system']['pending'] ?? []), TRUE)) {
- // Only show the warning when the post update function has run and
- // the module that is providing the database driver is not installed.
- $requirements['database_driver_provided_by_module'] = [
- 'title' => t('Database driver provided by module'),
- 'value' => t('Not installed'),
- 'description' => t('The current database driver is provided by the module: %module. The module is currently not installed. You should immediately <a href=":install">install</a> the module.', ['%module' => $provider, ':install' => Url::fromRoute('system.modules_list')->toString()]),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- }
- }
- }
-
- // Check xdebug.max_nesting_level, as some pages will not work if it is too
- // low.
- if (extension_loaded('xdebug')) {
- // Setting this value to 256 was considered adequate on Xdebug 2.3
- // (see http://bugs.xdebug.org/bug_view_page.php?bug_id=00001100)
- $minimum_nesting_level = 256;
- $current_nesting_level = ini_get('xdebug.max_nesting_level');
-
- if ($current_nesting_level < $minimum_nesting_level) {
- $requirements['xdebug_max_nesting_level'] = [
- 'title' => t('Xdebug settings'),
- 'value' => t('xdebug.max_nesting_level is set to %value.', ['%value' => $current_nesting_level]),
- '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]),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- }
-
- // Installations on Windows can run into limitations with MAX_PATH if the
- // Drupal root directory is too deep in the filesystem. Generally this shows
- // up in cached Twig templates and other public files with long directory or
- // file names. There is no definite root directory depth below which Drupal is
- // guaranteed to function correctly on Windows. Since problems are likely
- // with more than 100 characters in the Drupal root path, show an error.
- if (str_starts_with(PHP_OS, 'WIN')) {
- $depth = strlen(realpath(DRUPAL_ROOT . '/' . PublicStream::basePath()));
- if ($depth > 120) {
- $requirements['max_path_on_windows'] = [
- 'title' => t('Windows installation depth'),
- 'description' => t('The public files directory path is %depth characters. Paths longer than 120 characters will cause problems on Windows.', ['%depth' => $depth]),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- }
- // Check to see if dates will be limited to 1901-2038.
- if (PHP_INT_SIZE <= 4) {
- $requirements['limited_date_range'] = [
- 'title' => t('Limited date range'),
- 'value' => t('Your PHP installation has a limited date range.'),
- '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/system-requirements/limitations-of-32-bit-php']),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
-
- // During installs from configuration don't support install profiles that
- // implement hook_install.
- if ($phase == 'install' && !empty($install_state['config_install_path'])) {
- $install_hook = $install_state['parameters']['profile'] . '_install';
- if (function_exists($install_hook)) {
- $requirements['config_install'] = [
- 'title' => t('Configuration install'),
- 'value' => $install_state['parameters']['profile'],
- 'description' => t('The selected profile has a hook_install() implementation and therefore can not be installed from configuration.'),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- }
-
- if ($phase === 'runtime') {
- $settings = Settings::getAll();
- if (array_key_exists('install_profile', $settings)) {
- // The following message is only informational because not all site owners
- // have access to edit their settings.php as it may be controlled by their
- // hosting provider.
- $requirements['install_profile_in_settings'] = [
- 'title' => t('Install profile in settings'),
- 'value' => t("Drupal 9 no longer uses the \$settings['install_profile'] value in settings.php and it should be removed."),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
- }
-
- // Ensure that no module has a current schema version that is lower than the
- // one that was last removed.
- if ($phase == 'update') {
- $module_handler = \Drupal::moduleHandler();
- /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */
- $update_registry = \Drupal::service('update.update_hook_registry');
- $module_list = [];
- // hook_update_last_removed() is a procedural hook hook because we
- // do not have classes loaded that would be needed.
- // Simply inlining the old hook mechanism is better than making
- // ModuleInstaller::invoke() public.
- foreach ($module_handler->getModuleList() as $module => $extension) {
- $function = $module . '_update_last_removed';
- if (function_exists($function)) {
- $last_removed = $function();
- if ($last_removed && $last_removed > $update_registry->getInstalledVersion($module)) {
-
- /** @var \Drupal\Core\Extension\Extension $module_info */
- $module_info = $module_extension_list->get($module);
- $module_list[$module] = [
- 'name' => $module_info->info['name'],
- 'last_removed' => $last_removed,
- 'installed_version' => $update_registry->getInstalledVersion($module),
- ];
- }
- }
- }
-
- // If user module is in the list then only show a specific message for
- // Drupal core.
- if (isset($module_list['user'])) {
- $requirements['user_update_last_removed'] = [
- 'title' => t('The version of Drupal you are trying to update from is too old'),
- 'description' => t('Updating to Drupal @current_major is only supported from Drupal version @required_min_version or higher. If you are trying to update from an older version, first update to the latest version of Drupal @previous_major. (<a href=":url">Drupal upgrade guide</a>)', [
- '@current_major' => 10,
- '@required_min_version' => '9.4.0',
- '@previous_major' => 9,
- ':url' => 'https://www.drupal.org/docs/upgrading-drupal/drupal-8-and-higher',
- ]),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- else {
- foreach ($module_list as $module => $data) {
- $requirements[$module . '_update_last_removed'] = [
- 'title' => t('Unsupported schema version: @module', ['@module' => $data['name']]),
- '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).', [
- '%module' => $data['name'],
- '@last_removed_version' => $data['last_removed'],
- '@installed_version' => $data['installed_version'],
- ]),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- }
- // Also check post-updates. Only do this if we're not already showing an
- // error for hook_update_N().
- $missing_updates = [];
- if (empty($module_list)) {
- $existing_updates = \Drupal::service('keyvalue')->get('post_update')->get('existing_updates', []);
- $post_update_registry = \Drupal::service('update.post_update_registry');
- $modules = \Drupal::moduleHandler()->getModuleList();
- foreach ($modules as $module => $extension) {
- $module_info = $module_extension_list->get($module);
- $removed_post_updates = $post_update_registry->getRemovedPostUpdates($module);
- if ($missing_updates = array_diff(array_keys($removed_post_updates), $existing_updates)) {
- $versions = array_unique(array_intersect_key($removed_post_updates, array_flip($missing_updates)));
- $description = new PluralTranslatableMarkup(count($versions),
- 'The installed version of the %module module is too old to update. Update to a version prior to @versions first (missing updates: @missing_updates).',
- '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).',
- [
- '%module' => $module_info->info['name'],
- '@missing_updates' => implode(', ', $missing_updates),
- '@versions' => implode(', ', $versions),
- ]
- );
- $requirements[$module . '_post_update_removed'] = [
- 'title' => t('Missing updates for: @module', ['@module' => $module_info->info['name']]),
- 'description' => $description,
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- }
- }
-
- if (empty($missing_updates)) {
- foreach ($update_registry->getAllEquivalentUpdates() as $module => $equivalent_updates) {
- $module_info = $module_extension_list->get($module);
- foreach ($equivalent_updates as $future_update => $data) {
- $future_update_function_name = $module . '_update_' . $future_update;
- $ran_update_function_name = $module . '_update_' . $data['ran_update'];
- // If an update was marked as an equivalent by a previous update, and
- // both the previous update and the equivalent update are not found in
- // the current code base, prevent updating. This indicates a site
- // attempting to go 'backwards' in terms of database schema.
- // @see \Drupal\Core\Update\UpdateHookRegistry::markFutureUpdateEquivalent()
- if (!function_exists($ran_update_function_name) && !function_exists($future_update_function_name)) {
- // If the module is provided by core prepend helpful text as the
- // module does not exist in composer or Drupal.org.
- if (str_starts_with($module_info->getPathname(), 'core/')) {
- $future_version_string = 'Drupal Core ' . $data['future_version_string'];
- }
- else {
- $future_version_string = $data['future_version_string'];
- }
- $requirements[$module . '_equivalent_update_missing'] = [
- 'title' => t('Missing updates for: @module', ['@module' => $module_info->info['name']]),
- 'description' => t('The version of the %module module that you are attempting to update to is missing update @future_update (which was marked as an equivalent by @ran_update). Update to at least @future_version_string.', [
- '%module' => $module_info->info['name'],
- '@ran_update' => $data['ran_update'],
- '@future_update' => $future_update,
- '@future_version_string' => $future_version_string,
- ]),
- 'severity' => REQUIREMENT_ERROR,
- ];
- break;
- }
- }
- }
- }
- }
-
- // Add warning when twig debug option is enabled.
- if ($phase === 'runtime') {
- $development_settings = \Drupal::keyValue('development_settings');
- $twig_debug = $development_settings->get('twig_debug', FALSE);
- $twig_cache_disable = $development_settings->get('twig_cache_disable', FALSE);
- if ($twig_debug || $twig_cache_disable) {
- $requirements['twig_debug_enabled'] = [
- 'title' => t('Twig development mode'),
- 'value' => t('Twig development mode settings are turned on. Go to @link to disable them.', [
- '@link' => Link::createFromRoute(
- 'development settings page',
- 'system.development_settings',
- )->toString(),
- ]),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
- $render_cache_disabled = $development_settings->get('disable_rendered_output_cache_bins', FALSE);
- if ($render_cache_disabled) {
- $requirements['render_cache_disabled'] = [
- 'title' => t('Markup caching disabled'),
- 'value' => t('Render cache, dynamic page cache, and page cache are bypassed. Go to @link to enable them.', [
- '@link' => Link::createFromRoute(
- 'development settings page',
- 'system.development_settings',
- )->toString(),
- ]),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
- }
-
- return $requirements;
-}
-
-/**
* Implements hook_install().
*/
function system_install(): void {
@@ -1685,57 +105,6 @@ function system_update_11200(): void {
}
/**
- * Display requirements from security advisories.
- *
- * @param array[] $requirements
- * The requirements array as specified in hook_requirements().
- */
-function _system_advisories_requirements(array &$requirements): void {
- if (!\Drupal::config('system.advisories')->get('enabled')) {
- return;
- }
-
- /** @var \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher $fetcher */
- $fetcher = \Drupal::service('system.sa_fetcher');
- try {
- $advisories = $fetcher->getSecurityAdvisories(TRUE, 5);
- }
- catch (ClientExceptionInterface $exception) {
- $requirements['system_advisories']['title'] = t('Critical security announcements');
- $requirements['system_advisories']['severity'] = REQUIREMENT_WARNING;
- $requirements['system_advisories']['description'] = ['#theme' => 'system_security_advisories_fetch_error_message'];
- Error::logException(\Drupal::logger('system'), $exception, 'Failed to retrieve security advisory data.');
- return;
- }
-
- if (!empty($advisories)) {
- $advisory_links = [];
- $severity = REQUIREMENT_WARNING;
- foreach ($advisories as $advisory) {
- if (!$advisory->isPsa()) {
- $severity = REQUIREMENT_ERROR;
- }
- $advisory_links[] = new Link($advisory->getTitle(), Url::fromUri($advisory->getUrl()));
- }
- $requirements['system_advisories']['title'] = t('Critical security announcements');
- $requirements['system_advisories']['severity'] = $severity;
- $requirements['system_advisories']['description'] = [
- 'list' => [
- '#theme' => 'item_list',
- '#items' => $advisory_links,
- ],
- ];
- if (\Drupal::moduleHandler()->moduleExists('help')) {
- $requirements['system_advisories']['description']['help_link'] = Link::createFromRoute(
- 'What are critical security announcements?',
- 'help.page', ['name' => 'system'],
- ['fragment' => 'security-advisories']
- )->toRenderable();
- }
- }
-}
-
-/**
* Invalidate container because the module handler has changed.
*/
function system_update_11100(): void {
diff --git a/core/modules/system/system.libraries.yml b/core/modules/system/system.libraries.yml
index af0eeea05d20..acb02dd1f4b7 100644
--- a/core/modules/system/system.libraries.yml
+++ b/core/modules/system/system.libraries.yml
@@ -7,7 +7,6 @@ base:
css/components/container-inline.module.css: { weight: -10 }
css/components/clearfix.module.css: { weight: -10 }
css/components/hidden.module.css: { weight: -10 }
- css/components/item-list.module.css: { weight: -10 }
css/components/js.module.css: { weight: -10 }
css/components/reset-appearance.module.css: { weight: -10 }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index dbda1e1b6b4b..0fbcf9b6c1fd 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -307,6 +307,10 @@ function system_authorized_batch_process() {
}
/**
+ * @} End of "defgroup authorize".
+ */
+
+/**
* Implements hook_preprocess_HOOK() for block templates.
*/
function system_preprocess_block(&$variables): void {
diff --git a/core/modules/system/templates/details.html.twig b/core/modules/system/templates/details.html.twig
index 20e4ea7193e3..dcb1cf354ce5 100644
--- a/core/modules/system/templates/details.html.twig
+++ b/core/modules/system/templates/details.html.twig
@@ -34,7 +34,11 @@
</div>
{% endif %}
- {{ description }}
+ {%- if description -%}
+ {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %}
+ <div{{ description_attributes }}>{{ description }}</div>
+ {%- endif -%}
+
{{ children }}
{{ value }}
</details>
diff --git a/core/modules/system/templates/field-multiple-value-form.html.twig b/core/modules/system/templates/field-multiple-value-form.html.twig
index 832b9f61794a..ecd268690b46 100644
--- a/core/modules/system/templates/field-multiple-value-form.html.twig
+++ b/core/modules/system/templates/field-multiple-value-form.html.twig
@@ -16,7 +16,7 @@
* - attributes: HTML attributes to apply to the description container.
* - button: "Add another item" button.
*
- * @see template_preprocess_field_multiple_value_form()
+ * @see \Drupal\Core\Field\FieldPreprocess::preprocessFieldMultipleValueForm()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/templates/field.html.twig b/core/modules/system/templates/field.html.twig
index 1497678b50ad..2bef0a02e6f0 100644
--- a/core/modules/system/templates/field.html.twig
+++ b/core/modules/system/templates/field.html.twig
@@ -33,7 +33,7 @@
* - field_type: The type of the field.
* - label_display: The display settings for the label.
*
- * @see template_preprocess_field()
+ * @see \Drupal\Core\Field\FieldPreprocess::preprocessField()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/templates/image.html.twig b/core/modules/system/templates/image.html.twig
index 6411eaa3d07b..1f6d19d6c3e7 100644
--- a/core/modules/system/templates/image.html.twig
+++ b/core/modules/system/templates/image.html.twig
@@ -7,7 +7,7 @@
* - attributes: HTML attributes for the img tag.
* - style_name: (optional) The name of the image style applied.
*
- * @see template_preprocess_image()
+ * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/templates/install-page.html.twig b/core/modules/system/templates/install-page.html.twig
index f6091fd3b956..d9144e6a154b 100644
--- a/core/modules/system/templates/install-page.html.twig
+++ b/core/modules/system/templates/install-page.html.twig
@@ -6,7 +6,7 @@
* All available variables are mirrored in page.html.twig.
* Some may be blank but they are provided for consistency.
*
- * @see template_preprocess_install_page()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessInstallPage()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/templates/item-list.html.twig b/core/modules/system/templates/item-list.html.twig
index 1462cf41ae0f..c2babdab978e 100644
--- a/core/modules/system/templates/item-list.html.twig
+++ b/core/modules/system/templates/item-list.html.twig
@@ -16,7 +16,7 @@
* - context: A list of contextual data associated with the list. May contain:
* - list_style: The custom list style.
*
- * @see template_preprocess_item_list()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/templates/maintenance-page.html.twig b/core/modules/system/templates/maintenance-page.html.twig
index 748ed5a3aa4a..06fb6065f7a4 100644
--- a/core/modules/system/templates/maintenance-page.html.twig
+++ b/core/modules/system/templates/maintenance-page.html.twig
@@ -6,7 +6,7 @@
* All available variables are mirrored in page.html.twig.
* Some may be blank but they are provided for consistency.
*
- * @see template_preprocess_maintenance_page()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/templates/menu-local-action.html.twig b/core/modules/system/templates/menu-local-action.html.twig
index 0eb03a9534ab..e0280d5fcbc0 100644
--- a/core/modules/system/templates/menu-local-action.html.twig
+++ b/core/modules/system/templates/menu-local-action.html.twig
@@ -7,7 +7,7 @@
* - attributes: HTML attributes for the wrapper element.
* - link: A rendered link element.
*
- * @see template_preprocess_menu_local_action()
+ * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalAction()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/templates/menu-local-task.html.twig b/core/modules/system/templates/menu-local-task.html.twig
index ec02a8d530c4..b2a743940a77 100644
--- a/core/modules/system/templates/menu-local-task.html.twig
+++ b/core/modules/system/templates/menu-local-task.html.twig
@@ -11,7 +11,7 @@
* Note: This template renders the content for each task item in
* menu-local-tasks.html.twig.
*
- * @see template_preprocess_menu_local_task()
+ * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/templates/pager.html.twig b/core/modules/system/templates/pager.html.twig
index 199f0578dbdc..75047c1b95f2 100644
--- a/core/modules/system/templates/pager.html.twig
+++ b/core/modules/system/templates/pager.html.twig
@@ -28,7 +28,7 @@
* at the first page.
* - next: Present if the visible list of pages ends before the last page.
*
- * @see template_preprocess_pager()
+ * @see \Drupal\Core\Pager\PagerPreprocess::preprocessPager()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/templates/region.html.twig b/core/modules/system/templates/region.html.twig
index 219e14b0a4be..ddcaaa192df4 100644
--- a/core/modules/system/templates/region.html.twig
+++ b/core/modules/system/templates/region.html.twig
@@ -9,7 +9,7 @@
* - region: The name of the region variable as defined in the theme's
* .info.yml file.
*
- * @see template_preprocess_region()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/templates/table.html.twig b/core/modules/system/templates/table.html.twig
index cfcb0bf976c0..6a73cc1152a8 100644
--- a/core/modules/system/templates/table.html.twig
+++ b/core/modules/system/templates/table.html.twig
@@ -38,7 +38,7 @@
* - no_striping: A boolean indicating that the row should receive no striping.
* - header_columns: The number of columns in the header.
*
- * @see template_preprocess_table()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable()
*
* @ingroup themeable
*/
diff --git a/core/modules/system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz b/core/modules/system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz
index 5d8c99744690..077d0645ddc7 100644
--- a/core/modules/system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz
+++ b/core/modules/system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz
Binary files differ
diff --git a/core/modules/system/tests/fixtures/update/drupal-10.3.0.filled.standard.php.gz b/core/modules/system/tests/fixtures/update/drupal-10.3.0.filled.standard.php.gz
index 423f49a1d409..5db0b3a5aaeb 100644
--- a/core/modules/system/tests/fixtures/update/drupal-10.3.0.filled.standard.php.gz
+++ b/core/modules/system/tests/fixtures/update/drupal-10.3.0.filled.standard.php.gz
Binary files differ
diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module
index 5d7fdc3dc6b8..5a17e7a6fcdb 100644
--- a/core/modules/system/tests/modules/common_test/common_test.module
+++ b/core/modules/system/tests/modules/common_test/common_test.module
@@ -10,8 +10,9 @@ declare(strict_types=1);
/**
* Implements hook_TYPE_alter().
*
- * Same as common_test_drupal_alter_alter(), but here, we verify that themes
- * can also alter and come last.
+ * Same as CommonTestHooks::drupalAlterAlter(), but here, we verify that themes
+ * can also alter and come last. This file gets included by
+ * CommonTestHooks::includeThemeFunction().
*/
function olivero_drupal_alter_alter(&$data, &$arg2 = NULL, &$arg3 = NULL): void {
// Alter first argument.
@@ -40,27 +41,3 @@ function olivero_drupal_alter_alter(&$data, &$arg2 = NULL, &$arg3 = NULL): void
}
}
}
-
-/**
- * Implements MODULE_preprocess().
- *
- * @see RenderTest::testDrupalRenderThemePreprocessAttached()
- */
-function common_test_preprocess(&$variables, $hook): void {
- if (!\Drupal::state()->get('theme_preprocess_attached_test', FALSE)) {
- return;
- }
- $variables['#attached']['library'][] = 'test/generic_preprocess';
-}
-
-/**
- * Implements MODULE_preprocess_HOOK().
- *
- * @see RenderTest::testDrupalRenderThemePreprocessAttached()
- */
-function common_test_preprocess_common_test_render_element(&$variables): void {
- if (!\Drupal::state()->get('theme_preprocess_attached_test', FALSE)) {
- return;
- }
- $variables['#attached']['library'][] = 'test/specific_preprocess';
-}
diff --git a/core/modules/system/tests/modules/common_test/src/Hook/CommonTestHooks.php b/core/modules/system/tests/modules/common_test/src/Hook/CommonTestHooks.php
index a3e65453b04e..aa93bfb5083c 100644
--- a/core/modules/system/tests/modules/common_test/src/Hook/CommonTestHooks.php
+++ b/core/modules/system/tests/modules/common_test/src/Hook/CommonTestHooks.php
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Drupal\common_test\Hook;
-use Drupal\Core\Language\LanguageInterface;
-use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Hook\Attribute\Hook;
/**
@@ -59,53 +57,6 @@ class CommonTestHooks {
}
/**
- * Implements hook_theme().
- */
- #[Hook('theme')]
- public function theme() : array {
- return [
- 'common_test_foo' => [
- 'variables' => [
- 'foo' => 'foo',
- 'bar' => 'bar',
- ],
- ],
- 'common_test_render_element' => [
- 'render element' => 'foo',
- ],
- ];
- }
-
- /**
- * Implements hook_library_info_build().
- */
- #[Hook('library_info_build')]
- public function libraryInfoBuild(): array {
- $libraries = [];
- if (\Drupal::state()->get('common_test.library_info_build_test')) {
- $libraries['dynamic_library'] = ['version' => '1.0', 'css' => ['base' => ['common_test.css' => []]]];
- }
- return $libraries;
- }
-
- /**
- * Implements hook_library_info_alter().
- */
- #[Hook('library_info_alter')]
- public function libraryInfoAlter(&$libraries, $module): void {
- if ($module === 'core' && isset($libraries['loadjs'])) {
- // Change the version of loadjs to 0.0.
- $libraries['loadjs']['version'] = '0.0';
- // Make loadjs depend on jQuery Form to test library dependencies.
- $libraries['loadjs']['dependencies'][] = 'core/internal.jquery.form';
- }
- // Alter the dynamically registered library definition.
- if ($module === 'common_test' && isset($libraries['dynamic_library'])) {
- $libraries['dynamic_library']['dependencies'] = ['core/jquery'];
- }
- }
-
- /**
* Implements hook_cron().
*
* System module should handle if a module does not catch an exception and
@@ -118,80 +69,4 @@ class CommonTestHooks {
throw new \Exception('Uncaught exception');
}
- /**
- * Implements hook_page_attachments().
- *
- * @see \Drupal\system\Tests\Common\PageRenderTest::assertPageRenderHookExceptions()
- */
- #[Hook('page_attachments')]
- public function pageAttachments(array &$page): void {
- $page['#attached']['library'][] = 'core/foo';
- $page['#attached']['library'][] = 'core/bar';
- $page['#cache']['tags'] = ['example'];
- $page['#cache']['contexts'] = ['user.permissions'];
- if (\Drupal::state()->get('common_test.hook_page_attachments.descendant_attached', FALSE)) {
- $page['content']['#attached']['library'][] = 'core/jquery';
- }
- if (\Drupal::state()->get('common_test.hook_page_attachments.render_array', FALSE)) {
- $page['something'] = ['#markup' => 'test'];
- }
- if (\Drupal::state()->get('common_test.hook_page_attachments.early_rendering', FALSE)) {
- // Do some early rendering.
- $element = ['#markup' => '123'];
- \Drupal::service('renderer')->render($element);
- }
- }
-
- /**
- * Implements hook_page_attachments_alter().
- *
- * @see \Drupal\system\Tests\Common\PageRenderTest::assertPageRenderHookExceptions()
- */
- #[Hook('page_attachments_alter')]
- public function pageAttachmentsAlter(array &$page): void {
- // Remove a library that was added in common_test_page_attachments(), to
- // test that this hook can do what it claims to do.
- if (isset($page['#attached']['library']) && ($index = array_search('core/bar', $page['#attached']['library'])) && $index !== FALSE) {
- unset($page['#attached']['library'][$index]);
- }
- $page['#attached']['library'][] = 'core/baz';
- $page['#cache']['tags'] = ['example'];
- $page['#cache']['contexts'] = ['user.permissions'];
- if (\Drupal::state()->get('common_test.hook_page_attachments_alter.descendant_attached', FALSE)) {
- $page['content']['#attached']['library'][] = 'core/jquery';
- }
- if (\Drupal::state()->get('common_test.hook_page_attachments_alter.render_array', FALSE)) {
- $page['something'] = ['#markup' => 'test'];
- }
- }
-
- /**
- * Implements hook_js_alter().
- *
- * @see \Drupal\KernelTests\Core\Asset\AttachedAssetsTest::testAlter()
- */
- #[Hook('js_alter')]
- public function jsAlter(&$javascript, AttachedAssetsInterface $assets, LanguageInterface $language): void {
- // Attach alter.js above tableselect.js.
- $alter_js = \Drupal::service('extension.list.module')->getPath('common_test') . '/alter.js';
- if (array_key_exists($alter_js, $javascript) && array_key_exists('core/misc/tableselect.js', $javascript)) {
- $javascript[$alter_js]['weight'] = $javascript['core/misc/tableselect.js']['weight'] - 1;
- }
- }
-
- /**
- * Implements hook_js_settings_alter().
- *
- * @see \Drupal\system\Tests\Common\JavaScriptTest::testHeaderSetting()
- */
- #[Hook('js_settings_alter')]
- public function jsSettingsAlter(&$settings, AttachedAssetsInterface $assets): void {
- // Modify an existing setting.
- if (array_key_exists('pluralDelimiter', $settings)) {
- $settings['pluralDelimiter'] = '☃';
- }
- // Add a setting.
- $settings['foo'] = 'bar';
- }
-
}
diff --git a/core/modules/system/tests/modules/common_test/src/Hook/CommonTestThemeHooks.php b/core/modules/system/tests/modules/common_test/src/Hook/CommonTestThemeHooks.php
new file mode 100644
index 000000000000..f47116e8920d
--- /dev/null
+++ b/core/modules/system/tests/modules/common_test/src/Hook/CommonTestThemeHooks.php
@@ -0,0 +1,165 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\common_test\Hook;
+
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Asset\AttachedAssetsInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for common_test.
+ */
+class CommonTestThemeHooks {
+
+ /**
+ * Implements hook_theme().
+ */
+ #[Hook('theme')]
+ public function theme() : array {
+ return [
+ 'common_test_foo' => [
+ 'variables' => [
+ 'foo' => 'foo',
+ 'bar' => 'bar',
+ ],
+ ],
+ 'common_test_render_element' => [
+ 'render element' => 'foo',
+ ],
+ ];
+ }
+
+ /**
+ * Implements hook_library_info_build().
+ */
+ #[Hook('library_info_build')]
+ public function libraryInfoBuild(): array {
+ $libraries = [];
+ if (\Drupal::state()->get('common_test.library_info_build_test')) {
+ $libraries['dynamic_library'] = ['version' => '1.0', 'css' => ['base' => ['common_test.css' => []]]];
+ }
+ return $libraries;
+ }
+
+ /**
+ * Implements hook_library_info_alter().
+ */
+ #[Hook('library_info_alter')]
+ public function libraryInfoAlter(&$libraries, $module): void {
+ if ($module === 'core' && isset($libraries['loadjs'])) {
+ // Change the version of loadjs to 0.0.
+ $libraries['loadjs']['version'] = '0.0';
+ // Make loadjs depend on jQuery Form to test library dependencies.
+ $libraries['loadjs']['dependencies'][] = 'core/internal.jquery.form';
+ }
+ // Alter the dynamically registered library definition.
+ if ($module === 'common_test' && isset($libraries['dynamic_library'])) {
+ $libraries['dynamic_library']['dependencies'] = ['core/jquery'];
+ }
+ }
+
+ /**
+ * Implements hook_page_attachments().
+ *
+ * @see \Drupal\system\Tests\Common\PageRenderTest::assertPageRenderHookExceptions()
+ */
+ #[Hook('page_attachments')]
+ public function pageAttachments(array &$page): void {
+ $page['#attached']['library'][] = 'core/foo';
+ $page['#attached']['library'][] = 'core/bar';
+ $page['#cache']['tags'] = ['example'];
+ $page['#cache']['contexts'] = ['user.permissions'];
+ if (\Drupal::state()->get('common_test.hook_page_attachments.descendant_attached', FALSE)) {
+ $page['content']['#attached']['library'][] = 'core/jquery';
+ }
+ if (\Drupal::state()->get('common_test.hook_page_attachments.render_array', FALSE)) {
+ $page['something'] = ['#markup' => 'test'];
+ }
+ if (\Drupal::state()->get('common_test.hook_page_attachments.early_rendering', FALSE)) {
+ // Do some early rendering.
+ $element = ['#markup' => '123'];
+ \Drupal::service('renderer')->render($element);
+ }
+ }
+
+ /**
+ * Implements hook_page_attachments_alter().
+ *
+ * @see \Drupal\system\Tests\Common\PageRenderTest::assertPageRenderHookExceptions()
+ */
+ #[Hook('page_attachments_alter')]
+ public function pageAttachmentsAlter(array &$page): void {
+ // Remove a library that was added in common_test_page_attachments(), to
+ // test that this hook can do what it claims to do.
+ if (isset($page['#attached']['library']) && ($index = array_search('core/bar', $page['#attached']['library'])) && $index !== FALSE) {
+ unset($page['#attached']['library'][$index]);
+ }
+ $page['#attached']['library'][] = 'core/baz';
+ $page['#cache']['tags'] = ['example'];
+ $page['#cache']['contexts'] = ['user.permissions'];
+ if (\Drupal::state()->get('common_test.hook_page_attachments_alter.descendant_attached', FALSE)) {
+ $page['content']['#attached']['library'][] = 'core/jquery';
+ }
+ if (\Drupal::state()->get('common_test.hook_page_attachments_alter.render_array', FALSE)) {
+ $page['something'] = ['#markup' => 'test'];
+ }
+ }
+
+ /**
+ * Implements hook_js_alter().
+ *
+ * @see \Drupal\KernelTests\Core\Asset\AttachedAssetsTest::testAlter()
+ */
+ #[Hook('js_alter')]
+ public function jsAlter(&$javascript, AttachedAssetsInterface $assets, LanguageInterface $language): void {
+ // Attach alter.js above tableselect.js.
+ $alter_js = \Drupal::service('extension.list.module')->getPath('common_test') . '/alter.js';
+ if (array_key_exists($alter_js, $javascript) && array_key_exists('core/misc/tableselect.js', $javascript)) {
+ $javascript[$alter_js]['weight'] = $javascript['core/misc/tableselect.js']['weight'] - 1;
+ }
+ }
+
+ /**
+ * Implements hook_js_settings_alter().
+ *
+ * @see \Drupal\system\Tests\Common\JavaScriptTest::testHeaderSetting()
+ */
+ #[Hook('js_settings_alter')]
+ public function jsSettingsAlter(&$settings, AttachedAssetsInterface $assets): void {
+ // Modify an existing setting.
+ if (array_key_exists('pluralDelimiter', $settings)) {
+ $settings['pluralDelimiter'] = '☃';
+ }
+ // Add a setting.
+ $settings['foo'] = 'bar';
+ }
+
+ /**
+ * Implements hook_preprocess().
+ *
+ * @see RenderTest::testDrupalRenderThemePreprocessAttached()
+ */
+ #[Hook('preprocess')]
+ public function preprocess(&$variables, $hook): void {
+ if (!\Drupal::state()->get('theme_preprocess_attached_test', FALSE)) {
+ return;
+ }
+ $variables['#attached']['library'][] = 'test/generic_preprocess';
+ }
+
+ /**
+ * Implements hook_preprocess_HOOK().
+ *
+ * @see RenderTest::testDrupalRenderThemePreprocessAttached()
+ */
+ #[Hook('preprocess_common_test_render_element')]
+ public function commonTestRenderElement(&$variables): void {
+ if (!\Drupal::state()->get('theme_preprocess_attached_test', FALSE)) {
+ return;
+ }
+ $variables['#attached']['library'][] = 'test/specific_preprocess';
+ }
+
+}
diff --git a/core/modules/system/tests/modules/container_initialize/container_initialize.info.yml b/core/modules/system/tests/modules/container_initialize/container_initialize.info.yml
new file mode 100644
index 000000000000..46411d2ea544
--- /dev/null
+++ b/core/modules/system/tests/modules/container_initialize/container_initialize.info.yml
@@ -0,0 +1,5 @@
+name: 'Container initialize'
+type: module
+description: 'Support module for HookCollectorPass testing.'
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/container_initialize/container_initialize.module b/core/modules/system/tests/modules/container_initialize/container_initialize.module
new file mode 100644
index 000000000000..5c8e0aff74ea
--- /dev/null
+++ b/core/modules/system/tests/modules/container_initialize/container_initialize.module
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @file
+ * Used to test bare container calls in .module files.
+ */
+
+declare(strict_types=1);
+
+\Drupal::getContainer()->getParameter('site.path');
diff --git a/core/modules/system/tests/modules/delay_cache_tags_invalidation/src/Hook/DelayCacheTagsInvalidationHooks.php b/core/modules/system/tests/modules/delay_cache_tags_invalidation/src/Hook/DelayCacheTagsInvalidationHooks.php
index c543a1d3e533..f444b69a427b 100644
--- a/core/modules/system/tests/modules/delay_cache_tags_invalidation/src/Hook/DelayCacheTagsInvalidationHooks.php
+++ b/core/modules/system/tests/modules/delay_cache_tags_invalidation/src/Hook/DelayCacheTagsInvalidationHooks.php
@@ -25,11 +25,11 @@ class DelayCacheTagsInvalidationHooks {
}
// Read the pre-transaction cache writes.
// @see \Drupal\KernelTests\Core\Cache\EndOfTransactionQueriesTest::testEntitySave()
- \Drupal::state()->set('delay_cache_tags_invalidation_entity_test_insert' . '__pre-transaction_foobar', \Drupal::cache()->get('test_cache_pre-transaction_foobar'));
- \Drupal::state()->set('delay_cache_tags_invalidation_entity_test_insert' . '__pre-transaction_entity_test_list', \Drupal::cache()->get('test_cache_pre-transaction_entity_test_list'));
+ \Drupal::state()->set('delay_cache_tags_invalidation_entity_test_insert__pre-transaction_foobar', \Drupal::cache()->get('test_cache_pre-transaction_foobar'));
+ \Drupal::state()->set('delay_cache_tags_invalidation_entity_test_insert__pre-transaction_entity_test_list', \Drupal::cache()->get('test_cache_pre-transaction_entity_test_list'));
// Write during the transaction.
- \Drupal::cache()->set('delay_cache_tags_invalidation_entity_test_insert' . '__during_transaction_foobar', 'something', Cache::PERMANENT, ['foobar']);
- \Drupal::cache()->set('delay_cache_tags_invalidation_entity_test_insert' . '__during_transaction_entity_test_list', 'something', Cache::PERMANENT, ['entity_test_list']);
+ \Drupal::cache()->set('delay_cache_tags_invalidation_entity_test_insert__during_transaction_foobar', 'something', Cache::PERMANENT, ['foobar']);
+ \Drupal::cache()->set('delay_cache_tags_invalidation_entity_test_insert__during_transaction_entity_test_list', 'something', Cache::PERMANENT, ['entity_test_list']);
// Trigger a nested entity save and hence a nested transaction.
User::create(['name' => 'john doe', 'status' => 1])->save();
}
@@ -42,8 +42,8 @@ class DelayCacheTagsInvalidationHooks {
if ($entity->getAccountName() === 'john doe') {
// Read the in-transaction cache writes.
// @see delay_cache_tags_invalidation_entity_test_insert()
- \Drupal::state()->set('delay_cache_tags_invalidation_user_insert' . '__during_transaction_foobar', \Drupal::cache()->get('delay_cache_tags_invalidation_entity_test_insert__during_transaction_foobar'));
- \Drupal::state()->set('delay_cache_tags_invalidation_user_insert' . '__during_transaction_entity_test_list', \Drupal::cache()->get('delay_cache_tags_invalidation_entity_test_insert__during_transaction_entity_test_list'));
+ \Drupal::state()->set('delay_cache_tags_invalidation_user_insert__during_transaction_foobar', \Drupal::cache()->get('delay_cache_tags_invalidation_entity_test_insert__during_transaction_foobar'));
+ \Drupal::state()->set('delay_cache_tags_invalidation_user_insert__during_transaction_entity_test_list', \Drupal::cache()->get('delay_cache_tags_invalidation_entity_test_insert__during_transaction_entity_test_list'));
}
}
diff --git a/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php b/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php
index f54ad8f9a9a3..c052c5a488fb 100644
--- a/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php
+++ b/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php
@@ -17,7 +17,8 @@ class Deprecated extends RenderElementBase {
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
- parent::__construct($configuration, $plugin_id, $plugin_definition);
+ $elementInfoManager = \Drupal::service('plugin.manager.element_info');
+ parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager);
@trigger_error(__CLASS__ . ' is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/3068104', E_USER_DEPRECATED);
}
diff --git a/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php b/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php
index ea72bd033fb9..53150495cb9b 100644
--- a/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php
+++ b/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php
@@ -4,8 +4,9 @@ declare(strict_types=1);
namespace Drupal\element_info_test\Hook;
-use Drupal\element_info_test\ElementInfoTestNumberBuilder;
use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\element_info_test\ElementInfoTestNumberBuilder;
+use Drupal\element_info_test\Render\Element\Details;
/**
* Hook implementations for element_info_test.
@@ -30,6 +31,9 @@ class ElementInfoTestHooks {
if (\Drupal::state()->get('hook_element_plugin_alter:remove_weight', FALSE)) {
unset($definitions['weight']);
}
+
+ $definitions['details']['class'] = Details::class;
+ $definitions['details']['provider'] = 'element_info_test';
}
}
diff --git a/core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php b/core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php
new file mode 100644
index 000000000000..8e4d84558ca4
--- /dev/null
+++ b/core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\element_info_test\Render\Element;
+
+use Drupal\Core\Render\Attribute\RenderElement;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Render\Element\RenderElementBase;
+
+/**
+ * Provides a render element for a details element.
+ *
+ * Properties:
+ *
+ * @property $title
+ * The title of the details container. Defaults to "Details".
+ * @property $open
+ * Indicates whether the container should be open by default.
+ * Defaults to FALSE.
+ * @property $custom
+ * Confirm that this class has been swapped properly.
+ * @property $summary_attributes
+ * An array of attributes to apply to the <summary>
+ * element.
+ */
+#[RenderElement('details')]
+class Details extends RenderElementBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInfo(): array {
+ return [
+ '#open' => FALSE,
+ '#summary_attributes' => [],
+ '#custom' => 'Custom',
+ ];
+ }
+
+ /**
+ * Adds form element theming to details.
+ *
+ * @param array $element
+ * An associative array containing the properties and children of the
+ * details.
+ *
+ * @return array
+ * The modified element.
+ */
+ public static function preRenderDetails($element): array {
+ Element::setAttributes($element, ['custom']);
+
+ return $element;
+ }
+
+}
diff --git a/core/modules/system/tests/modules/entity_crud_hook_test/src/Hook/EntityCrudHookTestHooks.php b/core/modules/system/tests/modules/entity_crud_hook_test/src/Hook/EntityCrudHookTestHooks.php
index c552a268952a..8e16b55b4c69 100644
--- a/core/modules/system/tests/modules/entity_crud_hook_test/src/Hook/EntityCrudHookTestHooks.php
+++ b/core/modules/system/tests/modules/entity_crud_hook_test/src/Hook/EntityCrudHookTestHooks.php
@@ -17,7 +17,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('entity_create')]
public function entityCreate(EntityInterface $entity): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_create' . ' called for type ' . $entity->getEntityTypeId();
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_create called for type ' . $entity->getEntityTypeId();
}
/**
@@ -25,7 +25,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('block_create')]
public function blockCreate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_create' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_create called';
}
/**
@@ -33,7 +33,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('comment_create')]
public function commentCreate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_create' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_create called';
}
/**
@@ -41,7 +41,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('file_create')]
public function fileCreate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_create' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_create called';
}
/**
@@ -49,7 +49,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('node_create')]
public function nodeCreate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_create' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_create called';
}
/**
@@ -57,7 +57,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_term_create')]
public function taxonomyTermCreate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_create' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_create called';
}
/**
@@ -65,7 +65,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_vocabulary_create')]
public function taxonomyVocabularyCreate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_create' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_create called';
}
/**
@@ -73,7 +73,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('user_create')]
public function userCreate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_create' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_create called';
}
/**
@@ -81,7 +81,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('entity_presave')]
public function entityPresave(EntityInterface $entity): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_presave' . ' called for type ' . $entity->getEntityTypeId();
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_presave called for type ' . $entity->getEntityTypeId();
}
/**
@@ -89,7 +89,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('block_presave')]
public function blockPresave(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_presave' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_presave called';
}
/**
@@ -97,7 +97,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('comment_presave')]
public function commentPresave(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_presave' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_presave called';
}
/**
@@ -105,7 +105,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('file_presave')]
public function filePresave(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_presave' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_presave called';
}
/**
@@ -113,7 +113,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('node_presave')]
public function nodePresave(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_presave' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_presave called';
}
/**
@@ -121,7 +121,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_term_presave')]
public function taxonomyTermPresave(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_presave' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_presave called';
}
/**
@@ -129,7 +129,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_vocabulary_presave')]
public function taxonomyVocabularyPresave(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_presave' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_presave called';
}
/**
@@ -137,7 +137,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('user_presave')]
public function userPresave(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_presave' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_presave called';
}
/**
@@ -145,7 +145,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('entity_insert')]
public function entityInsert(EntityInterface $entity): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_insert' . ' called for type ' . $entity->getEntityTypeId();
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_insert called for type ' . $entity->getEntityTypeId();
}
/**
@@ -153,7 +153,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('block_insert')]
public function blockInsert(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_insert' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_insert called';
}
/**
@@ -161,7 +161,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('comment_insert')]
public function commentInsert(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_insert' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_insert called';
}
/**
@@ -169,7 +169,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('file_insert')]
public function fileInsert(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_insert' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_insert called';
}
/**
@@ -177,7 +177,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('node_insert')]
public function nodeInsert(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_insert' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_insert called';
}
/**
@@ -185,7 +185,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_term_insert')]
public function taxonomyTermInsert(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_insert' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_insert called';
}
/**
@@ -193,7 +193,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_vocabulary_insert')]
public function taxonomyVocabularyInsert(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_insert' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_insert called';
}
/**
@@ -201,7 +201,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('user_insert')]
public function userInsert(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_insert' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_insert called';
}
/**
@@ -209,7 +209,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('entity_preload')]
public function entityPreload(array $entities, $type): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_preload' . ' called for type ' . $type;
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_preload called for type ' . $type;
}
/**
@@ -217,7 +217,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('entity_load')]
public function entityLoad(array $entities, $type): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_load' . ' called for type ' . $type;
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_load called for type ' . $type;
}
/**
@@ -225,7 +225,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('block_load')]
public function blockLoad(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_load' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_load called';
}
/**
@@ -233,7 +233,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('comment_load')]
public function commentLoad(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_load' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_load called';
}
/**
@@ -241,7 +241,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('file_load')]
public function fileLoad(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_load' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_load called';
}
/**
@@ -249,7 +249,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('node_load')]
public function nodeLoad(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_load' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_load called';
}
/**
@@ -257,7 +257,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_term_load')]
public function taxonomyTermLoad(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_load' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_load called';
}
/**
@@ -265,7 +265,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_vocabulary_load')]
public function taxonomyVocabularyLoad(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_load' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_load called';
}
/**
@@ -273,7 +273,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('user_load')]
public function userLoad(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_load' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_load called';
}
/**
@@ -281,7 +281,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('entity_update')]
public function entityUpdate(EntityInterface $entity): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_update' . ' called for type ' . $entity->getEntityTypeId();
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_update called for type ' . $entity->getEntityTypeId();
}
/**
@@ -289,7 +289,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('block_update')]
public function blockUpdate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_update' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_update called';
}
/**
@@ -297,7 +297,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('comment_update')]
public function commentUpdate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_update' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_update called';
}
/**
@@ -305,7 +305,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('file_update')]
public function fileUpdate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_update' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_update called';
}
/**
@@ -313,7 +313,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('node_update')]
public function nodeUpdate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_update' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_update called';
}
/**
@@ -321,7 +321,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_term_update')]
public function taxonomyTermUpdate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_update' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_update called';
}
/**
@@ -329,7 +329,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_vocabulary_update')]
public function taxonomyVocabularyUpdate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_update' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_update called';
}
/**
@@ -337,7 +337,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('user_update')]
public function userUpdate(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_update' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_update called';
}
/**
@@ -345,7 +345,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('entity_predelete')]
public function entityPredelete(EntityInterface $entity): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_predelete' . ' called for type ' . $entity->getEntityTypeId();
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_predelete called for type ' . $entity->getEntityTypeId();
}
/**
@@ -353,7 +353,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('block_predelete')]
public function blockPredelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_predelete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_predelete called';
}
/**
@@ -361,7 +361,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('comment_predelete')]
public function commentPredelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_predelete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_predelete called';
}
/**
@@ -369,7 +369,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('file_predelete')]
public function filePredelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_predelete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_predelete called';
}
/**
@@ -377,7 +377,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('node_predelete')]
public function nodePredelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_predelete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_predelete called';
}
/**
@@ -385,7 +385,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_term_predelete')]
public function taxonomyTermPredelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_predelete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_predelete called';
}
/**
@@ -393,7 +393,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_vocabulary_predelete')]
public function taxonomyVocabularyPredelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_predelete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_predelete called';
}
/**
@@ -401,7 +401,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('user_predelete')]
public function userPredelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_predelete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_predelete called';
}
/**
@@ -409,7 +409,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('entity_delete')]
public function entityDelete(EntityInterface $entity): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_delete' . ' called for type ' . $entity->getEntityTypeId();
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_entity_delete called for type ' . $entity->getEntityTypeId();
}
/**
@@ -417,7 +417,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('block_delete')]
public function blockDelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_delete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_block_delete called';
}
/**
@@ -425,7 +425,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('comment_delete')]
public function commentDelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_delete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_comment_delete called';
}
/**
@@ -433,7 +433,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('file_delete')]
public function fileDelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_delete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_file_delete called';
}
/**
@@ -441,7 +441,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('node_delete')]
public function nodeDelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_delete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_node_delete called';
}
/**
@@ -449,7 +449,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_term_delete')]
public function taxonomyTermDelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_delete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_term_delete called';
}
/**
@@ -457,7 +457,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('taxonomy_vocabulary_delete')]
public function taxonomyVocabularyDelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_delete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_taxonomy_vocabulary_delete called';
}
/**
@@ -465,7 +465,7 @@ class EntityCrudHookTestHooks {
*/
#[Hook('user_delete')]
public function userDelete(): void {
- $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_delete' . ' called';
+ $GLOBALS['entity_crud_hook_test'][] = 'entity_crud_hook_test_user_delete called';
}
}
diff --git a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install b/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install
deleted file mode 100644
index beaa3cd15b70..000000000000
--- a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-/**
- * @file
- * Experimental Test Requirements module to test hook_requirements().
- */
-
-declare(strict_types=1);
-
-/**
- * Implements hook_requirements().
- */
-function experimental_module_requirements_test_requirements(): array {
- $requirements = [];
- if (\Drupal::state()->get('experimental_module_requirements_test_requirements', FALSE)) {
- $requirements['experimental_module_requirements_test_requirements'] = [
- 'severity' => REQUIREMENT_ERROR,
- 'description' => t('The Experimental Test Requirements module can not be installed.'),
- ];
- }
- return $requirements;
-}
diff --git a/core/modules/system/tests/modules/experimental_module_requirements_test/src/Install/Requirements/ExperimentalModuleRequirementsTestRequirements.php b/core/modules/system/tests/modules/experimental_module_requirements_test/src/Install/Requirements/ExperimentalModuleRequirementsTestRequirements.php
new file mode 100644
index 000000000000..53834f77c0e1
--- /dev/null
+++ b/core/modules/system/tests/modules/experimental_module_requirements_test/src/Install/Requirements/ExperimentalModuleRequirementsTestRequirements.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\experimental_module_requirements_test\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
+
+/**
+ * Install time requirements for the Experimental Requirements Test module.
+ */
+class ExperimentalModuleRequirementsTestRequirements implements InstallRequirementsInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getRequirements(): array {
+ $requirements = [];
+ if (\Drupal::state()->get('experimental_module_requirements_test_requirements', FALSE)) {
+ $requirements['experimental_module_requirements_test_requirements'] = [
+ 'severity' => RequirementSeverity::Error,
+ 'description' => t('The Experimental Test Requirements module can not be installed.'),
+ ];
+ }
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestClickedButtonForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestClickedButtonForm.php
index 542c4e162e2d..78328f9f8e4d 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestClickedButtonForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestClickedButtonForm.php
@@ -35,32 +35,28 @@ class FormTestClickedButtonForm extends FormBase {
'#type' => 'textfield',
];
+ // Get button configurations, filter out NULL values.
+ $args = array_filter([$first, $second, $third]);
+
+ // Define button types for each argument.
+ $button_types = [
+ 's' => 'submit',
+ 'i' => 'image_button',
+ 'b' => 'button',
+ ];
+
// Loop through each path argument, adding buttons based on the information
// in the argument. For example, if the path is
// form-test/clicked-button/s/i/rb, then 3 buttons are added: a 'submit', an
// 'image_button', and a 'button' with #access=FALSE. This enables form.test
// to test a variety of combinations.
- $i = 0;
- $args = [$first, $second, $third];
- foreach ($args as $arg) {
- $name = 'button' . ++$i;
- // 's', 'b', or 'i' in the argument define the button type wanted.
- if (!is_string($arg)) {
- $type = NULL;
- }
- elseif (str_contains($arg, 's')) {
- $type = 'submit';
- }
- elseif (str_contains($arg, 'b')) {
- $type = 'button';
- }
- elseif (str_contains($arg, 'i')) {
- $type = 'image_button';
- }
- else {
- $type = NULL;
- }
- if (isset($type)) {
+ foreach ($args as $index => $arg) {
+ // Get the button type based on the index of the argument.
+ $type = $button_types[$arg] ?? NULL;
+ $name = 'button' . ($index + 1);
+
+ if ($type) {
+ // Define the button.
$form[$name] = [
'#type' => $type,
'#name' => $name,
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php
index 9babda83ddc0..226eb705802d 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php
@@ -48,6 +48,11 @@ class FormTestGroupDetailsForm extends FormBase {
'data-summary-attribute' => 'test',
],
];
+ $form['description_attributes'] = [
+ '#type' => 'details',
+ '#title' => 'Details element with description',
+ '#description' => 'I am a details description',
+ ];
return $form;
}
diff --git a/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php b/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php
index cda2b92b3477..441ebaa1d128 100644
--- a/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php
+++ b/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php
@@ -6,6 +6,8 @@ namespace Drupal\form_test\Hook;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Render\Element\Submit;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\form_test\Callbacks;
@@ -16,6 +18,8 @@ class FormTestHooks {
use StringTranslationTrait;
+ public function __construct(protected ElementInfoManagerInterface $elementInfoManager) {}
+
/**
* Implements hook_form_FORM_ID_alter().
*/
@@ -55,13 +59,10 @@ class FormTestHooks {
*/
#[Hook('form_user_register_form_alter')]
public function formUserRegisterFormAlter(&$form, FormStateInterface $form_state) : void {
- $form['test_rebuild'] = [
- '#type' => 'submit',
- '#value' => $this->t('Rebuild'),
- '#submit' => [
- [Callbacks::class, 'userRegisterFormRebuild'],
- ],
- ];
+ $submit = $this->elementInfoManager->fromRenderable($form)
+ ->createChild('test_rebuild', Submit::class);
+ $submit->value = $this->t('Rebuild');
+ $submit->submit = [[Callbacks::class, 'userRegisterFormRebuild']];
}
/**
@@ -69,11 +70,12 @@ class FormTestHooks {
*/
#[Hook('form_form_test_vertical_tabs_access_form_alter')]
public function formFormTestVerticalTabsAccessFormAlter(&$form, &$form_state, $form_id) : void {
- $form['vertical_tabs1']['#access'] = FALSE;
- $form['vertical_tabs2']['#access'] = FALSE;
- $form['tabs3']['#access'] = TRUE;
- $form['fieldset1']['#access'] = FALSE;
- $form['container']['#access'] = FALSE;
+ $element_object = $this->elementInfoManager->fromRenderable($form);
+ $element_object->getChild('vertical_tabs1')->access = FALSE;
+ $element_object->getChild('vertical_tabs2')->access = FALSE;
+ $element_object->getChild('tab3')->access = FALSE;
+ $element_object->getChild('fieldset1')->access = FALSE;
+ $element_object->getChild('container')->access = FALSE;
}
}
diff --git a/core/modules/system/tests/modules/js_displace/js_displace.module b/core/modules/system/tests/modules/js_displace/js_displace.module
deleted file mode 100644
index 8b34072bd659..000000000000
--- a/core/modules/system/tests/modules/js_displace/js_displace.module
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-/**
- * @file
- * Functions to support testing Drupal.displace() JavaScript API.
- */
-
-declare(strict_types=1);
-
-/**
- * Implements hook_preprocess_html().
- */
-function js_displace_preprocess_html(&$variables): void {
- $variables['#attached']['library'][] = 'core/drupal.displace';
-}
diff --git a/core/modules/system/tests/modules/js_displace/src/Hook/JsDisplaceThemeHooks.php b/core/modules/system/tests/modules/js_displace/src/Hook/JsDisplaceThemeHooks.php
new file mode 100644
index 000000000000..d9b37274d46c
--- /dev/null
+++ b/core/modules/system/tests/modules/js_displace/src/Hook/JsDisplaceThemeHooks.php
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\js_displace\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Theme hook implementations for js_displace module.
+ */
+class JsDisplaceThemeHooks {
+
+ /**
+ * Implements hook_preprocess_HOOK().
+ */
+ #[Hook('preprocess_html')]
+ public function preprocessHtml(&$variables): void {
+ $variables['#attached']['library'][] = 'core/drupal.displace';
+ }
+
+}
diff --git a/core/modules/system/tests/modules/module_install_unmet_requirements/src/Install/Requirements/ModuleInstallUnmetRequirementsRequirements.php b/core/modules/system/tests/modules/module_install_unmet_requirements/src/Install/Requirements/ModuleInstallUnmetRequirementsRequirements.php
index 4d7367ff4141..b6a532a18022 100644
--- a/core/modules/system/tests/modules/module_install_unmet_requirements/src/Install/Requirements/ModuleInstallUnmetRequirementsRequirements.php
+++ b/core/modules/system/tests/modules/module_install_unmet_requirements/src/Install/Requirements/ModuleInstallUnmetRequirementsRequirements.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\module_install_unmet_requirements\Install\Requirements;
use Drupal\Core\Extension\InstallRequirementsInterface;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
/**
* Provides method for checking requirements during install time.
@@ -17,7 +18,7 @@ class ModuleInstallUnmetRequirementsRequirements implements InstallRequirementsI
public static function getRequirements(): array {
$requirements['testing_requirements'] = [
'title' => t('Testing requirements'),
- 'severity' => REQUIREMENT_ERROR,
+ 'severity' => RequirementSeverity::Error,
'description' => t('Testing requirements failed requirements.'),
];
diff --git a/core/modules/system/tests/modules/module_runtime_requirements/src/Hook/ModuleRuntimeRequirementsHooks.php b/core/modules/system/tests/modules/module_runtime_requirements/src/Hook/ModuleRuntimeRequirementsHooks.php
index 0fa4b2f6f804..31358a595d77 100644
--- a/core/modules/system/tests/modules/module_runtime_requirements/src/Hook/ModuleRuntimeRequirementsHooks.php
+++ b/core/modules/system/tests/modules/module_runtime_requirements/src/Hook/ModuleRuntimeRequirementsHooks.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Drupal\module_runtime_requirements\Hook;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -24,13 +25,13 @@ class ModuleRuntimeRequirementsHooks {
'title' => $this->t('RuntimeError'),
'value' => $this->t('None'),
'description' => $this->t('Runtime Error.'),
- 'severity' => REQUIREMENT_ERROR,
+ 'severity' => RequirementSeverity::Error,
],
'test.runtime.error.alter' => [
'title' => $this->t('RuntimeError'),
'value' => $this->t('None'),
'description' => $this->t('Runtime Error.'),
- 'severity' => REQUIREMENT_ERROR,
+ 'severity' => RequirementSeverity::Error,
],
];
}
@@ -44,7 +45,7 @@ class ModuleRuntimeRequirementsHooks {
'title' => $this->t('RuntimeWarning'),
'value' => $this->t('None'),
'description' => $this->t('Runtime Warning.'),
- 'severity' => REQUIREMENT_WARNING,
+ 'severity' => RequirementSeverity::Warning,
];
}
diff --git a/core/modules/system/tests/modules/module_test/module_test.file.inc b/core/modules/system/tests/modules/module_test/module_test.file.inc
index c0cee6f1905e..5ba5f5614ca7 100644
--- a/core/modules/system/tests/modules/module_test/module_test.file.inc
+++ b/core/modules/system/tests/modules/module_test/module_test.file.inc
@@ -16,3 +16,17 @@ declare(strict_types=1);
function module_test_test_hook(): array {
return ['module_test' => 'success!'];
}
+
+/**
+ * Implements hook_test_reset_implementations_hook().
+ */
+function module_test_test_reset_implementations_hook(): string {
+ return __FUNCTION__;
+}
+
+/**
+ * Implements hook_test_reset_implementations_alter().
+ */
+function module_test_test_reset_implementations_alter(array &$data): void {
+ $data[] = __FUNCTION__;
+}
diff --git a/core/modules/system/tests/modules/module_test_oop_preprocess/src/Hook/ModuleTestOopPreprocessThemeHooks.php b/core/modules/system/tests/modules/module_test_oop_preprocess/src/Hook/ModuleTestOopPreprocessThemeHooks.php
index 1cbb9e6b422b..db923382a21b 100644
--- a/core/modules/system/tests/modules/module_test_oop_preprocess/src/Hook/ModuleTestOopPreprocessThemeHooks.php
+++ b/core/modules/system/tests/modules/module_test_oop_preprocess/src/Hook/ModuleTestOopPreprocessThemeHooks.php
@@ -4,19 +4,19 @@ declare(strict_types=1);
namespace Drupal\module_test_oop_preprocess\Hook;
-use Drupal\Core\Hook\Attribute\Preprocess;
+use Drupal\Core\Hook\Attribute\Hook;
/**
* Hook implementations for module_test_oop_preprocess.
*/
class ModuleTestOopPreprocessThemeHooks {
- #[Preprocess]
+ #[Hook('preprocess')]
public function rootPreprocess($arg): mixed {
return $arg;
}
- #[Preprocess('test')]
+ #[Hook('preprocess_test')]
public function preprocessTest($arg): mixed {
return $arg;
}
diff --git a/core/modules/system/tests/modules/module_update_requirements/src/Hook/ModuleUpdateRequirementsHooks.php b/core/modules/system/tests/modules/module_update_requirements/src/Hook/ModuleUpdateRequirementsHooks.php
index f0666222f142..073baba95c9f 100644
--- a/core/modules/system/tests/modules/module_update_requirements/src/Hook/ModuleUpdateRequirementsHooks.php
+++ b/core/modules/system/tests/modules/module_update_requirements/src/Hook/ModuleUpdateRequirementsHooks.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Drupal\module_update_requirements\Hook;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -24,13 +25,13 @@ class ModuleUpdateRequirementsHooks {
'title' => $this->t('UpdateError'),
'value' => $this->t('None'),
'description' => $this->t('Update Error.'),
- 'severity' => REQUIREMENT_ERROR,
+ 'severity' => RequirementSeverity::Error,
],
'test.update.error.alter' => [
'title' => $this->t('UpdateError'),
'value' => $this->t('None'),
'description' => $this->t('Update Error.'),
- 'severity' => REQUIREMENT_ERROR,
+ 'severity' => RequirementSeverity::Error,
],
];
}
@@ -44,7 +45,7 @@ class ModuleUpdateRequirementsHooks {
'title' => $this->t('UpdateWarning'),
'value' => $this->t('None'),
'description' => $this->t('Update Warning.'),
- 'severity' => REQUIREMENT_WARNING,
+ 'severity' => RequirementSeverity::Warning,
];
}
diff --git a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
index fb84be133cd7..a93f726fafd0 100644
--- a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
+++ b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
@@ -7,6 +7,8 @@
declare(strict_types=1);
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
+
/**
* Implements hook_requirements().
*
@@ -19,20 +21,20 @@ function requirements1_test_requirements($phase): array {
if ('install' == $phase) {
$requirements['requirements1_test'] = [
'title' => t('Requirements 1 Test'),
- 'severity' => REQUIREMENT_ERROR,
+ 'severity' => RequirementSeverity::Error,
'description' => t('Requirements 1 Test failed requirements.'),
];
}
$requirements['requirements1_test_alterable'] = [
'title' => t('Requirements 1 Test Alterable'),
- 'severity' => REQUIREMENT_ERROR,
+ 'severity' => RequirementSeverity::Error,
'description' => t('A requirement that will be altered.'),
];
$requirements['requirements1_test_deletable'] = [
'title' => t('Requirements 1 Test Deletable'),
- 'severity' => REQUIREMENT_INFO,
+ 'severity' => RequirementSeverity::Info,
'description' => t('A requirement that will be deleted.'),
];
diff --git a/core/modules/system/tests/modules/requirements1_test/src/Hook/Requirements1TestHooks.php b/core/modules/system/tests/modules/requirements1_test/src/Hook/Requirements1TestHooks.php
index c766f6f423ae..ce3eebfb35b7 100644
--- a/core/modules/system/tests/modules/requirements1_test/src/Hook/Requirements1TestHooks.php
+++ b/core/modules/system/tests/modules/requirements1_test/src/Hook/Requirements1TestHooks.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Drupal\requirements1_test\Hook;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -22,7 +23,7 @@ class Requirements1TestHooks {
// Change the title.
$requirements['requirements1_test_alterable']['title'] = $this->t('Requirements 1 Test - Changed');
// Decrease the severity.
- $requirements['requirements1_test_alterable']['severity'] = REQUIREMENT_WARNING;
+ $requirements['requirements1_test_alterable']['severity'] = RequirementSeverity::Warning;
// Delete 'requirements1_test_deletable',
unset($requirements['requirements1_test_deletable']);
}
diff --git a/core/modules/system/tests/modules/sdc_test/components/my-banner/my-banner.component.yml b/core/modules/system/tests/modules/sdc_test/components/my-banner/my-banner.component.yml
index 195903e9ee74..0c89a0740a6b 100644
--- a/core/modules/system/tests/modules/sdc_test/components/my-banner/my-banner.component.yml
+++ b/core/modules/system/tests/modules/sdc_test/components/my-banner/my-banner.component.yml
@@ -29,6 +29,10 @@ props:
enum:
- ''
- _blank
+ meta:enum:
+ '': 'Open in same window'
+ _blank: 'Open in a new window'
+ x-translation-context: Banner link target
image:
title: Media Image
description: Background image for the banner.
diff --git a/core/modules/system/tests/modules/sdc_test/components/my-button/my-button.component.yml b/core/modules/system/tests/modules/sdc_test/components/my-button/my-button.component.yml
index 65b3c472096b..d0d4f8c73b46 100644
--- a/core/modules/system/tests/modules/sdc_test/components/my-button/my-button.component.yml
+++ b/core/modules/system/tests/modules/sdc_test/components/my-button/my-button.component.yml
@@ -24,3 +24,7 @@ props:
- power
- like
- external
+ meta:enum:
+ power: 'Power'
+ like: 'Like'
+ external: 'External'
diff --git a/core/modules/system/tests/modules/sdc_test/components/my-cta/my-cta.component.yml b/core/modules/system/tests/modules/sdc_test/components/my-cta/my-cta.component.yml
index 889dfe885205..6d16be49cf74 100644
--- a/core/modules/system/tests/modules/sdc_test/components/my-cta/my-cta.component.yml
+++ b/core/modules/system/tests/modules/sdc_test/components/my-cta/my-cta.component.yml
@@ -17,12 +17,23 @@ props:
type: string
title: URL
format: uri
+ examples:
+ - https://drupal.org
target:
type: string
title: Target
+ description: The target for opening the link.
enum:
- ''
- - _blank
+ - '_blank'
+ meta:enum:
+ '': 'Open in same window'
+ _blank: 'Open in a new window'
+ x-translation-context: CTA link target
+ default: ''
+ examples:
+ - ''
+ - '_blank'
attributes:
type: Drupal\Core\Template\Attribute
name: Attributes
diff --git a/core/modules/system/tests/modules/sdc_test_replacements/components/my-button/my-button.component.yml b/core/modules/system/tests/modules/sdc_test_replacements/components/my-button/my-button.component.yml
index b87f1180111e..053387cf9c4f 100644
--- a/core/modules/system/tests/modules/sdc_test_replacements/components/my-button/my-button.component.yml
+++ b/core/modules/system/tests/modules/sdc_test_replacements/components/my-button/my-button.component.yml
@@ -24,3 +24,7 @@ props:
- power
- like
- external
+ meta:enum:
+ power: 'Power'
+ like: 'Like'
+ external: 'External'
diff --git a/core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php
new file mode 100644
index 000000000000..b57e4c883972
--- /dev/null
+++ b/core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\system_test\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\dblog\Logger\DbLog;
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
+
+/**
+ * A controller that specifies an optional dependency.
+ */
+class OptionalServiceSystemTestController extends ControllerBase {
+
+ public function __construct(
+ #[Autowire('logger.dblog')]
+ public readonly ?DbLog $dbLog,
+ ) {}
+
+}
diff --git a/core/modules/system/tests/modules/test_htmx/css/style.css b/core/modules/system/tests/modules/test_htmx/css/style.css
new file mode 100644
index 000000000000..75b757dbe3c9
--- /dev/null
+++ b/core/modules/system/tests/modules/test_htmx/css/style.css
@@ -0,0 +1,3 @@
+.ajax-content {
+ background-color: red;
+}
diff --git a/core/modules/system/tests/modules/test_htmx/js/behavior.js b/core/modules/system/tests/modules/test_htmx/js/behavior.js
new file mode 100644
index 000000000000..5ca13501ceed
--- /dev/null
+++ b/core/modules/system/tests/modules/test_htmx/js/behavior.js
@@ -0,0 +1,14 @@
+((Drupal, once) => {
+ Drupal.behaviors.htmx_test = {
+ attach(context, settings) {
+ once('htmx-init', '.ajax-content', context).forEach((el) => {
+ el.innerText = 'initialized';
+ });
+ },
+ detach(context, settings, trigger) {
+ once.remove('htmx-init', '.ajax-content', context).forEach((el) => {
+ el.remove();
+ });
+ },
+ };
+})(Drupal, once);
diff --git a/core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php b/core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php
new file mode 100644
index 000000000000..9045a4c8b9cc
--- /dev/null
+++ b/core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php
@@ -0,0 +1,83 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\test_htmx\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Url;
+
+/**
+ * Returns responses for HTMX Test Attachments routes.
+ */
+final class HtmxTestAttachmentsController extends ControllerBase {
+
+ /**
+ * Builds the response.
+ *
+ * @return mixed[]
+ * A render array.
+ */
+ public function page(): array {
+ return self::generateHtmxButton();
+ }
+
+ /**
+ * Builds the HTMX response.
+ *
+ * @return mixed[]
+ * A render array.
+ */
+ public function replace(): array {
+ $build['content'] = [
+ '#type' => 'container',
+ '#attached' => [
+ 'library' => ['test_htmx/assets'],
+ ],
+ '#attributes' => [
+ 'class' => ['ajax-content'],
+ ],
+ 'example' => ['#markup' => 'Initial Content'],
+ ];
+
+ return $build;
+ }
+
+ /**
+ * Static helper to for reusable render array.
+ *
+ * @return array
+ * The render array.
+ */
+ public static function generateHtmxButton(): array {
+ $url = Url::fromRoute('test_htmx.attachments.replace');
+ $build['replace'] = [
+ '#type' => 'html_tag',
+ '#tag' => 'button',
+ '#attributes' => [
+ 'type' => 'button',
+ 'name' => 'replace',
+ 'data-hx-get' => $url->toString(),
+ 'data-hx-select' => 'div.ajax-content',
+ 'data-hx-target' => '[data-drupal-htmx-target]',
+ ],
+ '#value' => 'Click this',
+ '#attached' => [
+ 'library' => [
+ 'core/drupal.htmx',
+ ],
+ ],
+ ];
+
+ $build['content'] = [
+ '#type' => 'container',
+ '#attributes' => [
+ 'data-drupal-htmx-target' => TRUE,
+ 'class' => ['htmx-test-container'],
+ ],
+ ];
+
+ return $build;
+ }
+
+}
diff --git a/core/modules/system/tests/modules/test_htmx/src/Form/HtmxTestAjaxForm.php b/core/modules/system/tests/modules/test_htmx/src/Form/HtmxTestAjaxForm.php
new file mode 100644
index 000000000000..8fffbbc5f40b
--- /dev/null
+++ b/core/modules/system/tests/modules/test_htmx/src/Form/HtmxTestAjaxForm.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\test_htmx\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\test_htmx\Controller\HtmxTestAttachmentsController;
+
+/**
+ * A small form used to insert an HTMX powered element using ajax API.
+ */
+class HtmxTestAjaxForm extends FormBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId(): string {
+ return 'htmx_test_ajax_form';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state): array {
+ $build = [
+ 'ajax-button' => [
+ '#type' => 'button',
+ '#value' => 'Trigger Ajax',
+ '#submit_button' => FALSE,
+ '#ajax' => [
+ 'callback' => [
+ HtmxTestAttachmentsController::class,
+ 'generateHtmxButton',
+ ],
+ 'wrapper' => 'ajax-test-container',
+ ],
+ ],
+ '#suffix' => '<div id="ajax-test-container"></div>',
+ ];
+
+ return $build;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state): void {}
+
+}
diff --git a/core/modules/system/tests/modules/test_htmx/test_htmx.info.yml b/core/modules/system/tests/modules/test_htmx/test_htmx.info.yml
new file mode 100644
index 000000000000..c713e0624d93
--- /dev/null
+++ b/core/modules/system/tests/modules/test_htmx/test_htmx.info.yml
@@ -0,0 +1,4 @@
+name: 'HTMX Test Fixtures'
+type: module
+description: 'Test fixtures for HTMX integration'
+package: Testing
diff --git a/core/modules/system/tests/modules/test_htmx/test_htmx.libraries.yml b/core/modules/system/tests/modules/test_htmx/test_htmx.libraries.yml
new file mode 100644
index 000000000000..31ac1d2b8abe
--- /dev/null
+++ b/core/modules/system/tests/modules/test_htmx/test_htmx.libraries.yml
@@ -0,0 +1,10 @@
+assets:
+ version: VERSION
+ js:
+ js/behavior.js: {}
+ css:
+ theme:
+ css/style.css: {}
+ dependencies:
+ - core/drupal
+ - core/once
diff --git a/core/modules/system/tests/modules/test_htmx/test_htmx.routing.yml b/core/modules/system/tests/modules/test_htmx/test_htmx.routing.yml
new file mode 100644
index 000000000000..406c3027f3b1
--- /dev/null
+++ b/core/modules/system/tests/modules/test_htmx/test_htmx.routing.yml
@@ -0,0 +1,23 @@
+test_htmx.attachments.page:
+ path: '/htmx-test-attachments/page'
+ defaults:
+ _title: 'Page'
+ _controller: '\Drupal\test_htmx\Controller\HtmxTestAttachmentsController::page'
+ requirements:
+ _permission: 'access content'
+
+test_htmx.attachments.replace:
+ path: '/htmx-test-attachments/replace'
+ defaults:
+ _title: 'Ajax Content'
+ _controller: '\Drupal\test_htmx\Controller\HtmxTestAttachmentsController::replace'
+ requirements:
+ _permission: 'access content'
+
+test_htmx.attachments.ajax:
+ path: '/htmx-test-attachments/ajax'
+ defaults:
+ _title: 'Ajax'
+ _form: '\Drupal\test_htmx\Form\HtmxTestAjaxForm'
+ requirements:
+ _permission: 'access content'
diff --git a/core/modules/system/tests/modules/theme_suggestions_test/src/Hook/ThemeSuggestionsTestHooks.php b/core/modules/system/tests/modules/theme_suggestions_test/src/Hook/ThemeSuggestionsTestHooks.php
index 8f5d5ee18a8f..ff4086a0c2a8 100644
--- a/core/modules/system/tests/modules/theme_suggestions_test/src/Hook/ThemeSuggestionsTestHooks.php
+++ b/core/modules/system/tests/modules/theme_suggestions_test/src/Hook/ThemeSuggestionsTestHooks.php
@@ -16,7 +16,7 @@ class ThemeSuggestionsTestHooks {
*/
#[Hook('theme_suggestions_alter')]
public function themeSuggestionsAlter(array &$suggestions, array &$variables, $hook): void {
- \Drupal::messenger()->addStatus('theme_suggestions_test_theme_suggestions_alter' . '() executed.');
+ \Drupal::messenger()->addStatus('theme_suggestions_test_theme_suggestions_alter() executed.');
if ($hook == 'theme_test_general_suggestions') {
$suggestions[] = $hook . '__module_override';
$variables['module_hook'] = 'theme_suggestions_test_theme_suggestions_alter';
@@ -28,7 +28,7 @@ class ThemeSuggestionsTestHooks {
*/
#[Hook('theme_suggestions_theme_test_suggestions_alter')]
public function themeSuggestionsThemeTestSuggestionsAlter(array &$suggestions, array $variables): void {
- \Drupal::messenger()->addStatus('theme_suggestions_test_theme_suggestions_theme_test_suggestions_alter' . '() executed.');
+ \Drupal::messenger()->addStatus('theme_suggestions_test_theme_suggestions_theme_test_suggestions_alter() executed.');
$suggestions[] = 'theme_test_suggestions__module_override';
}
diff --git a/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php b/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php
index 00b51bc72b57..1d953ff8c337 100644
--- a/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php
+++ b/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php
@@ -19,10 +19,11 @@ class ThemeTestSubscriber implements EventSubscriberInterface {
/**
* The used container.
*
+ * @var object
+ *
* @todo This variable is never initialized, so we don't know what it is.
* See https://www.drupal.org/node/2721315
*/
- // phpcs:ignore Drupal.Commenting.VariableComment.Missing, Drupal.Commenting.VariableComment.MissingVar
protected $container;
/**
@@ -78,27 +79,10 @@ class ThemeTestSubscriber implements EventSubscriberInterface {
}
/**
- * Ensures that the theme registry was not initialized.
- */
- public function onView(RequestEvent $event) {
- $current_route = $this->currentRouteMatch->getRouteName();
- $entity_autocomplete_route = [
- 'system.entity_autocomplete',
- ];
-
- if (in_array($current_route, $entity_autocomplete_route)) {
- if ($this->container->initialized('theme.registry')) {
- throw new \Exception('registry initialized');
- }
- }
- }
-
- /**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events[KernelEvents::REQUEST][] = ['onRequest'];
- $events[KernelEvents::VIEW][] = ['onView', -1000];
return $events;
}
diff --git a/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestHooks.php b/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestHooks.php
index 8e27524449f1..9b3fe6cd66a5 100644
--- a/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestHooks.php
+++ b/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestHooks.php
@@ -85,7 +85,7 @@ class ThemeTestHooks {
*/
#[Hook('theme_suggestions_alter')]
public function themeSuggestionsAlter(array &$suggestions, array $variables, $hook): void {
- \Drupal::messenger()->addStatus('theme_test_theme_suggestions_alter' . '() executed for ' . $hook . '.');
+ \Drupal::messenger()->addStatus('theme_test_theme_suggestions_alter() executed for ' . $hook . '.');
}
/**
@@ -93,7 +93,7 @@ class ThemeTestHooks {
*/
#[Hook('theme_suggestions_theme_test_suggestions_alter')]
public function themeSuggestionsThemeTestSuggestionsAlter(array &$suggestions, array $variables): void {
- \Drupal::messenger()->addStatus('theme_test_theme_suggestions_theme_test_suggestions_alter' . '() executed.');
+ \Drupal::messenger()->addStatus('theme_test_theme_suggestions_theme_test_suggestions_alter() executed.');
}
/**
diff --git a/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestThemeHooks.php b/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestThemeHooks.php
index fc48756de51a..7bfc10ef0ef9 100644
--- a/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestThemeHooks.php
+++ b/core/modules/system/tests/modules/theme_test/src/Hook/ThemeTestThemeHooks.php
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Drupal\theme_test\Hook;
-use Drupal\Core\Hook\Attribute\Preprocess;
+use Drupal\Core\Hook\Attribute\Hook;
/**
* Hook implementations for theme_test.
@@ -14,7 +14,7 @@ class ThemeTestThemeHooks {
/**
* Implements hook_preprocess_HOOK().
*/
- #[Preprocess('theme_test_preprocess_suggestions__monkey')]
+ #[Hook('preprocess_theme_test_preprocess_suggestions__monkey')]
public function preprocessTestSuggestions(&$variables): void {
$variables['foo'] = 'Monkey';
}
diff --git a/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php b/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php
index 272ad65eff3a..f5d0c1501189 100644
--- a/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php
+++ b/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php
@@ -24,7 +24,7 @@ class TestLoader implements LoaderInterface {
/**
* {@inheritdoc}
*/
- public function exists(string $name) {
+ public function exists(string $name): bool {
return TRUE;
}
diff --git a/core/modules/system/tests/modules/update_script_test/src/Hook/UpdateScriptTestRequirements.php b/core/modules/system/tests/modules/update_script_test/src/Hook/UpdateScriptTestRequirements.php
index 5927e31e460b..e93fe8bb80fd 100644
--- a/core/modules/system/tests/modules/update_script_test/src/Hook/UpdateScriptTestRequirements.php
+++ b/core/modules/system/tests/modules/update_script_test/src/Hook/UpdateScriptTestRequirements.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\update_script_test\Hook;
use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Hook\Attribute\Hook;
/**
@@ -25,21 +26,21 @@ class UpdateScriptTestRequirements {
// Set a requirements warning or error when the test requests it.
$requirement_type = $this->configFactory->get('update_script_test.settings')->get('requirement_type');
switch ($requirement_type) {
- case REQUIREMENT_WARNING:
+ case RequirementSeverity::Warning->value:
$requirements['update_script_test'] = [
'title' => 'Update script test',
'value' => 'Warning',
'description' => 'This is a requirements warning provided by the update_script_test module.',
- 'severity' => REQUIREMENT_WARNING,
+ 'severity' => RequirementSeverity::Warning,
];
break;
- case REQUIREMENT_ERROR:
+ case RequirementSeverity::Error->value:
$requirements['update_script_test'] = [
'title' => 'Update script test',
'value' => 'Error',
'description' => 'This is a (buggy description fixed in update_script_test_requirements_alter()) requirements error provided by the update_script_test module.',
- 'severity' => REQUIREMENT_ERROR,
+ 'severity' => RequirementSeverity::Error,
];
break;
}
@@ -51,7 +52,7 @@ class UpdateScriptTestRequirements {
*/
#[Hook('update_requirements_alter')]
public function updateAlter(array &$requirements): void {
- if (isset($requirements['update_script_test']) && $requirements['update_script_test']['severity'] === REQUIREMENT_ERROR) {
+ if (isset($requirements['update_script_test']) && $requirements['update_script_test']['severity'] === RequirementSeverity::Error) {
$requirements['update_script_test']['description'] = 'This is a requirements error provided by the update_script_test module.';
}
}
diff --git a/core/modules/system/tests/modules/update_test_schema/src/Hook/UpdateTestSchemaRequirements.php b/core/modules/system/tests/modules/update_test_schema/src/Hook/UpdateTestSchemaRequirements.php
index de96ce3e36a5..3199527bd05c 100644
--- a/core/modules/system/tests/modules/update_test_schema/src/Hook/UpdateTestSchemaRequirements.php
+++ b/core/modules/system/tests/modules/update_test_schema/src/Hook/UpdateTestSchemaRequirements.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\update_test_schema\Hook;
use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Url;
@@ -22,7 +23,7 @@ class UpdateTestSchemaRequirements {
$requirements['path_alias_test'] = [
'title' => 'Path alias test',
'value' => 'Check a path alias for the admin page',
- 'severity' => REQUIREMENT_INFO,
+ 'severity' => RequirementSeverity::Info,
'description' => new FormattableMarkup('Visit <a href=":link">the structure page</a> to do many useful things.', [
':link' => Url::fromRoute('system.admin_structure')->toString(),
]),
diff --git a/core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php b/core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php
deleted file mode 100644
index cbef10d726ec..000000000000
--- a/core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\system\Functional\Datetime;
-
-use Drupal\Core\Datetime\DrupalDateTime;
-use Drupal\Tests\BrowserTestBase;
-use Drupal\user\Entity\User;
-
-/**
- * Tests DrupalDateTime functionality.
- *
- * @group Datetime
- */
-class DrupalDateTimeTest extends BrowserTestBase {
-
- /**
- * Set up required modules.
- *
- * @var string[]
- */
- protected static $modules = [];
-
- /**
- * {@inheritdoc}
- */
- protected $defaultTheme = 'stark';
-
- /**
- * Tests that DrupalDateTime can detect the right timezone to use.
- *
- * Test with a variety of less commonly used timezone names to
- * help ensure that the system timezone will be different than the
- * stated timezones.
- */
- public function testDateTimezone(): void {
- $date_string = '2007-01-31 21:00:00';
-
- // Make sure no site timezone has been set.
- $this->config('system.date')
- ->set('timezone.user.configurable', 0)
- ->set('timezone.default', NULL)
- ->save();
-
- // Detect the system timezone.
- $system_timezone = date_default_timezone_get();
-
- // Create a date object with an unspecified timezone, which should
- // end up using the system timezone.
- $date = new DrupalDateTime($date_string);
- $timezone = $date->getTimezone()->getName();
- $this->assertSame($system_timezone, $timezone, 'DrupalDateTime uses the system timezone when there is no site timezone.');
-
- // Create a date object with a specified timezone.
- $date = new DrupalDateTime($date_string, 'America/Yellowknife');
- $timezone = $date->getTimezone()->getName();
- $this->assertSame('America/Yellowknife', $timezone, 'DrupalDateTime uses the specified timezone if provided.');
-
- // Set a site timezone.
- $this->config('system.date')->set('timezone.default', 'Europe/Warsaw')->save();
-
- // Create a date object with an unspecified timezone, which should
- // end up using the site timezone.
- $date = new DrupalDateTime($date_string);
- $timezone = $date->getTimezone()->getName();
- $this->assertSame('Europe/Warsaw', $timezone, 'DrupalDateTime uses the site timezone if provided.');
-
- // Create user.
- $this->config('system.date')->set('timezone.user.configurable', 1)->save();
- $test_user = $this->drupalCreateUser([]);
- $this->drupalLogin($test_user);
-
- // Set up the user with a different timezone than the site.
- $edit = ['mail' => $test_user->getEmail(), 'timezone' => 'Asia/Manila'];
- $this->drupalGet('user/' . $test_user->id() . '/edit');
- $this->submitForm($edit, 'Save');
-
- // Reload the user and reset the timezone in AccountProxy::setAccount().
- \Drupal::entityTypeManager()->getStorage('user')->resetCache();
- $this->container->get('current_user')->setAccount(User::load($test_user->id()));
-
- // Create a date object with an unspecified timezone, which should
- // end up using the user timezone.
- $date = new DrupalDateTime($date_string);
- $timezone = $date->getTimezone()->getName();
- $this->assertSame('Asia/Manila', $timezone, 'DrupalDateTime uses the user timezone, if configurable timezones are used and it is set.');
- }
-
- /**
- * Tests the ability to override the time zone in the format method.
- */
- public function testTimezoneFormat(): void {
- // Create a date in UTC
- $date = DrupalDateTime::createFromTimestamp(87654321, 'UTC');
-
- // Verify that the date format method displays the default time zone.
- $this->assertEquals('1972/10/11 12:25:21 UTC', $date->format('Y/m/d H:i:s e'), 'Date has default UTC time zone and correct date/time.');
-
- // Verify that the format method can override the time zone.
- $this->assertEquals('1972/10/11 08:25:21 America/New_York', $date->format('Y/m/d H:i:s e', ['timezone' => 'America/New_York']), 'Date displayed overridden time zone and correct date/time');
-
- // Verify that the date format method still displays the default time zone
- // for the date object.
- $this->assertEquals('1972/10/11 12:25:21 UTC', $date->format('Y/m/d H:i:s e'), 'Date still has default UTC time zone and correct date/time');
- }
-
-}
diff --git a/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php b/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php
index 77eaa48575b0..f570d8031761 100644
--- a/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php
+++ b/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php
@@ -62,20 +62,23 @@ class EntityAddUITest extends BrowserTestBase {
$this->drupalGet('/entity_test_with_bundle/add');
$this->assertSession()->addressEquals('/entity_test_with_bundle/add/test');
- // Two bundles exist, confirm both are shown.
+ // Two bundles exist. Confirm both are shown and that they are ordered
+ // alphabetically by their labels, not by their IDs.
EntityTestBundle::create([
'id' => 'test2',
- 'label' => 'Test2 label',
+ 'label' => 'Aaa Test2 label',
'description' => 'My test2 description',
])->save();
$this->drupalGet('/entity_test_with_bundle/add');
$this->assertSession()->linkExists('Test label');
- $this->assertSession()->linkExists('Test2 label');
+ $this->assertSession()->linkExists('Aaa Test2 label');
$this->assertSession()->pageTextContains('My test description');
$this->assertSession()->pageTextContains('My test2 description');
- $this->clickLink('Test2 label');
+ $this->assertSession()->pageTextMatches('/Aaa Test2 label(.*)Test label/');
+
+ $this->clickLink('Aaa Test2 label');
$this->drupalGet('/entity_test_with_bundle/add/test2');
$this->submitForm(['name[0][value]' => 'test name'], 'Save');
@@ -106,7 +109,7 @@ class EntityAddUITest extends BrowserTestBase {
$this->drupalGet('/entity_test_with_bundle/add');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkExists('Test label');
- $this->assertSession()->linkExists('Test2 label');
+ $this->assertSession()->linkExists('Aaa Test2 label');
$this->assertSession()->linkNotExists('Forbidden to create bundle');
$this->assertSession()->linkNotExists('Test3 label');
$this->clickLink('Test label');
@@ -129,7 +132,7 @@ class EntityAddUITest extends BrowserTestBase {
$this->drupalGet('/entity_test_with_bundle/add');
$this->assertSession()->linkNotExists('Forbidden to create bundle');
$this->assertSession()->linkNotExists('Test label');
- $this->assertSession()->linkNotExists('Test2 label');
+ $this->assertSession()->linkNotExists('Aaa Test2 label');
$this->assertSession()->linkNotExists('Test3 label');
$this->assertSession()->linkExists('Add a new test entity bundle.');
}
diff --git a/core/modules/system/tests/src/Functional/Entity/Traits/EntityDefinitionTestTrait.php b/core/modules/system/tests/src/Functional/Entity/Traits/EntityDefinitionTestTrait.php
index e130b89bf327..94221d8165fe 100644
--- a/core/modules/system/tests/src/Functional/Entity/Traits/EntityDefinitionTestTrait.php
+++ b/core/modules/system/tests/src/Functional/Entity/Traits/EntityDefinitionTestTrait.php
@@ -24,7 +24,7 @@ trait EntityDefinitionTestTrait {
* (optional) Applies changes only for the specified entity type ID.
* Defaults to NULL.
*/
- protected function applyEntityUpdates($entity_type_id = NULL) {
+ protected function applyEntityUpdates($entity_type_id = NULL): void {
$complete_change_list = \Drupal::entityDefinitionUpdateManager()->getChangeList();
if ($complete_change_list) {
// In case there are changes, explicitly invalidate caches.
@@ -68,7 +68,7 @@ trait EntityDefinitionTestTrait {
* @param string $entity_type_id
* The entity type ID.
*/
- protected function doEntityUpdate($op, $entity_type_id) {
+ protected function doEntityUpdate($op, $entity_type_id): void {
$entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
$field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id);
switch ($op) {
@@ -96,7 +96,7 @@ trait EntityDefinitionTestTrait {
* @param array|null $original_storage_definition
* The original field storage definition.
*/
- protected function doFieldUpdate($op, $storage_definition = NULL, $original_storage_definition = NULL) {
+ protected function doFieldUpdate($op, $storage_definition = NULL, $original_storage_definition = NULL): void {
switch ($op) {
case EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED:
\Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionCreate($storage_definition);
@@ -115,7 +115,7 @@ trait EntityDefinitionTestTrait {
/**
* Enables a new entity type definition.
*/
- protected function enableNewEntityType() {
+ protected function enableNewEntityType(): void {
$this->state->set('entity_test_new', TRUE);
$this->applyEntityUpdates('entity_test_new');
}
@@ -123,7 +123,7 @@ trait EntityDefinitionTestTrait {
/**
* Resets the entity type definition.
*/
- protected function resetEntityType() {
+ protected function resetEntityType(): void {
$updated_entity_type = $this->getUpdatedEntityTypeDefinition(FALSE, FALSE);
$updated_field_storage_definitions = $this->getUpdatedFieldStorageDefinitions(FALSE, FALSE);
$this->entityDefinitionUpdateManager->updateFieldableEntityType($updated_entity_type, $updated_field_storage_definitions);
@@ -136,7 +136,7 @@ trait EntityDefinitionTestTrait {
* (optional) Whether the change should be performed by the entity
* definition update manager.
*/
- protected function updateEntityTypeToRevisionable($perform_update = FALSE) {
+ protected function updateEntityTypeToRevisionable($perform_update = FALSE): void {
$translatable = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update')->isTranslatable();
$updated_entity_type = $this->getUpdatedEntityTypeDefinition(TRUE, $translatable);
@@ -154,7 +154,7 @@ trait EntityDefinitionTestTrait {
* (optional) Whether the change should be performed by the entity
* definition update manager.
*/
- protected function updateEntityTypeToNotRevisionable($perform_update = FALSE) {
+ protected function updateEntityTypeToNotRevisionable($perform_update = FALSE): void {
$translatable = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update')->isTranslatable();
$updated_entity_type = $this->getUpdatedEntityTypeDefinition(FALSE, $translatable);
@@ -172,7 +172,7 @@ trait EntityDefinitionTestTrait {
* (optional) Whether the change should be performed by the entity
* definition update manager.
*/
- protected function updateEntityTypeToTranslatable($perform_update = FALSE) {
+ protected function updateEntityTypeToTranslatable($perform_update = FALSE): void {
$revisionable = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update')->isRevisionable();
$updated_entity_type = $this->getUpdatedEntityTypeDefinition($revisionable, TRUE);
@@ -190,7 +190,7 @@ trait EntityDefinitionTestTrait {
* (optional) Whether the change should be performed by the entity
* definition update manager.
*/
- protected function updateEntityTypeToNotTranslatable($perform_update = FALSE) {
+ protected function updateEntityTypeToNotTranslatable($perform_update = FALSE): void {
$revisionable = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update')->isRevisionable();
$updated_entity_type = $this->getUpdatedEntityTypeDefinition($revisionable, FALSE);
@@ -208,7 +208,7 @@ trait EntityDefinitionTestTrait {
* (optional) Whether the change should be performed by the entity
* definition update manager.
*/
- protected function updateEntityTypeToRevisionableAndTranslatable($perform_update = FALSE) {
+ protected function updateEntityTypeToRevisionableAndTranslatable($perform_update = FALSE): void {
$updated_entity_type = $this->getUpdatedEntityTypeDefinition(TRUE, TRUE);
$updated_field_storage_definitions = $this->getUpdatedFieldStorageDefinitions(TRUE, TRUE);
@@ -235,7 +235,7 @@ trait EntityDefinitionTestTrait {
* (optional) If the base field should be translatable or not. Defaults to
* FALSE.
*/
- protected function addBaseField($type = 'string', $entity_type_id = 'entity_test_update', $is_revisionable = FALSE, $set_label = TRUE, $is_translatable = FALSE) {
+ protected function addBaseField($type = 'string', $entity_type_id = 'entity_test_update', $is_revisionable = FALSE, $set_label = TRUE, $is_translatable = FALSE): void {
$definitions['new_base_field'] = BaseFieldDefinition::create($type)
->setName('new_base_field')
->setRevisionable($is_revisionable)
@@ -251,7 +251,7 @@ trait EntityDefinitionTestTrait {
/**
* Adds a long-named base field to the 'entity_test_update' entity type.
*/
- protected function addLongNameBaseField() {
+ protected function addLongNameBaseField(): void {
$key = 'entity_test_update.additional_base_field_definitions';
$definitions = $this->state->get($key, []);
$definitions['new_long_named_entity_reference_base_field'] = BaseFieldDefinition::create('entity_reference')
@@ -268,7 +268,7 @@ trait EntityDefinitionTestTrait {
* @param string $type
* (optional) The field type for the new field. Defaults to 'string'.
*/
- protected function addRevisionableBaseField($type = 'string') {
+ protected function addRevisionableBaseField($type = 'string'): void {
$definitions['new_base_field'] = BaseFieldDefinition::create($type)
->setName('new_base_field')
->setLabel(t('A new revisionable base field'))
@@ -279,14 +279,14 @@ trait EntityDefinitionTestTrait {
/**
* Modifies the new base field from 'string' to 'text'.
*/
- protected function modifyBaseField() {
+ protected function modifyBaseField(): void {
$this->addBaseField('text');
}
/**
* Promotes a field to an entity key.
*/
- protected function makeBaseFieldEntityKey() {
+ protected function makeBaseFieldEntityKey(): void {
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_update');
$entity_keys = $entity_type->getKeys();
$entity_keys['new_base_field'] = 'new_base_field';
@@ -300,21 +300,21 @@ trait EntityDefinitionTestTrait {
* @param string $entity_type_id
* (optional) The entity type ID the base field should be attached to.
*/
- protected function removeBaseField($entity_type_id = 'entity_test_update') {
+ protected function removeBaseField($entity_type_id = 'entity_test_update'): void {
$this->state->delete($entity_type_id . '.additional_base_field_definitions');
}
/**
* Adds a single-field index to the base field.
*/
- protected function addBaseFieldIndex() {
+ protected function addBaseFieldIndex(): void {
$this->state->set('entity_test_update.additional_field_index.entity_test_update.new_base_field', TRUE);
}
/**
* Removes the index added in addBaseFieldIndex().
*/
- protected function removeBaseFieldIndex() {
+ protected function removeBaseFieldIndex(): void {
$this->state->delete('entity_test_update.additional_field_index.entity_test_update.new_base_field');
}
@@ -328,7 +328,7 @@ trait EntityDefinitionTestTrait {
* @param bool $translatable
* (optional) Whether the field should be translatable. Defaults to FALSE.
*/
- protected function addBundleField($type = 'string', $revisionable = FALSE, $translatable = FALSE) {
+ protected function addBundleField($type = 'string', $revisionable = FALSE, $translatable = FALSE): void {
$definitions['new_bundle_field'] = FieldStorageDefinition::create($type)
->setName('new_bundle_field')
->setLabel(t('A new bundle field'))
@@ -342,14 +342,14 @@ trait EntityDefinitionTestTrait {
/**
* Modifies the new bundle field from 'string' to 'text'.
*/
- protected function modifyBundleField() {
+ protected function modifyBundleField(): void {
$this->addBundleField('text');
}
/**
* Removes the new bundle field from the 'entity_test_update' entity type.
*/
- protected function removeBundleField() {
+ protected function removeBundleField(): void {
$this->state->delete('entity_test_update.additional_field_storage_definitions');
$this->state->delete('entity_test_update.additional_bundle_field_definitions.test_bundle');
}
@@ -359,7 +359,7 @@ trait EntityDefinitionTestTrait {
*
* @see \Drupal\entity_test\EntityTestStorageSchema::getEntitySchema()
*/
- protected function addEntityIndex() {
+ protected function addEntityIndex(): void {
$indexes = [
'entity_test_update__new_index' => ['name', 'test_single_property'],
];
@@ -369,14 +369,14 @@ trait EntityDefinitionTestTrait {
/**
* Removes the index added in addEntityIndex().
*/
- protected function removeEntityIndex() {
+ protected function removeEntityIndex(): void {
$this->state->delete('entity_test_update.additional_entity_indexes');
}
/**
* Renames the base table to 'entity_test_update_new'.
*/
- protected function renameBaseTable() {
+ protected function renameBaseTable(): void {
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_update');
$entity_type->set('base_table', 'entity_test_update_new');
@@ -387,7 +387,7 @@ trait EntityDefinitionTestTrait {
/**
* Renames the data table to 'entity_test_update_data_new'.
*/
- protected function renameDataTable() {
+ protected function renameDataTable(): void {
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_update');
$entity_type->set('data_table', 'entity_test_update_data_new');
@@ -398,7 +398,7 @@ trait EntityDefinitionTestTrait {
/**
* Renames the revision table to 'entity_test_update_revision_new'.
*/
- protected function renameRevisionBaseTable() {
+ protected function renameRevisionBaseTable(): void {
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_update');
$entity_type->set('revision_table', 'entity_test_update_revision_new');
@@ -409,7 +409,7 @@ trait EntityDefinitionTestTrait {
/**
* Renames the revision data table to 'entity_test_update_revision_data_new'.
*/
- protected function renameRevisionDataTable() {
+ protected function renameRevisionDataTable(): void {
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_update');
$entity_type->set('revision_data_table', 'entity_test_update_revision_data_new');
@@ -420,7 +420,7 @@ trait EntityDefinitionTestTrait {
/**
* Removes the entity type.
*/
- protected function deleteEntityType() {
+ protected function deleteEntityType(): void {
$this->state->set('entity_test_update.entity_type', 'null');
}
diff --git a/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php b/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php
index 2a0886550191..fa6bda652deb 100644
--- a/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php
+++ b/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php
@@ -57,10 +57,7 @@ class FileTransferTest extends BrowserTestBase {
public function _buildFakeModule() {
$location = 'temporary://fake';
if (is_dir($location)) {
- $ret = 0;
- $output = [];
- exec('rm -Rf ' . escapeshellarg($location), $output, $ret);
- if ($ret != 0) {
+ if (!\Drupal::service('file_system')->deleteRecursive($location)) {
throw new \Exception('Error removing fake module directory.');
}
}
@@ -91,26 +88,10 @@ class FileTransferTest extends BrowserTestBase {
*/
public function testJail(): void {
$source = $this->_buildFakeModule();
-
- // This convoluted piece of code is here because our testing framework does
- // not support expecting exceptions.
- $got_it = FALSE;
- try {
- $this->testConnection->copyDirectory($source, sys_get_temp_dir());
- }
- catch (FileTransferException) {
- $got_it = TRUE;
- }
- $this->assertTrue($got_it, 'Was not able to copy a directory outside of the jailed area.');
-
- $got_it = TRUE;
- try {
- $this->testConnection->copyDirectory($source, $this->root . '/' . PublicStream::basePath());
- }
- catch (FileTransferException) {
- $got_it = FALSE;
- }
- $this->assertTrue($got_it, 'Was able to copy a directory inside of the jailed area');
+ $this->testConnection->copyDirectory($source, $this->root . '/' . PublicStream::basePath());
+ $this->expectException(FileTransferException::class);
+ $this->expectExceptionMessage('@directory is outside of the @jail');
+ $this->testConnection->copyDirectory($source, sys_get_temp_dir());
}
}
diff --git a/core/modules/system/tests/src/Functional/Form/ElementTest.php b/core/modules/system/tests/src/Functional/Form/ElementTest.php
index 4a9755fac7f5..0ebf9e4ce774 100644
--- a/core/modules/system/tests/src/Functional/Form/ElementTest.php
+++ b/core/modules/system/tests/src/Functional/Form/ElementTest.php
@@ -38,6 +38,7 @@ class ElementTest extends BrowserTestBase {
$this->testFormAutocomplete();
$this->testFormElementErrors();
$this->testDetailsSummaryAttributes();
+ $this->testDetailsDescriptionAttributes();
}
/**
@@ -230,4 +231,13 @@ class ElementTest extends BrowserTestBase {
$this->assertSession()->elementExists('css', 'summary[data-summary-attribute="test"]');
}
+ /**
+ * Tests description attributes of details.
+ */
+ protected function testDetailsDescriptionAttributes(): void {
+ $this->drupalGet('form-test/group-details');
+ $this->assertSession()->elementExists('css', 'details[aria-describedby="edit-description-attributes--description"]');
+ $this->assertSession()->elementExists('css', 'div[id="edit-description-attributes--description"]');
+ }
+
}
diff --git a/core/modules/system/tests/src/Functional/Form/FormTest.php b/core/modules/system/tests/src/Functional/Form/FormTest.php
index 6812903dccc6..f45e45e61592 100644
--- a/core/modules/system/tests/src/Functional/Form/FormTest.php
+++ b/core/modules/system/tests/src/Functional/Form/FormTest.php
@@ -6,7 +6,6 @@ namespace Drupal\Tests\system\Functional\Form;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
-use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Form\FormState;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
@@ -199,7 +198,7 @@ class FormTest extends BrowserTestBase {
$expected_key = array_search($error->getText(), $expected);
// If the error message is not one of the expected messages, fail.
if ($expected_key === FALSE) {
- $this->fail(new FormattableMarkup("Unexpected error message: @error", ['@error' => $error[0]]));
+ $this->fail("Unexpected error message: " . $error[0]);
}
// Remove the expected message from the list once it is found.
else {
@@ -209,7 +208,7 @@ class FormTest extends BrowserTestBase {
// Fail if any expected messages were not found.
foreach ($expected as $not_found) {
- $this->fail(new FormattableMarkup("Found error message: @error", ['@error' => $not_found]));
+ $this->fail("Found error message: " . $not_found);
}
// Verify that input elements are still empty.
@@ -610,14 +609,6 @@ class FormTest extends BrowserTestBase {
public function testNumber(): void {
$form = \Drupal::formBuilder()->getForm('\Drupal\form_test\Form\FormTestNumberForm');
- // Array with all the error messages to be checked.
- $error_messages = [
- 'no_number' => '%name must be a number.',
- 'too_low' => '%name must be higher than or equal to %min.',
- 'too_high' => '%name must be lower than or equal to %max.',
- 'step_mismatch' => '%name is not a valid number.',
- ];
-
// The expected errors.
$expected = [
'integer_no_number' => 'no_number',
@@ -648,21 +639,26 @@ class FormTest extends BrowserTestBase {
$this->submitForm([], 'Submit');
foreach ($expected as $element => $error) {
- // Create placeholder array.
- $placeholders = [
- '%name' => $form[$element]['#title'],
- '%min' => $form[$element]['#min'] ?? '0',
- '%max' => $form[$element]['#max'] ?? '0',
+ // Array with all the error messages to be checked.
+ $name = $form[$element]['#title'];
+ $min = $form[$element]['#min'] ?? '0';
+ $max = $form[$element]['#max'] ?? '0';
+
+ $error_messages = [
+ 'no_number' => "<em class=\"placeholder\">$name</em> must be a number.",
+ 'too_low' => "<em class=\"placeholder\">$name</em> must be higher than or equal to <em class=\"placeholder\">$min</em>.",
+ 'too_high' => "<em class=\"placeholder\">$name</em> must be lower than or equal to <em class=\"placeholder\">$max</em>.",
+ 'step_mismatch' => "<em class=\"placeholder\">$name</em> is not a valid number.",
];
foreach ($error_messages as $id => $message) {
// Check if the error exists on the page, if the current message ID is
// expected. Otherwise ensure that the error message is not present.
if ($id === $error) {
- $this->assertSession()->responseContains(new FormattableMarkup($message, $placeholders));
+ $this->assertSession()->responseContains($message);
}
else {
- $this->assertSession()->responseNotContains(new FormattableMarkup($message, $placeholders));
+ $this->assertSession()->responseNotContains($message);
}
}
}
diff --git a/core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php b/core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php
new file mode 100644
index 000000000000..59d69d1242cb
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php
@@ -0,0 +1,61 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\system\Functional\Hook;
+
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Core\Url;
+
+/**
+ * Tests services in .module files.
+ *
+ * @group Hook
+ */
+class HookCollectorPassTest extends BrowserTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $modules = ['container_initialize'];
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $defaultTheme = 'stark';
+
+ /**
+ * Tests installing a module with a Drupal container call outside functions.
+ *
+ * If this is removed then it needs to be moved to a test that installs modules through
+ * admin/modules.
+ */
+ public function testContainerOutsideFunction(): void {
+ $settings['settings']['rebuild_access'] = (object) [
+ 'value' => TRUE,
+ 'required' => TRUE,
+ ];
+
+ // This simulates installing the module and running a cache rebuild in a
+ // separate request.
+ $this->writeSettings($settings);
+ $this->rebuildAll();
+ $this->drupalGet(Url::fromUri('base:core/rebuild.php'));
+ $this->assertSession()->pageTextNotContains('ContainerNotInitializedException');
+ // Successful response from rebuild.php should redirect to the front page.
+ $this->assertSession()->addressEquals('/');
+
+ // If this file is removed then this test needs to be updated to trigger
+ // the container rebuild error from https://www.drupal.org/i/3505049
+ $config_module_file = $this->root . '/core/modules/system/tests/modules/container_initialize/container_initialize.module';
+ $this->assertFileExists($config_module_file, 'This test depends on a container call in a .module file');
+ // Confirm that the file still has a bare container call.
+ $bare_container = "declare(strict_types=1);
+
+\Drupal::getContainer()->getParameter('site.path');
+";
+ $file_content = file_get_contents($config_module_file);
+ $this->assertStringContainsString($bare_container, $file_content, 'container_initialize.module container test feature is missing.');
+ }
+
+}
diff --git a/core/modules/system/tests/src/Functional/Menu/LinksetControllerMultiLingualTest.php b/core/modules/system/tests/src/Functional/Menu/LinksetControllerMultiLingualTest.php
index d6b44d4bb9d9..b5e208474668 100644
--- a/core/modules/system/tests/src/Functional/Menu/LinksetControllerMultiLingualTest.php
+++ b/core/modules/system/tests/src/Functional/Menu/LinksetControllerMultiLingualTest.php
@@ -151,7 +151,7 @@ final class LinksetControllerMultiLingualTest extends LinksetControllerTestBase
]);
foreach (['aa', 'bb', 'cc'] as $language_code) {
$multi_lingual_menu_item->addTranslation($language_code, [
- 'title' => $language_code . '|' . 'A multi-lingual-node',
+ 'title' => $language_code . '|A multi-lingual-node',
]);
$multi_lingual_menu_item->save();
}
@@ -170,7 +170,7 @@ final class LinksetControllerMultiLingualTest extends LinksetControllerTestBase
]);
foreach (['aa', 'bb'] as $language_code) {
$multi_lingual_menu_item->addTranslation($language_code, [
- 'title' => $language_code . '|' . 'Second multi-lingual-node',
+ 'title' => $language_code . '|Second multi-lingual-node',
]);
$multi_lingual_menu_item->save();
}
@@ -189,7 +189,7 @@ final class LinksetControllerMultiLingualTest extends LinksetControllerTestBase
]);
foreach (['aa', 'bb'] as $language_code) {
$multi_lingual_menu_item->addTranslation($language_code, [
- 'title' => $language_code . '|' . 'Third multi-lingual-node',
+ 'title' => $language_code . '|Third multi-lingual-node',
]);
$multi_lingual_menu_item->save();
}
diff --git a/core/modules/system/tests/src/Functional/Module/GenericModuleTestBase.php b/core/modules/system/tests/src/Functional/Module/GenericModuleTestBase.php
index 7b3754bc34cb..d22f433a4147 100644
--- a/core/modules/system/tests/src/Functional/Module/GenericModuleTestBase.php
+++ b/core/modules/system/tests/src/Functional/Module/GenericModuleTestBase.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\Tests\system\Functional\Module;
use Drupal\Core\Database\Database;
+use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Tests\BrowserTestBase;
/**
@@ -50,9 +51,12 @@ abstract class GenericModuleTestBase extends BrowserTestBase {
if (empty($info['required'])) {
$connection = Database::getConnection();
- // When the database driver is provided by a module, then that module
- // cannot be uninstalled.
- if ($module !== $connection->getProvider()) {
+ // The module that provides the database driver, or is a dependency of
+ // the database driver, cannot be uninstalled.
+ $database_module_extension = \Drupal::service(ModuleExtensionList::class)->get($connection->getProvider());
+ $database_modules_required = $database_module_extension->requires ? array_keys($database_module_extension->requires) : [];
+ $database_modules_required[] = $connection->getProvider();
+ if (!in_array($module, $database_modules_required)) {
// Check that the module can be uninstalled and then re-installed again.
$this->preUnInstallSteps();
$this->assertTrue(\Drupal::service('module_installer')->uninstall([$module]), "Failed to uninstall '$module' module");
diff --git a/core/modules/system/tests/src/Functional/Module/UninstallTest.php b/core/modules/system/tests/src/Functional/Module/UninstallTest.php
index aeace5bb488b..6d1b93cc50db 100644
--- a/core/modules/system/tests/src/Functional/Module/UninstallTest.php
+++ b/core/modules/system/tests/src/Functional/Module/UninstallTest.php
@@ -22,7 +22,15 @@ class UninstallTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
- protected static $modules = ['module_test', 'user', 'views', 'node'];
+ protected static $modules = [
+ 'ckeditor5',
+ 'filter',
+ 'module_test',
+ 'node',
+ 'user',
+ 'views',
+ 'views_ui',
+ ];
/**
* {@inheritdoc}
@@ -118,6 +126,13 @@ class UninstallTest extends BrowserTestBase {
// Delete the node to allow node to be uninstalled.
$node->delete();
+ // Ensure dependent module full names are shown.
+ $this->assertSession()->pageTextContains('Required by: Views UI');
+ // Ensure matching machine names do not display.
+ $this->assertSession()->pageTextNotContains('Required by: Views UI (views_ui)');
+ // Ensure machine names that do not match do display.
+ $this->assertSession()->pageTextContains('Text Editor (editor)');
+
// Uninstall module_test.
$edit = [];
$edit['uninstall[module_test]'] = TRUE;
diff --git a/core/modules/system/tests/src/Functional/Module/VersionTest.php b/core/modules/system/tests/src/Functional/Module/VersionTest.php
index 7f3af598cfb8..1a98b7f246fa 100644
--- a/core/modules/system/tests/src/Functional/Module/VersionTest.php
+++ b/core/modules/system/tests/src/Functional/Module/VersionTest.php
@@ -8,6 +8,7 @@ namespace Drupal\Tests\system\Functional\Module;
* Tests module version dependencies.
*
* @group Module
+ * @group #slow
*/
class VersionTest extends ModuleTestBase {
diff --git a/core/modules/system/tests/src/Functional/SecurityAdvisories/SecurityAdvisoryTest.php b/core/modules/system/tests/src/Functional/SecurityAdvisories/SecurityAdvisoryTest.php
index 388e83f6fcc4..b297647194a9 100644
--- a/core/modules/system/tests/src/Functional/SecurityAdvisories/SecurityAdvisoryTest.php
+++ b/core/modules/system/tests/src/Functional/SecurityAdvisories/SecurityAdvisoryTest.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\Tests\system\Functional\SecurityAdvisories;
use Drupal\advisory_feed_test\AdvisoryTestClientMiddleware;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\Traits\Core\CronRunTrait;
@@ -140,10 +141,10 @@ class SecurityAdvisoryTest extends BrowserTestBase {
// If both PSA and non-PSA advisories are displayed they should be displayed
// as errors.
- $this->assertStatusReportLinks($mixed_advisory_links, REQUIREMENT_ERROR);
+ $this->assertStatusReportLinks($mixed_advisory_links, RequirementSeverity::Error);
// The advisories will be displayed on admin pages if the response was
// stored from the status report request.
- $this->assertAdminPageLinks($mixed_advisory_links, REQUIREMENT_ERROR);
+ $this->assertAdminPageLinks($mixed_advisory_links, RequirementSeverity::Error);
// Confirm that a user without the correct permission will not see the
// advisories on admin pages.
@@ -159,8 +160,8 @@ class SecurityAdvisoryTest extends BrowserTestBase {
$this->drupalLogin($this->user);
// Test cache.
AdvisoryTestClientMiddleware::setTestEndpoint($this->nonWorkingEndpoint);
- $this->assertAdminPageLinks($mixed_advisory_links, REQUIREMENT_ERROR);
- $this->assertStatusReportLinks($mixed_advisory_links, REQUIREMENT_ERROR);
+ $this->assertAdminPageLinks($mixed_advisory_links, RequirementSeverity::Error);
+ $this->assertStatusReportLinks($mixed_advisory_links, RequirementSeverity::Error);
// Tests transmit errors with a JSON endpoint.
$this->tempStore->delete('advisories_response');
@@ -195,8 +196,8 @@ class SecurityAdvisoryTest extends BrowserTestBase {
$this->assertAdvisoriesNotDisplayed($psa_advisory_links, ['system.admin']);
// If only PSA advisories are displayed they should be displayed as
// warnings.
- $this->assertStatusReportLinks($psa_advisory_links, REQUIREMENT_WARNING);
- $this->assertAdminPageLinks($psa_advisory_links, REQUIREMENT_WARNING);
+ $this->assertStatusReportLinks($psa_advisory_links, RequirementSeverity::Warning);
+ $this->assertAdminPageLinks($psa_advisory_links, RequirementSeverity::Warning);
AdvisoryTestClientMiddleware::setTestEndpoint($this->workingEndpointNonPsaOnly, TRUE);
$non_psa_advisory_links = [
@@ -205,8 +206,8 @@ class SecurityAdvisoryTest extends BrowserTestBase {
];
// If only non-PSA advisories are displayed they should be displayed as
// errors.
- $this->assertStatusReportLinks($non_psa_advisory_links, REQUIREMENT_ERROR);
- $this->assertAdminPageLinks($non_psa_advisory_links, REQUIREMENT_ERROR);
+ $this->assertStatusReportLinks($non_psa_advisory_links, RequirementSeverity::Error);
+ $this->assertAdminPageLinks($non_psa_advisory_links, RequirementSeverity::Error);
// Confirm that advisory fetching can be disabled after enabled.
$this->config('system.advisories')->set('enabled', FALSE)->save();
@@ -220,16 +221,15 @@ class SecurityAdvisoryTest extends BrowserTestBase {
*
* @param string[] $expected_link_texts
* The expected links' text.
- * @param int $error_or_warning
- * Whether the links are a warning or an error. Should be one of the
- * REQUIREMENT_* constants.
+ * @param \Drupal\Core\Extension\Requirement\RequirementSeverity $error_or_warning
+ * Whether the links are a warning or an error.
*
* @internal
*/
- private function assertAdminPageLinks(array $expected_link_texts, int $error_or_warning): void {
+ private function assertAdminPageLinks(array $expected_link_texts, RequirementSeverity $error_or_warning): void {
$assert = $this->assertSession();
$this->drupalGet(Url::fromRoute('system.admin'));
- if ($error_or_warning === REQUIREMENT_ERROR) {
+ if ($error_or_warning === RequirementSeverity::Error) {
$assert->pageTextContainsOnce('Error message');
$assert->pageTextNotContains('Warning message');
}
@@ -247,16 +247,15 @@ class SecurityAdvisoryTest extends BrowserTestBase {
*
* @param string[] $expected_link_texts
* The expected links' text.
- * @param int $error_or_warning
- * Whether the links are a warning or an error. Should be one of the
- * REQUIREMENT_* constants.
+ * @param \Drupal\Core\Extension\Requirement\RequirementSeverity::Error|\Drupal\Core\Extension\Requirement\RequirementSeverity::Warning $error_or_warning
+ * Whether the links are a warning or an error.
*
* @internal
*/
- private function assertStatusReportLinks(array $expected_link_texts, int $error_or_warning): void {
+ private function assertStatusReportLinks(array $expected_link_texts, RequirementSeverity $error_or_warning): void {
$this->drupalGet(Url::fromRoute('system.status'));
$assert = $this->assertSession();
- $selector = 'h3#' . ($error_or_warning === REQUIREMENT_ERROR ? 'error' : 'warning')
+ $selector = 'h3#' . $error_or_warning->status()
. ' ~ details.system-status-report__entry:contains("Critical security announcements")';
$assert->elementExists('css', $selector);
foreach ($expected_link_texts as $expected_link_text) {
diff --git a/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php b/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php
index 41d60b8a42a0..32487fa86045 100644
--- a/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php
+++ b/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Drupal\Tests\system\Functional\System;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Site\Settings;
use Drupal\Tests\BrowserTestBase;
@@ -58,7 +59,7 @@ class SitesDirectoryHardeningTest extends BrowserTestBase {
// Manually trigger the requirements check.
$requirements = $this->checkSystemRequirements();
- $this->assertEquals(REQUIREMENT_WARNING, $requirements['configuration_files']['severity'], 'Warning severity is properly set.');
+ $this->assertEquals(RequirementSeverity::Warning, $requirements['configuration_files']['severity'], 'Warning severity is properly set.');
$this->assertEquals('Protection disabled', (string) $requirements['configuration_files']['value']);
$description = strip_tags((string) \Drupal::service('renderer')->renderInIsolation($requirements['configuration_files']['description']));
$this->assertStringContainsString('settings.php is not protected from modifications and poses a security risk.', $description);
@@ -91,8 +92,7 @@ class SitesDirectoryHardeningTest extends BrowserTestBase {
* An array of system requirements.
*/
protected function checkSystemRequirements() {
- \Drupal::moduleHandler()->loadInclude('system', 'install');
- return system_requirements('runtime');
+ return \Drupal::moduleHandler()->invoke('system', 'runtime_requirements');
}
/**
diff --git a/core/modules/system/tests/src/Functional/Theme/FastTest.php b/core/modules/system/tests/src/Functional/Theme/FastTest.php
deleted file mode 100644
index 5cbb7d8f277b..000000000000
--- a/core/modules/system/tests/src/Functional/Theme/FastTest.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\system\Functional\Theme;
-
-use Drupal\Tests\BrowserTestBase;
-use Drupal\user\Entity\User;
-
-/**
- * Tests autocompletion not loading registry.
- *
- * @group Theme
- */
-class FastTest extends BrowserTestBase {
-
- /**
- * {@inheritdoc}
- */
- protected static $modules = ['theme_test'];
-
- /**
- * {@inheritdoc}
- */
- protected $defaultTheme = 'stark';
-
- /**
- * User allowed to access use profiles.
- *
- * @var \Drupal\user\Entity\User
- */
- protected User $account;
-
- /**
- * {@inheritdoc}
- */
- protected function setUp(): void {
- parent::setUp();
- $this->account = $this->drupalCreateUser(['access user profiles']);
- }
-
- /**
- * Tests access to user autocompletion and verify the correct results.
- */
- public function testUserAutocomplete(): void {
- $this->drupalLogin($this->account);
- $this->drupalGet('user/autocomplete', ['query' => ['q' => $this->account->getAccountName()]]);
- $this->assertSession()->responseContains($this->account->getAccountName());
- $this->assertSession()->pageTextNotContains('registry initialized');
- }
-
-}
diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathTestJavaScriptTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathTestJavaScriptTest.php
index ffaaba1119aa..32ca94f100b0 100644
--- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathTestJavaScriptTest.php
+++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathTestJavaScriptTest.php
@@ -50,7 +50,7 @@ class UpdatePathTestJavaScriptTest extends BrowserTestBase {
}
// Source is a root-relative URL. Transform it to an absolute URL to allow
// file_get_contents() to access the file.
- $src = preg_replace('#^' . $GLOBALS['base_path'] . '(.*)#i', $GLOBALS['base_url'] . '/' . '${1}', $script->getAttribute('src'));
+ $src = preg_replace('#^' . $GLOBALS['base_path'] . '(.*)#i', $GLOBALS['base_url'] . '/${1}', $script->getAttribute('src'));
$file_content = file_get_contents($src);
if (str_contains($file_content, 'window.drupalSettings =')) {
diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php
index f0f78b23c99c..5be7e48289f7 100644
--- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php
+++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\Tests\system\Functional\UpdateSystem;
use Drupal\Component\Serialization\Yaml;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Url;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
@@ -149,7 +150,7 @@ class UpdateScriptTest extends BrowserTestBase {
// First, run this test with pending updates to make sure they can be run
// successfully.
$this->drupalLogin($this->updateUser);
- $update_script_test_config->set('requirement_type', REQUIREMENT_WARNING)->save();
+ $update_script_test_config->set('requirement_type', RequirementSeverity::Warning->value)->save();
/** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */
$update_registry = \Drupal::service('update.update_hook_registry');
$update_registry->setInstalledVersion('update_script_test', $update_registry->getInstalledVersion('update_script_test') - 1);
@@ -177,7 +178,7 @@ class UpdateScriptTest extends BrowserTestBase {
// If there is a requirements error, it should be displayed even after
// clicking the link to proceed (since the problem that triggered the error
// has not been fixed).
- $update_script_test_config->set('requirement_type', REQUIREMENT_ERROR)->save();
+ $update_script_test_config->set('requirement_type', RequirementSeverity::Error->value)->save();
$this->drupalGet($this->updateUrl, ['external' => TRUE]);
$this->assertSession()->pageTextContains('This is a requirements error provided by the update_script_test module.');
$this->clickLink('try again');
@@ -185,7 +186,7 @@ class UpdateScriptTest extends BrowserTestBase {
// Ensure that changes to a module's requirements that would cause errors
// are displayed correctly.
- $update_script_test_config->set('requirement_type', REQUIREMENT_OK)->save();
+ $update_script_test_config->set('requirement_type', RequirementSeverity::OK->value)->save();
\Drupal::state()->set('update_script_test.system_info_alter', ['dependencies' => ['a_module_that_does_not_exist']]);
$this->drupalGet($this->updateUrl, ['external' => TRUE]);
$this->assertSession()->responseContains('a_module_that_does_not_exist (Missing)');
diff --git a/core/modules/system/tests/src/Kernel/DateFormatAccessControlHandlerTest.php b/core/modules/system/tests/src/Kernel/DateFormatAccessControlHandlerTest.php
index 6c8c42da59e8..82d866e985e4 100644
--- a/core/modules/system/tests/src/Kernel/DateFormatAccessControlHandlerTest.php
+++ b/core/modules/system/tests/src/Kernel/DateFormatAccessControlHandlerTest.php
@@ -77,6 +77,8 @@ class DateFormatAccessControlHandlerTest extends KernelTestBase {
* An array of test cases.
*/
public static function providerTestAccess(): array {
+ $originalContainer = \Drupal::hasContainer() ? \Drupal::getContainer() : NULL;
+
$c = new ContainerBuilder();
$cache_contexts_manager = (new Prophet())->prophesize(CacheContextsManager::class);
$cache_contexts_manager->assertValidTokens()->willReturn(TRUE);
@@ -84,7 +86,7 @@ class DateFormatAccessControlHandlerTest extends KernelTestBase {
$c->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($c);
- return [
+ $data = [
'No permission + unlocked' => [
[],
'unlocked',
@@ -122,6 +124,13 @@ class DateFormatAccessControlHandlerTest extends KernelTestBase {
AccessResult::allowed()->addCacheContexts(['user.permissions']),
],
];
+
+ // Restore the original container if needed.
+ if ($originalContainer) {
+ \Drupal::setContainer($originalContainer);
+ }
+
+ return $data;
}
}
diff --git a/core/modules/system/tests/src/Kernel/Element/StatusReportPageTest.php b/core/modules/system/tests/src/Kernel/Element/StatusReportPageTest.php
new file mode 100644
index 000000000000..630a3a997dde
--- /dev/null
+++ b/core/modules/system/tests/src/Kernel/Element/StatusReportPageTest.php
@@ -0,0 +1,58 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\system\Kernel\Element;
+
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\system\Element\StatusReportPage;
+
+include_once \DRUPAL_ROOT . '/core/includes/install.inc';
+
+/**
+ * Tests the status report page element.
+ *
+ * @group system
+ * @group legacy
+ */
+class StatusReportPageTest extends KernelTestBase {
+
+ /**
+ * Tests the status report page element.
+ */
+ public function testPeRenderCounters(): void {
+ $element = [
+ '#requirements' => [
+ 'foo' => [
+ 'title' => 'Foo',
+ 'severity' => \REQUIREMENT_INFO,
+ ],
+ 'baz' => [
+ 'title' => 'Baz',
+ 'severity' => RequirementSeverity::Warning,
+ ],
+ 'wiz' => [
+ 'title' => 'Wiz',
+ 'severity' => RequirementSeverity::Error,
+ ],
+ ],
+ ];
+ $this->expectDeprecation('Calling Drupal\system\Element\StatusReportPage::preRenderCounters() with an array of $requirements with \'severity\' with values not of type Drupal\Core\Extension\Requirement\RequirementSeverity enums is deprecated in drupal:11.2.0 and is required in drupal:12.0.0. See https://www.drupal.org/node/3410939');
+ $element = StatusReportPage::preRenderCounters($element);
+
+ $error = $element['#counters']['error'];
+ $this->assertEquals(1, $error['#amount']);
+ $this->assertEquals('error', $error['#severity']);
+
+ $warning = $element['#counters']['warning'];
+ $this->assertEquals(1, $warning['#amount']);
+ $this->assertEquals('warning', $warning['#severity']);
+
+ $checked = $element['#counters']['checked'];
+ $this->assertEquals(1, $checked['#amount']);
+ $this->assertEquals('checked', $checked['#severity']);
+
+ }
+
+}
diff --git a/core/modules/system/tests/src/Kernel/Module/RequirementsTest.php b/core/modules/system/tests/src/Kernel/Module/RequirementsTest.php
index 2258b08bc494..c22529a72db0 100644
--- a/core/modules/system/tests/src/Kernel/Module/RequirementsTest.php
+++ b/core/modules/system/tests/src/Kernel/Module/RequirementsTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Drupal\Tests\system\Kernel\Module;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\KernelTests\KernelTestBase;
/**
@@ -28,7 +29,7 @@ class RequirementsTest extends KernelTestBase {
$requirements = $this->container->get('system.manager')->listRequirements();
// @see requirements1_test_requirements_alter()
$this->assertEquals('Requirements 1 Test - Changed', $requirements['requirements1_test_alterable']['title']);
- $this->assertEquals(REQUIREMENT_WARNING, $requirements['requirements1_test_alterable']['severity']);
+ $this->assertEquals(RequirementSeverity::Warning, $requirements['requirements1_test_alterable']['severity']);
$this->assertArrayNotHasKey('requirements1_test_deletable', $requirements);
}
diff --git a/core/modules/system/tests/src/Kernel/System/CronQueueTest.php b/core/modules/system/tests/src/Kernel/System/CronQueueTest.php
index 96a02f8f1644..069a26c3eb5f 100644
--- a/core/modules/system/tests/src/Kernel/System/CronQueueTest.php
+++ b/core/modules/system/tests/src/Kernel/System/CronQueueTest.php
@@ -70,7 +70,6 @@ class CronQueueTest extends KernelTestBase {
parent::setUp();
$this->connection = Database::getConnection();
- $this->cron = \Drupal::service('cron');
$time = $this->prophesize('Drupal\Component\Datetime\TimeInterface');
$time->getCurrentTime()->willReturn($this->currentTime);
@@ -91,6 +90,8 @@ class CronQueueTest extends KernelTestBase {
});
$this->container->set('queue', $queue_factory->reveal());
+ // Instantiate the `cron` service after the mock queue factory is set.
+ $this->cron = \Drupal::service('cron');
}
/**
diff --git a/core/modules/system/tests/src/Kernel/System/RunTimeRequirementsTest.php b/core/modules/system/tests/src/Kernel/System/RunTimeRequirementsTest.php
index af027b48051e..e39e509cb141 100644
--- a/core/modules/system/tests/src/Kernel/System/RunTimeRequirementsTest.php
+++ b/core/modules/system/tests/src/Kernel/System/RunTimeRequirementsTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Drupal\Tests\system\Kernel\System;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\KernelTests\KernelTestBase;
@@ -31,7 +32,7 @@ class RunTimeRequirementsTest extends KernelTestBase {
'title' => 'RuntimeError',
'value' => 'None',
'description' => 'Runtime Error.',
- 'severity' => REQUIREMENT_ERROR,
+ 'severity' => RequirementSeverity::Error,
];
$requirements = \Drupal::service('system.manager')->listRequirements()['test.runtime.error'];
$this->assertEquals($testRequirements, $requirements);
@@ -40,7 +41,7 @@ class RunTimeRequirementsTest extends KernelTestBase {
'title' => 'RuntimeWarning',
'value' => 'None',
'description' => 'Runtime Warning.',
- 'severity' => REQUIREMENT_WARNING,
+ 'severity' => RequirementSeverity::Warning,
];
$requirementsAlter = \Drupal::service('system.manager')->listRequirements()['test.runtime.error.alter'];
$this->assertEquals($testRequirementsAlter, $requirementsAlter);
diff --git a/core/modules/system/tests/src/Unit/Event/SecurityFileUploadEventSubscriberTest.php b/core/modules/system/tests/src/Unit/Event/SecurityFileUploadEventSubscriberTest.php
index 79b9f52812e8..2cca7450089a 100644
--- a/core/modules/system/tests/src/Unit/Event/SecurityFileUploadEventSubscriberTest.php
+++ b/core/modules/system/tests/src/Unit/Event/SecurityFileUploadEventSubscriberTest.php
@@ -85,12 +85,17 @@ class SecurityFileUploadEventSubscriberTest extends UnitTestCase {
'no extension produces no errors' => ['foo', '', 'foo'],
'filename is munged' => ['foo.phar.png.php.jpg', 'jpg png', 'foo.phar_.png_.php_.jpg'],
'filename is munged regardless of case' => ['FOO.pHAR.PNG.PhP.jpg', 'jpg png', 'FOO.pHAR_.PNG_.PhP_.jpg'],
- 'null bytes are removed' => ['foo' . chr(0) . '.txt' . chr(0), '', 'foo.txt'],
+ 'null bytes are removed even if some extensions are allowed' => [
+ 'foo' . chr(0) . '.html' . chr(0),
+ 'txt',
+ 'foo.html',
+ ],
'dot files are renamed' => ['.git', '', 'git'],
- 'htaccess files are renamed even if allowed' => ['.htaccess', 'htaccess txt', '.htaccess_.txt', '.htaccess'],
+ 'htaccess files are renamed even if allowed' => ['.htaccess', 'htaccess txt', 'htaccess'],
'.phtml extension allowed with .phtml file' => ['foo.phtml', 'phtml', 'foo.phtml'],
'.phtml, .txt extension allowed with .phtml file' => ['foo.phtml', 'phtml txt', 'foo.phtml_.txt', 'foo.phtml'],
'All extensions allowed with .phtml file' => ['foo.phtml', '', 'foo.phtml_.txt', 'foo.phtml'],
+ 'dot files are renamed even if allowed and not in security list' => ['.git', 'git', 'git'],
];
}
@@ -147,18 +152,10 @@ class SecurityFileUploadEventSubscriberTest extends UnitTestCase {
// The following filename would be rejected by 'FileExtension' constraint
// and therefore remains unchanged.
'.php is not munged when it would be rejected' => ['foo.php.php', 'jpg'],
- '.php is not munged when it would be rejected and filename contains null byte character' => [
- 'foo.' . chr(0) . 'php.php',
- 'jpg',
- ],
'extension less files are not munged when they would be rejected' => [
'foo',
'jpg',
],
- 'dot files are not munged when they would be rejected' => [
- '.htaccess',
- 'jpg png',
- ],
];
}
diff --git a/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php b/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php
index ab42b418125a..8a2cfe22f03c 100644
--- a/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php
+++ b/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\Tests\system\Unit\Pager;
use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Pager\PagerPreprocess;
use Drupal\Core\Template\AttributeString;
use Drupal\Tests\UnitTestCase;
@@ -12,10 +13,17 @@ use Drupal\Tests\UnitTestCase;
* Tests pager preprocessing.
*
* @group system
+ *
+ * @coversDefaultClass \Drupal\Core\Pager\PagerPreprocess
*/
class PreprocessPagerTest extends UnitTestCase {
/**
+ * Pager preprocess instance.
+ */
+ protected PagerPreprocess $pagerPreprocess;
+
+ /**
* {@inheritdoc}
*/
protected function setUp(): void {
@@ -39,21 +47,19 @@ class PreprocessPagerTest extends UnitTestCase {
$pager_manager->method('getPager')->willReturn($pager);
$pager_manager->method('getUpdatedParameters')->willReturn('');
+ $this->pagerPreprocess = new PagerPreprocess($pager_manager);
+
$container = new ContainerBuilder();
- $container->set('pager.manager', $pager_manager);
$container->set('url_generator', $url_generator);
- // template_preprocess_pager() renders translatable attribute values.
- $container->set('string_translation', $this->getStringTranslationStub());
\Drupal::setContainer($container);
}
/**
- * Tests template_preprocess_pager() when an empty #quantity is passed.
+ * Tests when an empty #quantity is passed.
*
- * @covers ::template_preprocess_pager
+ * @covers ::preprocessPager
*/
public function testQuantityNotSet(): void {
- require_once $this->root . '/core/includes/theme.inc';
$variables = [
'pager' => [
'#element' => '',
@@ -63,18 +69,17 @@ class PreprocessPagerTest extends UnitTestCase {
'#tags' => '',
],
];
- template_preprocess_pager($variables);
+ $this->pagerPreprocess->preprocessPager($variables);
$this->assertEquals(['first', 'previous'], array_keys($variables['items']));
}
/**
- * Tests template_preprocess_pager() when a #quantity value is passed.
+ * Tests when a #quantity value is passed.
*
- * @covers ::template_preprocess_pager
+ * @covers ::preprocessPager
*/
public function testQuantitySet(): void {
- require_once $this->root . '/core/includes/theme.inc';
$variables = [
'pager' => [
'#element' => '2',
@@ -84,7 +89,7 @@ class PreprocessPagerTest extends UnitTestCase {
'#tags' => '',
],
];
- template_preprocess_pager($variables);
+ $this->pagerPreprocess->preprocessPager($variables);
$this->assertEquals(['first', 'previous', 'pages'], array_keys($variables['items']));
/** @var \Drupal\Core\Template\AttributeString $attribute */
@@ -94,12 +99,11 @@ class PreprocessPagerTest extends UnitTestCase {
}
/**
- * Tests template_preprocess_pager() when an empty #pagination_heading_level value is passed.
+ * Tests when an empty #pagination_heading_level value is passed.
*
- * @covers ::template_preprocess_pager
+ * @covers ::preprocessPager
*/
public function testEmptyPaginationHeadingLevelSet(): void {
- require_once $this->root . '/core/includes/theme.inc';
$variables = [
'pager' => [
'#element' => '2',
@@ -110,18 +114,17 @@ class PreprocessPagerTest extends UnitTestCase {
'#tags' => '',
],
];
- template_preprocess_pager($variables);
+ $this->pagerPreprocess->preprocessPager($variables);
$this->assertEquals('h4', $variables['pagination_heading_level']);
}
/**
- * Tests template_preprocess_pager() when no #pagination_heading_level is passed.
+ * Tests when no #pagination_heading_level is passed.
*
- * @covers ::template_preprocess_pager
+ * @covers ::preprocessPager
*/
public function testPaginationHeadingLevelNotSet(): void {
- require_once $this->root . '/core/includes/theme.inc';
$variables = [
'pager' => [
'#element' => '',
@@ -131,18 +134,17 @@ class PreprocessPagerTest extends UnitTestCase {
'#tags' => '',
],
];
- template_preprocess_pager($variables);
+ $this->pagerPreprocess->preprocessPager($variables);
$this->assertEquals('h4', $variables['pagination_heading_level']);
}
/**
- * Tests template_preprocess_pager() when a #pagination_heading_level value is passed.
+ * Tests when a #pagination_heading_level value is passed.
*
- * @covers ::template_preprocess_pager
+ * @covers ::preprocessPager
*/
public function testPaginationHeadingLevelSet(): void {
- require_once $this->root . '/core/includes/theme.inc';
$variables = [
'pager' => [
'#element' => '2',
@@ -153,18 +155,17 @@ class PreprocessPagerTest extends UnitTestCase {
'#tags' => '',
],
];
- template_preprocess_pager($variables);
+ $this->pagerPreprocess->preprocessPager($variables);
$this->assertEquals('h5', $variables['pagination_heading_level']);
}
/**
- * Test template_preprocess_pager() with an invalid #pagination_heading_level.
+ * Test with an invalid #pagination_heading_level.
*
- * @covers ::template_preprocess_pager
+ * @covers ::preprocessPager
*/
public function testPaginationHeadingLevelInvalid(): void {
- require_once $this->root . '/core/includes/theme.inc';
$variables = [
'pager' => [
'#element' => '2',
@@ -175,7 +176,7 @@ class PreprocessPagerTest extends UnitTestCase {
'#tags' => '',
],
];
- template_preprocess_pager($variables);
+ $this->pagerPreprocess->preprocessPager($variables);
$this->assertEquals('h4', $variables['pagination_heading_level']);
}
diff --git a/core/modules/system/tests/themes/test_theme/test_theme.theme b/core/modules/system/tests/themes/test_theme/test_theme.theme
index 80214af02d0b..d0b3b2b71bf1 100644
--- a/core/modules/system/tests/themes/test_theme/test_theme.theme
+++ b/core/modules/system/tests/themes/test_theme/test_theme.theme
@@ -54,7 +54,7 @@ function test_theme_theme_suggestions_alter(array &$suggestions, array &$variabl
// the theme_suggestions_test module can be picked up when that module is
// enabled.
if ($hook == 'theme_test_general_suggestions') {
- array_unshift($suggestions, 'theme_test_general_suggestions__' . 'theme_override');
+ array_unshift($suggestions, 'theme_test_general_suggestions__theme_override');
$variables['theme_hook'] = 'test_theme_theme_suggestions_alter';
}
}
@@ -68,7 +68,7 @@ function test_theme_theme_suggestions_theme_test_suggestions_alter(array &$sugge
// suggestion to the beginning of the array so that the suggestion added by
// the theme_suggestions_test module can be picked up when that module is
// enabled.
- array_unshift($suggestions, 'theme_test_suggestions__' . 'theme_override');
+ array_unshift($suggestions, 'theme_test_suggestions__theme_override');
}
/**