summaryrefslogtreecommitdiffstatshomepage
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/.phpstan-baseline.php55
-rw-r--r--core/MAINTAINERS.txt1
-rw-r--r--core/composer.json3
-rw-r--r--core/core.services.yml9
-rw-r--r--core/includes/theme.inc421
-rw-r--r--core/lib/Drupal/Core/Breadcrumb/BreadcrumbPreprocess.php32
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php4
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php34
-rw-r--r--core/lib/Drupal/Core/Entity/Query/QueryInterface.php6
-rw-r--r--core/lib/Drupal/Core/Menu/MenuPreprocess.php73
-rw-r--r--core/lib/Drupal/Core/Recipe/InputConfigurator.php28
-rw-r--r--core/lib/Drupal/Core/Recipe/Recipe.php6
-rw-r--r--core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php3
-rw-r--r--core/lib/Drupal/Core/Render/Element/Table.php2
-rw-r--r--core/lib/Drupal/Core/Theme/ImagePreprocess.php90
-rw-r--r--core/lib/Drupal/Core/Theme/ThemeCommonElements.php14
-rw-r--r--core/lib/Drupal/Core/Theme/ThemePreprocess.php444
-rw-r--r--core/modules/block_content/src/BlockContentTypeInterface.php11
-rw-r--r--core/modules/block_content/src/Entity/BlockContentType.php7
-rw-r--r--core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php20
-rw-r--r--core/modules/block_content/tests/src/Kernel/BlockContentTest.php13
-rw-r--r--core/modules/field_ui/field_ui.module3
-rw-r--r--core/modules/menu_ui/src/Hook/MenuUiHooks.php32
-rw-r--r--core/modules/menu_ui/tests/src/Kernel/MenuDeleteTest.php86
-rw-r--r--core/modules/migrate/migrate.api.php4
-rw-r--r--core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php2
-rw-r--r--core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php3
-rw-r--r--core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php2
-rw-r--r--core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php5
-rw-r--r--core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php2
-rw-r--r--core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php2
-rw-r--r--core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php2
-rw-r--r--core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php3
-rw-r--r--core/modules/migrate_drupal_ui/src/Form/ReviewForm.php2
-rw-r--r--core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php2
-rw-r--r--core/modules/node/node.module4
-rw-r--r--core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php6
-rw-r--r--core/modules/package_manager/tests/src/Build/PackageInstallTest.php6
-rw-r--r--core/modules/package_manager/tests/src/Build/PackageUpdateTest.php5
-rw-r--r--core/modules/system/src/Controller/SystemController.php2
-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/templates/details.html.twig6
-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/region.html.twig2
-rw-r--r--core/modules/system/templates/table.html.twig2
-rw-r--r--core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install24
-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/FormTestGroupDetailsForm.php5
-rw-r--r--core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php21
-rw-r--r--core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php17
-rw-r--r--core/modules/system/tests/src/Functional/Form/ElementTest.php10
-rw-r--r--core/modules/system/tests/src/Functional/Module/UninstallTest.php17
-rw-r--r--core/modules/system/tests/src/Functional/Theme/FastTest.php52
-rw-r--r--core/modules/user/src/Hook/UserRequirements.php1
-rw-r--r--core/modules/user/tests/src/Kernel/UserRequirementsTest.php18
-rw-r--r--core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig2
-rw-r--r--core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig2
-rw-r--r--core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig2
-rw-r--r--core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig2
-rw-r--r--core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig3
-rw-r--r--core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig2
-rw-r--r--core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig2
-rw-r--r--core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig2
-rw-r--r--core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig2
-rwxr-xr-xcore/scripts/dev/commit-code-check.sh15
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php5
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php8
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php8
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php8
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php8
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php5
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php5
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php11
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php8
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php4
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php4
-rw-r--r--core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php23
-rw-r--r--core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php4
-rw-r--r--core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php15
-rw-r--r--core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php50
-rw-r--r--core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php30
-rw-r--r--core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php59
-rw-r--r--core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php26
-rw-r--r--core/tests/fixtures/default_content/node/48475954-e878-439c-9d3d-226724a44269.yml47
-rw-r--r--core/tests/fixtures/default_content/workspace/inner_test.yml24
-rw-r--r--core/tests/fixtures/default_content/workspace/test_workspace.yml19
-rw-r--r--core/themes/claro/claro.theme2
-rw-r--r--core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig2
-rw-r--r--core/themes/claro/templates/classy/dataset/item-list.html.twig2
-rw-r--r--core/themes/claro/templates/classy/dataset/table.html.twig2
-rw-r--r--core/themes/claro/templates/classy/field/image.html.twig2
-rw-r--r--core/themes/claro/templates/classy/layout/region.html.twig2
-rw-r--r--core/themes/claro/templates/details.html.twig7
-rw-r--r--core/themes/claro/templates/install-page.html.twig2
-rw-r--r--core/themes/claro/templates/maintenance-page--front.html.twig2
-rw-r--r--core/themes/claro/templates/maintenance-page.html.twig2
-rw-r--r--core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig2
-rw-r--r--core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig2
-rw-r--r--core/themes/claro/templates/navigation/menu-local-task.html.twig2
-rw-r--r--core/themes/claro/templates/region--breadcrumb.html.twig2
-rw-r--r--core/themes/olivero/olivero.theme2
-rw-r--r--core/themes/olivero/templates/dataset/item-list--search-results.html.twig2
-rw-r--r--core/themes/olivero/templates/dataset/item-list.html.twig2
-rw-r--r--core/themes/olivero/templates/form/details.html.twig3
-rw-r--r--core/themes/olivero/templates/layout/region--breadcrumb.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--content-above.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--content-below.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--content.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--footer-bottom.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--footer-top.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--header.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--highlighted.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--primary-menu.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--secondary-menu.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--sidebar.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region--social.html.twig2
-rw-r--r--core/themes/olivero/templates/layout/region.html.twig2
-rw-r--r--core/themes/olivero/templates/maintenance-page.html.twig2
-rw-r--r--core/themes/olivero/templates/menu-local-action.html.twig2
-rw-r--r--core/themes/olivero/templates/navigation/menu-local-task.html.twig2
-rw-r--r--core/themes/stable9/templates/dataset/item-list.html.twig2
-rw-r--r--core/themes/stable9/templates/dataset/table.html.twig2
-rw-r--r--core/themes/stable9/templates/field/image.html.twig2
-rw-r--r--core/themes/stable9/templates/form/details.html.twig6
-rw-r--r--core/themes/stable9/templates/layout/install-page.html.twig2
-rw-r--r--core/themes/stable9/templates/layout/maintenance-page.html.twig2
-rw-r--r--core/themes/stable9/templates/layout/region.html.twig2
-rw-r--r--core/themes/stable9/templates/navigation/menu-local-action.html.twig2
-rw-r--r--core/themes/stable9/templates/navigation/menu-local-task.html.twig2
-rw-r--r--core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig2
-rw-r--r--core/themes/starterkit_theme/templates/dataset/item-list.html.twig2
-rw-r--r--core/themes/starterkit_theme/templates/dataset/table.html.twig2
-rw-r--r--core/themes/starterkit_theme/templates/field/image.html.twig2
-rw-r--r--core/themes/starterkit_theme/templates/form/details.html.twig3
-rw-r--r--core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig2
-rw-r--r--core/themes/starterkit_theme/templates/layout/region.html.twig2
-rw-r--r--core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig2
-rw-r--r--core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig2
182 files changed, 1632 insertions, 716 deletions
diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php
index de0cfedb16f1..21b7ee78a040 100644
--- a/core/.phpstan-baseline.php
+++ b/core/.phpstan-baseline.php
@@ -32453,12 +32453,6 @@ $ignoreErrors[] = [
'path' => __DIR__ . '/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php',
];
$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\theme_test\\\\EventSubscriber\\\\ThemeTestSubscriber\\:\\:onView\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php',
-];
-$ignoreErrors[] = [
'message' => '#^Method Drupal\\\\theme_test\\\\ThemeTestController\\:\\:generalSuggestionAlter\\(\\) has no return type specified\\.$#',
'identifier' => 'missingType.return',
'count' => 1,
@@ -45069,31 +45063,6 @@ $ignoreErrors[] = [
'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/EntityWorkspaceConflictConstraintValidatorTest.php',
];
$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:initializeWorkspacesModule\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php',
-];
-$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:switchToWorkspace\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php',
-];
-$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:assertWorkspaceAssociation\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php',
-];
-$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:createWorkspaceHierarchy\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php',
-];
-
-$ignoreErrors[] = [
'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\EntityWorkspaceConflictConstraintValidatorTest\\:\\:initializeWorkspacesModule\\(\\) has no return type specified\\.$#',
'identifier' => 'missingType.return',
'count' => 1,
@@ -45226,6 +45195,30 @@ $ignoreErrors[] = [
'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceEntityRepositoryTest.php',
];
$ignoreErrors[] = [
+ 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:assertWorkspaceAssociation\\(\\) has no return type specified\\.$#',
+ 'identifier' => 'missingType.return',
+ 'count' => 1,
+ 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php',
+];
+$ignoreErrors[] = [
+ 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:createWorkspaceHierarchy\\(\\) has no return type specified\\.$#',
+ 'identifier' => 'missingType.return',
+ 'count' => 1,
+ 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php',
+];
+$ignoreErrors[] = [
+ 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:initializeWorkspacesModule\\(\\) has no return type specified\\.$#',
+ 'identifier' => 'missingType.return',
+ 'count' => 1,
+ 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php',
+];
+$ignoreErrors[] = [
+ 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:switchToWorkspace\\(\\) has no return type specified\\.$#',
+ 'identifier' => 'missingType.return',
+ 'count' => 1,
+ 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php',
+];
+$ignoreErrors[] = [
'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceInformationTest\\:\\:assertWorkspaceAssociation\\(\\) has no return type specified\\.$#',
'identifier' => 'missingType.return',
'count' => 1,
diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt
index a81dcc29d301..09256dab56e1 100644
--- a/core/MAINTAINERS.txt
+++ b/core/MAINTAINERS.txt
@@ -483,7 +483,6 @@ Testing
Usability
- Cristina Chumillas 'ckrina' https://www.drupal.org/u/ckrina
-- Roy Scholten 'yoroy' https://www.drupal.org/u/yoroy
- Bojhan Somers 'Bojhan' https://www.drupal.org/u/bojhan
diff --git a/core/composer.json b/core/composer.json
index 1ef99e7496b0..63ebe5803f1f 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -57,7 +57,8 @@
"conflict": {
"drupal/automatic_updates": "<4",
"drupal/project_browser": "<2.1",
- "drush/drush": "<12.4.3"
+ "drush/drush": "<12.4.3",
+ "dealerdirect/phpcodesniffer-composer-installer": "1.1.0"
},
"replace": {
"drupal/core-annotation": "self.version",
diff --git a/core/core.services.yml b/core/core.services.yml
index 33e9498bbc4b..9f7c1a2d11cd 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -854,6 +854,9 @@ services:
class: Drupal\Core\Menu\ContextualLinkManager
arguments: ['@controller_resolver', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user', '@request_stack']
Drupal\Core\Menu\ContextualLinkManagerInterface: '@plugin.manager.menu.contextual_link'
+ Drupal\Core\Menu\MenuPreprocess:
+ class: Drupal\Core\Menu\MenuPreprocess
+ autowire: true
plugin.manager.display_variant:
class: Drupal\Core\Display\VariantManager
parent: default_plugin_manager
@@ -1535,6 +1538,9 @@ services:
tags:
- { name: service_collector, tag: breadcrumb_builder, call: addBuilder }
Drupal\Core\Breadcrumb\ChainBreadcrumbBuilderInterface: '@breadcrumb'
+ Drupal\Core\Breadcrumb\BreadcrumbPreprocess:
+ class: Drupal\Core\Breadcrumb\BreadcrumbPreprocess
+ autowire: true
token:
class: Drupal\Core\Utility\Token
arguments: ['@module_handler', '@cache.default', '@language_manager', '@cache_tags.invalidator', '@renderer']
@@ -1575,6 +1581,9 @@ services:
Drupal\Core\Theme\ThemePreprocess:
class: Drupal\Core\Theme\ThemePreprocess
autowire: true
+ Drupal\Core\Theme\ImagePreprocess:
+ class: Drupal\Core\Theme\ImagePreprocess
+ autowire: true
Drupal\Core\Datetime\DatePreprocess:
class: Drupal\Core\Datetime\DatePreprocess
autowire: true
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 6c5d563cb63a..587f9e70c200 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -11,16 +11,15 @@
use Drupal\Core\Datetime\DatePreprocess;
use Drupal\Core\Field\FieldPreprocess;
use Drupal\Core\Pager\PagerPreprocess;
+use Drupal\Core\Breadcrumb\BreadcrumbPreprocess;
+use Drupal\Core\Menu\MenuPreprocess;
+use Drupal\Core\Theme\ImagePreprocess;
use Drupal\Core\Theme\ThemePreprocess;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\StorageException;
-use Drupal\Core\Template\Attribute;
use Drupal\Core\Template\AttributeHelper;
use Drupal\Core\Theme\ThemeCommonElements;
use Drupal\Core\Theme\ThemeSettings;
-use Drupal\Core\Render\Element;
-use Drupal\Core\Utility\TableSort;
-use Drupal\Core\Installer\InstallerKernel;
/**
* @defgroup content_flags Content markers
@@ -483,49 +482,15 @@ function template_preprocess_links(&$variables): void {
* - sizes: The sizes attribute for viewport-based selection of images.
* phpcs:ignore
* - http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:viewport-based-selection-2
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_image(&$variables): void {
- /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
- $file_url_generator = \Drupal::service('file_url_generator');
-
- if (!empty($variables['uri'])) {
- $variables['attributes']['src'] = $file_url_generator->generateString($variables['uri']);
- }
- // Generate a srcset attribute conforming to the spec at
- // https://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-srcset
- if (!empty($variables['srcset'])) {
- $srcset = [];
- foreach ($variables['srcset'] as $src) {
- // URI is mandatory.
- $source = $file_url_generator->generateString($src['uri']);
- if (isset($src['width']) && !empty($src['width'])) {
- $source .= ' ' . $src['width'];
- }
- elseif (isset($src['multiplier']) && !empty($src['multiplier'])) {
- $source .= ' ' . $src['multiplier'];
- }
- $srcset[] = $source;
- }
- $variables['attributes']['srcset'] = implode(', ', $srcset);
- }
-
- foreach (['width', 'height', 'alt', 'title', 'sizes'] as $key) {
- if (isset($variables[$key])) {
- // If the property has already been defined in the attributes,
- // do not override, including NULL.
- if (AttributeHelper::attributeExists($key, $variables['attributes'])) {
- continue;
- }
- $variables['attributes'][$key] = $variables[$key];
- }
- }
-
- // Without dimensions specified, layout shifts can occur,
- // which are more noticeable on pages that take some time to load.
- // As a result, only mark images as lazy load that have dimensions.
- if (isset($variables['width'], $variables['height']) && !isset($variables['attributes']['loading'])) {
- $variables['attributes']['loading'] = 'lazy';
- }
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(ImagePreprocess::class)->preprocessImage($variables);
}
/**
@@ -619,167 +584,15 @@ function template_preprocess_image(&$variables): void {
* - sticky: Use a "sticky" table header.
* - empty: The message to display in an extra row if table does not have any
* rows.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_table(&$variables): void {
- // Format the table columns:
- if (!empty($variables['colgroups'])) {
- foreach ($variables['colgroups'] as &$colgroup) {
- // Check if we're dealing with a simple or complex column
- if (isset($colgroup['data'])) {
- $cols = $colgroup['data'];
- unset($colgroup['data']);
- $colgroup_attributes = $colgroup;
- }
- else {
- $cols = $colgroup;
- $colgroup_attributes = [];
- }
- $colgroup = [];
- $colgroup['attributes'] = new Attribute($colgroup_attributes);
- $colgroup['cols'] = [];
-
- // Build columns.
- if (is_array($cols) && !empty($cols)) {
- foreach ($cols as $col_key => $col) {
- $colgroup['cols'][$col_key]['attributes'] = new Attribute($col);
- }
- }
- }
- }
-
- // Build an associative array of responsive classes keyed by column.
- $responsive_classes = [];
-
- // Format the table header:
- $ts = [];
- $header_columns = 0;
- if (!empty($variables['header'])) {
- $ts = TableSort::getContextFromRequest($variables['header'], \Drupal::request());
-
- // Use a separate index with responsive classes as headers
- // may be associative.
- $responsive_index = -1;
- foreach ($variables['header'] as $col_key => $cell) {
- // Increase the responsive index.
- $responsive_index++;
-
- if (!is_array($cell)) {
- $header_columns++;
- $cell_content = $cell;
- $cell_attributes = new Attribute();
- $is_header = TRUE;
- }
- else {
- if (isset($cell['colspan'])) {
- $header_columns += $cell['colspan'];
- }
- else {
- $header_columns++;
- }
- $cell_content = '';
- if (isset($cell['data'])) {
- $cell_content = $cell['data'];
- unset($cell['data']);
- }
- // Flag the cell as a header or not and remove the flag.
- $is_header = $cell['header'] ?? TRUE;
- unset($cell['header']);
-
- // Track responsive classes for each column as needed. Only the header
- // cells for a column are marked up with the responsive classes by a
- // module developer or themer. The responsive classes on the header
- // cells must be transferred to the content cells.
- if (!empty($cell['class']) && is_array($cell['class'])) {
- if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) {
- $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM;
- }
- elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) {
- $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW;
- }
- }
-
- TableSort::header($cell_content, $cell, $variables['header'], $ts);
-
- // TableSort::header() removes the 'sort', 'initial_click_sort' and
- // 'field' keys.
- $cell_attributes = new Attribute($cell);
- }
- $variables['header'][$col_key] = [];
- $variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td';
- $variables['header'][$col_key]['attributes'] = $cell_attributes;
- $variables['header'][$col_key]['content'] = $cell_content;
- }
- }
- $variables['header_columns'] = $header_columns;
-
- // Rows and footer have the same structure.
- $sections = ['rows' , 'footer'];
- foreach ($sections as $section) {
- if (!empty($variables[$section])) {
- foreach ($variables[$section] as $row_key => $row) {
- $cells = $row;
- $row_attributes = [];
-
- // Check if we're dealing with a simple or complex row
- if (isset($row['data'])) {
- $cells = $row['data'];
- $variables['no_striping'] = $row['no_striping'] ?? FALSE;
-
- // Set the attributes array and exclude 'data' and 'no_striping'.
- $row_attributes = $row;
- unset($row_attributes['data']);
- unset($row_attributes['no_striping']);
- }
-
- // Build row.
- $variables[$section][$row_key] = [];
- $variables[$section][$row_key]['attributes'] = new Attribute($row_attributes);
- $variables[$section][$row_key]['cells'] = [];
- if (!empty($cells)) {
- // Reset the responsive index.
- $responsive_index = -1;
- foreach ($cells as $col_key => $cell) {
- // Increase the responsive index.
- $responsive_index++;
-
- if (!is_array($cell)) {
- $cell_content = $cell;
- $cell_attributes = [];
- $is_header = FALSE;
- }
- else {
- $cell_content = '';
- if (isset($cell['data'])) {
- $cell_content = $cell['data'];
- unset($cell['data']);
- }
-
- // Flag the cell as a header or not and remove the flag.
- $is_header = !empty($cell['header']);
- unset($cell['header']);
-
- $cell_attributes = $cell;
- }
- // Active table sort information.
- if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) {
- $variables[$section][$row_key]['cells'][$col_key]['active_table_sort'] = TRUE;
- }
- // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM
- // class from header to cell as needed.
- if (isset($responsive_classes[$responsive_index])) {
- $cell_attributes['class'][] = $responsive_classes[$responsive_index];
- }
- $variables[$section][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td';
- $variables[$section][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes);
- $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content;
- }
- }
- }
- }
- }
- if (empty($variables['no_striping'])) {
- $variables['attributes']['data-striping'] = 1;
- }
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(ThemePreprocess::class)->preprocessTable($variables);
}
/**
@@ -799,56 +612,14 @@ function template_preprocess_table(&$variables): void {
* - list_type: The type of list to return (e.g. "ul", "ol").
* - wrapper_attributes: HTML attributes to be applied to the list wrapper.
*
- * @see https://www.drupal.org/node/1842756
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_item_list(&$variables): void {
- $variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']);
- $variables['#attached']['library'][] = 'core/drupal.item-list';
- foreach ($variables['items'] as &$item) {
- $attributes = [];
- // If the item value is an array, then it is a render array.
- if (is_array($item)) {
- // List items support attributes via the '#wrapper_attributes' property.
- if (isset($item['#wrapper_attributes'])) {
- $attributes = $item['#wrapper_attributes'];
- }
- // Determine whether there are any child elements in the item that are not
- // fully-specified render arrays. If there are any, then the child
- // elements present nested lists and we automatically inherit the render
- // array properties of the current list to them.
- foreach (Element::children($item) as $key) {
- $child = &$item[$key];
- // If this child element does not specify how it can be rendered, then
- // we need to inherit the render properties of the current list.
- if (!isset($child['#type']) && !isset($child['#theme']) && !isset($child['#markup'])) {
- // Since item-list.html.twig supports both strings and render arrays
- // as items, the items of the nested list may have been specified as
- // the child elements of the nested list, instead of #items. For
- // convenience, we automatically move them into #items.
- if (!isset($child['#items'])) {
- // This is the same condition as in
- // \Drupal\Core\Render\Element::children(), which cannot be used
- // here, since it triggers an error on string values.
- foreach ($child as $child_key => $child_value) {
- if (is_int($child_key) || $child_key === '' || $child_key[0] !== '#') {
- $child['#items'][$child_key] = $child_value;
- unset($child[$child_key]);
- }
- }
- }
- // Lastly, inherit the original theme variables of the current list.
- $child['#theme'] = $variables['theme_hook_original'];
- $child['#list_type'] = $variables['list_type'];
- }
- }
- }
-
- // Set the item's value and attributes for the template.
- $item = [
- 'value' => $item,
- 'attributes' => new Attribute($attributes),
- ];
- }
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(ThemePreprocess::class)->preprocessItemList($variables);
}
/**
@@ -882,27 +653,15 @@ function template_preprocess_container(&$variables): void {
* It's the caller's responsibility to ensure this array's items contain no
* dangerous HTML such as <script> tags.
* - active: The key for the currently active maintenance task.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_maintenance_task_list(&$variables): void {
- $items = $variables['items'];
- $active = $variables['active'];
-
- $done = isset($items[$active]) || $active == NULL;
- foreach ($items as $k => $item) {
- $variables['tasks'][$k]['item'] = $item;
- $variables['tasks'][$k]['attributes'] = new Attribute();
- if ($active == $k) {
- $variables['tasks'][$k]['attributes']->addClass('is-active');
- $variables['tasks'][$k]['status'] = t('active');
- $done = FALSE;
- }
- else {
- if ($done) {
- $variables['tasks'][$k]['attributes']->addClass('done');
- $variables['tasks'][$k]['status'] = t('done');
- }
- }
- }
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(ThemePreprocess::class)->preprocessMaintenanceTaskList($variables);
}
/**
@@ -1052,9 +811,15 @@ function theme_get_suggestions($args, $base, $delimiter = '__'): array {
* Prepares variables for tablesort indicators.
*
* Default template: tablesort-indicator.html.twig.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_tablesort_indicator(&$variables): void {
- $variables['#attached']['library'][] = 'core/drupal.tablesort';
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(ThemePreprocess::class)->preprocessTablesortIndicator($variables);
}
/**
@@ -1066,25 +831,14 @@ function template_preprocess_tablesort_indicator(&$variables): void {
* An associative array containing:
* - content - An array of page content.
*
- * @see system_page_attachments()
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_maintenance_page(&$variables): void {
- // @todo Rename the templates to page--maintenance + page--install.
- \Drupal::service(ThemePreprocess::class)->preprocessPage($variables);
-
- // @see system_page_attachments()
- $variables['#attached']['library'][] = 'system/maintenance';
-
- // Maintenance page and install page need branding info in variables because
- // there is no blocks.
- $site_config = \Drupal::config('system.site');
- $variables['logo'] = theme_get_setting('logo.url');
- $variables['site_name'] = $site_config->get('name');
- $variables['site_slogan'] = $site_config->get('slogan');
-
- // Maintenance page and install page need page title in variable because there
- // are no blocks.
- $variables['title'] = $variables['page']['#title'];
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(ThemePreprocess::class)->preprocessMaintenancePage($variables);
}
/**
@@ -1096,21 +850,14 @@ function template_preprocess_maintenance_page(&$variables): void {
* An associative array containing:
* - content - An array of page content.
*
- * @see template_preprocess_maintenance_page()
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_install_page(&$variables): void {
- $installer_active_task = NULL;
- if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE === 'install' && InstallerKernel::installationAttempted()) {
- $installer_active_task = $GLOBALS['install_state']['active_task'];
- }
-
- template_preprocess_maintenance_page($variables);
-
- // Override the site name that is displayed on the page, since Drupal is
- // still in the process of being installed.
- $distribution_name = drupal_install_profile_distribution_name();
- $variables['site_name'] = $distribution_name;
- $variables['site_version'] = $installer_active_task ? drupal_install_profile_distribution_version() : '';
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(ThemePreprocess::class)->preprocessInstallPage($variables);
}
/**
@@ -1125,11 +872,15 @@ function template_preprocess_install_page(&$variables): void {
* @param array $variables
* An associative array containing:
* - elements: An associative array containing properties of the region.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_region(&$variables): void {
- // Create the $content variable that templates expect.
- $variables['content'] = $variables['elements']['#children'];
- $variables['region'] = $variables['elements']['#region'];
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(ThemePreprocess::class)->preprocessRegion($variables);
}
/**
@@ -1182,13 +933,15 @@ function template_preprocess_field_multiple_value_form(&$variables): void {
* @param array $variables
* An associative array containing:
* - links: A list of \Drupal\Core\Link objects which should be rendered.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_breadcrumb(&$variables): void {
- $variables['breadcrumb'] = [];
- /** @var \Drupal\Core\Link $link */
- foreach ($variables['links'] as $key => $link) {
- $variables['breadcrumb'][$key] = ['text' => $link->getText(), 'url' => $link->getUrl()->toString()];
- }
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(BreadcrumbPreprocess::class)->preprocessBreadcrumb($variables);
}
/**
@@ -1212,7 +965,7 @@ function template_preprocess_breadcrumb(&$variables): void {
* - #route_parameters: An associative array of the route parameters.
* - #quantity: The number of pages in the list.
*
- * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Initial
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
* template_preprocess functions are registered directly in hook_theme().
*
* @see https://www.drupal.org/node/3504125
@@ -1233,26 +986,15 @@ function template_preprocess_pager(&$variables): void {
* - #link: A menu link array with 'title', 'url', and (optionally)
* 'localized_options' keys.
* - #active: A boolean indicating whether the local task is active.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_menu_local_task(&$variables): void {
- $link = $variables['element']['#link'];
- $link += [
- 'localized_options' => [],
- ];
- $link_text = $link['title'];
-
- if (!empty($variables['element']['#active'])) {
- $variables['is_active'] = TRUE;
- }
-
- $link['localized_options']['set_active_class'] = TRUE;
-
- $variables['link'] = [
- '#type' => 'link',
- '#title' => $link_text,
- '#url' => $link['url'],
- '#options' => $link['localized_options'],
- ];
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(MenuPreprocess::class)->preprocessMenuLocalTask($variables);
}
/**
@@ -1265,22 +1007,15 @@ function template_preprocess_menu_local_task(&$variables): void {
* - element: A render element containing:
* - #link: A menu link array with 'title', 'url', and (optionally)
* 'localized_options' keys.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial
+ * template_preprocess functions are registered directly in hook_theme().
+ *
+ * @see https://www.drupal.org/node/3504125
*/
function template_preprocess_menu_local_action(&$variables): void {
- $link = $variables['element']['#link'];
- $link += [
- 'localized_options' => [],
- ];
- $link['localized_options']['attributes']['class'][] = 'button';
- $link['localized_options']['attributes']['class'][] = 'button-action';
- $link['localized_options']['set_active_class'] = TRUE;
-
- $variables['link'] = [
- '#type' => 'link',
- '#title' => $link['title'],
- '#options' => $link['localized_options'],
- '#url' => $link['url'],
- ];
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED);
+ \Drupal::service(MenuPreprocess::class)->preprocessMenuLocalAction($variables);
}
/**
diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbPreprocess.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbPreprocess.php
new file mode 100644
index 000000000000..4de3fde48a45
--- /dev/null
+++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbPreprocess.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\Core\Breadcrumb;
+
+/**
+ * Breadcrumb theme preprocess.
+ *
+ * @internal
+ */
+class BreadcrumbPreprocess {
+
+ /**
+ * Prepares variables for breadcrumb templates.
+ *
+ * Default template: breadcrumb.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - links: A list of \Drupal\Core\Link objects which should be rendered.
+ */
+ public function preprocessBreadcrumb(array &$variables): void {
+ $variables['breadcrumb'] = [];
+ /** @var \Drupal\Core\Link $link */
+ foreach ($variables['links'] as $key => $link) {
+ $variables['breadcrumb'][$key] = [
+ 'text' => $link->getText(),
+ 'url' => $link->getUrl()->toString(),
+ ];
+ }
+ }
+
+}
diff --git a/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php b/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php
index 617699bda15f..a0824c65f17f 100644
--- a/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php
+++ b/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php
@@ -34,6 +34,10 @@ trait AutowireTrait {
}
if (!$container->has($service)) {
+ if ($parameter->allowsNull()) {
+ $args[] = NULL;
+ continue;
+ }
throw new AutowiringFailedException($service, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s::_construct()", you should configure its value explicitly.', $service, $parameter->getName(), static::class));
}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php
index 514cd9e9f550..13aa3dcf3f78 100644
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php
@@ -3,8 +3,10 @@
namespace Drupal\Core\DependencyInjection\Compiler;
use Drupal\Component\Utility\Reflection;
+use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
@@ -178,7 +180,8 @@ class TaggedHandlersPass implements CompilerPassInterface {
foreach ($this->tagCache[$tag] ?? [] as $id => $attributes) {
// Validate the interface.
$handler = $container->getDefinition($id);
- if (!is_a($handler->getClass(), $interface, TRUE)) {
+ $class = $this->resolveDefinitionClass($handler, $container);
+ if (!is_a($class, $interface, TRUE)) {
throw new LogicException("Service '$id' for consumer '$consumer_id' does not implement $interface.");
}
$handlers[$id] = $attributes[0]['priority'] ?? 0;
@@ -249,4 +252,33 @@ class TaggedHandlersPass implements CompilerPassInterface {
$consumer->addArgument(array_keys($handlers));
}
+ /**
+ * Resolves the definition class.
+ *
+ * @param \Symfony\Component\DependencyInjection\Definition $definition
+ * The service definition.
+ * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+ * The service container.
+ *
+ * @return class-string|null
+ * The resolved class-string or null if the class cannot be resolved.
+ */
+ protected function resolveDefinitionClass(Definition $definition, ContainerBuilder $container): ?string {
+ $class = $definition->getClass();
+ if ($class) {
+ return $class;
+ }
+
+ if (!$definition instanceof ChildDefinition) {
+ return NULL;
+ }
+
+ if (!$container->hasDefinition($definition->getParent())) {
+ return NULL;
+ }
+
+ $parent = $container->getDefinition($definition->getParent());
+ return $this->resolveDefinitionClass($parent, $container);
+ }
+
}
diff --git a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php
index 30a2220777ee..ff3384063ef5 100644
--- a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php
+++ b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php
@@ -204,9 +204,9 @@ interface QueryInterface extends AlterableInterface {
*
* @param array $headers
* An array of headers of the same structure as described in
- * template_preprocess_table(). Use a 'specifier' in place of a 'field' to
- * specify what to sort on. This can be an entity or a field as described
- * in condition().
+ * \Drupal\Core\Theme\ThemePreprocess::preprocessTable(). Use a 'specifier'
+ * in place of a 'field' to specify what to sort on. This can be an entity
+ * or a field as described in condition().
*
* @return $this
*/
diff --git a/core/lib/Drupal/Core/Menu/MenuPreprocess.php b/core/lib/Drupal/Core/Menu/MenuPreprocess.php
new file mode 100644
index 000000000000..a0834704c531
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/MenuPreprocess.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Drupal\Core\Menu;
+
+/**
+ * Menu theme preprocess.
+ *
+ * @internal
+ */
+class MenuPreprocess {
+
+ /**
+ * Prepares variables for single local task link templates.
+ *
+ * Default template: menu-local-task.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - element: A render element containing:
+ * - #link: A menu link array with 'title', 'url', and (optionally)
+ * 'localized_options' keys.
+ * - #active: A boolean indicating whether the local task is active.
+ */
+ public function preprocessMenuLocalTask(array &$variables): void {
+ $link = $variables['element']['#link'];
+ $link += [
+ 'localized_options' => [],
+ ];
+ $link_text = $link['title'];
+
+ if (!empty($variables['element']['#active'])) {
+ $variables['is_active'] = TRUE;
+ }
+
+ $link['localized_options']['set_active_class'] = TRUE;
+
+ $variables['link'] = [
+ '#type' => 'link',
+ '#title' => $link_text,
+ '#url' => $link['url'],
+ '#options' => $link['localized_options'],
+ ];
+ }
+
+ /**
+ * Prepares variables for single local action link templates.
+ *
+ * Default template: menu-local-action.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - element: A render element containing:
+ * - #link: A menu link array with 'title', 'url', and (optionally)
+ * 'localized_options' keys.
+ */
+ public function preprocessMenuLocalAction(array &$variables): void {
+ $link = $variables['element']['#link'];
+ $link += [
+ 'localized_options' => [],
+ ];
+ $link['localized_options']['attributes']['class'][] = 'button';
+ $link['localized_options']['attributes']['class'][] = 'button-action';
+ $link['localized_options']['set_active_class'] = TRUE;
+
+ $variables['link'] = [
+ '#type' => 'link',
+ '#title' => $link['title'],
+ '#options' => $link['localized_options'],
+ '#url' => $link['url'],
+ ];
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Recipe/InputConfigurator.php b/core/lib/Drupal/Core/Recipe/InputConfigurator.php
index cec8e588611c..3d1f871abbb9 100644
--- a/core/lib/Drupal/Core/Recipe/InputConfigurator.php
+++ b/core/lib/Drupal/Core/Recipe/InputConfigurator.php
@@ -171,12 +171,17 @@ final class InputConfigurator {
* Returns the default value for an input definition.
*
* @param array $definition
- * An input definition. Must contain a `source` element, which can be either
- * 'config' or 'value'. If `source` is 'config', then there must also be a
- * `config` element, which is a two-element indexed array containing
- * (in order) the name of an extant config object, and a property path
- * within that object. If `source` is 'value', then there must be a `value`
- * element, which will be returned as-is.
+ * An input definition. Must contain a `source` element, which can be one
+ * of `config`, `env`, or `value`:
+ * - If `source` is `config`, there must also be a `config` element, which
+ * is a two-element indexed array containing (in order) the name of an
+ * extant config object, and a property path within that object.
+ * - If `source` is `env`, there must also be an `env` element, which is
+ * the name of an environment variable to return. The value will always
+ * be returned as a string. If the environment variable is not set, an
+ * empty string will be returned.
+ * - If `source` is 'value', then there must be a `value` element, which
+ * will be returned as-is.
*
* @return mixed
* The default value.
@@ -192,6 +197,17 @@ final class InputConfigurator {
}
return $config->get($key);
}
+ elseif ($settings['source'] === 'env') {
+ // getenv() accepts NULL to return an array of all environment variables,
+ // but this makes no sense in a recipe. There is no valid situation where
+ // the name of the environment variable should be empty.
+ if (empty($settings['env'])) {
+ throw new \RuntimeException("The name of the environment variable cannot be empty.");
+ }
+ // If the variable doesn't exist, getenv() returns FALSE; we can represent
+ // that as an empty string.
+ return (string) getenv($settings['env']);
+ }
return $settings['value'];
}
diff --git a/core/lib/Drupal/Core/Recipe/Recipe.php b/core/lib/Drupal/Core/Recipe/Recipe.php
index 888f54e4f42c..1eb42b79343b 100644
--- a/core/lib/Drupal/Core/Recipe/Recipe.php
+++ b/core/lib/Drupal/Core/Recipe/Recipe.php
@@ -237,7 +237,7 @@ final class Recipe {
'default' => new Required([
new Collection([
'source' => new Required([
- new Choice(['value', 'config']),
+ new Choice(['value', 'config', 'env']),
]),
'value' => new Optional(),
'config' => new Optional([
@@ -250,6 +250,10 @@ final class Recipe {
]),
]),
]),
+ 'env' => new Optional([
+ new Type('string'),
+ new NotBlank(),
+ ]),
]),
new Callback(self::validateDefaultValueDefinition(...)),
]),
diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php
index 0c63ce2ac495..5ce90c946550 100644
--- a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php
+++ b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php
@@ -88,7 +88,8 @@ class BareHtmlPageRenderer implements BareHtmlPageRendererInterface {
* The page to attach to.
*/
public function systemPageAttachments(array &$page): void {
- // Ensure the same CSS is loaded in template_preprocess_maintenance_page().
+ // Ensure the same CSS is loaded in
+ // \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage().
$page['#attached']['library'][] = 'system/base';
if (\Drupal::service('router.admin_context')->isAdminRoute()) {
$page['#attached']['library'][] = 'system/admin';
diff --git a/core/lib/Drupal/Core/Render/Element/Table.php b/core/lib/Drupal/Core/Render/Element/Table.php
index c9af86146829..957b868ed63e 100644
--- a/core/lib/Drupal/Core/Render/Element/Table.php
+++ b/core/lib/Drupal/Core/Render/Element/Table.php
@@ -402,7 +402,7 @@ class Table extends FormElementBase {
* @return array
* Associative array of rendered child elements for a table.
*
- * @see template_preprocess_table()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable()
* @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments()
* @see drupal_attach_tabledrag()
*/
diff --git a/core/lib/Drupal/Core/Theme/ImagePreprocess.php b/core/lib/Drupal/Core/Theme/ImagePreprocess.php
new file mode 100644
index 000000000000..2f43ffe4c089
--- /dev/null
+++ b/core/lib/Drupal/Core/Theme/ImagePreprocess.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Drupal\Core\Theme;
+
+use Drupal\Core\File\FileUrlGeneratorInterface;
+use Drupal\Core\Template\AttributeHelper;
+
+/**
+ * Image theme preprocess.
+ *
+ * @internal
+ */
+class ImagePreprocess {
+
+ public function __construct(protected FileUrlGeneratorInterface $fileUrlGenerator) {
+ }
+
+ /**
+ * Prepares variables for image templates.
+ *
+ * Default template: image.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - uri: Either the path of the image file (relative to base_path()) or a
+ * full URL.
+ * - width: The width of the image (if known).
+ * - height: The height of the image (if known).
+ * - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0
+ * always require an alt attribute. The HTML 5 draft allows the alt
+ * attribute to be omitted in some cases. Therefore, this variable
+ * defaults to an empty string, but can be set to NULL for the attribute
+ * to be omitted. Usually, neither omission nor an empty string satisfies
+ * accessibility requirements, so it is strongly encouraged for code
+ * building variables for image.html.twig templates to pass a meaningful
+ * value for this variable.
+ * - https://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8
+ * - https://www.w3.org/TR/xhtml1/dtds.html
+ * - http://dev.w3.org/html5/spec/Overview.html#alt
+ * - title: The title text is displayed when the image is hovered in some
+ * popular browsers.
+ * - attributes: Associative array of attributes to be placed in the img
+ * tag.
+ * - srcset: Array of multiple URIs and sizes/multipliers.
+ * - sizes: The sizes attribute for viewport-based selection of images.
+ * phpcs:ignore
+ * - http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:viewport-based-selection-2
+ */
+ public function preprocessImage(array &$variables): void {
+ if (!empty($variables['uri'])) {
+ $variables['attributes']['src'] = $this->fileUrlGenerator->generateString($variables['uri']);
+ }
+ // Generate a srcset attribute conforming to the spec at
+ // https://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-srcset
+ if (!empty($variables['srcset'])) {
+ $srcset = [];
+ foreach ($variables['srcset'] as $src) {
+ // URI is mandatory.
+ $source = $this->fileUrlGenerator->generateString($src['uri']);
+ if (isset($src['width']) && !empty($src['width'])) {
+ $source .= ' ' . $src['width'];
+ }
+ elseif (isset($src['multiplier']) && !empty($src['multiplier'])) {
+ $source .= ' ' . $src['multiplier'];
+ }
+ $srcset[] = $source;
+ }
+ $variables['attributes']['srcset'] = implode(', ', $srcset);
+ }
+
+ foreach (['width', 'height', 'alt', 'title', 'sizes'] as $key) {
+ if (isset($variables[$key])) {
+ // If the property has already been defined in the attributes,
+ // do not override, including NULL.
+ if (AttributeHelper::attributeExists($key, $variables['attributes'])) {
+ continue;
+ }
+ $variables['attributes'][$key] = $variables[$key];
+ }
+ }
+
+ // Without dimensions specified, layout shifts can occur,
+ // which are more noticeable on pages that take some time to load.
+ // As a result, only mark images as lazy load that have dimensions.
+ if (isset($variables['width'], $variables['height']) && !isset($variables['attributes']['loading'])) {
+ $variables['attributes']['loading'] = 'lazy';
+ }
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Theme/ThemeCommonElements.php b/core/lib/Drupal/Core/Theme/ThemeCommonElements.php
index 0d5e7c6638a5..50755c302a70 100644
--- a/core/lib/Drupal/Core/Theme/ThemeCommonElements.php
+++ b/core/lib/Drupal/Core/Theme/ThemeCommonElements.php
@@ -4,8 +4,10 @@ declare(strict_types=1);
namespace Drupal\Core\Theme;
+use Drupal\Core\Breadcrumb\BreadcrumbPreprocess;
use Drupal\Core\Datetime\DatePreprocess;
use Drupal\Core\Field\FieldPreprocess;
+use Drupal\Core\Menu\MenuPreprocess;
use Drupal\Core\Pager\PagerPreprocess;
/**
@@ -36,6 +38,7 @@ class ThemeCommonElements {
],
'region' => [
'render element' => 'elements',
+ 'initial preprocess' => ThemePreprocess::class . ':preprocessRegion',
],
'time' => [
'variables' => [
@@ -100,11 +103,13 @@ class ThemeCommonElements {
'srcset' => [],
'style_name' => NULL,
],
+ 'initial preprocess' => ImagePreprocess::class . ':preprocessImage',
],
'breadcrumb' => [
'variables' => [
'links' => [],
],
+ 'initial preprocess' => BreadcrumbPreprocess::class . ':preprocessBreadcrumb',
],
'table' => [
'variables' => [
@@ -118,11 +123,13 @@ class ThemeCommonElements {
'responsive' => TRUE,
'empty' => '',
],
+ 'initial preprocess' => ThemePreprocess::class . ':preprocessTable',
],
'tablesort_indicator' => [
'variables' => [
'style' => NULL,
],
+ 'initial preprocess' => ThemePreprocess::class . ':preprocessTablesortIndicator',
],
'mark' => [
'variables' => [
@@ -139,6 +146,7 @@ class ThemeCommonElements {
'empty' => NULL,
'context' => [],
],
+ 'initial preprocess' => ThemePreprocess::class . ':preprocessItemList',
],
'feed_icon' => [
'variables' => [
@@ -157,12 +165,13 @@ class ThemeCommonElements {
'indentation' => [
'variables' => ['size' => 1],
],
- // From theme.maintenance.inc.
'maintenance_page' => [
'render element' => 'page',
+ 'initial preprocess' => ThemePreprocess::class . ':preprocessMaintenancePage',
],
'install_page' => [
'render element' => 'page',
+ 'initial preprocess' => ThemePreprocess::class . ':preprocessInstallPage',
],
'maintenance_task_list' => [
'variables' => [
@@ -170,6 +179,7 @@ class ThemeCommonElements {
'active' => NULL,
'variant' => NULL,
],
+ 'initial preprocess' => ThemePreprocess::class . ':preprocessMaintenanceTaskList',
],
'authorize_report' => [
'variables' => [
@@ -193,9 +203,11 @@ class ThemeCommonElements {
],
'menu_local_task' => [
'render element' => 'element',
+ 'initial preprocess' => MenuPreprocess::class . ':preprocessMenuLocalTask',
],
'menu_local_action' => [
'render element' => 'element',
+ 'initial preprocess' => MenuPreprocess::class . ':preprocessMenuLocalAction',
],
'menu_local_tasks' => [
'variables' => [
diff --git a/core/lib/Drupal/Core/Theme/ThemePreprocess.php b/core/lib/Drupal/Core/Theme/ThemePreprocess.php
index 2a93d5b06c91..f712e3d9ff98 100644
--- a/core/lib/Drupal/Core/Theme/ThemePreprocess.php
+++ b/core/lib/Drupal/Core/Theme/ThemePreprocess.php
@@ -5,15 +5,18 @@ namespace Drupal\Core\Theme;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Installer\InstallerKernel;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Path\PathMatcherInterface;
+use Drupal\Core\Render\Element;
use Drupal\Core\Render\Markup;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
+use Drupal\Core\Utility\TableSort;
/**
* Preprocess for common/core theme templates.
@@ -356,4 +359,445 @@ class ThemePreprocess {
}
}
+ /**
+ * Prepares variables for maintenance page templates.
+ *
+ * Default template: maintenance-page.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - content - An array of page content.
+ *
+ * @see system_page_attachments()
+ */
+ public function preprocessMaintenancePage(array &$variables): void {
+ // @todo Rename the templates to page--maintenance + page--install.
+ $this->preprocessPage($variables);
+
+ // @see system_page_attachments()
+ $variables['#attached']['library'][] = 'system/maintenance';
+
+ // Maintenance page and install page need branding info in variables because
+ // there is no blocks.
+ $site_config = $this->configFactory->get('system.site');
+ $variables['logo'] = theme_get_setting('logo.url');
+ $variables['site_name'] = $site_config->get('name');
+ $variables['site_slogan'] = $site_config->get('slogan');
+
+ // Maintenance page and install page need page title in variable because
+ // there are no blocks.
+ $variables['title'] = $variables['page']['#title'];
+ }
+
+ /**
+ * Prepares variables for install page templates.
+ *
+ * Default template: install-page.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - content - An array of page content.
+ *
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage()
+ */
+ public function preprocessInstallPage(array &$variables): void {
+ $installer_active_task = NULL;
+ if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE === 'install' && InstallerKernel::installationAttempted()) {
+ $installer_active_task = $GLOBALS['install_state']['active_task'];
+ }
+
+ $this->preprocessMaintenancePage($variables);
+
+ // Override the site name that is displayed on the page, since Drupal is
+ // still in the process of being installed.
+ $distribution_name = drupal_install_profile_distribution_name();
+ $variables['site_name'] = $distribution_name;
+ $variables['site_version'] = $installer_active_task ? drupal_install_profile_distribution_version() : '';
+ }
+
+ /**
+ * Prepares variables for region templates.
+ *
+ * Default template: region.html.twig.
+ *
+ * Prepares the values passed to the theme_region function to be passed into a
+ * pluggable template engine. Uses the region name to generate a template file
+ * suggestions.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - elements: An associative array containing properties of the region.
+ */
+ public function preprocessRegion(array &$variables): void {
+ // Create the $content variable that templates expect.
+ $variables['content'] = $variables['elements']['#children'];
+ $variables['region'] = $variables['elements']['#region'];
+ }
+
+ /**
+ * Prepares variables for table templates.
+ *
+ * Default template: table.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - header: An array containing the table headers. Each element of the
+ * array can be either a localized string or an associative array with the
+ * following keys:
+ * - data: The localized title of the table column, as a string or render
+ * array.
+ * - field: The database field represented in the table column (required
+ * if user is to be able to sort on this column).
+ * - sort: A default sort order for this column ("asc" or "desc"). Only
+ * one column should be given a default sort order because table sorting
+ * only applies to one column at a time.
+ * - initial_click_sort: Set the initial sort of the column when clicked.
+ * Defaults to "asc".
+ * - class: An array of values for the 'class' attribute. In particular,
+ * the least important columns that can be hidden on narrow and medium
+ * width screens should have a 'priority-low' class, referenced with the
+ * RESPONSIVE_PRIORITY_LOW constant. Columns that should be shown on
+ * medium+ wide screens should be marked up with a class of
+ * 'priority-medium', referenced by with the RESPONSIVE_PRIORITY_MEDIUM
+ * constant. Themes may hide columns with one of these two classes on
+ * narrow viewports to save horizontal space.
+ * - Any HTML attributes, such as "colspan", to apply to the column header
+ * cell.
+ * - rows: An array of table rows. Every row is an array of cells, or an
+ * associative array with the following keys:
+ * - data: An array of cells.
+ * - Any HTML attributes, such as "class", to apply to the table row.
+ * - no_striping: A Boolean indicating that the row should receive no
+ * 'even / odd' styling. Defaults to FALSE.
+ * Each cell can be either a string or an associative array with the
+ * following keys:
+ * - data: The string or render array to display in the table cell.
+ * - header: Indicates this cell is a header.
+ * - Any HTML attributes, such as "colspan", to apply to the table cell.
+ * Here's an example for $rows:
+ * @code
+ * $rows = [
+ * // Simple row
+ * [
+ * 'Cell 1', 'Cell 2', 'Cell 3'
+ * ],
+ * // Row with attributes on the row and some of its cells.
+ * [
+ * 'data' => ['Cell 1', ['data' => 'Cell 2', 'colspan' => 2]], 'class' => ['funky']
+ * ],
+ * ];
+ * @endcode
+ * - footer: An array of table rows which will be printed within a <tfoot>
+ * tag, in the same format as the rows element (see above).
+ * - attributes: An array of HTML attributes to apply to the table tag.
+ * - caption: A localized string to use for the <caption> tag.
+ * - colgroups: An array of column groups. Each element of the array can be
+ * either:
+ * - An array of columns, each of which is an associative array of HTML
+ * attributes applied to the <col> element.
+ * - An array of attributes applied to the <colgroup> element, which must
+ * include a "data" attribute. To add attributes to <col> elements,
+ * set the "data" attribute with an array of columns, each of which is
+ * an associative array of HTML attributes.
+ * Here's an example for $colgroup:
+ * @code
+ * $colgroup = [
+ * // <colgroup> with one <col> element.
+ * [
+ * [
+ * 'class' => ['funky'], // Attribute for the <col> element.
+ * ],
+ * ],
+ * // <colgroup> with attributes and inner <col> elements.
+ * [
+ * 'data' => [
+ * [
+ * 'class' => ['funky'], // Attribute for the <col> element.
+ * ],
+ * ],
+ * 'class' => ['jazzy'], // Attribute for the <colgroup> element.
+ * ],
+ * ];
+ * @endcode
+ * These optional tags are used to group and set properties on columns
+ * within a table. For example, one may easily group three columns and
+ * apply same background style to all.
+ * - sticky: Use a "sticky" table header.
+ * - empty: The message to display in an extra row if table does not have
+ * any rows.
+ */
+ public function preprocessTable(array &$variables): void {
+ // Format the table columns:
+ if (!empty($variables['colgroups'])) {
+ foreach ($variables['colgroups'] as &$colgroup) {
+ // Check if we're dealing with a simple or complex column
+ if (isset($colgroup['data'])) {
+ $cols = $colgroup['data'];
+ unset($colgroup['data']);
+ $colgroup_attributes = $colgroup;
+ }
+ else {
+ $cols = $colgroup;
+ $colgroup_attributes = [];
+ }
+ $colgroup = [];
+ $colgroup['attributes'] = new Attribute($colgroup_attributes);
+ $colgroup['cols'] = [];
+
+ // Build columns.
+ if (is_array($cols) && !empty($cols)) {
+ foreach ($cols as $col_key => $col) {
+ $colgroup['cols'][$col_key]['attributes'] = new Attribute($col);
+ }
+ }
+ }
+ }
+
+ // Build an associative array of responsive classes keyed by column.
+ $responsive_classes = [];
+
+ // Format the table header:
+ $ts = [];
+ $header_columns = 0;
+ if (!empty($variables['header'])) {
+ $ts = TableSort::getContextFromRequest($variables['header'], \Drupal::request());
+
+ // Use a separate index with responsive classes as headers
+ // may be associative.
+ $responsive_index = -1;
+ foreach ($variables['header'] as $col_key => $cell) {
+ // Increase the responsive index.
+ $responsive_index++;
+
+ if (!is_array($cell)) {
+ $header_columns++;
+ $cell_content = $cell;
+ $cell_attributes = new Attribute();
+ $is_header = TRUE;
+ }
+ else {
+ if (isset($cell['colspan'])) {
+ $header_columns += $cell['colspan'];
+ }
+ else {
+ $header_columns++;
+ }
+ $cell_content = '';
+ if (isset($cell['data'])) {
+ $cell_content = $cell['data'];
+ unset($cell['data']);
+ }
+ // Flag the cell as a header or not and remove the flag.
+ $is_header = $cell['header'] ?? TRUE;
+ unset($cell['header']);
+
+ // Track responsive classes for each column as needed. Only the header
+ // cells for a column are marked up with the responsive classes by a
+ // module developer or themer. The responsive classes on the header
+ // cells must be transferred to the content cells.
+ if (!empty($cell['class']) && is_array($cell['class'])) {
+ if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) {
+ $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM;
+ }
+ elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) {
+ $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW;
+ }
+ }
+
+ TableSort::header($cell_content, $cell, $variables['header'], $ts);
+
+ // TableSort::header() removes the 'sort', 'initial_click_sort' and
+ // 'field' keys.
+ $cell_attributes = new Attribute($cell);
+ }
+ $variables['header'][$col_key] = [];
+ $variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td';
+ $variables['header'][$col_key]['attributes'] = $cell_attributes;
+ $variables['header'][$col_key]['content'] = $cell_content;
+ }
+ }
+ $variables['header_columns'] = $header_columns;
+
+ // Rows and footer have the same structure.
+ $sections = ['rows' , 'footer'];
+ foreach ($sections as $section) {
+ if (!empty($variables[$section])) {
+ foreach ($variables[$section] as $row_key => $row) {
+ $cells = $row;
+ $row_attributes = [];
+
+ // Check if we're dealing with a simple or complex row
+ if (isset($row['data'])) {
+ $cells = $row['data'];
+ $variables['no_striping'] = $row['no_striping'] ?? FALSE;
+
+ // Set the attributes array and exclude 'data' and 'no_striping'.
+ $row_attributes = $row;
+ unset($row_attributes['data']);
+ unset($row_attributes['no_striping']);
+ }
+
+ // Build row.
+ $variables[$section][$row_key] = [];
+ $variables[$section][$row_key]['attributes'] = new Attribute($row_attributes);
+ $variables[$section][$row_key]['cells'] = [];
+ if (!empty($cells)) {
+ // Reset the responsive index.
+ $responsive_index = -1;
+ foreach ($cells as $col_key => $cell) {
+ // Increase the responsive index.
+ $responsive_index++;
+
+ if (!is_array($cell)) {
+ $cell_content = $cell;
+ $cell_attributes = [];
+ $is_header = FALSE;
+ }
+ else {
+ $cell_content = '';
+ if (isset($cell['data'])) {
+ $cell_content = $cell['data'];
+ unset($cell['data']);
+ }
+
+ // Flag the cell as a header or not and remove the flag.
+ $is_header = !empty($cell['header']);
+ unset($cell['header']);
+
+ $cell_attributes = $cell;
+ }
+ // Active table sort information.
+ if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) {
+ $variables[$section][$row_key]['cells'][$col_key]['active_table_sort'] = TRUE;
+ }
+ // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM
+ // class from header to cell as needed.
+ if (isset($responsive_classes[$responsive_index])) {
+ $cell_attributes['class'][] = $responsive_classes[$responsive_index];
+ }
+ $variables[$section][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td';
+ $variables[$section][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes);
+ $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content;
+ }
+ }
+ }
+ }
+ }
+ if (empty($variables['no_striping'])) {
+ $variables['attributes']['data-striping'] = 1;
+ }
+ }
+
+ /**
+ * Prepares variables for tablesort indicators.
+ *
+ * Default template: tablesort-indicator.html.twig.
+ */
+ public function preprocessTablesortIndicator(array &$variables): void {
+ $variables['#attached']['library'][] = 'core/drupal.tablesort';
+ }
+
+ /**
+ * Prepares variables for item list templates.
+ *
+ * Default template: item-list.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - items: An array of items to be displayed in the list. Each item can be
+ * either a string or a render array. If #type, #theme, or #markup
+ * properties are not specified for child render arrays, they will be
+ * inherited from the parent list, allowing callers to specify larger
+ * nested lists without having to explicitly specify and repeat the
+ * render properties for all nested child lists.
+ * - title: A title to be prepended to the list.
+ * - list_type: The type of list to return (e.g. "ul", "ol").
+ * - wrapper_attributes: HTML attributes to be applied to the list wrapper.
+ *
+ * @see https://www.drupal.org/node/1842756
+ */
+ public function preprocessItemList(array &$variables): void {
+ $variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']);
+ $variables['#attached']['library'][] = 'core/drupal.item-list';
+ foreach ($variables['items'] as &$item) {
+ $attributes = [];
+ // If the item value is an array, then it is a render array.
+ if (is_array($item)) {
+ // List items support attributes via the '#wrapper_attributes' property.
+ if (isset($item['#wrapper_attributes'])) {
+ $attributes = $item['#wrapper_attributes'];
+ }
+ // Determine whether there are any child elements in the item that are
+ // not fully-specified render arrays. If there are any, then the child
+ // elements present nested lists and we automatically inherit the render
+ // array properties of the current list to them.
+ foreach (Element::children($item) as $key) {
+ $child = &$item[$key];
+ // If this child element does not specify how it can be rendered, then
+ // we need to inherit the render properties of the current list.
+ if (!isset($child['#type']) && !isset($child['#theme']) && !isset($child['#markup'])) {
+ // Since item-list.html.twig supports both strings and render arrays
+ // as items, the items of the nested list may have been specified as
+ // the child elements of the nested list, instead of #items. For
+ // convenience, we automatically move them into #items.
+ if (!isset($child['#items'])) {
+ // This is the same condition as in
+ // \Drupal\Core\Render\Element::children(), which cannot be used
+ // here, since it triggers an error on string values.
+ foreach ($child as $child_key => $child_value) {
+ if (is_int($child_key) || $child_key === '' || $child_key[0] !== '#') {
+ $child['#items'][$child_key] = $child_value;
+ unset($child[$child_key]);
+ }
+ }
+ }
+ // Lastly, inherit the original theme variables of the current list.
+ $child['#theme'] = $variables['theme_hook_original'];
+ $child['#list_type'] = $variables['list_type'];
+ }
+ }
+ }
+
+ // Set the item's value and attributes for the template.
+ $item = [
+ 'value' => $item,
+ 'attributes' => new Attribute($attributes),
+ ];
+ }
+ }
+
+ /**
+ * Prepares variables for maintenance task list templates.
+ *
+ * Default template: maintenance-task-list.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - items: An associative array of maintenance tasks.
+ * It's the caller's responsibility to ensure this array's items contain
+ * no dangerous HTML such as <script> tags.
+ * - active: The key for the currently active maintenance task.
+ */
+ public function preprocessMaintenanceTaskList(array &$variables): void {
+ $items = $variables['items'];
+ $active = $variables['active'];
+
+ $done = isset($items[$active]) || $active == NULL;
+ foreach ($items as $k => $item) {
+ $variables['tasks'][$k]['item'] = $item;
+ $variables['tasks'][$k]['attributes'] = new Attribute();
+ if ($active == $k) {
+ $variables['tasks'][$k]['attributes']->addClass('is-active');
+ $variables['tasks'][$k]['status'] = $this->t('active');
+ $done = FALSE;
+ }
+ else {
+ if ($done) {
+ $variables['tasks'][$k]['attributes']->addClass('done');
+ $variables['tasks'][$k]['status'] = $this->t('done');
+ }
+ }
+ }
+ }
+
}
diff --git a/core/modules/block_content/src/BlockContentTypeInterface.php b/core/modules/block_content/src/BlockContentTypeInterface.php
index aaf7957b8952..9c12709b2a55 100644
--- a/core/modules/block_content/src/BlockContentTypeInterface.php
+++ b/core/modules/block_content/src/BlockContentTypeInterface.php
@@ -3,19 +3,12 @@
namespace Drupal\block_content;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\EntityDescriptionInterface;
use Drupal\Core\Entity\RevisionableEntityBundleInterface;
/**
* Provides an interface defining a block type entity.
*/
-interface BlockContentTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface {
-
- /**
- * Returns the description of the block type.
- *
- * @return string
- * The description of the type of this block.
- */
- public function getDescription();
+interface BlockContentTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface, EntityDescriptionInterface {
}
diff --git a/core/modules/block_content/src/Entity/BlockContentType.php b/core/modules/block_content/src/Entity/BlockContentType.php
index ecbc6c3866d2..fa6fe3955038 100644
--- a/core/modules/block_content/src/Entity/BlockContentType.php
+++ b/core/modules/block_content/src/Entity/BlockContentType.php
@@ -100,6 +100,13 @@ class BlockContentType extends ConfigEntityBundleBase implements BlockContentTyp
/**
* {@inheritdoc}
*/
+ public function setDescription($description): static {
+ return $this->set('description', $description);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function shouldCreateNewRevision() {
return $this->revision;
}
diff --git a/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php b/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php
index fba8362cd2cd..2ec82c2195b8 100644
--- a/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php
+++ b/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php
@@ -58,14 +58,26 @@ class BlockContentTypeTest extends BlockContentTestBase {
}
/**
- * Tests the order of the block content types on the add page.
+ * Tests the block types on the block/add page.
*/
- public function testBlockContentAddPageOrder(): void {
- $this->createBlockContentType(['id' => 'bundle_1', 'label' => 'Bundle 1']);
- $this->createBlockContentType(['id' => 'bundle_2', 'label' => 'Aaa Bundle 2']);
+ public function testBlockContentAddPage(): void {
+ $this->createBlockContentType([
+ 'id' => 'bundle_1',
+ 'label' => 'Bundle 1',
+ 'description' => 'Bundle 1 description',
+ ]);
+ $this->createBlockContentType([
+ 'id' => 'bundle_2',
+ 'label' => 'Aaa Bundle 2',
+ 'description' => 'Bundle 2 description',
+ ]);
$this->drupalLogin($this->adminUser);
$this->drupalGet('block/add');
+ // Ensure bundles are ordered by their label, not id.
$this->assertSession()->pageTextMatches('/Aaa Bundle 2(.*)Bundle 1/');
+ // Block type descriptions should display.
+ $this->assertSession()->pageTextContains('Bundle 1 description');
+ $this->assertSession()->pageTextContains('Bundle 2 description');
}
/**
diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentTest.php
index bb0186431e9a..fe07b3d5652e 100644
--- a/core/modules/block_content/tests/src/Kernel/BlockContentTest.php
+++ b/core/modules/block_content/tests/src/Kernel/BlockContentTest.php
@@ -38,6 +38,19 @@ class BlockContentTest extends KernelTestBase {
}
/**
+ * Tests BlockContentType functionality.
+ */
+ public function testBlockContentType(): void {
+ $type = BlockContentType::create([
+ 'id' => 'foo',
+ 'label' => 'Foo',
+ ]);
+ $this->assertSame('', $type->getDescription());
+ $type->setDescription('Test description');
+ $this->assertSame('Test description', $type->getDescription());
+ }
+
+ /**
* Tests the editing links for BlockContentBlock.
*/
public function testOperationLinks(): void {
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index 8904dd925e57..67c049fa5057 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -5,6 +5,7 @@
*/
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Theme\ThemePreprocess;
use Drupal\field_ui\FieldUI;
/**
@@ -18,7 +19,7 @@ use Drupal\field_ui\FieldUI;
* rendered as a table.
*/
function template_preprocess_field_ui_table(&$variables): void {
- template_preprocess_table($variables);
+ \Drupal::service(ThemePreprocess::class)->preprocessTable($variables);
}
/**
diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
index 09e7c99f898c..f36dcea82063 100644
--- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php
+++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
@@ -293,6 +293,38 @@ class MenuUiHooks {
}
/**
+ * Implements hook_ENTITY_TYPE_delete().
+ */
+ #[Hook('menu_delete')]
+ public function menuDelete(EntityInterface $entity): void {
+ if (!$this->entityTypeManager->hasDefinition('node_type')) {
+ return;
+ }
+
+ // Remove the menu from content type third party settings.
+ $menu_id = $entity->id();
+ $parent_prefix = $menu_id . ':';
+ $storage = $this->entityTypeManager->getStorage('node_type');
+ foreach ($storage->loadMultiple() as $content_type) {
+ $third_party_settings = $original_third_party_settings = $content_type->getThirdPartySettings('menu_ui');
+ if (isset($third_party_settings['available_menus']) && in_array($menu_id, $third_party_settings['available_menus'])) {
+ $key = array_search($menu_id, $third_party_settings['available_menus']);
+ if ($key !== FALSE) {
+ unset($third_party_settings['available_menus'][$key]);
+ }
+ $content_type->setThirdPartySetting('menu_ui', 'available_menus', $third_party_settings['available_menus']);
+ }
+ if (isset($third_party_settings['parent']) && substr($third_party_settings['parent'], 0, strlen($parent_prefix)) == $parent_prefix) {
+ $third_party_settings['parent'] = '';
+ $content_type->setThirdPartySetting('menu_ui', 'parent', $third_party_settings['parent']);
+ }
+ if ($third_party_settings != $original_third_party_settings) {
+ $content_type->save();
+ }
+ }
+ }
+
+ /**
* Implements hook_system_breadcrumb_alter().
*/
#[Hook('system_breadcrumb_alter')]
diff --git a/core/modules/menu_ui/tests/src/Kernel/MenuDeleteTest.php b/core/modules/menu_ui/tests/src/Kernel/MenuDeleteTest.php
new file mode 100644
index 000000000000..3bb81874387c
--- /dev/null
+++ b/core/modules/menu_ui/tests/src/Kernel/MenuDeleteTest.php
@@ -0,0 +1,86 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\menu_ui\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\menu_ui\Hook\MenuUiHooks;
+use Drupal\node\Entity\NodeType;
+use Drupal\system\Entity\Menu;
+
+/**
+ * Tests the menu_delete hook.
+ *
+ * @group menu_ui
+ */
+class MenuDeleteTest extends KernelTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $modules = ['node', 'menu_ui', 'system'];
+
+ /**
+ * @covers \Drupal\menu_ui\Hook\MenuUiHooks::menuDelete
+ * @dataProvider providerMenuDelete
+ */
+ public function testMenuDelete($settings, $expected): void {
+ $menu = Menu::create([
+ 'id' => 'mock',
+ 'label' => $this->randomMachineName(16),
+ 'description' => 'Description text',
+ ]);
+ $menu->save();
+ $content_type = NodeType::create([
+ 'status' => TRUE,
+ 'dependencies' => [
+ 'module' => ['menu_ui'],
+ ],
+ 'third_party_settings' => [
+ 'menu_ui' => $settings,
+ ],
+ 'name' => 'Test type',
+ 'type' => 'test_type',
+ ]);
+ $content_type->save();
+ $this->assertEquals($settings['available_menus'], $content_type->getThirdPartySetting('menu_ui', 'available_menus'));
+ $this->assertEquals($settings['parent'], $content_type->getThirdPartySetting('menu_ui', 'parent'));
+
+ $hooks = new MenuUiHooks(\Drupal::entityTypeManager());
+ $hooks->menuDelete($menu);
+
+ $content_type = NodeType::load('test_type');
+ $this->assertEquals($expected['available_menus'], $content_type->getThirdPartySetting('menu_ui', 'available_menus'));
+ $this->assertEquals($expected['parent'], $content_type->getThirdPartySetting('menu_ui', 'parent'));
+ }
+
+ /**
+ * Provides data for testMenuDelete().
+ */
+ public static function providerMenuDelete(): array {
+ return [
+ [
+ ['available_menus' => ['mock'], 'parent' => 'mock:'],
+ ['available_menus' => [], 'parent' => ''],
+ ],
+ [
+ ['available_menus' => ['mock'], 'parent' => 'mock:menu_link_content:e0cd7689-016e-43e4-af8f-7ce82801ab95'],
+ ['available_menus' => [], 'parent' => ''],
+ ],
+ [
+ ['available_menus' => ['main', 'mock'], 'parent' => 'mock:'],
+ ['available_menus' => ['main'], 'parent' => ''],
+ ],
+ [
+ ['available_menus' => ['main'], 'parent' => 'main:'],
+ ['available_menus' => ['main'], 'parent' => 'main:'],
+ ],
+ [
+ ['available_menus' => ['main'], 'parent' => 'main:menu_link_content:e0cd7689-016e-43e4-af8f-7ce82801ab95'],
+ ['available_menus' => ['main'], 'parent' => 'main:menu_link_content:e0cd7689-016e-43e4-af8f-7ce82801ab95'],
+ ],
+ ];
+ }
+
+}
diff --git a/core/modules/migrate/migrate.api.php b/core/modules/migrate/migrate.api.php
index 1e58b0090ff2..5d2af7db180e 100644
--- a/core/modules/migrate/migrate.api.php
+++ b/core/modules/migrate/migrate.api.php
@@ -156,7 +156,7 @@ function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, Migr
if ($migration->id() == 'd6_filter_formats') {
$value = $source->getDatabase()->query('SELECT [value] FROM {variable} WHERE [name] = :name', [':name' => 'my_module_filter_foo_' . $row->getSourceProperty('format')])->fetchField();
if ($value) {
- $row->setSourceProperty('settings:my_module:foo', unserialize($value));
+ $row->setSourceProperty('settings:my_module:foo', unserialize($value, ['allowed_classes' => FALSE]));
}
}
}
@@ -179,7 +179,7 @@ function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, Migr
function hook_migrate_MIGRATION_ID_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
$value = $source->getDatabase()->query('SELECT [value] FROM {variable} WHERE [name] = :name', [':name' => 'my_module_filter_foo_' . $row->getSourceProperty('format')])->fetchField();
if ($value) {
- $row->setSourceProperty('settings:my_module:foo', unserialize($value));
+ $row->setSourceProperty('settings:my_module:foo', unserialize($value, ['allowed_classes' => FALSE]));
}
}
diff --git a/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php b/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php
index 9adf60b46ffe..30cc28562e8d 100644
--- a/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php
+++ b/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php
@@ -65,7 +65,7 @@ class AnnotatedClassDiscoveryAutomatedProviders extends AnnotatedClassDiscovery
if (isset($cached['id'])) {
// Explicitly unserialize this to create a new object
// instance.
- $definitions[$cached['id']] = unserialize($cached['content']);
+ $definitions[$cached['id']] = unserialize($cached['content'], ['allowed_classes' => FALSE]);
}
continue;
}
diff --git a/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php b/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php
index dc70496282f3..a5351c748620 100644
--- a/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php
+++ b/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php
@@ -71,7 +71,8 @@ class ConfigEntity extends SqlBase {
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
- $row->setSourceProperty('data', unserialize($row->getSourceProperty('data')));
+ // @see \Drupal\Core\Config\DatabaseStorage::decode()
+ $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'), ['allowed_classes' => FALSE]));
return parent::prepareRow($row);
}
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php
index 64ac2031a3fc..06d9bbccc974 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php
@@ -173,7 +173,7 @@ abstract class DrupalSqlBase extends SqlBase implements DependentPluginInterface
catch (\Exception) {
$result = FALSE;
}
- return $result !== FALSE ? unserialize($result) : $default;
+ return $result !== FALSE ? unserialize($result, ['allowed_classes' => ['stdClass']]) : $default;
}
/**
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php
index 0b83a099afb2..f99cd69e8983 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php
@@ -129,7 +129,10 @@ class Variable extends DrupalSqlBase {
// Create an ID field so we can record migration in the map table.
// Arbitrarily, use the first variable name.
$values['id'] = reset($this->variables);
- return $values + array_map('unserialize', $this->prepareQuery()->execute()->fetchAllKeyed());
+ return $values + array_map(
+ fn($data) => unserialize($data, ['allowed_classes' => FALSE]),
+ $this->prepareQuery()->execute()->fetchAllKeyed(),
+ );
}
/**
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php
index ee9b6268ebbf..e2bcbddc8d3c 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php
@@ -66,7 +66,7 @@ class VariableMultiRow extends DrupalSqlBase {
*/
public function prepareRow(Row $row) {
if ($value = $row->getSourceProperty('value')) {
- $row->setSourceProperty('value', unserialize($value));
+ $row->setSourceProperty('value', unserialize($value, ['allowed_classes' => FALSE]));
}
return parent::prepareRow($row);
}
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php
index 11323f4bf7a0..f9e6ef61d75c 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php
@@ -78,7 +78,7 @@ class VariableTranslation extends DrupalSqlBase {
foreach ($result as $i18n_variable) {
foreach ($values as $key => $value) {
if ($values[$key]['language'] === $i18n_variable->language) {
- $values[$key][$i18n_variable->name] = unserialize($i18n_variable->value);
+ $values[$key][$i18n_variable->name] = unserialize($i18n_variable->value, ['allowed_classes' => FALSE]);
break;
}
}
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php
index 56121db822ac..82d638fa3d8f 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php
@@ -78,7 +78,7 @@ class VariableTranslation extends DrupalSqlBase {
foreach ($values as $key => $value) {
if ($values[$key]['language'] === $variable_store['realm_key']) {
if ($variable_store['serialized']) {
- $values[$key][$variable_store['name']] = unserialize($variable_store['value']);
+ $values[$key][$variable_store['name']] = unserialize($variable_store['value'], ['allowed_classes' => FALSE]);
break;
}
else {
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php
index b1d409f4017a..1bb5b4f51bf5 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php
@@ -88,7 +88,8 @@ class Config extends DrupalSqlBase {
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
- $row->setSourceProperty('data', unserialize($row->getSourceProperty('data')));
+ // @see \Drupal\Core\Config\DatabaseStorage::decode()
+ $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'), ['allowed_classes' => FALSE]));
return parent::prepareRow($row);
}
diff --git a/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php
index a28dada3e9cc..c6e76787f316 100644
--- a/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php
+++ b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php
@@ -286,7 +286,7 @@ class ReviewForm extends MigrateUpgradeFormBase {
foreach ($migration_state as $source_machine_name => $destination_modules) {
$data = NULL;
if (isset($this->systemData['module'][$source_machine_name]['info'])) {
- $data = unserialize($this->systemData['module'][$source_machine_name]['info']);
+ $data = unserialize($this->systemData['module'][$source_machine_name]['info'], ['allowed_classes' => FALSE]);
}
$source_module_name = $data['name'] ?? $source_machine_name;
// Get the names of all the destination modules.
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php
index 759f59b50a84..a6633fed5e9f 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php
@@ -269,7 +269,7 @@ abstract class MigrateUpgradeTestBase extends BrowserTestBase {
// Convert $source_id into a keyless array so that
// \Drupal\migrate\Plugin\migrate\id_map\Sql::getSourceHash() works as
// expected.
- $source_id_values = array_values(unserialize($source_id));
+ $source_id_values = array_values(unserialize($source_id, ['allowed_classes' => FALSE]));
$row = $id_map->getRowBySource($source_id_values);
$destination = serialize($id_map->currentDestination());
$message = "Migration of $source_id to $destination as part of the {$migration->id()} migration. The source row status is " . $row['source_row_status'];
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 1fb4071ba47d..d68a04fcbc8a 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -125,8 +125,12 @@ function node_get_type_label(NodeInterface $node) {
*
* @return string
* The node type description.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Use $node_type->getDescription() instead.
+ * @see https://www.drupal.org/node/3531945
*/
function node_type_get_description(NodeTypeInterface $node_type) {
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Use $node_type->getDescription() instead. See https://www.drupal.org/node/3531945', E_USER_DEPRECATED);
return $node_type->getDescription();
}
diff --git a/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php b/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php
index 18c87b11956b..4cc4405d4c81 100644
--- a/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php
+++ b/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php
@@ -4,13 +4,15 @@ declare(strict_types=1);
namespace Drupal\Tests\package_manager\Build;
+use PHPUnit\Framework\Attributes\Group;
+
/**
* Tests installing packages in a stage directory.
*
- * @group package_manager
- * @group #slow
* @internal
*/
+#[Group('package_manager')]
+#[Group('#slow')]
class PackageInstallSubmoduleTest extends TemplateProjectTestBase {
/**
diff --git a/core/modules/package_manager/tests/src/Build/PackageInstallTest.php b/core/modules/package_manager/tests/src/Build/PackageInstallTest.php
index 362343eaa91a..283c6aefa2cf 100644
--- a/core/modules/package_manager/tests/src/Build/PackageInstallTest.php
+++ b/core/modules/package_manager/tests/src/Build/PackageInstallTest.php
@@ -4,13 +4,15 @@ declare(strict_types=1);
namespace Drupal\Tests\package_manager\Build;
+use PHPUnit\Framework\Attributes\Group;
+
/**
* Tests installing packages in a stage directory.
*
- * @group package_manager
- * @group #slow
* @internal
*/
+#[Group('package_manager')]
+#[Group('#slow')]
class PackageInstallTest extends TemplateProjectTestBase {
/**
diff --git a/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php b/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php
index 2b9ef4aa894e..da6fc5eae320 100644
--- a/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php
+++ b/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php
@@ -5,14 +5,15 @@ declare(strict_types=1);
namespace Drupal\Tests\package_manager\Build;
use Drupal\package_manager_test_api\ControllerSandboxManager;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests updating packages in a stage directory.
*
- * @group package_manager
- * @group #slow
* @internal
*/
+#[Group('package_manager')]
+#[Group('#slow')]
class PackageUpdateTest extends TemplateProjectTestBase {
/**
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/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/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/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/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/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 483a1d01717e..000000000000
--- a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-/**
- * @file
- * Experimental Test Requirements module to test hook_requirements().
- */
-
-declare(strict_types=1);
-
-use Drupal\Core\Extension\Requirement\RequirementSeverity;
-
-/**
- * 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' => RequirementSeverity::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/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/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/theme_test/src/EventSubscriber/ThemeTestSubscriber.php b/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php
index 6c6f67d77eb8..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
@@ -79,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/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/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/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/user/src/Hook/UserRequirements.php b/core/modules/user/src/Hook/UserRequirements.php
index f317ced58bc4..46155e55e3cb 100644
--- a/core/modules/user/src/Hook/UserRequirements.php
+++ b/core/modules/user/src/Hook/UserRequirements.php
@@ -49,6 +49,7 @@ class UserRequirements {
$query->addExpression('LOWER(mail)', 'lower_mail');
$query->isNotNull('mail');
$query->groupBy('lower_mail');
+ $query->groupBy('langcode');
$query->having('COUNT(uid) > :matches', [':matches' => 1]);
$conflicts = $query->countQuery()->execute()->fetchField();
diff --git a/core/modules/user/tests/src/Kernel/UserRequirementsTest.php b/core/modules/user/tests/src/Kernel/UserRequirementsTest.php
index 146ab9c8b904..746370a15d61 100644
--- a/core/modules/user/tests/src/Kernel/UserRequirementsTest.php
+++ b/core/modules/user/tests/src/Kernel/UserRequirementsTest.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\Tests\user\Kernel;
use Drupal\KernelTests\KernelTestBase;
+use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
@@ -70,4 +71,21 @@ class UserRequirementsTest extends KernelTestBase {
$this->assertArrayNotHasKey('conflicting emails', $output);
}
+ /**
+ * Tests that the requirements check does not flag user translations.
+ */
+ public function testTranslatedUserEmail(): void {
+ \Drupal::service('module_installer')->install(['language']);
+ ConfigurableLanguage::createFromLangcode('is')->save();
+
+ $output = $this->moduleHandler->invoke('user', 'runtime_requirements');
+ $this->assertArrayNotHasKey('conflicting emails', $output);
+
+ $user = $this->createUser([], 'User A', FALSE, ['mail' => 'unique@example.com']);
+ $user->addTranslation('is')->save();
+
+ $output = $this->moduleHandler->invoke('user', 'runtime_requirements');
+ $this->assertArrayNotHasKey('conflicting emails', $output);
+ }
+
}
diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig
index e9928fd77660..4940f047477e 100644
--- a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig
+++ b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig
@@ -17,7 +17,7 @@
* results, the following data is set:
* - plugin: The search plugin ID, for example "node_search".
*
- * @see template_preprocess_item_list()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList()
*/
#}
{%
diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig
index 20541b0b7e66..6e7b8e317b1d 100644
--- a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig
+++ b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/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()
*/
#}
{% if context.list_style %}
diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig
index cdfe0bff7e73..d7e6459bd4a4 100644
--- a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig
+++ b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/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()
*/
#}
<table{{ attributes }}>
diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig
index 31f782bb60a8..90d955c180a6 100644
--- a/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig
+++ b/core/profiles/demo_umami/themes/umami/templates/classy/field/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()
*/
#}
{%
diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig
index 3f6e8ddf05c9..e2ae382b56fa 100644
--- a/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig
+++ b/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig
@@ -32,7 +32,8 @@
</div>
{% endif %}
{%- if description -%}
- <div class="details-description">{{ description }}</div>
+ {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %}
+ <div {{ description_attributes.addClass(['details-description']) }}>{{ description }}</div>
{%- endif -%}
{%- if children -%}
{{ children }}
diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig
index 7463b0238ca3..edd2783619b8 100644
--- a/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig
+++ b/core/profiles/demo_umami/themes/umami/templates/classy/layout/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()
*/
#}
<div class="layout-container">
diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig
index 95e71cec37e4..a4e8cc0af72a 100644
--- a/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig
+++ b/core/profiles/demo_umami/themes/umami/templates/classy/layout/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()
*/
#}
{%
diff --git a/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig b/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig
index b1d11d5458c5..99122f1668e0 100644
--- a/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig
+++ b/core/profiles/demo_umami/themes/umami/templates/components/navigation/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()
*/
#}
<li{{ attributes.addClass('tabs__tab', is_active ? 'is-active') }}>{{ link }}</li>
diff --git a/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig b/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig
index a0b90e94cb6c..d55f70c54ae9 100644
--- a/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig
+++ b/core/profiles/demo_umami/themes/umami/templates/layout/region--header.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()
*/
#}
{%
diff --git a/core/scripts/dev/commit-code-check.sh b/core/scripts/dev/commit-code-check.sh
index 541692236263..42d2ac670ad8 100755
--- a/core/scripts/dev/commit-code-check.sh
+++ b/core/scripts/dev/commit-code-check.sh
@@ -25,6 +25,7 @@ contains_element() {
return 1
}
+MEMORY_UNLIMITED=0
CACHED=0
DRUPALCI=0
BRANCH=""
@@ -58,12 +59,22 @@ while test $# -gt 0; do
DRUPALCI=1
shift
;;
+ --memory-unlimited)
+ MEMORY_UNLIMITED=1
+ shift
+ ;;
*)
break
;;
esac
done
+memory_limit=""
+
+if [[ "$MEMORY_UNLIMITED" == "1" ]]; then
+ memory_limit="--memory-limit=-1"
+fi
+
# Set up variables to make colored output simple. Color output is disabled on
# DrupalCI because it is breaks reporting.
# @todo https://www.drupal.org/project/drupalci_testbot/issues/3181869
@@ -238,11 +249,11 @@ printf "\n"
# APCu is disabled to ensure that the composer classmap is not corrupted.
if [[ $PHPSTAN_DIST_FILE_CHANGED == "1" ]] || [[ "$DRUPALCI" == "1" ]]; then
printf "\nRunning PHPStan on *all* files.\n"
- php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan.neon.dist"
+ php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan.neon.dist" $memory_limit
else
# Only run PHPStan on changed files locally.
printf "\nRunning PHPStan on changed files.\n"
- php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan-partial.neon" $ABS_FILES
+ php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan-partial.neon" $ABS_FILES $memory_limit
fi
if [ "$?" -ne "0" ]; then
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php
index d1c07c20124e..1211cfb351b7 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests Ajax callbacks on FAPI elements.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class AjaxCallbacksTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php
index b2ae9386bd0a..1eff2c19bbee 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php
@@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the usage of form caching for AJAX forms.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class AjaxFormCacheTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php
index c9a4b3ef2722..347e8ac86f1a 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the Ajax image buttons work with key press events.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class AjaxFormImageButtonTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php
index a80a627a89cc..c11430fdebff 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Performs tests on AJAX forms in cached pages.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class AjaxFormPageCacheTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php
index a8282b2b6cfe..6618ec6acede 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests that form elements in groups work correctly with AJAX.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class AjaxInGroupTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php
index 180381e45c1b..3f4543996f1f 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php
@@ -8,12 +8,12 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
use Drupal\Tests\file\Functional\FileFieldCreationTrait;
use Drupal\Tests\TestFileCreationTrait;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests maintenance message during an AJAX call.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class AjaxMaintenanceModeTest extends WebDriverTestBase {
use FieldUiTestTrait;
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php
index 03bd477123c1..284a3e35fa9b 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php
@@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\Component\Utility\UrlHelper;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests AJAX responses.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class AjaxTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php
index cc81d142d453..877f255a6591 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Performs tests on AJAX framework commands.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class CommandsTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php
index be434633e5ef..beab4c6a242c 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php
@@ -7,14 +7,13 @@ namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\ajax_test\Controller\AjaxTestController;
use Drupal\Core\Ajax\OpenModalDialogWithUrl;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
// cspell:ignore testdialog
-
/**
* Performs tests on opening and manipulating dialogs via AJAX commands.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class DialogTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php
index 60fd1f40b95f..5040430abae4 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Various tests of AJAX behavior.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class ElementValidationTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php
index 7a9b91c52b90..2a27a09a987a 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests setting focus via AJAX command.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class FocusFirstCommandTest extends WebDriverTestBase {
/**
* {@inheritdoc}
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php
index 2ee6b8ddb2ce..68a9683a4906 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php
@@ -5,12 +5,13 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests that form values are properly delivered to AJAX callbacks.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class FormValuesTest extends WebDriverTestBase {
/**
@@ -33,9 +34,8 @@ class FormValuesTest extends WebDriverTestBase {
/**
* Submits forms with select and checkbox elements via Ajax.
- *
- * @dataProvider formModeProvider
*/
+ #[DataProvider('formModeProvider')]
public function testSimpleAjaxFormValue($form_mode): void {
$this->drupalGet('ajax_forms_test_get_form');
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php
index 69b49916163e..f18bf953dd82 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php
@@ -5,13 +5,13 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\ExpectationFailedException;
/**
* Tests adding messages via AJAX command.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class MessageCommandTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php
index 6a0026976a90..40ed7ac76704 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php
@@ -8,12 +8,12 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests AJAX-enabled forms when multiple instances of the form are on a page.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class MultiFormTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php
index 6b2a17d4c6eb..92905a0c08ba 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php
@@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\hold_test\HoldTestHelper;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the throbber.
- *
- * @group Ajax
*/
+#[Group('Ajax')]
class ThrobberTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php b/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php
index 0a61c5f03cbe..adade6bc4208 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php
@@ -4,12 +4,14 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests;
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\IgnoreDeprecations;
+
/**
* Tests that unnecessary or untracked XHRs will cause a test failure.
- *
- * @group javascript
- * @group legacy
*/
+#[Group('javascript')]
+#[IgnoreDeprecations]
class AjaxWaitTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php b/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php
index 2f11f7525eed..74c88ac48eed 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests;
use PHPUnit\Framework\AssertionFailedError;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests if we can execute JavaScript in the browser.
- *
- * @group javascript
*/
+#[Group('javascript')]
class BrowserWithJavascriptTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php
index 5b77de188aa2..95847a5d2290 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Components;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the correct rendering of components.
- *
- * @group sdc
*/
+#[Group('sdc')]
class ComponentRenderTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php
index f16b300441de..3684ec3c4976 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Core;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Test race condition for CSRF tokens for simultaneous requests.
- *
- * @group Session
*/
+#[Group('Session')]
class CsrfTokenRaceTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php
index 53b2c5dcb7a5..6e615f4b4f91 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php
@@ -10,12 +10,12 @@ use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the 'timestamp' formatter when is used with time difference setting.
- *
- * @group Field
*/
+#[Group('Field')]
class TimestampFormatterWithTimeDiffTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php
index 0b5176182390..f35b5c72171b 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php
@@ -7,12 +7,12 @@ namespace Drupal\FunctionalJavascriptTests\Core\Field;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\views\Tests\ViewTestData;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the timestamp formatter used with time difference setting in views.
- *
- * @group Field
*/
+#[Group('Field')]
class TimestampFormatterWithTimeDiffViewsTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php
index 7c8af15a2e31..f538690daf99 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Core\Form;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests for form grouping elements.
- *
- * @group form
*/
+#[Group('form')]
class FormGroupingElementsTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php
index 189af6e49e61..ac41f01653fe 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php
@@ -6,6 +6,7 @@ namespace Drupal\FunctionalJavascriptTests\Core\Form;
use Drupal\filter\Entity\FilterFormat;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the state of elements based on another elements.
@@ -14,9 +15,8 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
* module under 'system' (core/modules/system/tests/module/form_test).
*
* @see Drupal\form_test\Form\JavascriptStatesForm
- *
- * @group javascript
*/
+#[Group('javascript')]
class JavascriptStatesTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php
index ce4cf583cd8c..4acec8e11102 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php
@@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Core;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\js_message_test\Controller\JSMessageTestController;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests core/drupal.message library.
- *
- * @group Javascript
*/
+#[Group('Javascript')]
class JsMessageTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php
index 3df154e90500..b990be12549c 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Core;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests for the machine name field.
- *
- * @group field
*/
+#[Group('field')]
class MachineNameTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php
index 8adf7a5685a5..94387c04633e 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php
@@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Core\Session;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\menu_link_content\Entity\MenuLinkContent;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests that sessions don't expire.
- *
- * @group session
*/
+#[Group('session')]
class SessionTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php
index abc6fd4c5ca4..2085b5518419 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php
@@ -5,13 +5,13 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Dialog;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
/**
* Tests jQuery events deprecations.
- *
- * @group dialog
*/
+#[Group('dialog')]
class DialogDeprecationsTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php
index ed73ac1ff706..abda27ed3b70 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Dialog;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the JavaScript functionality of the dialog position.
- *
- * @group dialog
*/
+#[Group('dialog')]
class DialogPositionTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php b/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php
index 971af632a993..18fe44d8c4c3 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php
@@ -4,18 +4,18 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\EntityReference;
-use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
-use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\entity_test\Entity\EntityTest;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\node\Traits\NodeCreationTrait;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the output of entity reference autocomplete widgets.
- *
- * @group entity_reference
*/
+#[Group('entity_reference')]
class EntityReferenceAutocompleteWidgetTest extends WebDriverTestBase {
use ContentTypeCreationTrait;
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php
index e45026d8960a..cb2fce7a36c1 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php
@@ -4,12 +4,14 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests;
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\IgnoreDeprecations;
+
/**
* Tests Javascript deprecation notices.
- *
- * @group javascript
- * @group legacy
*/
+#[Group('javascript')]
+#[IgnoreDeprecations]
class JavascriptDeprecationTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php
index a310d4c9e6e4..c67e27551c07 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php
@@ -4,11 +4,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests;
+use PHPUnit\Framework\Attributes\Group;
+
/**
* Tests that Drupal.throwError can be suppressed to allow a test to pass.
- *
- * @group javascript
*/
+#[Group('javascript')]
class JavascriptErrorsSuppressionTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php
index 3fb355523406..5f96e95dc2bb 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests;
use PHPUnit\Framework\AssertionFailedError;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests that Drupal.throwError will cause a test failure.
- *
- * @group javascript
*/
+#[Group('javascript')]
class JavascriptErrorsTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php
index 31ec8c91675b..8054a7395997 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php
@@ -4,11 +4,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests;
+use PHPUnit\Framework\Attributes\Group;
+
/**
* Tests Drupal settings retrieval in WebDriverTestBase tests.
- *
- * @group javascript
*/
+#[Group('javascript')]
class JavascriptGetDrupalSettingsTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php b/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php
index ad9ba4f89d8e..19cc4bf3a401 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php
@@ -5,15 +5,15 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\MachineName;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
-
use Drupal\language\Entity\ConfigurableLanguage;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the machine name transliteration functionality.
- *
- * @group javascript
- * @group #slow
*/
+#[Group('javascript')]
+#[Group('#slow')]
class MachineNameTransliterationTest extends WebDriverTestBase {
/**
@@ -46,9 +46,8 @@ class MachineNameTransliterationTest extends WebDriverTestBase {
/**
* Test for machine name transliteration functionality.
- *
- * @dataProvider machineNameInputOutput
*/
+ #[DataProvider('machineNameInputOutput')]
public function testMachineNameTransliterations($langcode, $input, $output): void {
$page = $this->getSession()->getPage();
if ($langcode !== 'en') {
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php b/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php
index a9350a1976e6..0a14594369b6 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php
@@ -7,12 +7,12 @@ namespace Drupal\FunctionalJavascriptTests\TableDrag;
use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\ExpectationException;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests draggable table.
- *
- * @group javascript
*/
+#[Group('javascript')]
class TableDragTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php
index 1ab9d65b3be7..e598f5cc04a4 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php
@@ -6,16 +6,18 @@ namespace Drupal\FunctionalJavascriptTests\Tests;
use Behat\Mink\Driver\Selenium2Driver;
use Drupal\entity_test\Entity\EntityTest;
+use Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\file\Functional\FileFieldCreationTrait;
use Drupal\Tests\TestFileCreationTrait;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the DrupalSelenium2Driver methods.
- *
- * @coversDefaultClass \Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver
- * @group javascript
*/
+#[CoversClass(DrupalSelenium2Driver::class)]
+#[Group('javascript')]
class DrupalSelenium2DriverTest extends WebDriverTestBase {
use TestFileCreationTrait;
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php
index 866f04e3a72a..a8018c7b013a 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php
@@ -5,13 +5,13 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Tests;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
use WebDriver\Exception;
/**
* Tests fault tolerant interactions.
- *
- * @group javascript
*/
+#[Group('javascript')]
class JSInteractionTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php
index f200ed28ac98..6388bd0844e1 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php
@@ -8,12 +8,12 @@ use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\ElementHtmlException;
use Drupal\Component\Utility\Timer;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests for the JSWebAssert class.
- *
- * @group javascript
*/
+#[Group('javascript')]
class JSWebAssertTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php
index dbc7874c735f..62ae39c4db49 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php
@@ -5,14 +5,14 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Theme;
use Drupal\Tests\block\FunctionalJavascript\BlockFilterTest;
+use PHPUnit\Framework\Attributes\Group;
/**
* Runs BlockFilterTest in Claro.
*
- * @group block
- *
* @see \Drupal\Tests\block\FunctionalJavascript\BlockFilterTest.
*/
+#[Group('block')]
class ClaroBlockFilterTest extends BlockFilterTest {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php
index 85476468d815..c8e018ebfbe5 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php
@@ -6,14 +6,14 @@ namespace Drupal\FunctionalJavascriptTests\Theme;
use Drupal\entity_test\EntityTestHelper;
use Drupal\Tests\field_ui\FunctionalJavascript\EntityDisplayTest;
+use PHPUnit\Framework\Attributes\Group;
/**
* Runs EntityDisplayTest in Claro.
*
- * @group claro
- *
* @see \Drupal\Tests\field_ui\FunctionalJavascript\EntityDisplayTest.
*/
+#[Group('claro')]
class ClaroEntityDisplayTest extends EntityDisplayTest {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php
index 32f6256396b3..930c7f963a7b 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php
@@ -5,14 +5,14 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Theme;
use Drupal\Tests\menu_ui\FunctionalJavascript\MenuUiJavascriptTest;
+use PHPUnit\Framework\Attributes\Group;
/**
* Runs MenuUiJavascriptTest in Claro.
*
- * @group claro
- *
* @see \Drupal\Tests\menu_ui\FunctionalJavascript\MenuUiJavascriptTest;
*/
+#[Group('claro')]
class ClaroMenuUiJavascriptTest extends MenuUiJavascriptTest {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php
index c78ff99c80a3..7f110c1a4282 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php
@@ -9,12 +9,12 @@ use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\media_library\FunctionalJavascript\MediaLibraryTestBase;
use Drupal\Tests\TestFileCreationTrait;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests that buttons in modals are not in their button pane.
- *
- * @group claro
*/
+#[Group('claro')]
class ClaroModalDisplayTest extends MediaLibraryTestBase {
use TestFileCreationTrait;
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php
index af01db8d2a8f..c090f658dc56 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Theme;
use Drupal\Tests\user\FunctionalJavascript\PasswordConfirmWidgetTest;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests the password confirm widget with Claro theme.
- *
- * @group claro
*/
+#[Group('claro')]
class ClaroPasswordConfirmWidgetTest extends PasswordConfirmWidgetTest {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php
index 9938ad22d095..7dda74a5348e 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php
@@ -5,14 +5,14 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Theme;
use Drupal\FunctionalJavascriptTests\TableDrag\TableDragTest;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests draggable tables with Claro theme.
*
- * @group claro
- *
* @see \Drupal\FunctionalJavascriptTests\TableDrag\TableDragTest
*/
+#[Group('claro')]
class ClaroTableDragTest extends TableDragTest {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php
index e752b79a65af..3f6c21ad8755 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php
@@ -7,12 +7,12 @@ namespace Drupal\FunctionalJavascriptTests\Theme;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\node\Traits\NodeCreationTrait;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests Claro's Views Bulk Operations form.
- *
- * @group claro
*/
+#[Group('claro')]
class ClaroViewsBulkOperationsTest extends WebDriverTestBase {
use ContentTypeCreationTrait;
use NodeCreationTrait;
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php
index 28b3c46a9996..67474ad08a60 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Theme;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Runs tests on Views UI using Claro.
- *
- * @group claro
*/
+#[Group('claro')]
class ClaroViewsUiTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php
index ee1261a354b0..24d3ca85af26 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Drupal\FunctionalJavascriptTests\Theme;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use PHPUnit\Framework\Attributes\Group;
/**
* Tests usage of localStorage.
- *
- * @group olivero
*/
+#[Group('olivero')]
final class OliveroAvoidStorageUsingTest extends WebDriverTestBase {
/**
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php
index 6b0472702c0c..74278efdf4bc 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php
@@ -6,14 +6,14 @@ namespace Drupal\FunctionalJavascriptTests\Theme;
use Drupal\FunctionalJavascriptTests\Core\JsMessageTest;
use Drupal\js_message_test\Controller\JSMessageTestController;
+use PHPUnit\Framework\Attributes\Group;
/**
* Runs OliveroMessagesTest in Olivero.
*
- * @group olivero
- *
* @see \Drupal\FunctionalJavascriptTests\Core\JsMessageTest.
*/
+#[Group('olivero')]
class OliveroMessagesTest extends JsMessageTest {
/**
diff --git a/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php b/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php
index f7509fd72775..f280ad493a57 100644
--- a/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php
+++ b/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php
@@ -35,6 +35,7 @@ use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
use Drupal\user\UserInterface;
+use Drupal\workspaces\Entity\Workspace;
use Psr\Log\LogLevel;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@@ -72,6 +73,7 @@ class ContentImportTest extends BrowserTestBase {
'system',
'taxonomy',
'user',
+ 'workspaces',
];
/**
@@ -180,6 +182,22 @@ class ContentImportTest extends BrowserTestBase {
);
};
$this->assertTrue($logger->hasRecordThatPasses($predicate, LogLevel::WARNING));
+
+ // Visit a page that is published in a non-live workspace; we should not be
+ // able to see it, because we don't have permission.
+ $node_in_workspace = $this->container->get(EntityRepositoryInterface::class)
+ ->loadEntityByUuid('node', '48475954-e878-439c-9d3d-226724a44269');
+ $this->assertInstanceOf(NodeInterface::class, $node_in_workspace);
+ $node_url = $node_in_workspace->toUrl();
+ $this->drupalGet($node_url);
+ $assert_session = $this->assertSession();
+ $assert_session->statusCodeEquals(403);
+ // If we log in with administrative privileges (i.e., we can look at any
+ // workspace), we should be able to see it.
+ $this->drupalLogin($this->adminAccount);
+ $this->drupalGet($node_url);
+ $assert_session->statusCodeEquals(200);
+ $assert_session->pageTextContains($node_in_workspace->label());
}
/**
@@ -303,6 +321,11 @@ class ContentImportTest extends BrowserTestBase {
$this->assertInstanceOf(Section::class, $section);
$this->assertCount(2, $section->getComponents());
$this->assertSame('system_powered_by_block', $section->getComponent('03b45f14-cf74-469a-8398-edf3383ce7fa')->getPluginId());
+
+ // Workspaces should have been imported with their parent references intact.
+ $workspaces = Workspace::loadMultiple();
+ $this->assertArrayHasKey('test_workspace', $workspaces);
+ $this->assertSame('test_workspace', $workspaces['inner_test']?->parent->entity->id());
}
/**
diff --git a/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php b/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php
index d33d7c4942ab..994ac39d2707 100644
--- a/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php
+++ b/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php
@@ -90,8 +90,8 @@ class InstallerNonDefaultDatabaseDriverTest extends InstallerTestBase {
// uninstalled being dependencies of the "driver_test" module.
$this->drupalGet('admin/modules/uninstall');
$this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-driver-test"]', "The following reason prevents Contrib database driver test from being uninstalled: The module 'Contrib database driver test' is providing the database driver '{$this->testDriverName}'.");
- $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-mysql"]', "The following reason prevents MySQL from being uninstalled: Required by: driver_test");
- $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-pgsql"]', "The following reason prevents PostgreSQL from being uninstalled: Required by: driver_test");
+ $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-mysql"]', "The following reason prevents MySQL from being uninstalled: Required by: Contrib database driver test (driver_test)");
+ $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-pgsql"]', "The following reason prevents PostgreSQL from being uninstalled: Required by: Contrib database driver test (driver_test)");
}
/**
diff --git a/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php b/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php
index 91cc24234e1a..9882d3d9ef0f 100644
--- a/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php
@@ -4,8 +4,10 @@ declare(strict_types=1);
namespace Drupal\KernelTests\Core\Controller;
+use Drupal\dblog\Logger\DbLog;
use Drupal\KernelTests\KernelTestBase;
use Drupal\system_test\Controller\BrokenSystemTestController;
+use Drupal\system_test\Controller\OptionalServiceSystemTestController;
use Drupal\system_test\Controller\SystemTestController;
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
@@ -52,4 +54,17 @@ class ControllerBaseTest extends KernelTestBase {
$this->container->get('class_resolver')->getInstanceFromDefinition(BrokenSystemTestController::class);
}
+ /**
+ * @covers ::create
+ */
+ public function testCreateOptional(): void {
+ $service = $this->container->get('class_resolver')->getInstanceFromDefinition(OptionalServiceSystemTestController::class);
+ $this->assertInstanceOf(OptionalServiceSystemTestController::class, $service);
+ $this->assertNull($service->dbLog);
+ $this->container->get('module_installer')->install(['dblog']);
+ $service = $this->container->get('class_resolver')->getInstanceFromDefinition(OptionalServiceSystemTestController::class);
+ $this->assertInstanceOf(OptionalServiceSystemTestController::class, $service);
+ $this->assertInstanceOf(DbLog::class, $service->dbLog);
+ }
+
}
diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php
index fdfa189b880b..8fc0c2013d29 100644
--- a/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php
@@ -319,4 +319,54 @@ YAML
$recipe->input->collectAll($collector);
}
+ /**
+ * Tests getting default input values from environment variables.
+ */
+ public function testDefaultInputFromEnvironmentVariables(): void {
+ $this->config('system.site')
+ ->set('name', 'Hello Thar')
+ ->set('slogan', 'Very important')
+ ->save();
+
+ $recipe = $this->createRecipe(<<<YAML
+name: 'Input from environment variables'
+input:
+ name:
+ data_type: string
+ description: The name of the site.
+ default:
+ source: env
+ env: SITE_NAME
+ slogan:
+ data_type: string
+ description: The site slogan.
+ default:
+ source: env
+ env: SITE_SLOGAN
+config:
+ actions:
+ system.site:
+ simpleConfigUpdate:
+ name: \${name}
+ slogan: \${slogan}
+YAML
+ );
+ putenv('SITE_NAME=Input Test');
+
+ // Mock a collector that only returns the default value.
+ $collector = $this->createMock(InputCollectorInterface::class);
+ $collector->expects($this->any())
+ ->method('collectValue')
+ ->withAnyParameters()
+ ->willReturnArgument(2);
+ $recipe->input->collectAll($collector);
+
+ RecipeRunner::processRecipe($recipe);
+ $config = $this->config('system.site');
+ $this->assertSame('Input Test', $config->get('name'));
+ // There was no SITE_SLOGAN environment variable, so it should have been
+ // set to an empty string.
+ $this->assertSame('', $config->get('slogan'));
+ }
+
}
diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php
index 62f14df4d202..64b4c17869f5 100644
--- a/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php
@@ -761,6 +761,36 @@ extra:
YAML,
NULL,
];
+ yield 'input env variable name is not a string' => [
+ <<<YAML
+name: Bad input
+input:
+ bad_news:
+ data_type: string
+ description: 'Bad default definition'
+ default:
+ source: env
+ env: -40
+YAML,
+ [
+ '[input][bad_news][default][env]' => ['This value should be of type string.'],
+ ],
+ ];
+ yield 'input env variable name is empty' => [
+ <<<YAML
+name: Bad input
+input:
+ bad_news:
+ data_type: string
+ description: 'Bad default definition'
+ default:
+ source: env
+ env: ''
+YAML,
+ [
+ '[input][bad_news][default][env]' => ['This value should not be blank.'],
+ ],
+ ];
}
/**
diff --git a/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php b/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php
index ad6f98c3cf78..bd64798d2406 100644
--- a/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php
+++ b/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php
@@ -19,18 +19,55 @@ class FinderTest extends UnitTestCase {
*/
public function testFoundDataIsInDependencyOrder(): void {
$finder = new Finder(__DIR__ . '/../../../../fixtures/default_content');
+ $actual_order = array_keys($finder->data);
- $expected_order = [
- // First is the author of the node.
- '94503467-be7f-406c-9795-fc25baa22203',
- // Next, the taxonomy term referenced by the node.
- '550f86ad-aa11-4047-953f-636d42889f85',
- // Then we have the node itself, since it has no other dependencies.
- 'e1714f23-70c0-4493-8e92-af1901771921',
- // Finally, the menu link to the node.
- '3434bd5a-d2cd-4f26-bf79-a7f6b951a21b',
- ];
- $this->assertSame($expected_order, array_slice(array_keys($finder->data), 0, 4));
+ $node_uuid = 'e1714f23-70c0-4493-8e92-af1901771921';
+ // The author of the node should come before the node itself. We're using
+ // named arguments here purely for clarity.
+ $this->assertRelativeOrder(
+ $actual_order,
+ earlier: '94503467-be7f-406c-9795-fc25baa22203',
+ later: $node_uuid,
+ );
+ // Same with the taxonomy term referenced by the node.
+ $this->assertRelativeOrder(
+ $actual_order,
+ earlier: '550f86ad-aa11-4047-953f-636d42889f85',
+ later: $node_uuid,
+ );
+ // The menu link to the node should come after the node.
+ $this->assertRelativeOrder(
+ $actual_order,
+ earlier: $node_uuid,
+ later: '3434bd5a-d2cd-4f26-bf79-a7f6b951a21b',
+ );
+
+ // A node that is in a workspace should come after the workspace itself.
+ $this->assertRelativeOrder(
+ $actual_order,
+ earlier: '384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d',
+ later: '48475954-e878-439c-9d3d-226724a44269',
+ );
+ }
+
+ /**
+ * Asserts that an item in an array comes before another item in that array.
+ *
+ * @param array $haystack
+ * The array to examine.
+ * @param mixed $earlier
+ * The item which should come first.
+ * @param mixed $later
+ * The item which should come after.
+ */
+ private function assertRelativeOrder(array $haystack, mixed $earlier, mixed $later): void {
+ $haystack = array_values($haystack);
+ $earlier_index = array_search($earlier, $haystack, TRUE);
+ $later_index = array_search($later, $haystack, TRUE);
+ $this->assertIsInt($earlier_index);
+ $this->assertIsInt($later_index);
+ // "Later" should be greater than "earlier".
+ $this->assertGreaterThan($earlier_index, $later_index);
}
/**
diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php
index df504f4b7269..cdfb897046c6 100644
--- a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php
+++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php
@@ -6,6 +6,7 @@ namespace Drupal\Tests\Core\DependencyInjection\Compiler;
use Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass;
use Drupal\Tests\UnitTestCase;
+use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
@@ -307,6 +308,31 @@ class TaggedHandlersPassTest extends UnitTestCase {
}
/**
+ * Tests child handler with parent service.
+ *
+ * @covers ::process
+ */
+ public function testProcessChildDefinition(): void {
+ $container = $this->buildContainer();
+
+ $container
+ ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer')
+ ->addTag('service_collector');
+ $container
+ ->register('root_handler', __NAMESPACE__ . '\ValidHandler');
+ $container->addDefinitions([
+ 'parent_handler' => new ChildDefinition('root_handler'),
+ 'child_handler' => (new ChildDefinition('parent_handler'))->addTag('consumer_id'),
+ ]);
+
+ $handler_pass = new TaggedHandlersPass();
+ $handler_pass->process($container);
+
+ $method_calls = $container->getDefinition('consumer_id')->getMethodCalls();
+ $this->assertCount(1, $method_calls);
+ }
+
+ /**
* Tests consumer method with extra parameters.
*
* @covers ::process
diff --git a/core/tests/fixtures/default_content/node/48475954-e878-439c-9d3d-226724a44269.yml b/core/tests/fixtures/default_content/node/48475954-e878-439c-9d3d-226724a44269.yml
new file mode 100644
index 000000000000..bae16bcd3f9e
--- /dev/null
+++ b/core/tests/fixtures/default_content/node/48475954-e878-439c-9d3d-226724a44269.yml
@@ -0,0 +1,47 @@
+_meta:
+ version: '1.0'
+ entity_type: node
+ uuid: 48475954-e878-439c-9d3d-226724a44269
+ bundle: page
+ default_langcode: en
+ depends:
+ 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d: workspace
+default:
+ revision_uid:
+ -
+ target_id: 1
+ status:
+ -
+ value: false
+ uid:
+ -
+ target_id: 1
+ title:
+ -
+ value: 'A happy little workspace'
+ created:
+ -
+ value: 1751155670
+ promote:
+ -
+ value: false
+ sticky:
+ -
+ value: false
+ revision_translation_affected:
+ -
+ value: true
+ path:
+ -
+ alias: ''
+ langcode: en
+ # TRICKY! Default Content does not export the `workspace` field because it skips internal
+ # properties, but core's exporter should be sure to include it.
+ workspace:
+ -
+ target_id: test_workspace
+ body:
+ -
+ value: 'This page lives in a workspace! How neat!'
+ format: plain_text
+ summary: ''
diff --git a/core/tests/fixtures/default_content/workspace/inner_test.yml b/core/tests/fixtures/default_content/workspace/inner_test.yml
new file mode 100644
index 000000000000..45a1bcc22dcb
--- /dev/null
+++ b/core/tests/fixtures/default_content/workspace/inner_test.yml
@@ -0,0 +1,24 @@
+_meta:
+ version: '1.0'
+ entity_type: workspace
+ uuid: 93f5b0b4-ada9-4bcd-a11d-f7329e9afe21
+ depends:
+ 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d: workspace
+default:
+ # TRICKY! Default Content does not export the `id` field, but core's exporter should.
+ # Without it, the import will fail.
+ id:
+ -
+ value: inner_test
+ uid:
+ -
+ target_id: 1
+ label:
+ -
+ value: 'Inner Test'
+ parent:
+ -
+ entity: 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d
+ created:
+ -
+ value: 1751154834
diff --git a/core/tests/fixtures/default_content/workspace/test_workspace.yml b/core/tests/fixtures/default_content/workspace/test_workspace.yml
new file mode 100644
index 000000000000..c22379406797
--- /dev/null
+++ b/core/tests/fixtures/default_content/workspace/test_workspace.yml
@@ -0,0 +1,19 @@
+_meta:
+ version: '1.0'
+ entity_type: workspace
+ uuid: 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d
+default:
+ # TRICKY! Default Content does not export the `id` field, but core's exporter should.
+ # Without it, the import will fail.
+ id:
+ -
+ value: test_workspace
+ uid:
+ -
+ target_id: 1
+ label:
+ -
+ value: 'Test Workspace'
+ created:
+ -
+ value: 1751154825
diff --git a/core/themes/claro/claro.theme b/core/themes/claro/claro.theme
index 81255d798a3e..e6b5cc443163 100644
--- a/core/themes/claro/claro.theme
+++ b/core/themes/claro/claro.theme
@@ -1181,7 +1181,7 @@ function claro_preprocess_table(&$variables): void {
$first_cell_key = array_key_first($row['cells']);
// The 'attributes' key is always here and it is an
// \Drupal\Core\Template\Attribute.
- // @see template_preprocess_table();
+ // @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable();
$row['cells'][$first_cell_key]['attributes']->addClass('tabledrag-cell');
// Check that the first cell is empty or not.
diff --git a/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig b/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig
index e9928fd77660..4940f047477e 100644
--- a/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig
+++ b/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig
@@ -17,7 +17,7 @@
* results, the following data is set:
* - plugin: The search plugin ID, for example "node_search".
*
- * @see template_preprocess_item_list()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList()
*/
#}
{%
diff --git a/core/themes/claro/templates/classy/dataset/item-list.html.twig b/core/themes/claro/templates/classy/dataset/item-list.html.twig
index 20541b0b7e66..6e7b8e317b1d 100644
--- a/core/themes/claro/templates/classy/dataset/item-list.html.twig
+++ b/core/themes/claro/templates/classy/dataset/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()
*/
#}
{% if context.list_style %}
diff --git a/core/themes/claro/templates/classy/dataset/table.html.twig b/core/themes/claro/templates/classy/dataset/table.html.twig
index cdfe0bff7e73..d7e6459bd4a4 100644
--- a/core/themes/claro/templates/classy/dataset/table.html.twig
+++ b/core/themes/claro/templates/classy/dataset/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()
*/
#}
<table{{ attributes }}>
diff --git a/core/themes/claro/templates/classy/field/image.html.twig b/core/themes/claro/templates/classy/field/image.html.twig
index 31f782bb60a8..90d955c180a6 100644
--- a/core/themes/claro/templates/classy/field/image.html.twig
+++ b/core/themes/claro/templates/classy/field/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()
*/
#}
{%
diff --git a/core/themes/claro/templates/classy/layout/region.html.twig b/core/themes/claro/templates/classy/layout/region.html.twig
index 95e71cec37e4..a4e8cc0af72a 100644
--- a/core/themes/claro/templates/classy/layout/region.html.twig
+++ b/core/themes/claro/templates/classy/layout/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()
*/
#}
{%
diff --git a/core/themes/claro/templates/details.html.twig b/core/themes/claro/templates/details.html.twig
index 196f6f21e03f..bf5037ead199 100644
--- a/core/themes/claro/templates/details.html.twig
+++ b/core/themes/claro/templates/details.html.twig
@@ -75,7 +75,12 @@
</div>
{% endif %}
{%- if description -%}
- <div class="claro-details__description{{ disabled ? ' is-disabled' }}">{{ description }}</div>
+ {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %}
+ {% set description_classes = [
+ 'claro-details__description',
+ disabled ? 'is-disabled',
+ ] %}
+ <div{{ description_attributes.addClass(description_classes) }}>{{ description }}</div>
{%- endif -%}
{%- if children -%}
{{ children }}
diff --git a/core/themes/claro/templates/install-page.html.twig b/core/themes/claro/templates/install-page.html.twig
index bd22c5a94598..f7456333c8d1 100644
--- a/core/themes/claro/templates/install-page.html.twig
+++ b/core/themes/claro/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()
*/
#}
<div class="layout-container">
diff --git a/core/themes/claro/templates/maintenance-page--front.html.twig b/core/themes/claro/templates/maintenance-page--front.html.twig
index 12a364ee6a00..daeaf2d7ca23 100644
--- a/core/themes/claro/templates/maintenance-page--front.html.twig
+++ b/core/themes/claro/templates/maintenance-page--front.html.twig
@@ -9,7 +9,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()
*/
#}
<div class="layout-container">
diff --git a/core/themes/claro/templates/maintenance-page.html.twig b/core/themes/claro/templates/maintenance-page.html.twig
index 9bbb9a0e0ce4..ef40bbcbec0e 100644
--- a/core/themes/claro/templates/maintenance-page.html.twig
+++ b/core/themes/claro/templates/maintenance-page.html.twig
@@ -9,7 +9,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()
* @see maintenance-page--front.html.twig
*/
#}
diff --git a/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig b/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig
index d7931e16b1d5..302e437c8a1f 100644
--- a/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig
+++ b/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig
@@ -20,7 +20,7 @@
* - list_style: The custom list style.
*
* @see claro_preprocess_item_list__media_library_add_form_media_list()
- * @see template_preprocess_item_list()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList()
*/
#}
{% if items -%}
diff --git a/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig b/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig
index be73cf7bb89c..7c8a59ebd35a 100644
--- a/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig
+++ b/core/themes/claro/templates/navigation/menu-local-task--views-ui.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()
*
* @todo remove this after https://www.drupal.org/node/3051605 has been solved.
*/
diff --git a/core/themes/claro/templates/navigation/menu-local-task.html.twig b/core/themes/claro/templates/navigation/menu-local-task.html.twig
index b135a5ddcf3b..b0b6ce08db94 100644
--- a/core/themes/claro/templates/navigation/menu-local-task.html.twig
+++ b/core/themes/claro/templates/navigation/menu-local-task.html.twig
@@ -12,7 +12,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()
*/
#}
{%
diff --git a/core/themes/claro/templates/region--breadcrumb.html.twig b/core/themes/claro/templates/region--breadcrumb.html.twig
index a66f43131d60..e58c6a01a2d5 100644
--- a/core/themes/claro/templates/region--breadcrumb.html.twig
+++ b/core/themes/claro/templates/region--breadcrumb.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()
*/
#}
{%
diff --git a/core/themes/olivero/olivero.theme b/core/themes/olivero/olivero.theme
index 88aa7531379c..8ddfb49b87ff 100644
--- a/core/themes/olivero/olivero.theme
+++ b/core/themes/olivero/olivero.theme
@@ -83,7 +83,7 @@ function olivero_preprocess_maintenance_page(&$variables): void {
// By default, site_name is set to Drupal if no db connection is available
// or during site installation. Setting site_name to an empty string makes
// the site and update pages look cleaner.
- // @see template_preprocess_maintenance_page
+ // @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage()
if (!$variables['db_is_active']) {
$variables['site_name'] = '';
}
diff --git a/core/themes/olivero/templates/dataset/item-list--search-results.html.twig b/core/themes/olivero/templates/dataset/item-list--search-results.html.twig
index 6bd6441738d6..665634e86885 100644
--- a/core/themes/olivero/templates/dataset/item-list--search-results.html.twig
+++ b/core/themes/olivero/templates/dataset/item-list--search-results.html.twig
@@ -17,7 +17,7 @@
* results, the following data is set:
* - plugin: The search plugin ID, for example "node_search".
*
- * @see template_preprocess_item_list()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList()
*/
#}
{%
diff --git a/core/themes/olivero/templates/dataset/item-list.html.twig b/core/themes/olivero/templates/dataset/item-list.html.twig
index 7c7a6e365bfe..06b1e2ebe4fe 100644
--- a/core/themes/olivero/templates/dataset/item-list.html.twig
+++ b/core/themes/olivero/templates/dataset/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()
*/
#}
{% if context.list_style %}
diff --git a/core/themes/olivero/templates/form/details.html.twig b/core/themes/olivero/templates/form/details.html.twig
index c7a7f9c416a2..e4de3d168152 100644
--- a/core/themes/olivero/templates/form/details.html.twig
+++ b/core/themes/olivero/templates/form/details.html.twig
@@ -50,7 +50,8 @@
</div>
{% endif %}
{%- if description -%}
- <div class="olivero-details__description">{{ description }}</div>
+ {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %}
+ <div{{ description_attributes.addClass(['olivero-details__description']) }}>{{ description }}</div>
{%- endif -%}
{%- if children -%}
{{ children }}
diff --git a/core/themes/olivero/templates/layout/region--breadcrumb.html.twig b/core/themes/olivero/templates/layout/region--breadcrumb.html.twig
index 5dbd5d45ffd7..a76183c62c4d 100644
--- a/core/themes/olivero/templates/layout/region--breadcrumb.html.twig
+++ b/core/themes/olivero/templates/layout/region--breadcrumb.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()
*/
#}
diff --git a/core/themes/olivero/templates/layout/region--content-above.html.twig b/core/themes/olivero/templates/layout/region--content-above.html.twig
index e50082ea3952..1a86195d928e 100644
--- a/core/themes/olivero/templates/layout/region--content-above.html.twig
+++ b/core/themes/olivero/templates/layout/region--content-above.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()
*/
#}
diff --git a/core/themes/olivero/templates/layout/region--content-below.html.twig b/core/themes/olivero/templates/layout/region--content-below.html.twig
index c0f9ed29e5e2..5cae5f8b825d 100644
--- a/core/themes/olivero/templates/layout/region--content-below.html.twig
+++ b/core/themes/olivero/templates/layout/region--content-below.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()
*/
#}
{%
diff --git a/core/themes/olivero/templates/layout/region--content.html.twig b/core/themes/olivero/templates/layout/region--content.html.twig
index 7f86b5810362..60e53660b1f8 100644
--- a/core/themes/olivero/templates/layout/region--content.html.twig
+++ b/core/themes/olivero/templates/layout/region--content.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()
*/
#}
diff --git a/core/themes/olivero/templates/layout/region--footer-bottom.html.twig b/core/themes/olivero/templates/layout/region--footer-bottom.html.twig
index 36ffd885f95a..9677f84970ec 100644
--- a/core/themes/olivero/templates/layout/region--footer-bottom.html.twig
+++ b/core/themes/olivero/templates/layout/region--footer-bottom.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()
*/
#}
diff --git a/core/themes/olivero/templates/layout/region--footer-top.html.twig b/core/themes/olivero/templates/layout/region--footer-top.html.twig
index 8baaf314056c..c657df25ba3b 100644
--- a/core/themes/olivero/templates/layout/region--footer-top.html.twig
+++ b/core/themes/olivero/templates/layout/region--footer-top.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()
*/
#}
diff --git a/core/themes/olivero/templates/layout/region--header.html.twig b/core/themes/olivero/templates/layout/region--header.html.twig
index 8ecee6633649..47912006894d 100644
--- a/core/themes/olivero/templates/layout/region--header.html.twig
+++ b/core/themes/olivero/templates/layout/region--header.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()
*/
#}
diff --git a/core/themes/olivero/templates/layout/region--highlighted.html.twig b/core/themes/olivero/templates/layout/region--highlighted.html.twig
index 6c4293680741..b3dd5f2570c1 100644
--- a/core/themes/olivero/templates/layout/region--highlighted.html.twig
+++ b/core/themes/olivero/templates/layout/region--highlighted.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()
*/
#}
diff --git a/core/themes/olivero/templates/layout/region--primary-menu.html.twig b/core/themes/olivero/templates/layout/region--primary-menu.html.twig
index 32ade151fe6e..f1fa999007e3 100644
--- a/core/themes/olivero/templates/layout/region--primary-menu.html.twig
+++ b/core/themes/olivero/templates/layout/region--primary-menu.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()
*/
#}
diff --git a/core/themes/olivero/templates/layout/region--secondary-menu.html.twig b/core/themes/olivero/templates/layout/region--secondary-menu.html.twig
index ce7312194fe3..e64c0743b98b 100644
--- a/core/themes/olivero/templates/layout/region--secondary-menu.html.twig
+++ b/core/themes/olivero/templates/layout/region--secondary-menu.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()
*/
#}
diff --git a/core/themes/olivero/templates/layout/region--sidebar.html.twig b/core/themes/olivero/templates/layout/region--sidebar.html.twig
index a3f88864f958..8b44262fee3a 100644
--- a/core/themes/olivero/templates/layout/region--sidebar.html.twig
+++ b/core/themes/olivero/templates/layout/region--sidebar.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()
*/
#}
{%
diff --git a/core/themes/olivero/templates/layout/region--social.html.twig b/core/themes/olivero/templates/layout/region--social.html.twig
index 6f3cf697408d..dff6e3bbda7f 100644
--- a/core/themes/olivero/templates/layout/region--social.html.twig
+++ b/core/themes/olivero/templates/layout/region--social.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()
*/
#}
diff --git a/core/themes/olivero/templates/layout/region.html.twig b/core/themes/olivero/templates/layout/region.html.twig
index 651a0112a56c..df39e7d8b451 100644
--- a/core/themes/olivero/templates/layout/region.html.twig
+++ b/core/themes/olivero/templates/layout/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()
*/
#}
{%
diff --git a/core/themes/olivero/templates/maintenance-page.html.twig b/core/themes/olivero/templates/maintenance-page.html.twig
index f1c97e5c6e7f..cce49aea822a 100644
--- a/core/themes/olivero/templates/maintenance-page.html.twig
+++ b/core/themes/olivero/templates/maintenance-page.html.twig
@@ -5,7 +5,7 @@
*
* All available variables are mirrored in page.html.twig.
*
- * @see template_preprocess_maintenance_page()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage()
*/
#}
diff --git a/core/themes/olivero/templates/menu-local-action.html.twig b/core/themes/olivero/templates/menu-local-action.html.twig
index f78e6f538f9c..12bf7d27ec53 100644
--- a/core/themes/olivero/templates/menu-local-action.html.twig
+++ b/core/themes/olivero/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/themes/olivero/templates/navigation/menu-local-task.html.twig b/core/themes/olivero/templates/navigation/menu-local-task.html.twig
index 34bbb1c44d4c..50b71dabee56 100644
--- a/core/themes/olivero/templates/navigation/menu-local-task.html.twig
+++ b/core/themes/olivero/templates/navigation/menu-local-task.html.twig
@@ -12,7 +12,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()
*/
#}
<li{{ attributes.addClass('tabs__tab', is_active ? 'is-active') }}>
diff --git a/core/themes/stable9/templates/dataset/item-list.html.twig b/core/themes/stable9/templates/dataset/item-list.html.twig
index 86cc63670c92..4fceba2d0702 100644
--- a/core/themes/stable9/templates/dataset/item-list.html.twig
+++ b/core/themes/stable9/templates/dataset/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()
*/
#}
{% if context.list_style %}
diff --git a/core/themes/stable9/templates/dataset/table.html.twig b/core/themes/stable9/templates/dataset/table.html.twig
index d9e12ff284f8..89ab32c486e7 100644
--- a/core/themes/stable9/templates/dataset/table.html.twig
+++ b/core/themes/stable9/templates/dataset/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()
*/
#}
<table{{ attributes }}>
diff --git a/core/themes/stable9/templates/field/image.html.twig b/core/themes/stable9/templates/field/image.html.twig
index b342eee6dada..bb3d34e23210 100644
--- a/core/themes/stable9/templates/field/image.html.twig
+++ b/core/themes/stable9/templates/field/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()
*/
#}
<img{{ attributes }} />
diff --git a/core/themes/stable9/templates/form/details.html.twig b/core/themes/stable9/templates/form/details.html.twig
index 19879959273d..5a6538a21cce 100644
--- a/core/themes/stable9/templates/form/details.html.twig
+++ b/core/themes/stable9/templates/form/details.html.twig
@@ -32,7 +32,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/themes/stable9/templates/layout/install-page.html.twig b/core/themes/stable9/templates/layout/install-page.html.twig
index e2d3381e4858..3a7a346edfe6 100644
--- a/core/themes/stable9/templates/layout/install-page.html.twig
+++ b/core/themes/stable9/templates/layout/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()
*/
#}
<div class="layout-container">
diff --git a/core/themes/stable9/templates/layout/maintenance-page.html.twig b/core/themes/stable9/templates/layout/maintenance-page.html.twig
index de0acaabbbb4..cdbb3c0e11fe 100644
--- a/core/themes/stable9/templates/layout/maintenance-page.html.twig
+++ b/core/themes/stable9/templates/layout/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()
*/
#}
<header role="banner">
diff --git a/core/themes/stable9/templates/layout/region.html.twig b/core/themes/stable9/templates/layout/region.html.twig
index e5e36d07410b..400b985d07f6 100644
--- a/core/themes/stable9/templates/layout/region.html.twig
+++ b/core/themes/stable9/templates/layout/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()
*/
#}
{% if content %}
diff --git a/core/themes/stable9/templates/navigation/menu-local-action.html.twig b/core/themes/stable9/templates/navigation/menu-local-action.html.twig
index 27872837abdc..138d29a00997 100644
--- a/core/themes/stable9/templates/navigation/menu-local-action.html.twig
+++ b/core/themes/stable9/templates/navigation/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()
*/
#}
<li{{ attributes }}>{{ link }}</li>
diff --git a/core/themes/stable9/templates/navigation/menu-local-task.html.twig b/core/themes/stable9/templates/navigation/menu-local-task.html.twig
index b6c3ca241913..2bd91d6bf64f 100644
--- a/core/themes/stable9/templates/navigation/menu-local-task.html.twig
+++ b/core/themes/stable9/templates/navigation/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()
*/
#}
<li{{ attributes }}>{{ link }}</li>
diff --git a/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig b/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig
index e9928fd77660..4940f047477e 100644
--- a/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig
+++ b/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig
@@ -17,7 +17,7 @@
* results, the following data is set:
* - plugin: The search plugin ID, for example "node_search".
*
- * @see template_preprocess_item_list()
+ * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList()
*/
#}
{%
diff --git a/core/themes/starterkit_theme/templates/dataset/item-list.html.twig b/core/themes/starterkit_theme/templates/dataset/item-list.html.twig
index 20541b0b7e66..6e7b8e317b1d 100644
--- a/core/themes/starterkit_theme/templates/dataset/item-list.html.twig
+++ b/core/themes/starterkit_theme/templates/dataset/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()
*/
#}
{% if context.list_style %}
diff --git a/core/themes/starterkit_theme/templates/dataset/table.html.twig b/core/themes/starterkit_theme/templates/dataset/table.html.twig
index cdfe0bff7e73..d7e6459bd4a4 100644
--- a/core/themes/starterkit_theme/templates/dataset/table.html.twig
+++ b/core/themes/starterkit_theme/templates/dataset/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()
*/
#}
<table{{ attributes }}>
diff --git a/core/themes/starterkit_theme/templates/field/image.html.twig b/core/themes/starterkit_theme/templates/field/image.html.twig
index 31f782bb60a8..90d955c180a6 100644
--- a/core/themes/starterkit_theme/templates/field/image.html.twig
+++ b/core/themes/starterkit_theme/templates/field/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()
*/
#}
{%
diff --git a/core/themes/starterkit_theme/templates/form/details.html.twig b/core/themes/starterkit_theme/templates/form/details.html.twig
index c554096da9d7..5dea0b485f1a 100644
--- a/core/themes/starterkit_theme/templates/form/details.html.twig
+++ b/core/themes/starterkit_theme/templates/form/details.html.twig
@@ -32,7 +32,8 @@
</div>
{% endif %}
{%- if description -%}
- <div class="details-description">{{ description }}</div>
+ {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %}
+ <div{{ description_attributes.addClass(['details-description']) }}>{{ description }}</div>
{%- endif -%}
{%- if children -%}
{{ children }}
diff --git a/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig b/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig
index 7463b0238ca3..edd2783619b8 100644
--- a/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig
+++ b/core/themes/starterkit_theme/templates/layout/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()
*/
#}
<div class="layout-container">
diff --git a/core/themes/starterkit_theme/templates/layout/region.html.twig b/core/themes/starterkit_theme/templates/layout/region.html.twig
index 95e71cec37e4..a4e8cc0af72a 100644
--- a/core/themes/starterkit_theme/templates/layout/region.html.twig
+++ b/core/themes/starterkit_theme/templates/layout/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()
*/
#}
{%
diff --git a/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig b/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig
index 27872837abdc..138d29a00997 100644
--- a/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig
+++ b/core/themes/starterkit_theme/templates/navigation/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()
*/
#}
<li{{ attributes }}>{{ link }}</li>
diff --git a/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig b/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig
index b8559815b9e9..ce62d46c9f84 100644
--- a/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig
+++ b/core/themes/starterkit_theme/templates/navigation/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()
*/
#}
<li{{ attributes.addClass(is_active ? 'is-active') }}>{{ link }}</li>