summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules
diff options
context:
space:
mode:
Diffstat (limited to 'core/modules')
-rw-r--r--core/modules/big_pipe/big_pipe.module1
-rw-r--r--core/modules/big_pipe/tests/modules/big_pipe_messages_test/big_pipe_messages_test.info.yml5
-rw-r--r--core/modules/big_pipe/tests/modules/big_pipe_messages_test/src/Hook/BigPipeMessagesHooks.php42
-rw-r--r--core/modules/big_pipe/tests/src/Functional/BigPipeTest.php14
-rw-r--r--core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php1
-rw-r--r--core/modules/block/src/Hook/BlockHooks.php13
-rw-r--r--core/modules/block_content/config/optional/views.view.block_content.yml1
-rw-r--r--core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_redirect_destination.yml1
-rw-r--r--core/modules/ckeditor5/src/HTMLRestrictions.php2
-rw-r--r--core/modules/ckeditor5/src/Plugin/CKEditor5PluginDefinition.php3
-rw-r--r--core/modules/ckeditor5/src/SmartDefaultSettings.php2
-rw-r--r--core/modules/ckeditor5/tests/src/Unit/HTMLRestrictionsTest.php8
-rw-r--r--core/modules/comment/config/optional/views.view.comment.yml1
-rw-r--r--core/modules/comment/src/Plugin/views/field/LastTimestamp.php2
-rw-r--r--core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_operations.yml1
-rw-r--r--core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_schema.yml1
-rw-r--r--core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_new_comments.yml5
-rw-r--r--core/modules/content_moderation/config/optional/views.view.moderated_content.yml1
-rw-r--r--core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php2
-rw-r--r--core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.latest.yml5
-rw-r--r--core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_sort_base_table.yml1
-rw-r--r--core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_sort_revision_table.yml1
-rw-r--r--core/modules/content_translation/tests/modules/content_translation_test_views/test_views/views.view.test_entity_translations_link.yml1
-rw-r--r--core/modules/contextual/src/Hook/ContextualThemeHooks.php14
-rw-r--r--core/modules/datetime/datetime.module4
-rw-r--r--core/modules/datetime/src/DateTimeViewsHelper.php3
-rw-r--r--core/modules/dblog/config/optional/views.view.watchdog.yml1
-rw-r--r--core/modules/field/src/Hook/FieldHooks.php19
-rw-r--r--core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php4
-rw-r--r--core/modules/field_ui/src/Form/FieldConfigEditForm.php3
-rw-r--r--core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php3
-rw-r--r--core/modules/field_ui/src/Hook/FieldUiHooks.php6
-rw-r--r--core/modules/file/config/optional/views.view.files.yml2
-rw-r--r--core/modules/file/tests/modules/file_test_views/config/optional/views.view.test_file_user_file_data.yml1
-rw-r--r--core/modules/filter/filter.module2
-rw-r--r--core/modules/filter/src/Entity/FilterFormat.php2
-rw-r--r--core/modules/filter/src/FilterFormatInterface.php2
-rw-r--r--core/modules/image/image.install38
-rw-r--r--core/modules/image/src/Hook/ImageRequirements.php57
-rw-r--r--core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php4
-rw-r--r--core/modules/image/tests/modules/image_test_views/test_views/views.view.test_image_user_image_data.yml1
-rw-r--r--core/modules/jsonapi/jsonapi.install51
-rw-r--r--core/modules/jsonapi/src/Hook/JsonapiHooks.php2
-rw-r--r--core/modules/jsonapi/src/Hook/JsonapiRequirements.php72
-rw-r--r--core/modules/jsonapi/src/Normalizer/Value/TemporaryArrayObjectThrowingExceptions.php2
-rw-r--r--core/modules/language/src/Form/NegotiationBrowserDeleteForm.php2
-rw-r--r--core/modules/language/src/LanguageNegotiatorInterface.php2
-rw-r--r--core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php3
-rw-r--r--core/modules/layout_builder/src/Plugin/ConfigAction/AddComponent.php133
-rw-r--r--core/modules/layout_builder/src/Plugin/ConfigAction/Deriver/AddComponentDeriver.php48
-rw-r--r--core/modules/layout_builder/src/Plugin/Derivative/ExtraFieldBlockDeriver.php2
-rw-r--r--core/modules/layout_builder/src/SectionListTrait.php2
-rw-r--r--core/modules/layout_builder/src/SectionStorageInterface.php2
-rw-r--r--core/modules/layout_builder/tests/modules/layout_builder_defaults_test/config/schema/layout_builder_defaults_test.schema.yml8
-rw-r--r--core/modules/layout_builder/tests/src/Kernel/Plugin/ConfigAction/AddComponentTest.php401
-rw-r--r--core/modules/layout_discovery/layout_discovery.install22
-rw-r--r--core/modules/layout_discovery/src/Install/Requirements/LayoutDiscoveryRequirements.php28
-rw-r--r--core/modules/locale/locale.install66
-rw-r--r--core/modules/locale/src/Hook/LocaleRequirements.php82
-rw-r--r--core/modules/locale/src/LocaleConfigManager.php6
-rw-r--r--core/modules/locale/src/StreamWrapper/TranslationsStream.php1
-rw-r--r--core/modules/media/config/optional/views.view.media.yml1
-rw-r--r--core/modules/media/media.install98
-rw-r--r--core/modules/media/src/Hook/MediaRequirementsHooks.php98
-rw-r--r--core/modules/media/src/Install/Requirements/MediaRequirements.php39
-rw-r--r--core/modules/media/src/MediaSourceBase.php3
-rw-r--r--core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml2
-rw-r--r--core/modules/media_library/config/install/views.view.media_library.yml1
-rw-r--r--core/modules/media_library/tests/modules/media_library_test/src/Form/TestNodeFormOverride.php2
-rw-r--r--core/modules/menu_ui/src/Hook/MenuUiHooks.php7
-rw-r--r--core/modules/migrate/tests/src/Kernel/MigrateSourceTestBase.php6
-rw-r--r--core/modules/mysql/src/Driver/Database/mysql/Schema.php4
-rw-r--r--core/modules/mysql/src/Hook/MysqlRequirements.php (renamed from core/modules/mysql/mysql.install)36
-rw-r--r--core/modules/navigation/navigation.install19
-rw-r--r--core/modules/navigation/src/Hook/NavigationRequirements.php38
-rw-r--r--core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php6
-rw-r--r--core/modules/node/config/optional/views.view.content.yml1
-rw-r--r--core/modules/node/config/optional/views.view.glossary.yml1
-rw-r--r--core/modules/node/config/schema/node.schema.yml4
-rw-r--r--core/modules/node/node.install127
-rw-r--r--core/modules/node/node.services.yml13
-rw-r--r--core/modules/node/src/Entity/Node.php2
-rw-r--r--core/modules/node/src/Entity/NodeType.php2
-rw-r--r--core/modules/node/src/Form/NodeForm.php (renamed from core/modules/node/src/NodeForm.php)4
-rw-r--r--core/modules/node/src/Form/NodeTypeForm.php (renamed from core/modules/node/src/NodeTypeForm.php)2
-rw-r--r--core/modules/node/src/Hook/NodeRequirements.php154
-rw-r--r--core/modules/node/src/Plugin/views/wizard/Node.php10
-rw-r--r--core/modules/node/src/Plugin/views/wizard/NodeRevision.php5
-rw-r--r--core/modules/node/tests/modules/node_no_default_author/node_no_default_author.info.yml5
-rw-r--r--core/modules/node/tests/modules/node_no_default_author/src/Hook/NodeNoDefaultAuthorHooks.php31
-rw-r--r--core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml10
-rw-r--r--core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml2
-rw-r--r--core/modules/node/tests/src/Functional/NodeEditFormTest.php10
-rw-r--r--core/modules/node/tests/src/Kernel/NodeRequirementsStatusFilterWarningTest.php3
-rw-r--r--core/modules/package_manager/package_manager.install82
-rw-r--r--core/modules/package_manager/src/Hook/PackageManagerRequirementsHooks.php136
-rw-r--r--core/modules/package_manager/src/Install/Requirements/PackageManagerRequirements.php52
-rw-r--r--core/modules/package_manager/src/Validator/ComposerPluginsValidator.php1
-rw-r--r--core/modules/pgsql/pgsql.install36
-rw-r--r--core/modules/pgsql/src/Driver/Database/pgsql/Schema.php3
-rw-r--r--core/modules/pgsql/src/Hook/PgsqlRequirementsHooks.php54
-rw-r--r--core/modules/pgsql/src/Install/Requirements/PgsqlRequirements.php49
-rw-r--r--core/modules/search/search.install32
-rw-r--r--core/modules/search/src/Entity/SearchPage.php4
-rw-r--r--core/modules/search/src/Hook/SearchRequirements.php50
-rw-r--r--core/modules/sqlite/src/Driver/Database/sqlite/Schema.php4
-rw-r--r--core/modules/system/src/EventSubscriber/AccessRouteAlterSubscriber.php2
-rw-r--r--core/modules/system/src/Plugin/migrate/process/d6/SystemUpdate7000.php2
-rw-r--r--core/modules/system/system.module10
-rw-r--r--core/modules/system/system.post_update.php2
-rw-r--r--core/modules/system/templates/authorize-report.html.twig5
-rw-r--r--core/modules/system/tests/modules/element_info_test/src/Element/DeprecatedExtendsFormElement.php5
-rw-r--r--core/modules/system/tests/modules/element_info_test/src/Element/DeprecatedExtendsRenderElement.php5
-rw-r--r--core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml1
-rw-r--r--core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php4
-rw-r--r--core/modules/system/tests/modules/requirements1_test/requirements1_test.install2
-rw-r--r--core/modules/system/tests/modules/update_script_test/src/Hook/UpdateScriptTestRequirements.php59
-rw-r--r--core/modules/system/tests/modules/update_script_test/update_script_test.install42
-rw-r--r--core/modules/system/tests/modules/update_test_schema/src/Hook/UpdateTestSchemaRequirements.php33
-rw-r--r--core/modules/system/tests/modules/update_test_schema/update_test_schema.install19
-rw-r--r--core/modules/system/tests/src/Functional/FileTransfer/TestFileTransfer.php6
-rw-r--r--core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php5
-rw-r--r--core/modules/update/src/Hook/UpdateHooks.php11
-rw-r--r--core/modules/update/src/Hook/UpdateRequirements.php161
-rw-r--r--core/modules/update/src/UpdateRoot.php10
-rw-r--r--core/modules/update/tests/src/Functional/UpdateManagerTest.php37
-rw-r--r--core/modules/update/tests/src/Functional/UpdateMiscTest.php17
-rw-r--r--core/modules/update/tests/src/Kernel/UpdateDeleteFileIfStaleTest.php1
-rw-r--r--core/modules/update/update.authorize.inc12
-rw-r--r--core/modules/update/update.fetch.inc9
-rw-r--r--core/modules/update/update.install139
-rw-r--r--core/modules/update/update.manager.inc15
-rw-r--r--core/modules/update/update.module35
-rw-r--r--core/modules/update/update.post_update.php23
-rw-r--r--core/modules/update/update.services.yml1
-rw-r--r--core/modules/user/config/optional/views.view.user_admin_people.yml1
-rw-r--r--core/modules/user/src/Hook/UserRequirements.php67
-rw-r--r--core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_bulk_form.yml2
-rw-r--r--core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml1
-rw-r--r--core/modules/user/tests/src/Kernel/UserRequirementsTest.php18
-rw-r--r--core/modules/user/user.install45
-rw-r--r--core/modules/views/config/schema/views.schema.yml15
-rw-r--r--core/modules/views/src/FieldViewsDataProvider.php31
-rw-r--r--core/modules/views/src/Hook/ViewsHooks.php16
-rw-r--r--core/modules/views/src/Hook/ViewsViewsHooks.php26
-rw-r--r--core/modules/views/src/Plugin/views/argument/LanguageArgument.php12
-rw-r--r--core/modules/views/src/Plugin/views/display/Block.php11
-rw-r--r--core/modules/views/src/Plugin/views/field/Boolean.php5
-rw-r--r--core/modules/views/src/Plugin/views/field/Url.php2
-rw-r--r--core/modules/views/src/Plugin/views/filter/FilterPluginBase.php10
-rw-r--r--core/modules/views/src/Plugin/views/style/Table.php2
-rw-r--r--core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php5
-rw-r--r--core/modules/views/src/ViewExecutable.php2
-rw-r--r--core/modules/views/src/ViewsConfigUpdater.php10
-rw-r--r--core/modules/views/tests/fixtures/update/views-block-items-per-page.php48
-rw-r--r--core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml9
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_distinct_click_sorting.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml2
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_placeholder_text.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache_none.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml1
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml2
-rw-r--r--core/modules/views/tests/src/Functional/Plugin/ContextualFiltersBlockContextTest.php2
-rw-r--r--core/modules/views/tests/src/Functional/Update/BlockItemsPerPageUpdateTest.php41
-rw-r--r--core/modules/views/tests/src/Functional/Wizard/ItemsPerPageTest.php121
-rw-r--r--core/modules/views/tests/src/Kernel/Plugin/ViewsBlockTest.php22
-rw-r--r--core/modules/views/tests/src/Unit/Plugin/views/display/BlockTest.php18
-rw-r--r--core/modules/views/views.api.php5
-rw-r--r--core/modules/views/views.post_update.php23
-rw-r--r--core/modules/views_ui/admin.inc4
-rw-r--r--core/modules/workspaces/src/EntityQuery/Tables.php2
-rw-r--r--core/modules/workspaces/src/Install/Requirements/WorkspacesRequirements.php31
-rw-r--r--core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php3
-rw-r--r--core/modules/workspaces/workspaces.install19
181 files changed, 2686 insertions, 1095 deletions
diff --git a/core/modules/big_pipe/big_pipe.module b/core/modules/big_pipe/big_pipe.module
index 75509d6f649e..d04104799cd3 100644
--- a/core/modules/big_pipe/big_pipe.module
+++ b/core/modules/big_pipe/big_pipe.module
@@ -22,6 +22,7 @@ function big_pipe_theme_suggestions_big_pipe_interface_preview(array $variables)
// Use simplified template suggestion, if any.
// For example, this simplifies
+ // phpcs:ignore Drupal.Files.LineLength
// big-pipe-interface-preview--Drupal-block-BlockViewBuilder--lazyBuilder--<BLOCK ID>.html.twig
// to
// big-pipe-interface-preview--block--<BLOCK ID>.html.twig
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_messages_test/big_pipe_messages_test.info.yml b/core/modules/big_pipe/tests/modules/big_pipe_messages_test/big_pipe_messages_test.info.yml
new file mode 100644
index 000000000000..f7522b47887c
--- /dev/null
+++ b/core/modules/big_pipe/tests/modules/big_pipe_messages_test/big_pipe_messages_test.info.yml
@@ -0,0 +1,5 @@
+name: 'BigPipe messages test'
+type: module
+description: 'Forces the messages placeholder to go via the big pipe strategy for testing purposes.'
+package: Testing
+version: VERSION
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_messages_test/src/Hook/BigPipeMessagesHooks.php b/core/modules/big_pipe/tests/modules/big_pipe_messages_test/src/Hook/BigPipeMessagesHooks.php
new file mode 100644
index 000000000000..bb8212bb2848
--- /dev/null
+++ b/core/modules/big_pipe/tests/modules/big_pipe_messages_test/src/Hook/BigPipeMessagesHooks.php
@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\big_pipe_messages_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Security\Attribute\TrustedCallback;
+
+/**
+ * Hook implementations for big_pipe_test.
+ */
+class BigPipeMessagesHooks {
+
+ /**
+ * Implements hook_element_info_alter().
+ */
+ #[Hook('element_info_alter')]
+ public function elementInfoAlter(array &$info): void {
+ $info['status_messages']['#pre_render'][] = static::class . '::preRenderMessages';
+ }
+
+ /**
+ * Pre render callback.
+ *
+ * Removes #placeholder_strategy from the messages element to force the
+ * messages placeholder to go via the big pipe strategy for testing purposes.
+ */
+ #[TrustedCallback]
+ public static function preRenderMessages(array $element): array {
+ if (isset($element['#attached']['placeholders'])) {
+ $key = key($element['#attached']['placeholders']);
+ unset($element['#attached']['placeholders'][$key]['#placeholder_strategy_denylist']);
+ }
+ if (isset($element['messages']['#attached']['placeholders'])) {
+ $key = key($element['messages']['#attached']['placeholders']);
+ unset($element['messages']['#attached']['placeholders'][$key]['#placeholder_strategy_denylist']);
+ }
+ return $element;
+ }
+
+}
diff --git a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php b/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
index 26e92fc14982..1ca75bb57262 100644
--- a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
+++ b/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
@@ -32,7 +32,7 @@ class BigPipeTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
- protected static $modules = ['big_pipe', 'big_pipe_test', 'dblog'];
+ protected static $modules = ['big_pipe', 'big_pipe_messages_test', 'big_pipe_test', 'dblog'];
/**
* {@inheritdoc}
@@ -88,9 +88,10 @@ class BigPipeTest extends BrowserTestBase {
// 2. Session (authenticated).
$this->drupalLogin($this->rootUser);
+ $this->drupalGet(Url::fromRoute('big_pipe_test'));
$this->assertSessionCookieExists('1');
$this->assertBigPipeNoJsCookieExists('0');
- $this->assertSession()->responseContains('<noscript><meta http-equiv="Refresh" content="0; URL=' . base_path() . 'big_pipe/no-js?destination=' . UrlHelper::encodePath(base_path() . 'user/1?check_logged_in=1') . '" />' . "\n" . '</noscript>');
+ $this->assertSession()->responseContains('<noscript><meta http-equiv="Refresh" content="0; URL=' . base_path() . 'big_pipe/no-js?destination=' . UrlHelper::encodePath(base_path() . 'big_pipe_test') . '" />' . "\n" . '</noscript>');
$this->assertSession()->responseNotContains($no_js_to_js_markup);
$this->assertBigPipeNoJsMetaRefreshRedirect();
$this->assertBigPipeNoJsCookieExists('1');
@@ -103,10 +104,10 @@ class BigPipeTest extends BrowserTestBase {
// 3. Session (anonymous).
$this->drupalGet(Url::fromRoute('user.login', [], ['query' => ['trigger_session' => 1]]));
- $this->drupalGet(Url::fromRoute('user.login'));
+ $this->drupalGet(Url::fromRoute('big_pipe_test'));
$this->assertSessionCookieExists('1');
$this->assertBigPipeNoJsCookieExists('0');
- $this->assertSession()->responseContains('<noscript><meta http-equiv="Refresh" content="0; URL=' . base_path() . 'big_pipe/no-js?destination=' . base_path() . 'user/login" />' . "\n" . '</noscript>');
+ $this->assertSession()->responseContains('<noscript><meta http-equiv="Refresh" content="0; URL=' . base_path() . 'big_pipe/no-js?destination=' . base_path() . 'big_pipe_test" />' . "\n" . '</noscript>');
$this->assertSession()->responseNotContains($no_js_to_js_markup);
$this->assertBigPipeNoJsMetaRefreshRedirect();
$this->assertBigPipeNoJsCookieExists('1');
@@ -314,14 +315,9 @@ class BigPipeTest extends BrowserTestBase {
// @see performMetaRefresh()
$this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence'));
- // cspell:disable-next-line
- $big_pipe_placeholder_id = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
- $expected_placeholder_replacement = '<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
$this->assertSession()->pageTextContains('The count is 1.');
$this->assertSession()->responseNotContains('The count is 2.');
$this->assertSession()->responseNotContains('The count is 3.');
- $raw_content = $this->getSession()->getPage()->getContent();
- $this->assertSame(1, substr_count($raw_content, $expected_placeholder_replacement), 'Only one placeholder replacement was found for the duplicate #lazy_builder arrays.');
// By calling performMetaRefresh() here, we simulate JavaScript being
// disabled, because as far as the BigPipe module is concerned, it is
diff --git a/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php b/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php
index 20244a8fa098..8e4abff5734f 100644
--- a/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php
+++ b/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php
@@ -22,6 +22,7 @@ class BigPipeRegressionTest extends WebDriverTestBase {
*/
protected static $modules = [
'big_pipe',
+ 'big_pipe_messages_test',
'big_pipe_regression_test',
];
diff --git a/core/modules/block/src/Hook/BlockHooks.php b/core/modules/block/src/Hook/BlockHooks.php
index 98cd3992fbc4..657109309a30 100644
--- a/core/modules/block/src/Hook/BlockHooks.php
+++ b/core/modules/block/src/Hook/BlockHooks.php
@@ -82,13 +82,14 @@ class BlockHooks {
* block.html.twig is used.
*
* Most themes use their own copy of block.html.twig. The default is located
- * inside "core/modules/block/templates/block.html.twig". Look in there for the
- * full list of available variables.
+ * inside "core/modules/block/templates/block.html.twig". Look in there for
+ * the full list of available variables.
*
* @param array $variables
* An associative array containing:
- * - elements: An associative array containing the properties of the element.
- * Properties used: #block, #configuration, #children, #plugin_id.
+ * - elements: An associative array containing the properties of the
+ * element. Properties used: #block, #configuration, #children,
+ * and #plugin_id.
*/
public function preprocessBlock(&$variables): void {
$variables['configuration'] = $variables['elements']['#configuration'];
@@ -98,8 +99,8 @@ class BlockHooks {
$variables['in_preview'] = $variables['elements']['#in_preview'] ?? FALSE;
$variables['label'] = !empty($variables['configuration']['label_display']) ? $variables['configuration']['label'] : '';
$variables['content'] = $variables['elements']['content'];
- // A block's label is configuration: it is static. Allow dynamic labels to be
- // set in the render array.
+ // A block's label is configuration: it is static. Allow dynamic labels to
+ // be set in the render array.
if (isset($variables['elements']['content']['#title']) && !empty($variables['configuration']['label_display'])) {
$variables['label'] = $variables['elements']['content']['#title'];
}
diff --git a/core/modules/block_content/config/optional/views.view.block_content.yml b/core/modules/block_content/config/optional/views.view.block_content.yml
index 1bccbb446467..a8da86eede0b 100644
--- a/core/modules/block_content/config/optional/views.view.block_content.yml
+++ b/core/modules/block_content/config/optional/views.view.block_content.yml
@@ -496,6 +496,7 @@ display:
empty_table: true
caption: ''
description: ''
+ class: ''
row:
type: fields
query:
diff --git a/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_redirect_destination.yml b/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_redirect_destination.yml
index 8f902c94bda2..df0c7314f9a6 100644
--- a/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_redirect_destination.yml
+++ b/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_redirect_destination.yml
@@ -63,6 +63,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/ckeditor5/src/HTMLRestrictions.php b/core/modules/ckeditor5/src/HTMLRestrictions.php
index eb35e61b1e93..e10e81ef46da 100644
--- a/core/modules/ckeditor5/src/HTMLRestrictions.php
+++ b/core/modules/ckeditor5/src/HTMLRestrictions.php
@@ -1102,7 +1102,7 @@ final class HTMLRestrictions {
}
/**
- * Extracts the subset of plain tags (attributes omitted) from allowed elements.
+ * Extracts plain tags (attributes omitted) from allowed elements.
*
* @return \Drupal\ckeditor5\HTMLRestrictions
* The extracted subset of the given set of HTML restrictions.
diff --git a/core/modules/ckeditor5/src/Plugin/CKEditor5PluginDefinition.php b/core/modules/ckeditor5/src/Plugin/CKEditor5PluginDefinition.php
index fdce2aceade6..8ef1e8aea449 100644
--- a/core/modules/ckeditor5/src/Plugin/CKEditor5PluginDefinition.php
+++ b/core/modules/ckeditor5/src/Plugin/CKEditor5PluginDefinition.php
@@ -467,7 +467,8 @@ final class CKEditor5PluginDefinition extends PluginDefinition implements Plugin
* Whether this plugin has an asset library to load on the admin UI.
*
* @return bool
- * TRUE if the plugin has an asset library to load on the admin UI, FALSE otherwise.
+ * TRUE if the plugin has an asset library to load on the admin UI, FALSE
+ * otherwise.
*
* @see \Drupal\ckeditor5\Annotation\DrupalAspectsOfCKEditor5Plugin::$admin_library
*/
diff --git a/core/modules/ckeditor5/src/SmartDefaultSettings.php b/core/modules/ckeditor5/src/SmartDefaultSettings.php
index ec3f90fa250c..3c2d0d9ec2c0 100644
--- a/core/modules/ckeditor5/src/SmartDefaultSettings.php
+++ b/core/modules/ckeditor5/src/SmartDefaultSettings.php
@@ -76,7 +76,7 @@ final class SmartDefaultSettings {
}
/**
- * Computes the closest possible equivalent settings for switching to CKEditor 5.
+ * Computes the closest equivalent settings for switching to CKEditor 5.
*
* @param \Drupal\editor\EditorInterface|null $text_editor
* The editor being reconfigured for CKEditor 5; infer the settings based on
diff --git a/core/modules/ckeditor5/tests/src/Unit/HTMLRestrictionsTest.php b/core/modules/ckeditor5/tests/src/Unit/HTMLRestrictionsTest.php
index f512c008ceeb..dfd6c1db1da8 100644
--- a/core/modules/ckeditor5/tests/src/Unit/HTMLRestrictionsTest.php
+++ b/core/modules/ckeditor5/tests/src/Unit/HTMLRestrictionsTest.php
@@ -1131,8 +1131,8 @@ class HTMLRestrictionsTest extends UnitTestCase {
'expected_union' => 'a',
];
yield 'attribute restrictions are different: <ol type=*> vs <ol type="A"> — vice versa' => [
- 'b' => new HTMLRestrictions(['ol' => ['type' => ['A' => TRUE]]]),
- 'a' => new HTMLRestrictions(['ol' => ['type' => TRUE]]),
+ 'a' => new HTMLRestrictions(['ol' => ['type' => ['A' => TRUE]]]),
+ 'b' => new HTMLRestrictions(['ol' => ['type' => TRUE]]),
'expected_diff' => HTMLRestrictions::emptySet(),
'expected_intersection' => 'a',
'expected_union' => 'b',
@@ -1145,8 +1145,8 @@ class HTMLRestrictionsTest extends UnitTestCase {
'expected_union' => 'a',
];
yield 'attribute restrictions are different: <ol type=*> vs <ol type="1"> — vice versa' => [
- 'b' => new HTMLRestrictions(['ol' => ['type' => ['1' => TRUE]]]),
- 'a' => new HTMLRestrictions(['ol' => ['type' => TRUE]]),
+ 'a' => new HTMLRestrictions(['ol' => ['type' => ['1' => TRUE]]]),
+ 'b' => new HTMLRestrictions(['ol' => ['type' => TRUE]]),
'expected_diff' => HTMLRestrictions::emptySet(),
'expected_intersection' => 'a',
'expected_union' => 'b',
diff --git a/core/modules/comment/config/optional/views.view.comment.yml b/core/modules/comment/config/optional/views.view.comment.yml
index a54fb25883dd..f3edd69dc65f 100644
--- a/core/modules/comment/config/optional/views.view.comment.yml
+++ b/core/modules/comment/config/optional/views.view.comment.yml
@@ -823,6 +823,7 @@ display:
empty_table: true
caption: ''
description: ''
+ class: ''
row:
type: fields
query:
diff --git a/core/modules/comment/src/Plugin/views/field/LastTimestamp.php b/core/modules/comment/src/Plugin/views/field/LastTimestamp.php
index c0bd35f30cae..3f1698d12363 100644
--- a/core/modules/comment/src/Plugin/views/field/LastTimestamp.php
+++ b/core/modules/comment/src/Plugin/views/field/LastTimestamp.php
@@ -9,7 +9,7 @@ use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
/**
- * Field handler to display the timestamp of a comment with the count of comments.
+ * Displays the timestamp of a comment with the count of comments.
*
* @ingroup views_field_handlers
*/
diff --git a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_operations.yml b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_operations.yml
index fdabeedeed4a..50b74fc66422 100644
--- a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_operations.yml
+++ b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_operations.yml
@@ -68,6 +68,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_schema.yml b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_schema.yml
index 04fcb57e2a8b..e174974fe2f8 100644
--- a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_schema.yml
+++ b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_schema.yml
@@ -235,6 +235,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: false
columns:
diff --git a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_new_comments.yml b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_new_comments.yml
index b2e2cf4a41c2..fb0736cf4486 100644
--- a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_new_comments.yml
+++ b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_new_comments.yml
@@ -38,6 +38,11 @@ display:
type: full
style:
type: table
+ options:
+ grouping: { }
+ class: ''
+ row_class: ''
+ default_row_class: true
row:
type: fields
fields:
diff --git a/core/modules/content_moderation/config/optional/views.view.moderated_content.yml b/core/modules/content_moderation/config/optional/views.view.moderated_content.yml
index ae4fda31e816..f7e80ac1162c 100644
--- a/core/modules/content_moderation/config/optional/views.view.moderated_content.yml
+++ b/core/modules/content_moderation/config/optional/views.view.moderated_content.yml
@@ -765,6 +765,7 @@ display:
empty_table: true
caption: ''
description: ''
+ class: ''
row:
type: fields
query:
diff --git a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
index 41174b60490b..eadb49642080 100644
--- a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
+++ b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
@@ -56,7 +56,7 @@ class ModerationStateFieldItemList extends FieldItemList {
// It is possible that the bundle does not exist at this point. For example,
// the node type form creates a fake Node entity to get default values.
- // @see \Drupal\node\NodeTypeForm::form()
+ // @see \Drupal\node\Form\NodeTypeForm::form()
$workflow = $moderation_info->getWorkFlowForEntity($entity);
return $workflow ? $workflow->getTypePlugin()->getInitialState($entity)->id() : NULL;
}
diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.latest.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.latest.yml
index 83a6b62da89b..8d2a8519c32f 100644
--- a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.latest.yml
+++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.latest.yml
@@ -348,6 +348,11 @@ display:
group_items: { }
style:
type: table
+ options:
+ grouping: { }
+ class: ''
+ row_class: ''
+ default_row_class: true
row:
type: fields
query:
diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_sort_base_table.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_sort_base_table.yml
index 16ba5ba97ac1..9c1cb4e26dce 100644
--- a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_sort_base_table.yml
+++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_sort_base_table.yml
@@ -206,6 +206,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
columns:
diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_sort_revision_table.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_sort_revision_table.yml
index 1e7e3b48ceaf..c2f10a8a729c 100644
--- a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_sort_revision_table.yml
+++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_sort_revision_table.yml
@@ -205,6 +205,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
columns:
diff --git a/core/modules/content_translation/tests/modules/content_translation_test_views/test_views/views.view.test_entity_translations_link.yml b/core/modules/content_translation/tests/modules/content_translation_test_views/test_views/views.view.test_entity_translations_link.yml
index ffa73e8edef8..d5c5c667a00d 100644
--- a/core/modules/content_translation/tests/modules/content_translation_test_views/test_views/views.view.test_entity_translations_link.yml
+++ b/core/modules/content_translation/tests/modules/content_translation_test_views/test_views/views.view.test_entity_translations_link.yml
@@ -37,6 +37,7 @@ display:
style:
type: table
options:
+ class: ''
columns:
name: name
translation_link: translation_link
diff --git a/core/modules/contextual/src/Hook/ContextualThemeHooks.php b/core/modules/contextual/src/Hook/ContextualThemeHooks.php
index 47db1f9bde6a..760a42c97854 100644
--- a/core/modules/contextual/src/Hook/ContextualThemeHooks.php
+++ b/core/modules/contextual/src/Hook/ContextualThemeHooks.php
@@ -38,14 +38,16 @@ class ContextualThemeHooks {
if (isset($element) && is_array($element) && !empty($element['#contextual_links'])) {
$variables['#cache']['contexts'][] = 'user.permissions';
if ($this->currentUser->hasPermission('access contextual links')) {
- // Mark this element as potentially having contextual links attached to it.
+ // Mark this element as potentially having contextual links attached to
+ // it.
$variables['attributes']['class'][] = 'contextual-region';
- // Renders a contextual links placeholder unconditionally, thus not breaking
- // the render cache. Although the empty placeholder is rendered for all
- // users, contextual_page_attachments() only adds the asset library for
- // users with the 'access contextual links' permission, thus preventing
- // unnecessary HTTP requests for users without that permission.
+ // Renders a contextual links placeholder unconditionally, thus not
+ // breaking the render cache. Although the empty placeholder is rendered
+ // for all users, contextual_page_attachments() only adds the asset
+ // library for users with the 'access contextual links' permission, thus
+ // preventing unnecessary HTTP requests for users without that
+ // permission.
$variables['title_suffix']['contextual_links'] = [
'#type' => 'contextual_links_placeholder',
'#id' => _contextual_links_to_id($element['#contextual_links']),
diff --git a/core/modules/datetime/datetime.module b/core/modules/datetime/datetime.module
index 94cfaf6e9063..b5ff7ffa23cb 100644
--- a/core/modules/datetime/datetime.module
+++ b/core/modules/datetime/datetime.module
@@ -17,7 +17,9 @@ use Drupal\field\FieldStorageConfigInterface;
* @param \Drupal\field\FieldStorageConfigInterface $field_storage
* The field storage config entity.
* @param array $data
- * Field view data or FieldViewsDataProvider::defaultFieldImplementation($field_storage) if empty.
+ * Field view data or
+ * FieldViewsDataProvider::defaultFieldImplementation($field_storage) if
+ * empty.
* @param string $column_name
* The schema column name with the datetime value.
*
diff --git a/core/modules/datetime/src/DateTimeViewsHelper.php b/core/modules/datetime/src/DateTimeViewsHelper.php
index 61d959e70efd..5e2f49b9e1d8 100644
--- a/core/modules/datetime/src/DateTimeViewsHelper.php
+++ b/core/modules/datetime/src/DateTimeViewsHelper.php
@@ -29,7 +29,8 @@ class DateTimeViewsHelper {
* @param \Drupal\field\FieldStorageConfigInterface $field_storage
* The field storage config entity.
* @param array $data
- * Field view data or views_field_default_views_data($field_storage) if empty.
+ * Field view data or views_field_default_views_data($field_storage) if
+ * empty.
* @param string $column_name
* The schema column name with the datetime value.
*
diff --git a/core/modules/dblog/config/optional/views.view.watchdog.yml b/core/modules/dblog/config/optional/views.view.watchdog.yml
index 3050881f0de7..224519da1e5c 100644
--- a/core/modules/dblog/config/optional/views.view.watchdog.yml
+++ b/core/modules/dblog/config/optional/views.view.watchdog.yml
@@ -654,6 +654,7 @@ display:
empty_table: false
caption: ''
description: ''
+ class: ''
row:
type: fields
query:
diff --git a/core/modules/field/src/Hook/FieldHooks.php b/core/modules/field/src/Hook/FieldHooks.php
index aa759522f458..274482f9ada6 100644
--- a/core/modules/field/src/Hook/FieldHooks.php
+++ b/core/modules/field/src/Hook/FieldHooks.php
@@ -313,8 +313,8 @@ class FieldHooks {
/**
* Implements hook_ENTITY_TYPE_update() for 'field_storage_config'.
*
- * Reset the field handler settings, when the storage target_type is changed on
- * an entity reference field.
+ * Reset the field handler settings, when the storage target_type is changed
+ * on an entity reference field.
*/
#[Hook('field_storage_config_update')]
public function fieldStorageConfigUpdate(FieldStorageConfigInterface $field_storage): void {
@@ -330,13 +330,14 @@ class FieldHooks {
if ($class !== $item_class && !is_subclass_of($class, $item_class)) {
return;
}
- // If target_type changed, reset the handler in the fields using that storage.
+ // If target_type changed, reset the handler in the fields using that
+ // storage.
if ($field_storage->getSetting('target_type') !== $field_storage->getOriginal()->getSetting('target_type')) {
foreach ($field_storage->getBundles() as $bundle) {
$field = FieldConfig::loadByName($field_storage->getTargetEntityTypeId(), $bundle, $field_storage->getName());
- // Reset the handler settings. This triggers field_field_config_presave(),
- // which will take care of reassigning the handler to the correct
- // derivative for the new target_type.
+ // Reset the handler settings. This triggers
+ // field_field_config_presave(), which will take care of reassigning the
+ // handler to the correct derivative for the new target_type.
$field->setSetting('handler_settings', []);
$field->save();
}
@@ -394,9 +395,9 @@ class FieldHooks {
return;
}
// In case we removed all the target bundles allowed by the field in
- // EntityReferenceItem::onDependencyRemoval() or field_entity_bundle_delete()
- // we have to log a critical message because the field will not function
- // correctly anymore.
+ // EntityReferenceItem::onDependencyRemoval() or
+ // field_entity_bundle_delete() we have to log a critical message because
+ // the field will not function correctly anymore.
$handler_settings = $field->getSetting('handler_settings');
if (isset($handler_settings['target_bundles']) && $handler_settings['target_bundles'] === []) {
\Drupal::logger('entity_reference')->critical('The %field_name entity reference field (entity_type: %entity_type, bundle: %bundle) no longer has any valid bundle it can reference. The field is not working correctly anymore and has to be adjusted.', [
diff --git a/core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php b/core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php
index 652b72b49ba0..c2fddcda697f 100644
--- a/core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php
+++ b/core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php
@@ -70,7 +70,7 @@ trait FieldLayoutEntityDisplayTrait {
}
/**
- * Implements \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface::setLayout().
+ * {@inheritdoc}
*/
public function setLayout(LayoutInterface $layout) {
$this->setLayoutId($layout->getPluginId(), $layout->getConfiguration());
@@ -78,7 +78,7 @@ trait FieldLayoutEntityDisplayTrait {
}
/**
- * Implements \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface::getLayout().
+ * {@inheritdoc}
*/
public function getLayout() {
return $this->doGetLayout($this->getLayoutId(), $this->getLayoutSettings());
diff --git a/core/modules/field_ui/src/Form/FieldConfigEditForm.php b/core/modules/field_ui/src/Form/FieldConfigEditForm.php
index 04e6ebb92f8f..d723d93fa900 100644
--- a/core/modules/field_ui/src/Form/FieldConfigEditForm.php
+++ b/core/modules/field_ui/src/Form/FieldConfigEditForm.php
@@ -530,7 +530,8 @@ class FieldConfigEditForm extends EntityForm {
* The parent entity that the field is attached to.
*
* @return \Drupal\Core\TypedData\TypedDataInterface
- * The typed data object representing the field configuration and its default value.
+ * The typed data object representing the field configuration and its
+ * default value.
*/
private function getTypedData(FieldConfigInterface $field_config, FieldableEntityInterface $parent): TypedDataInterface {
// Make sure that typed data manager is re-generating the instance. This
diff --git a/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php
index 4e9aafe8dbc6..2dd200cfe3ef 100644
--- a/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php
+++ b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php
@@ -263,7 +263,8 @@ class FieldStorageConfigEditForm extends EntityForm {
* returns that cardinality or NULL if no cardinality has been enforced.
*
* @return int|null
- * The enforced cardinality as an integer, or NULL if no cardinality is enforced.
+ * The enforced cardinality as an integer, or NULL if no cardinality is
+ * enforced.
*/
protected function getEnforcedCardinality() {
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
diff --git a/core/modules/field_ui/src/Hook/FieldUiHooks.php b/core/modules/field_ui/src/Hook/FieldUiHooks.php
index 06434e2ec4df..374cb331fc57 100644
--- a/core/modules/field_ui/src/Hook/FieldUiHooks.php
+++ b/core/modules/field_ui/src/Hook/FieldUiHooks.php
@@ -238,8 +238,8 @@ class FieldUiHooks {
#[Hook('form_field_ui_field_storage_add_form_alter')]
public function formFieldUiFieldStorageAddFormAlter(array &$form) : void {
$optgroup = (string) $this->t('Reference');
- // Move the "Entity reference" option to the end of the list and rename it to
- // "Other".
+ // Move the "Entity reference" option to the end of the list and rename it
+ // to "Other".
unset($form['add']['new_storage_type']['#options'][$optgroup]['entity_reference']);
$form['add']['new_storage_type']['#options'][$optgroup]['entity_reference'] = $this->t('Other…');
}
@@ -249,7 +249,7 @@ class FieldUiHooks {
*
* Adds a button 'Save and manage fields' to forms.
*
- * @see \Drupal\node\NodeTypeForm
+ * @see \Drupal\node\Form\NodeTypeForm
* @see \Drupal\comment\CommentTypeForm
* @see \Drupal\media\MediaTypeForm
* @see \Drupal\block_content\BlockContentTypeForm
diff --git a/core/modules/file/config/optional/views.view.files.yml b/core/modules/file/config/optional/views.view.files.yml
index be8ba838966e..c24c710714ad 100644
--- a/core/modules/file/config/optional/views.view.files.yml
+++ b/core/modules/file/config/optional/views.view.files.yml
@@ -761,6 +761,7 @@ display:
empty_table: true
caption: ''
description: ''
+ class: ''
row:
type: fields
query:
@@ -1152,6 +1153,7 @@ display:
empty_table: true
caption: ''
description: ''
+ class: ''
row:
type: fields
options: { }
diff --git a/core/modules/file/tests/modules/file_test_views/config/optional/views.view.test_file_user_file_data.yml b/core/modules/file/tests/modules/file_test_views/config/optional/views.view.test_file_user_file_data.yml
index 7924edd65726..78fd4196bf70 100644
--- a/core/modules/file/tests/modules/file_test_views/config/optional/views.view.test_file_user_file_data.yml
+++ b/core/modules/file/tests/modules/file_test_views/config/optional/views.view.test_file_user_file_data.yml
@@ -28,6 +28,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module
index 89b8e2e1e6f9..38da47a437ed 100644
--- a/core/modules/filter/filter.module
+++ b/core/modules/filter/filter.module
@@ -637,7 +637,7 @@ function _filter_autop($text) {
// and comments. We don't apply any processing to the contents of these tags
// to avoid messing up code. We look for matched pairs and allow basic
// nesting. For example:
- // "processed <pre> ignored <script> ignored </script> ignored </pre> processed"
+ // "processed<pre>ignored<script>ignored</script>ignored</pre>processed"
$chunks = preg_split('@(<!--.*?-->|</?(?:pre|script|style|object|iframe|drupal-media|svg|!--)[^>]*>)@i', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
// Note: PHP ensures the array consists of alternating delimiters and literals
// and begins and ends with a literal (inserting NULL as required).
diff --git a/core/modules/filter/src/Entity/FilterFormat.php b/core/modules/filter/src/Entity/FilterFormat.php
index 12e7846163b6..8b785122dc49 100644
--- a/core/modules/filter/src/Entity/FilterFormat.php
+++ b/core/modules/filter/src/Entity/FilterFormat.php
@@ -99,7 +99,7 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface, En
protected $weight = 0;
/**
- * List of user role IDs to grant access to use this format on initial creation.
+ * List of role IDs to grant access to use this format on initial creation.
*
* This property is always empty and unused for existing text formats.
*
diff --git a/core/modules/filter/src/FilterFormatInterface.php b/core/modules/filter/src/FilterFormatInterface.php
index 62a704a6fcbe..8c63f33f0a6b 100644
--- a/core/modules/filter/src/FilterFormatInterface.php
+++ b/core/modules/filter/src/FilterFormatInterface.php
@@ -10,7 +10,7 @@ use Drupal\Core\Config\Entity\ConfigEntityInterface;
interface FilterFormatInterface extends ConfigEntityInterface {
/**
- * Returns the ordered collection of filter plugin instances or an individual plugin instance.
+ * Returns a sorted collection of filter plugins or an individual instance.
*
* @param string $instance_id
* (optional) The ID of a filter plugin instance to return.
diff --git a/core/modules/image/image.install b/core/modules/image/image.install
index b99e17924ef6..be8fd0272343 100644
--- a/core/modules/image/image.install
+++ b/core/modules/image/image.install
@@ -33,44 +33,6 @@ function image_uninstall(): void {
}
/**
- * Implements hook_requirements().
- */
-function image_requirements($phase): array {
- if ($phase != 'runtime') {
- return [];
- }
-
- $toolkit = \Drupal::service('image.toolkit.manager')->getDefaultToolkit();
- if ($toolkit) {
- $plugin_definition = $toolkit->getPluginDefinition();
- $requirements = [
- 'image.toolkit' => [
- 'title' => t('Image toolkit'),
- 'value' => $toolkit->getPluginId(),
- 'description' => $plugin_definition['title'],
- ],
- ];
-
- foreach ($toolkit->getRequirements() as $key => $requirement) {
- $namespaced_key = 'image.toolkit.' . $toolkit->getPluginId() . '.' . $key;
- $requirements[$namespaced_key] = $requirement;
- }
- }
- else {
- $requirements = [
- 'image.toolkit' => [
- 'title' => t('Image toolkit'),
- 'value' => t('None'),
- 'description' => t("No image toolkit is configured on the site. Check PHP installed extensions or add a contributed toolkit that doesn't require a PHP extension. Make sure that at least one valid image toolkit is installed."),
- 'severity' => REQUIREMENT_ERROR,
- ],
- ];
- }
-
- return $requirements;
-}
-
-/**
* Implements hook_update_last_removed().
*/
function image_update_last_removed(): int {
diff --git a/core/modules/image/src/Hook/ImageRequirements.php b/core/modules/image/src/Hook/ImageRequirements.php
new file mode 100644
index 000000000000..cf631bfe3754
--- /dev/null
+++ b/core/modules/image/src/Hook/ImageRequirements.php
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\image\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\ImageToolkit\ImageToolkitManager;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Requirements for the Image module.
+ */
+class ImageRequirements {
+
+ use StringTranslationTrait;
+
+ public function __construct(
+ protected readonly ImageToolkitManager $imageToolkitManager,
+ ) {}
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $toolkit = $this->imageToolkitManager->getDefaultToolkit();
+ if ($toolkit) {
+ $plugin_definition = $toolkit->getPluginDefinition();
+ $requirements = [
+ 'image.toolkit' => [
+ 'title' => $this->t('Image toolkit'),
+ 'value' => $toolkit->getPluginId(),
+ 'description' => $plugin_definition['title'],
+ ],
+ ];
+
+ foreach ($toolkit->getRequirements() as $key => $requirement) {
+ $namespaced_key = 'image.toolkit.' . $toolkit->getPluginId() . '.' . $key;
+ $requirements[$namespaced_key] = $requirement;
+ }
+ }
+ else {
+ $requirements = [
+ 'image.toolkit' => [
+ 'title' => $this->t('Image toolkit'),
+ 'value' => $this->t('None'),
+ 'description' => $this->t("No image toolkit is configured on the site. Check PHP installed extensions or add a contributed toolkit that doesn't require a PHP extension. Make sure that at least one valid image toolkit is installed."),
+ 'severity' => REQUIREMENT_ERROR,
+ ],
+ ];
+ }
+
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
index 4830686472ce..9d58ad40cc70 100644
--- a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
+++ b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
@@ -108,9 +108,7 @@ class ImageWidget extends FileWidget {
}
/**
- * Overrides \Drupal\file\Plugin\Field\FieldWidget\FileWidget::formMultipleElements().
- *
- * Special handling for draggable multiple widgets and 'add more' button.
+ * {@inheritdoc}
*/
protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
$elements = parent::formMultipleElements($items, $form, $form_state);
diff --git a/core/modules/image/tests/modules/image_test_views/test_views/views.view.test_image_user_image_data.yml b/core/modules/image/tests/modules/image_test_views/test_views/views.view.test_image_user_image_data.yml
index cd3cf68914c4..97b13e5332e3 100644
--- a/core/modules/image/tests/modules/image_test_views/test_views/views.view.test_image_user_image_data.yml
+++ b/core/modules/image/tests/modules/image_test_views/test_views/views.view.test_image_user_image_data.yml
@@ -28,6 +28,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/jsonapi/jsonapi.install b/core/modules/jsonapi/jsonapi.install
index c5a60c424fbe..47efd893ae0e 100644
--- a/core/modules/jsonapi/jsonapi.install
+++ b/core/modules/jsonapi/jsonapi.install
@@ -5,8 +5,6 @@
* Module install file.
*/
-use Drupal\Core\Url;
-
/**
* Implements hook_install().
*/
@@ -28,55 +26,6 @@ function jsonapi_install(): void {
}
/**
- * Implements hook_requirements().
- */
-function jsonapi_requirements($phase): array {
- $requirements = [];
- if ($phase === 'runtime') {
- $module_handler = \Drupal::moduleHandler();
- $potential_conflicts = [
- 'content_translation',
- 'config_translation',
- 'language',
- ];
- $should_warn = array_reduce($potential_conflicts, function ($should_warn, $module_name) use ($module_handler) {
- return $should_warn ?: $module_handler->moduleExists($module_name);
- }, FALSE);
- if ($should_warn) {
- $requirements['jsonapi_multilingual_support'] = [
- 'title' => t('JSON:API multilingual support'),
- 'value' => t('Limited'),
- 'severity' => REQUIREMENT_INFO,
- 'description' => t('Some multilingual features currently do not work well with JSON:API. See the <a href=":jsonapi-docs">JSON:API multilingual support documentation</a> for more information on the current status of multilingual support.', [
- ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/translations',
- ]),
- ];
- }
- $requirements['jsonapi_revision_support'] = [
- 'title' => t('JSON:API revision support'),
- 'value' => t('Limited'),
- 'severity' => REQUIREMENT_INFO,
- 'description' => t('Revision support is currently read-only and only for the "Content" and "Media" entity types in JSON:API. See the <a href=":jsonapi-docs">JSON:API revision support documentation</a> for more information on the current status of revision support.', [
- ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/revisions',
- ]),
- ];
- $requirements['jsonapi_read_only_mode'] = [
- 'title' => t('JSON:API allowed operations'),
- 'value' => t('Read-only'),
- 'severity' => REQUIREMENT_INFO,
- ];
- if (!\Drupal::configFactory()->get('jsonapi.settings')->get('read_only')) {
- $requirements['jsonapi_read_only_mode']['value'] = t('All (create, read, update, delete)');
- $requirements['jsonapi_read_only_mode']['description'] = t('It is recommended to <a href=":configure-url">configure</a> JSON:API to only accept all operations if the site requires it. <a href=":docs">Learn more about securing your site with JSON:API.</a>', [
- ':docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/security-considerations',
- ':configure-url' => Url::fromRoute('jsonapi.settings')->toString(),
- ]);
- }
- }
- return $requirements;
-}
-
-/**
* Implements hook_update_last_removed().
*/
function jsonapi_update_last_removed(): int {
diff --git a/core/modules/jsonapi/src/Hook/JsonapiHooks.php b/core/modules/jsonapi/src/Hook/JsonapiHooks.php
index 7db75a3297be..7db5b77b0e80 100644
--- a/core/modules/jsonapi/src/Hook/JsonapiHooks.php
+++ b/core/modules/jsonapi/src/Hook/JsonapiHooks.php
@@ -218,7 +218,7 @@ class JsonapiHooks {
public function jsonapiShortcutFilterAccess(EntityTypeInterface $entity_type, AccountInterface $account): array {
// @see \Drupal\shortcut\ShortcutAccessControlHandler::checkAccess()
// \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
- // (shortcut_set = $shortcut_set_storage->getDisplayedToUser($current_user)),
+ // "shortcut_set = $shortcut_set_storage->getDisplayedToUser($current_user)"
// so this does not have to.
return [
JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer shortcuts')->orIf(AccessResult::allowedIfHasPermissions($account, [
diff --git a/core/modules/jsonapi/src/Hook/JsonapiRequirements.php b/core/modules/jsonapi/src/Hook/JsonapiRequirements.php
new file mode 100644
index 000000000000..5cc0225e1839
--- /dev/null
+++ b/core/modules/jsonapi/src/Hook/JsonapiRequirements.php
@@ -0,0 +1,72 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\jsonapi\Hook;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+
+/**
+ * Requirements for the JSON:API module.
+ */
+class JsonapiRequirements {
+
+ use StringTranslationTrait;
+
+ public function __construct(
+ protected readonly ConfigFactoryInterface $configFactory,
+ protected readonly ModuleHandlerInterface $moduleHandler,
+ ) {}
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+ $potential_conflicts = [
+ 'content_translation',
+ 'config_translation',
+ 'language',
+ ];
+ $should_warn = array_reduce($potential_conflicts, function ($should_warn, $module_name) {
+ return $should_warn ?: $this->moduleHandler->moduleExists($module_name);
+ }, FALSE);
+ if ($should_warn) {
+ $requirements['jsonapi_multilingual_support'] = [
+ 'title' => $this->t('JSON:API multilingual support'),
+ 'value' => $this->t('Limited'),
+ 'severity' => REQUIREMENT_INFO,
+ 'description' => $this->t('Some multilingual features currently do not work well with JSON:API. See the <a href=":jsonapi-docs">JSON:API multilingual support documentation</a> for more information on the current status of multilingual support.', [
+ ':jsonapi-docs' => 'https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module/translations',
+ ]),
+ ];
+ }
+ $requirements['jsonapi_revision_support'] = [
+ 'title' => $this->t('JSON:API revision support'),
+ 'value' => $this->t('Limited'),
+ 'severity' => REQUIREMENT_INFO,
+ 'description' => $this->t('Revision support is currently read-only and only for the "Content" and "Media" entity types in JSON:API. See the <a href=":jsonapi-docs">JSON:API revision support documentation</a> for more information on the current status of revision support.', [
+ ':jsonapi-docs' => 'https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module/revisions',
+ ]),
+ ];
+ $requirements['jsonapi_read_only_mode'] = [
+ 'title' => $this->t('JSON:API allowed operations'),
+ 'value' => $this->t('Read-only'),
+ 'severity' => REQUIREMENT_INFO,
+ ];
+ if (!$this->configFactory->get('jsonapi.settings')->get('read_only')) {
+ $requirements['jsonapi_read_only_mode']['value'] = $this->t('All (create, read, update, delete)');
+ $requirements['jsonapi_read_only_mode']['description'] = $this->t('It is recommended to <a href=":configure-url">configure</a> JSON:API to only accept all operations if the site requires it. <a href=":docs">Learn more about securing your site with JSON:API.</a>', [
+ ':docs' => 'https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module/security-considerations',
+ ':configure-url' => Url::fromRoute('jsonapi.settings')->toString(),
+ ]);
+ }
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/jsonapi/src/Normalizer/Value/TemporaryArrayObjectThrowingExceptions.php b/core/modules/jsonapi/src/Normalizer/Value/TemporaryArrayObjectThrowingExceptions.php
index d6e766416b0b..0f3c74f1ef53 100644
--- a/core/modules/jsonapi/src/Normalizer/Value/TemporaryArrayObjectThrowingExceptions.php
+++ b/core/modules/jsonapi/src/Normalizer/Value/TemporaryArrayObjectThrowingExceptions.php
@@ -97,7 +97,7 @@ class TemporaryArrayObjectThrowingExceptions extends \ArrayObject {
}
/**
- * Gets the class name of the array iterator that is used by \ArrayObject::getIterator().
+ * Gets the class name of the iterator used by \ArrayObject::getIterator().
*
* @throws \Exception
* This class does not support this action but it must implement it, because
diff --git a/core/modules/language/src/Form/NegotiationBrowserDeleteForm.php b/core/modules/language/src/Form/NegotiationBrowserDeleteForm.php
index fc87845eb610..28c63580ba70 100644
--- a/core/modules/language/src/Form/NegotiationBrowserDeleteForm.php
+++ b/core/modules/language/src/Form/NegotiationBrowserDeleteForm.php
@@ -8,7 +8,7 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
- * Defines a confirmation form for deleting a browser language negotiation mapping.
+ * The confirmation form for deleting a browser language negotiation mapping.
*
* @internal
*/
diff --git a/core/modules/language/src/LanguageNegotiatorInterface.php b/core/modules/language/src/LanguageNegotiatorInterface.php
index 6893639889cd..b0ca173d3dbc 100644
--- a/core/modules/language/src/LanguageNegotiatorInterface.php
+++ b/core/modules/language/src/LanguageNegotiatorInterface.php
@@ -165,7 +165,7 @@ interface LanguageNegotiatorInterface {
public function getPrimaryNegotiationMethod($type);
/**
- * Checks whether a language negotiation method is enabled for a language type.
+ * Checks if a language negotiation method is enabled for a language type.
*
* @param string $method_id
* The language negotiation method ID.
diff --git a/core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php b/core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php
index 7bbb4f514718..193674e5a03c 100644
--- a/core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php
+++ b/core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php
@@ -54,7 +54,8 @@ class LayoutBuilderAccessCheck implements AccessInterface {
$access = $access->andIf(AccessResult::allowedIfHasPermission($account, 'configure any layout'));
}
- // Disables access to inline blocks add_block routes if the section storage opts out.
+ // Disables access to inline blocks add_block routes if the section storage
+ // opts out.
// Check if inline block access should be disabled.
if ($operation === 'add_block' && !($section_storage->getPluginDefinition()->get('allow_inline_blocks') ?? TRUE)) {
$route_name = $this->route_match->getRouteName();
diff --git a/core/modules/layout_builder/src/Plugin/ConfigAction/AddComponent.php b/core/modules/layout_builder/src/Plugin/ConfigAction/AddComponent.php
new file mode 100644
index 000000000000..57781f55afe6
--- /dev/null
+++ b/core/modules/layout_builder/src/Plugin/ConfigAction/AddComponent.php
@@ -0,0 +1,133 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\layout_builder\Plugin\ConfigAction;
+
+use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\Config\Action\Attribute\ConfigAction;
+use Drupal\Core\Config\Action\ConfigActionException;
+use Drupal\Core\Config\Action\ConfigActionPluginInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\layout_builder\Plugin\ConfigAction\Deriver\AddComponentDeriver;
+use Drupal\Core\Config\ConfigManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\layout_builder\SectionComponent;
+use Drupal\layout_builder\SectionListInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Adds a component to a layout builder section.
+ *
+ * An example of using this in a recipe's config actions would be:
+ * @code
+ * dashboard.dashboard.welcome:
+ * addComponentToLayout:
+ * section: 0
+ * position: 4
+ * component:
+ * region:
+ * layout_twocol_section: 'second'
+ * default_region: content
+ * configuration:
+ * id: dashboard_text_block
+ * label: 'My new dashboard block'
+ * label_display: 'visible'
+ * provider: 'dashboard'
+ * context_mapping: { }
+ * text:
+ * value: '<p>My new block text</p>'
+ * format: 'basic_html'
+ * @endcode
+ * This will add a component to a layout region, given by the `section` index.
+ * The `position` will determine where it will be inserted, starting at 0. If is
+ * higher than the actual number of components in the region, it will be placed
+ * last.
+ * The `component` defines the actual component we are adding to the layout.
+ * Sections can have multiple regions. A `region` mapping will determine which
+ * region to use based on the id of the layout. If no matching is found, it will
+ * use the `default_region`.
+ * The `configuration` array will include the plugin configuration, including a
+ * mandatory `id` for the plugin ID. It should validate against the config
+ * schema of the plugin.
+ * The `additional` array will be copied as is, as that is ignored by config
+ * schema.
+ *
+ * @internal
+ * This API is experimental.
+ */
+#[ConfigAction(
+ id: 'add_layout_component',
+ admin_label: new TranslatableMarkup('Add component to layout'),
+ deriver: AddComponentDeriver::class,
+)]
+final class AddComponent implements ConfigActionPluginInterface, ContainerFactoryPluginInterface {
+
+ public function __construct(
+ private readonly ConfigManagerInterface $configManager,
+ private readonly UuidInterface $uuidGenerator,
+ private readonly string $pluginId,
+ ) {}
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
+ assert(is_array($plugin_definition));
+ return new static(
+ $container->get(ConfigManagerInterface::class),
+ $container->get(UuidInterface::class),
+ $plugin_id,
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function apply(string $configName, mixed $value): void {
+ assert(is_array($value));
+ $section_delta = $value['section'];
+ $position = $value['position'];
+
+ assert(is_int($section_delta));
+ assert(is_int($position));
+
+ $entity = $this->configManager->loadConfigEntityByName($configName);
+ if (!$entity instanceof SectionListInterface) {
+ throw new ConfigActionException("No entity found for applying the addComponentToLayout action.");
+ }
+
+ $section = $entity->getSection($section_delta);
+ $component = $value['component'];
+ $region = $component['default_region'] ?? NULL;
+ if (array_key_exists('region', $component) && is_array($component['region'])) {
+ // Since the recipe author might not know ahead of time what layout the
+ // section is using, they should supply a map whose keys are layout IDs
+ // and values are region names, so we know where to place this component.
+ // If the section layout ID is not in the map, they should supply the
+ // name of a fallback region. If all that fails, give up with an
+ // exception.
+ $region = $component['region'][$section->getLayoutId()] ??
+ $component['default_region'] ??
+ throw new ConfigActionException("Cannot determine which region of the section to place this component into, because no default region was provided.");
+ }
+ if ($region === NULL) {
+ throw new ConfigActionException("Cannot determine which region of the section to place this component into, because no region was provided.");
+ }
+ if (!isset($value['component']['configuration']) || !isset($value['component']['configuration']['id'])) {
+ throw new ConfigActionException("Cannot determine the component configuration, or misses a plugin ID.");
+ }
+ // If no weight were set, there would be a warning. So we set a
+ // default, which will be overridden in insertComponent anyway.
+ // We also need to generate the UUID here, or it could be null.
+ $uuid = $component['uuid'] ?? $this->uuidGenerator->generate();
+ $component = new SectionComponent($uuid, $region, $component['configuration'], $component['additional'] ?? []);
+ // If the position is higher than the number of components, just put it last
+ // instead of failing.
+ $position = min($position, count($section->getComponentsByRegion($region)));
+ $section->insertComponent($position, $component);
+ $entity->setSection($section_delta, $section);
+ $entity->save();
+ }
+
+}
diff --git a/core/modules/layout_builder/src/Plugin/ConfigAction/Deriver/AddComponentDeriver.php b/core/modules/layout_builder/src/Plugin/ConfigAction/Deriver/AddComponentDeriver.php
new file mode 100644
index 000000000000..5cc1443b64df
--- /dev/null
+++ b/core/modules/layout_builder/src/Plugin/ConfigAction/Deriver/AddComponentDeriver.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\layout_builder\Plugin\ConfigAction\Deriver;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Drupal\layout_builder\SectionListInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @internal
+ * This API is experimental.
+ */
+final class AddComponentDeriver extends DeriverBase implements ContainerDeriverInterface {
+
+ public function __construct(
+ private readonly EntityTypeManagerInterface $entityTypeManager,
+ ) {}
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, $base_plugin_id): static {
+ return new static(
+ $container->get(EntityTypeManagerInterface::class),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDerivativeDefinitions($base_plugin_definition): array {
+ $entity_types = [];
+ foreach ($this->entityTypeManager->getDefinitions() as $entity_type) {
+ if ($entity_type->entityClassImplements(ConfigEntityInterface::class) && $entity_type->entityClassImplements(SectionListInterface::class)) {
+ $entity_types[] = $entity_type->id();
+ }
+ }
+ $base_plugin_definition['entity_types'] = $entity_types;
+ $this->derivatives['addComponentToLayout'] = $base_plugin_definition;
+ return $this->derivatives;
+ }
+
+}
diff --git a/core/modules/layout_builder/src/Plugin/Derivative/ExtraFieldBlockDeriver.php b/core/modules/layout_builder/src/Plugin/Derivative/ExtraFieldBlockDeriver.php
index 087ef13d85e5..9a0dd6a9782c 100644
--- a/core/modules/layout_builder/src/Plugin/Derivative/ExtraFieldBlockDeriver.php
+++ b/core/modules/layout_builder/src/Plugin/Derivative/ExtraFieldBlockDeriver.php
@@ -146,7 +146,7 @@ class ExtraFieldBlockDeriver extends DeriverBase implements ContainerDeriverInte
}
/**
- * Gets a list of entity type and bundle tuples that have layout builder enabled.
+ * Gets the list of entity type and bundle tuples with layout builder enabled.
*
* @return array
* A structured array with entity type as first key, bundle as second.
diff --git a/core/modules/layout_builder/src/SectionListTrait.php b/core/modules/layout_builder/src/SectionListTrait.php
index cf338fa7654e..625fb58dc233 100644
--- a/core/modules/layout_builder/src/SectionListTrait.php
+++ b/core/modules/layout_builder/src/SectionListTrait.php
@@ -57,7 +57,7 @@ trait SectionListTrait {
*
* @return $this
*/
- protected function setSection($delta, Section $section) {
+ public function setSection($delta, Section $section) {
$sections = $this->getSections();
$sections[$delta] = $section;
$this->setSections($sections);
diff --git a/core/modules/layout_builder/src/SectionStorageInterface.php b/core/modules/layout_builder/src/SectionStorageInterface.php
index ffd559f63aa3..2c9293749649 100644
--- a/core/modules/layout_builder/src/SectionStorageInterface.php
+++ b/core/modules/layout_builder/src/SectionStorageInterface.php
@@ -136,7 +136,7 @@ interface SectionStorageInterface extends SectionListInterface, PluginInspection
public function isApplicable(RefinableCacheableDependencyInterface $cacheability);
/**
- * Overrides \Drupal\Component\Plugin\PluginInspectionInterface::getPluginDefinition().
+ * {@inheritdoc}
*
* @return \Drupal\layout_builder\SectionStorage\SectionStorageDefinition
* The section storage definition.
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_defaults_test/config/schema/layout_builder_defaults_test.schema.yml b/core/modules/layout_builder/tests/modules/layout_builder_defaults_test/config/schema/layout_builder_defaults_test.schema.yml
index cd7d15152603..804642885ef7 100644
--- a/core/modules/layout_builder/tests/modules/layout_builder_defaults_test/config/schema/layout_builder_defaults_test.schema.yml
+++ b/core/modules/layout_builder/tests/modules/layout_builder_defaults_test/config/schema/layout_builder_defaults_test.schema.yml
@@ -4,3 +4,11 @@ layout_builder.section.third_party.layout_builder_defaults_test:
which_party:
label: 'Which party?'
type: string
+
+block.settings.my_plugin_id:
+ type: block_settings
+ label: 'My plugin ID for AddComponentTest'
+ mapping:
+ some_configuration:
+ type: string
+ label: 'Some configuration value'
diff --git a/core/modules/layout_builder/tests/src/Kernel/Plugin/ConfigAction/AddComponentTest.php b/core/modules/layout_builder/tests/src/Kernel/Plugin/ConfigAction/AddComponentTest.php
new file mode 100644
index 000000000000..e91daa37ef7c
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Kernel/Plugin/ConfigAction/AddComponentTest.php
@@ -0,0 +1,401 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\layout_builder\Kernel\Plugin\ConfigAction;
+
+use Drupal\Core\Config\Action\ConfigActionException;
+use Drupal\Core\Config\Action\ConfigActionManager;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\entity_test\EntityTestHelper;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage;
+use Drupal\layout_builder\Section;
+use Drupal\layout_builder\SectionListInterface;
+use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
+
+/**
+ * @coversDefaultClass \Drupal\layout_builder\Plugin\ConfigAction\AddComponent
+ *
+ * @group layout_builder
+ */
+class AddComponentTest extends KernelTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $modules = [
+ 'layout_discovery',
+ 'layout_builder',
+ 'layout_builder_defaults_test',
+ 'entity_test',
+ 'field',
+ 'system',
+ 'user',
+ ];
+
+ /**
+ * The plugin.
+ */
+ private readonly DefaultsSectionStorage $plugin;
+
+ /**
+ * The config action manager.
+ */
+ private readonly ConfigActionManager $configActionManager;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void {
+ parent::setUp();
+
+ EntityTestHelper::createBundle('bundle_with_extra_fields');
+ $this->installEntitySchema('entity_test');
+ $this->installEntitySchema('user');
+ $this->installConfig(['layout_builder_defaults_test']);
+
+ $this->plugin = $this->container->get(SectionStorageManagerInterface::class)->createInstance('defaults');
+ $this->configActionManager = $this->container->get('plugin.manager.config_action');
+
+ // Add some extra empty sections.
+ $view_display = $this->container->get(EntityTypeManagerInterface::class)
+ ->getStorage('entity_view_display')
+ ->load('entity_test.bundle_with_extra_fields.default');
+ assert($view_display instanceof SectionListInterface);
+ $view_display->insertSection(1, new Section('layout_onecol'));
+ $view_display->insertSection(2, new Section('layout_threecol_25_50_25'));
+ $view_display->save();
+ }
+
+ /**
+ * Tests adding a component to a view display using a config action.
+ *
+ * @dataProvider provider
+ */
+ public function testAddComponent(array $config_action_value, string $expected_region, int $added_component_expected_weight, int $existing_component_expected_weight, ?array $expected_error = NULL): void {
+ if ($expected_error !== NULL) {
+ $this->expectException($expected_error[0]);
+ $this->expectExceptionMessage($expected_error[1]);
+ }
+ $this->configActionManager->applyAction(
+ 'addComponentToLayout',
+ 'core.entity_view_display.entity_test.bundle_with_extra_fields.default',
+ $config_action_value,
+ );
+
+ $view_display = $this->container->get(EntityTypeManagerInterface::class)
+ ->getStorage('entity_view_display')
+ ->load('entity_test.bundle_with_extra_fields.default');
+ $this->plugin->setContextValue('display', $view_display);
+ $components = $this->plugin->getSection(0)->getComponents();
+ $uuid = end($components)->getUuid();
+
+ // If we pass the same existing UUID, we replace it.
+ $is_replacing = $added_component_expected_weight === $existing_component_expected_weight;
+ $expected_existing_plugin = $is_replacing ? 'my_plugin_id' : 'extra_field_block:entity_test:bundle_with_extra_fields:display_extra_field';
+ $this->assertCount($is_replacing ? 1 : 2, $components);
+ $this->assertSame($expected_existing_plugin, $components['1445597a-c674-431d-ac0a-277d99347a7f']->getPluginId());
+ $this->assertSame('my_plugin_id', $components[$uuid]->getPluginId());
+ $this->assertSame($expected_region, $components[$uuid]->getRegion());
+ $this->assertSame($added_component_expected_weight, $components[$uuid]->getWeight());
+ // Assert weight of the existing component in the layout_twocol_section
+ // first region.
+ $this->assertSame($existing_component_expected_weight, $components['1445597a-c674-431d-ac0a-277d99347a7f']->getWeight());
+ // Assert the component configuration (defined with its config schema), and the
+ // additional configuration (ignored in config schema)
+ $this->assertSame($config_action_value['component']['configuration'], $components[$uuid]->get('configuration'));
+ $this->assertSame($config_action_value['component']['additional'] ?? [], $components[$uuid]->get('additional'));
+ }
+
+ /**
+ * Data provider for testAddComponent.
+ */
+ public static function provider(): \Generator {
+ yield 'add component at first position of a non-empty region' => [
+ [
+ 'section' => 0,
+ 'position' => 0,
+ 'component' => [
+ 'region' => [
+ 'layout_test_plugin' => 'content',
+ 'layout_twocol_section' => 'first',
+ ],
+ 'default_region' => 'content',
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ ],
+ ],
+ ],
+ 'first',
+ 1,
+ 2,
+ ];
+ yield 'edit existing component by giving the same uuid' => [
+ [
+ 'section' => 0,
+ 'position' => 0,
+ 'component' => [
+ 'uuid' => '1445597a-c674-431d-ac0a-277d99347a7f',
+ 'region' => [
+ 'layout_test_plugin' => 'content',
+ 'layout_twocol_section' => 'first',
+ ],
+ 'default_region' => 'content',
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ ],
+ ],
+ ],
+ 'first',
+ 1,
+ 1,
+ ];
+ yield 'add component at second position of a non-empty region' => [
+ [
+ 'section' => 0,
+ 'position' => 1,
+ 'component' => [
+ 'region' => [
+ 'layout_test_plugin' => 'content',
+ 'layout_twocol_section' => 'first',
+ ],
+ 'default_region' => 'content',
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ 'some_configuration' => 'my_configuration_value',
+ ],
+ 'additional' => [
+ 'some_additional_value' => 'my_custom_value',
+ ],
+ ],
+ ],
+ 'first',
+ 2,
+ 1,
+ ];
+ yield 'add component at a position larger than the region size on an empty region' => [
+ [
+ 'section' => 0,
+ 'position' => 4,
+ 'component' => [
+ 'region' => [
+ 'layout_test_plugin' => 'content',
+ 'layout_twocol_section' => 'second',
+ ],
+ 'default_region' => 'content',
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ 'some_configuration' => 'my_configuration_value',
+ ],
+ 'additional' => [
+ 'some_additional_value' => 'my_custom_value',
+ ],
+ ],
+ ],
+ 'second',
+ // As there is no other block in that section's region, weight is 0 no matter
+ // of the 4th position we asked for.
+ 0,
+ 1,
+ ];
+ yield 'add component at a region not defined in the mapping' => [
+ [
+ 'section' => 0,
+ 'position' => 4,
+ 'component' => [
+ 'region' => [
+ 'layout_test_plugin' => 'content',
+ ],
+ 'default_region' => 'second',
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ ],
+ ],
+ ],
+ // Assigned to the default region, as no mapping matched.
+ 'second',
+ 0,
+ 1,
+ ];
+ yield 'add component at a region defined in the mapping while no default region exist' => [
+ [
+ 'section' => 0,
+ 'position' => 4,
+ 'component' => [
+ 'region' => [
+ 'layout_twocol_section' => 'second',
+ ],
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ ],
+ ],
+ ],
+ // Assigned to the matching region, even if no default_region.
+ 'second',
+ 0,
+ 1,
+ ];
+ yield 'add component with only default_region and no region mapping' => [
+ [
+ 'section' => 0,
+ 'position' => 4,
+ 'component' => [
+ 'default_region' => 'second',
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ ],
+ ],
+ ],
+ // Assigned to the default region, even with no mapping.
+ 'second',
+ 0,
+ 1,
+ ];
+ yield 'exception when cannot determine a region with mapping and default' => [
+ [
+ 'section' => 0,
+ 'position' => 4,
+ 'component' => [
+ 'region' => [
+ 'layout_test_plugin' => 'content',
+ ],
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ ],
+ ],
+ ],
+ 'second',
+ 0,
+ 1,
+ // No default_region, no matching region, so we error.
+ [
+ ConfigActionException::class,
+ 'Cannot determine which region of the section to place this component into, because no default region was provided.',
+ ],
+ yield 'exception when no region given' => [
+ [
+ 'section' => 0,
+ 'position' => 4,
+ 'component' => [
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ ],
+ ],
+ ],
+ 'second',
+ 0,
+ 1,
+ // No default_region, no matching region, so we error.
+ [
+ ConfigActionException::class,
+ 'Cannot determine which region of the section to place this component into, because no region was provided.',
+ ],
+ ],
+ yield 'exception when no configuration given' => [
+ [
+ 'section' => 0,
+ 'position' => 4,
+ 'component' => [
+ 'region' => [
+ 'layout_test_plugin' => 'content',
+ ],
+ 'default_region' => 'content',
+ ],
+ ],
+ 'second',
+ 0,
+ 1,
+ // No component configuration.
+ [
+ ConfigActionException::class,
+ 'Cannot determine the component configuration, or misses a plugin ID.',
+ ],
+ ],
+ yield 'exception when no id in configuration is given' => [
+ [
+ 'section' => 0,
+ 'position' => 4,
+ 'component' => [
+ 'region' => [
+ 'layout_test_plugin' => 'content',
+ ],
+ 'default_region' => 'content',
+ 'configuration' => [
+ 'no_id' => 'my_plugin_id',
+ ],
+ ],
+ ],
+ 'second',
+ 0,
+ 1,
+ // No component configuration id.
+ [
+ ConfigActionException::class,
+ 'Cannot determine the component configuration, or misses a plugin ID.',
+ ],
+ ],
+
+ ];
+ }
+
+ /**
+ * Tests that adding a component to another section works as expected.
+ */
+ public function testAddComponentToEmptyRegionThatIsNotFirst(): void {
+ $this->configActionManager->applyAction(
+ 'addComponentToLayout',
+ 'core.entity_view_display.entity_test.bundle_with_extra_fields.default',
+ [
+ 'section' => 2,
+ 'position' => 4,
+ 'component' => [
+ 'region' => [
+ 'layout_twocol_section' => 'second',
+ 'layout_threecol_25_50_25' => 'bottom',
+ ],
+ 'default_region' => 'content',
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ ],
+ ],
+ ]);
+ $view_display = $this->container->get(EntityTypeManagerInterface::class)
+ ->getStorage('entity_view_display')
+ ->load('entity_test.bundle_with_extra_fields.default');
+ $this->plugin->setContextValue('display', $view_display);
+
+ $this->assertCount(1, $this->plugin->getSection(0)->getComponents());
+ $this->assertCount(0, $this->plugin->getSection(1)->getComponents());
+ $this->assertCount(1, $this->plugin->getSection(2)->getComponents());
+
+ $components = $this->plugin->getSection(2)->getComponents();
+ $uuid = end($components)->getUuid();
+
+ $this->assertSame('bottom', $components[$uuid]->getRegion());
+ $this->assertSame(0, $components[$uuid]->getWeight());
+ $this->assertSame(['id' => 'my_plugin_id'], $components[$uuid]->get('configuration'));
+ }
+
+ /**
+ * Tests that applying the config action to a missing entity fails.
+ */
+ public function testActionFailsIfEntityNotFound(): void {
+ $this->expectException(ConfigActionException::class);
+ $this->expectExceptionMessage('No entity found for applying the addComponentToLayout action.');
+ $this->configActionManager->applyAction(
+ 'addComponentToLayout',
+ 'core.entity_view_display.entity_test.bundle_with_extra_fields.missing_view_mode',
+ [
+ 'section' => 0,
+ 'position' => 4,
+ 'component' => [
+ 'default_region' => 'content',
+ 'configuration' => [
+ 'id' => 'my_plugin_id',
+ ],
+ ],
+ ]);
+ }
+
+}
diff --git a/core/modules/layout_discovery/layout_discovery.install b/core/modules/layout_discovery/layout_discovery.install
deleted file mode 100644
index 28222bc093ba..000000000000
--- a/core/modules/layout_discovery/layout_discovery.install
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update, and uninstall functions for the Layout Discovery module.
- */
-
-/**
- * Implements hook_requirements().
- */
-function layout_discovery_requirements($phase): array {
- $requirements = [];
- if ($phase === 'install') {
- if (\Drupal::moduleHandler()->moduleExists('layout_plugin')) {
- $requirements['layout_discovery'] = [
- 'description' => t('Layout Discovery cannot be installed because the Layout Plugin module is installed and incompatible.'),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- }
- return $requirements;
-}
diff --git a/core/modules/layout_discovery/src/Install/Requirements/LayoutDiscoveryRequirements.php b/core/modules/layout_discovery/src/Install/Requirements/LayoutDiscoveryRequirements.php
new file mode 100644
index 000000000000..d6b8ef7fc24d
--- /dev/null
+++ b/core/modules/layout_discovery/src/Install/Requirements/LayoutDiscoveryRequirements.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\layout_discovery\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+
+/**
+ * Install time requirements for the layout_discovery module.
+ */
+class LayoutDiscoveryRequirements implements InstallRequirementsInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getRequirements(): array {
+ $requirements = [];
+ if (\Drupal::moduleHandler()->moduleExists('layout_plugin')) {
+ $requirements['layout_discovery'] = [
+ 'description' => t('Layout Discovery cannot be installed because the Layout Plugin module is installed and incompatible.'),
+ 'severity' => REQUIREMENT_ERROR,
+ ];
+ }
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index f583df4426eb..457d37890fa0 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -8,72 +8,6 @@
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Hook\Attribute\ProceduralHookScanStop;
-use Drupal\Core\Link;
-use Drupal\Core\Url;
-
-/**
- * Implements hook_requirements().
- */
-function locale_requirements($phase): array {
- $requirements = [];
- if ($phase == 'runtime') {
- $available_updates = [];
- $untranslated = [];
- $languages = locale_translatable_language_list();
-
- if ($languages) {
- // Determine the status of the translation updates per language.
- $status = locale_translation_get_status();
- if ($status) {
- foreach ($status as $project) {
- foreach ($project as $langcode => $project_info) {
- if (empty($project_info->type)) {
- $untranslated[$langcode] = $languages[$langcode]->getName();
- }
- elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE) {
- $available_updates[$langcode] = $languages[$langcode]->getName();
- }
- }
- }
-
- if ($available_updates || $untranslated) {
- if ($available_updates) {
- $requirements['locale_translation'] = [
- 'title' => t('Translation update status'),
- 'value' => Link::fromTextAndUrl(t('Updates available'), Url::fromRoute('locale.translate_status'))->toString(),
- 'severity' => REQUIREMENT_WARNING,
- 'description' => t('Updates available for: @languages. See the <a href=":updates">Available translation updates</a> page for more information.', ['@languages' => implode(', ', $available_updates), ':updates' => Url::fromRoute('locale.translate_status')->toString()]),
- ];
- }
- else {
- $requirements['locale_translation'] = [
- 'title' => t('Translation update status'),
- 'value' => t('Missing translations'),
- 'severity' => REQUIREMENT_INFO,
- 'description' => t('Missing translations for: @languages. See the <a href=":updates">Available translation updates</a> page for more information.', ['@languages' => implode(', ', $untranslated), ':updates' => Url::fromRoute('locale.translate_status')->toString()]),
- ];
- }
- }
- else {
- $requirements['locale_translation'] = [
- 'title' => t('Translation update status'),
- 'value' => t('Up to date'),
- 'severity' => REQUIREMENT_OK,
- ];
- }
- }
- else {
- $requirements['locale_translation'] = [
- 'title' => t('Translation update status'),
- 'value' => Link::fromTextAndUrl(t('Can not determine status'), Url::fromRoute('locale.translate_status'))->toString(),
- 'severity' => REQUIREMENT_WARNING,
- 'description' => t('No translation status is available. See the <a href=":updates">Available translation updates</a> page for more information.', [':updates' => Url::fromRoute('locale.translate_status')->toString()]),
- ];
- }
- }
- }
- return $requirements;
-}
/**
* Implements hook_install().
diff --git a/core/modules/locale/src/Hook/LocaleRequirements.php b/core/modules/locale/src/Hook/LocaleRequirements.php
new file mode 100644
index 000000000000..6664a64d42bb
--- /dev/null
+++ b/core/modules/locale/src/Hook/LocaleRequirements.php
@@ -0,0 +1,82 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\locale\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Link;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+
+/**
+ * Requirements for the Locale module.
+ */
+class LocaleRequirements {
+
+ use StringTranslationTrait;
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+ $available_updates = [];
+ $untranslated = [];
+ $languages = locale_translatable_language_list();
+
+ if ($languages) {
+ // Determine the status of the translation updates per language.
+ $status = locale_translation_get_status();
+ if ($status) {
+ foreach ($status as $project) {
+ foreach ($project as $langcode => $project_info) {
+ if (empty($project_info->type)) {
+ $untranslated[$langcode] = $languages[$langcode]->getName();
+ }
+ elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE) {
+ $available_updates[$langcode] = $languages[$langcode]->getName();
+ }
+ }
+ }
+
+ if ($available_updates || $untranslated) {
+ if ($available_updates) {
+ $requirements['locale_translation'] = [
+ 'title' => $this->t('Translation update status'),
+ 'value' => Link::fromTextAndUrl($this->t('Updates available'), Url::fromRoute('locale.translate_status'))->toString(),
+ 'severity' => REQUIREMENT_WARNING,
+ 'description' => $this->t('Updates available for: @languages. See the <a href=":updates">Available translation updates</a> page for more information.', ['@languages' => implode(', ', $available_updates), ':updates' => Url::fromRoute('locale.translate_status')->toString()]),
+ ];
+ }
+ else {
+ $requirements['locale_translation'] = [
+ 'title' => $this->t('Translation update status'),
+ 'value' => $this->t('Missing translations'),
+ 'severity' => REQUIREMENT_INFO,
+ 'description' => $this->t('Missing translations for: @languages. See the <a href=":updates">Available translation updates</a> page for more information.', ['@languages' => implode(', ', $untranslated), ':updates' => Url::fromRoute('locale.translate_status')->toString()]),
+ ];
+ }
+ }
+ else {
+ $requirements['locale_translation'] = [
+ 'title' => $this->t('Translation update status'),
+ 'value' => $this->t('Up to date'),
+ 'severity' => REQUIREMENT_OK,
+ ];
+ }
+ }
+ else {
+ $requirements['locale_translation'] = [
+ 'title' => $this->t('Translation update status'),
+ 'value' => Link::fromTextAndUrl($this->t('Can not determine status'), Url::fromRoute('locale.translate_status'))->toString(),
+ 'severity' => REQUIREMENT_WARNING,
+ 'description' => $this->t('No translation status is available. See the <a href=":updates">Available translation updates</a> page for more information.', [':updates' => Url::fromRoute('locale.translate_status')->toString()]),
+ ];
+ }
+ }
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/locale/src/LocaleConfigManager.php b/core/modules/locale/src/LocaleConfigManager.php
index 6756e85aa41c..133bf7518e94 100644
--- a/core/modules/locale/src/LocaleConfigManager.php
+++ b/core/modules/locale/src/LocaleConfigManager.php
@@ -289,9 +289,9 @@ class LocaleConfigManager {
* Gets configuration names associated with components.
*
* @param array $components
- * (optional) An associative array containing component types as keys and lists
- * of components as values. If not provided or is empty, the method returns all
- * configuration names.
+ * (optional) An associative array containing component types as keys and
+ * lists of components as values. If not provided or is empty, the method
+ * returns all configuration names.
*
* @return array
* Array of configuration object names.
diff --git a/core/modules/locale/src/StreamWrapper/TranslationsStream.php b/core/modules/locale/src/StreamWrapper/TranslationsStream.php
index 980c048e290b..40517477379c 100644
--- a/core/modules/locale/src/StreamWrapper/TranslationsStream.php
+++ b/core/modules/locale/src/StreamWrapper/TranslationsStream.php
@@ -44,6 +44,7 @@ class TranslationsStream extends LocalStream {
}
/**
+ * phpcs:ignore Drupal.Files.LineLength
* Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::getExternalUrl().
*
* @throws \LogicException
diff --git a/core/modules/media/config/optional/views.view.media.yml b/core/modules/media/config/optional/views.view.media.yml
index c7a32129b33e..329ac2433870 100644
--- a/core/modules/media/config/optional/views.view.media.yml
+++ b/core/modules/media/config/optional/views.view.media.yml
@@ -857,6 +857,7 @@ display:
empty_table: true
caption: ''
description: ''
+ class: ''
row:
type: fields
query:
diff --git a/core/modules/media/media.install b/core/modules/media/media.install
index 48ed664fdef7..62b2eece6193 100644
--- a/core/modules/media/media.install
+++ b/core/modules/media/media.install
@@ -9,107 +9,9 @@ use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Hook\Attribute\ProceduralHookScanStop;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\Core\Url;
-use Drupal\image\Plugin\Field\FieldType\ImageItem;
-use Drupal\media\Entity\MediaType;
use Drupal\user\RoleInterface;
/**
- * Implements hook_requirements().
- */
-function media_requirements($phase): array {
- $requirements = [];
- if ($phase == 'install') {
- $destination = 'public://media-icons/generic';
- \Drupal::service('file_system')->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
- $is_writable = is_writable($destination);
- $is_directory = is_dir($destination);
- if (!$is_writable || !$is_directory) {
- if (!$is_directory) {
- $error = t('The directory %directory does not exist.', ['%directory' => $destination]);
- }
- else {
- $error = t('The directory %directory is not writable.', ['%directory' => $destination]);
- }
- $description = t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', [':handbook_url' => 'https://www.drupal.org/server-permissions']);
- if (!empty($error)) {
- $description = $error . ' ' . $description;
- $requirements['media']['description'] = $description;
- $requirements['media']['severity'] = REQUIREMENT_ERROR;
- }
- }
- }
- elseif ($phase === 'runtime') {
- $module_handler = \Drupal::service('module_handler');
- foreach (MediaType::loadMultiple() as $type) {
- // Load the default display.
- $display = \Drupal::service('entity_display.repository')
- ->getViewDisplay('media', $type->id());
-
- // Check for missing source field definition.
- $source_field_definition = $type->getSource()->getSourceFieldDefinition($type);
- if (empty($source_field_definition)) {
- $requirements['media_missing_source_field_' . $type->id()] = [
- 'title' => t('Media'),
- 'description' => t('The source field definition for the %type media type is missing.',
- [
- '%type' => $type->label(),
- ]
- ),
- 'severity' => REQUIREMENT_ERROR,
- ];
- continue;
- }
-
- // When a new media type with an image source is created we're
- // configuring the default entity view display using the 'large' image
- // style. Unfortunately, if a site builder has deleted the 'large' image
- // style, we need some other image style to use, but at this point, we
- // can't really know the site builder's intentions. So rather than do
- // something surprising, we're leaving the embedded media without an
- // image style and adding a warning that the site builder might want to
- // add an image style.
- // @see Drupal\media\Plugin\media\Source\Image::prepareViewDisplay
- if (!is_a($source_field_definition->getItemDefinition()->getClass(), ImageItem::class, TRUE)) {
- continue;
- }
-
- $component = $display->getComponent($source_field_definition->getName());
- if (empty($component) || $component['type'] !== 'image' || !empty($component['settings']['image_style'])) {
- continue;
- }
-
- $action_item = '';
- if ($module_handler->moduleExists('field_ui') && \Drupal::currentUser()->hasPermission('administer media display')) {
- $url = Url::fromRoute('entity.entity_view_display.media.default', [
- 'media_type' => $type->id(),
- ])->toString();
- $action_item = new TranslatableMarkup('If you would like to change this, <a href=":display">add an image style to the %field_name field</a>.',
- [
- '%field_name' => $source_field_definition->label(),
- ':display' => $url,
- ]);
- }
- $requirements['media_default_image_style_' . $type->id()] = [
- 'title' => t('Media'),
- 'description' => new TranslatableMarkup('The default display for the %type media type is not currently using an image style on the %field_name field. Not using an image style can lead to much larger file downloads. @action_item',
- [
- '%field_name' => $source_field_definition->label(),
- '@action_item' => $action_item,
- '%type' => $type->label(),
- ]
- ),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
-
- }
-
- return $requirements;
-}
-
-/**
* Implements hook_install().
*/
#[ProceduralHookScanStop]
diff --git a/core/modules/media/src/Hook/MediaRequirementsHooks.php b/core/modules/media/src/Hook/MediaRequirementsHooks.php
new file mode 100644
index 000000000000..cedbb2fd8200
--- /dev/null
+++ b/core/modules/media/src/Hook/MediaRequirementsHooks.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\media\Hook;
+
+use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Url;
+use Drupal\image\Plugin\Field\FieldType\ImageItem;
+use Drupal\media\Entity\MediaType;
+
+/**
+ * Requirements checks for Media module.
+ */
+class MediaRequirementsHooks {
+
+ use StringTranslationTrait;
+
+ public function __construct(
+ protected readonly AccountInterface $currentUser,
+ protected readonly ModuleHandlerInterface $moduleHandler,
+ protected readonly EntityDisplayRepositoryInterface $entityDisplayRepository,
+ ) {}
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+ foreach (MediaType::loadMultiple() as $type) {
+ // Load the default display.
+ $display = $this->entityDisplayRepository->getViewDisplay('media', $type->id());
+
+ // Check for missing source field definition.
+ $source_field_definition = $type->getSource()->getSourceFieldDefinition($type);
+ if (empty($source_field_definition)) {
+ $requirements['media_missing_source_field_' . $type->id()] = [
+ 'title' => $this->t('Media'),
+ 'description' => $this->t('The source field definition for the %type media type is missing.',
+ [
+ '%type' => $type->label(),
+ ]
+ ),
+ 'severity' => REQUIREMENT_ERROR,
+ ];
+ continue;
+ }
+
+ // When a new media type with an image source is created we're
+ // configuring the default entity view display using the 'large' image
+ // style. Unfortunately, if a site builder has deleted the 'large' image
+ // style, we need some other image style to use, but at this point, we
+ // can't really know the site builder's intentions. So rather than do
+ // something surprising, we're leaving the embedded media without an
+ // image style and adding a warning that the site builder might want to
+ // add an image style.
+ // @see Drupal\media\Plugin\media\Source\Image::prepareViewDisplay
+ if (!is_a($source_field_definition->getItemDefinition()->getClass(), ImageItem::class, TRUE)) {
+ continue;
+ }
+
+ $component = $display->getComponent($source_field_definition->getName());
+ if (empty($component) || $component['type'] !== 'image' || !empty($component['settings']['image_style'])) {
+ continue;
+ }
+
+ $action_item = '';
+ if ($this->moduleHandler->moduleExists('field_ui') && $this->currentUser->hasPermission('administer media display')) {
+ $url = Url::fromRoute('entity.entity_view_display.media.default', [
+ 'media_type' => $type->id(),
+ ])->toString();
+ $action_item = new TranslatableMarkup('If you would like to change this, <a href=":display">add an image style to the %field_name field</a>.',
+ [
+ '%field_name' => $source_field_definition->label(),
+ ':display' => $url,
+ ]);
+ }
+ $requirements['media_default_image_style_' . $type->id()] = [
+ 'title' => $this->t('Media'),
+ 'description' => new TranslatableMarkup('The default display for the %type media type is not currently using an image style on the %field_name field. Not using an image style can lead to much larger file downloads. @action_item',
+ [
+ '%field_name' => $source_field_definition->label(),
+ '@action_item' => $action_item,
+ '%type' => $type->label(),
+ ]
+ ),
+ 'severity' => REQUIREMENT_WARNING,
+ ];
+ }
+
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/media/src/Install/Requirements/MediaRequirements.php b/core/modules/media/src/Install/Requirements/MediaRequirements.php
new file mode 100644
index 000000000000..a69a79aaf811
--- /dev/null
+++ b/core/modules/media/src/Install/Requirements/MediaRequirements.php
@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\media\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+use Drupal\Core\File\FileSystemInterface;
+
+/**
+ * Install time requirements for the media module.
+ */
+class MediaRequirements implements InstallRequirementsInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getRequirements(): array {
+ $requirements = [];
+ $destination = 'public://media-icons/generic';
+ \Drupal::service('file_system')->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
+ $is_writable = is_writable($destination);
+ $is_directory = is_dir($destination);
+ if (!$is_writable || !$is_directory) {
+ if (!$is_directory) {
+ $error = t('The directory %directory does not exist.', ['%directory' => $destination]);
+ }
+ else {
+ $error = t('The directory %directory is not writable.', ['%directory' => $destination]);
+ }
+ $description = t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', [':handbook_url' => 'https://www.drupal.org/server-permissions']);
+ $description = $error . ' ' . $description;
+ $requirements['media']['description'] = $description;
+ $requirements['media']['severity'] = REQUIREMENT_ERROR;
+ }
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/media/src/MediaSourceBase.php b/core/modules/media/src/MediaSourceBase.php
index f7197963cca8..181ebed86253 100644
--- a/core/modules/media/src/MediaSourceBase.php
+++ b/core/modules/media/src/MediaSourceBase.php
@@ -321,7 +321,8 @@ abstract class MediaSourceBase extends PluginBase implements MediaSourceInterfac
if ($tries) {
$id .= '_' . $tries;
- // Ensure the suffixed field name does not exceed the maximum allowed length.
+ // Ensure the suffixed field name does not exceed the maximum allowed
+ // length.
if (strlen($id) > EntityTypeInterface::ID_MAX_LENGTH) {
$id = substr($base_id, 0, (EntityTypeInterface::ID_MAX_LENGTH - strlen('_' . $tries))) . '_' . $tries;
}
diff --git a/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml b/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml
index 8f7f0c8493df..f194dfb53fa0 100644
--- a/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml
+++ b/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml
@@ -138,6 +138,8 @@ display:
arguments: { }
style:
type: table
+ options:
+ class: ''
row:
type: fields
relationships: { }
diff --git a/core/modules/media_library/config/install/views.view.media_library.yml b/core/modules/media_library/config/install/views.view.media_library.yml
index 5450c17c9379..70974811ee4f 100644
--- a/core/modules/media_library/config/install/views.view.media_library.yml
+++ b/core/modules/media_library/config/install/views.view.media_library.yml
@@ -1346,6 +1346,7 @@ display:
options:
row_class: 'media-library-item media-library-item--table js-media-library-item js-click-to-select'
default_row_class: true
+ class: ''
row:
type: fields
defaults:
diff --git a/core/modules/media_library/tests/modules/media_library_test/src/Form/TestNodeFormOverride.php b/core/modules/media_library/tests/modules/media_library_test/src/Form/TestNodeFormOverride.php
index 4dda6ff75444..50305332a25d 100644
--- a/core/modules/media_library/tests/modules/media_library_test/src/Form/TestNodeFormOverride.php
+++ b/core/modules/media_library/tests/modules/media_library_test/src/Form/TestNodeFormOverride.php
@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Drupal\media_library_test\Form;
use Drupal\Core\Form\FormStateInterface;
-use Drupal\node\NodeForm;
+use Drupal\node\Form\NodeForm;
/**
* Override NodeForm to test media library form submission semantics.
diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
index 9b32ffa18247..09e7c99f898c 100644
--- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php
+++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
@@ -115,13 +115,14 @@ class MenuUiHooks {
return $entity->access('update', NULL, TRUE)->andIf($entity->access('delete', NULL, TRUE));
}
else {
- // If the node has no corresponding menu link, users needs to permission to create one.
+ // If the node has no corresponding menu link, users needs to permission
+ // to create one.
return $this->entityTypeManager->getAccessControlHandler('menu_link_content')->createAccess(NULL, NULL, [], TRUE);
}
}
/**
- * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
+ * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\Form\NodeForm.
*
* Adds menu item fields to the node form.
*
@@ -231,7 +232,7 @@ class MenuUiHooks {
}
/**
- * Implements hook_form_FORM_ID_alter() for \Drupal\node\NodeTypeForm.
+ * Implements hook_form_FORM_ID_alter() for \Drupal\node\Form\NodeTypeForm.
*
* Adds menu options to the node type form.
*
diff --git a/core/modules/migrate/tests/src/Kernel/MigrateSourceTestBase.php b/core/modules/migrate/tests/src/Kernel/MigrateSourceTestBase.php
index 1999e3ad86f4..9e508da9b225 100644
--- a/core/modules/migrate/tests/src/Kernel/MigrateSourceTestBase.php
+++ b/core/modules/migrate/tests/src/Kernel/MigrateSourceTestBase.php
@@ -85,9 +85,9 @@ abstract class MigrateSourceTestBase extends KernelTestBase {
* The fully qualified class name of the plugin to be tested.
*/
protected function getPluginClass() {
- $covers = $this->getTestClassCovers();
- if (!empty($covers)) {
- return $covers[0];
+ $covers = $this->valueObjectForEvents()->metadata()->isCovers()->isClassLevel()->asArray();
+ if (isset($covers[0])) {
+ return $covers[0]->target();
}
else {
$this->fail('No plugin class was specified');
diff --git a/core/modules/mysql/src/Driver/Database/mysql/Schema.php b/core/modules/mysql/src/Driver/Database/mysql/Schema.php
index 85e71af18d12..a8b9c07564e8 100644
--- a/core/modules/mysql/src/Driver/Database/mysql/Schema.php
+++ b/core/modules/mysql/src/Driver/Database/mysql/Schema.php
@@ -67,7 +67,7 @@ class Schema extends DatabaseSchema {
}
/**
- * Build a condition to match a table name against a standard information_schema.
+ * Builds a condition to match a table name with the information schema.
*
* MySQL uses databases like schemas rather than catalogs so when we build a
* condition to query the information_schema.tables, we set the default
@@ -133,7 +133,7 @@ class Schema extends DatabaseSchema {
}
/**
- * Create an SQL string for a field to be used in table creation or alteration.
+ * Creates an SQL string for a field used in table creation or alteration.
*
* @param string $name
* Name of the field.
diff --git a/core/modules/mysql/mysql.install b/core/modules/mysql/src/Hook/MysqlRequirements.php
index 547f9cab1cd0..ef305d41a34b 100644
--- a/core/modules/mysql/mysql.install
+++ b/core/modules/mysql/src/Hook/MysqlRequirements.php
@@ -1,20 +1,27 @@
<?php
-/**
- * @file
- * Install, update and uninstall functions for the mysql module.
- */
+declare(strict_types=1);
+
+namespace Drupal\mysql\Hook;
use Drupal\Core\Database\Database;
+use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Render\Markup;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
- * Implements hook_requirements().
+ * Requirements for the MySQL module.
*/
-function mysql_requirements($phase): array {
- $requirements = [];
+class MysqlRequirements {
- if ($phase === 'runtime') {
+ use StringTranslationTrait;
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
// Test with MySql databases.
if (Database::isActiveConnection()) {
$connection = Database::getConnection();
@@ -51,27 +58,28 @@ function mysql_requirements($phase): array {
}
else {
$severity_level = REQUIREMENT_ERROR;
- $description[] = t('This is not supported by Drupal.');
+ $description[] = $this->t('This is not supported by Drupal.');
}
- $description[] = t('The recommended level for Drupal is "READ COMMITTED".');
+ $description[] = $this->t('The recommended level for Drupal is "READ COMMITTED".');
}
if (!empty($tables_missing_primary_key)) {
- $description[] = t('For this to work correctly, all tables must have a primary key. The following table(s) do not have a primary key: @tables.', ['@tables' => implode(', ', $tables_missing_primary_key)]);
+ $description[] = $this->t('For this to work correctly, all tables must have a primary key. The following table(s) do not have a primary key: @tables.', ['@tables' => implode(', ', $tables_missing_primary_key)]);
}
- $description[] = t('See the <a href=":performance_doc">setting MySQL transaction isolation level</a> page for more information.', [
+ $description[] = $this->t('See the <a href=":performance_doc">setting MySQL transaction isolation level</a> page for more information.', [
':performance_doc' => 'https://www.drupal.org/docs/system-requirements/setting-the-mysql-transaction-isolation-level',
]);
$requirements['mysql_transaction_level'] = [
- 'title' => t('Transaction isolation level'),
+ 'title' => $this->t('Transaction isolation level'),
'severity' => $severity_level,
'value' => $isolation_level,
'description' => Markup::create(implode(' ', $description)),
];
}
+
+ return $requirements;
}
- return $requirements;
}
diff --git a/core/modules/navigation/navigation.install b/core/modules/navigation/navigation.install
index e4280b472ac2..77390203123a 100644
--- a/core/modules/navigation/navigation.install
+++ b/core/modules/navigation/navigation.install
@@ -22,25 +22,6 @@ function navigation_install(bool $is_syncing): void {
}
/**
- * Implements hook_requirements().
- */
-function navigation_requirements($phase): array {
- $requirements = [];
-
- if ($phase === 'runtime') {
- if (\Drupal::moduleHandler()->moduleExists('toolbar')) {
- $requirements['toolbar'] = [
- 'title' => t('Toolbar and Navigation modules are both installed'),
- 'value' => t('The Navigation module is a complete replacement for the Toolbar module and disables its functionality when both modules are installed. If you are planning to continue using Navigation module, you can uninstall the Toolbar module now.'),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
- }
-
- return $requirements;
-}
-
-/**
* Reorganizes the values for the logo settings.
*/
function navigation_update_11001(array &$sandbox): void {
diff --git a/core/modules/navigation/src/Hook/NavigationRequirements.php b/core/modules/navigation/src/Hook/NavigationRequirements.php
new file mode 100644
index 000000000000..ae04608bfd66
--- /dev/null
+++ b/core/modules/navigation/src/Hook/NavigationRequirements.php
@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\navigation\Hook;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Requirements for the navigation module.
+ */
+class NavigationRequirements {
+
+ use StringTranslationTrait;
+
+ public function __construct(
+ protected readonly ModuleHandlerInterface $moduleHandler,
+ ) {}
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+ if ($this->moduleHandler->moduleExists('toolbar')) {
+ $requirements['toolbar'] = [
+ 'title' => $this->t('Toolbar and Navigation modules are both installed'),
+ 'value' => $this->t('The Navigation module is a complete replacement for the Toolbar module and disables its functionality when both modules are installed. If you are planning to continue using Navigation module, you can uninstall the Toolbar module now.'),
+ 'severity' => REQUIREMENT_WARNING,
+ ];
+ }
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php b/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php
index 3264d769c195..2371bef31aac 100644
--- a/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php
+++ b/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php
@@ -73,14 +73,14 @@ class PerformanceTest extends PerformanceTestBase {
$expected = [
'QueryCount' => 4,
- 'CacheGetCount' => 49,
+ 'CacheGetCount' => 48,
'CacheGetCountByBin' => [
'config' => 11,
'data' => 4,
'discovery' => 10,
'bootstrap' => 6,
'dynamic_page_cache' => 1,
- 'render' => 16,
+ 'render' => 15,
'menu' => 1,
],
'CacheSetCount' => 2,
@@ -91,7 +91,7 @@ class PerformanceTest extends PerformanceTestBase {
'CacheTagInvalidationCount' => 0,
'CacheTagLookupQueryCount' => 14,
'ScriptCount' => 3,
- 'ScriptBytes' => 215500,
+ 'ScriptBytes' => 213500,
'StylesheetCount' => 2,
'StylesheetBytes' => 46000,
];
diff --git a/core/modules/node/config/optional/views.view.content.yml b/core/modules/node/config/optional/views.view.content.yml
index d0bbb09cf5a0..09ba3dcc35ae 100644
--- a/core/modules/node/config/optional/views.view.content.yml
+++ b/core/modules/node/config/optional/views.view.content.yml
@@ -625,6 +625,7 @@ display:
empty_table: true
caption: ''
description: ''
+ class: ''
row:
type: fields
query:
diff --git a/core/modules/node/config/optional/views.view.glossary.yml b/core/modules/node/config/optional/views.view.glossary.yml
index 2698473d5271..868d51fb9e26 100644
--- a/core/modules/node/config/optional/views.view.glossary.yml
+++ b/core/modules/node/config/optional/views.view.glossary.yml
@@ -343,6 +343,7 @@ display:
summary: ''
order: asc
empty_table: false
+ class: ''
row:
type: fields
options:
diff --git a/core/modules/node/config/schema/node.schema.yml b/core/modules/node/config/schema/node.schema.yml
index 08fe92cc401e..8c81f68ae604 100644
--- a/core/modules/node/config/schema/node.schema.yml
+++ b/core/modules/node/config/schema/node.schema.yml
@@ -24,7 +24,7 @@ node.type.*:
label: 'Machine-readable name'
constraints:
# Node type machine names are specifically limited to 32 characters.
- # @see \Drupal\node\NodeTypeForm::form()
+ # @see \Drupal\node\Form\NodeTypeForm::form()
Length:
max: 32
description:
@@ -50,7 +50,7 @@ node.type.*:
constraints:
# These are the values of the DRUPAL_DISABLED, DRUPAL_OPTIONAL, and
# DRUPAL_REQUIRED constants.
- # @see \Drupal\node\NodeTypeForm::form()
+ # @see \Drupal\node\Form\NodeTypeForm::form()
Choice: [0, 1, 2]
display_submitted:
type: boolean
diff --git a/core/modules/node/node.install b/core/modules/node/node.install
index fb5c14c47675..4e232bdc2de9 100644
--- a/core/modules/node/node.install
+++ b/core/modules/node/node.install
@@ -5,137 +5,10 @@
* Install, update and uninstall functions for the node module.
*/
-use Drupal\Core\Link;
-use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
-use Drupal\Core\Url;
use Drupal\Core\Database\Database;
use Drupal\user\RoleInterface;
/**
- * Implements hook_requirements().
- */
-function node_requirements($phase): array {
- $requirements = [];
- if ($phase === 'runtime') {
- // Only show rebuild button if there are either 0, or 2 or more, rows
- // in the {node_access} table, or if there are modules that
- // implement hook_node_grants().
- $grant_count = \Drupal::entityTypeManager()->getAccessControlHandler('node')->countGrants();
- $has_node_grants_implementations = \Drupal::moduleHandler()->hasImplementations('node_grants');
- if ($grant_count != 1 || $has_node_grants_implementations) {
- $value = \Drupal::translation()->formatPlural($grant_count, 'One permission in use', '@count permissions in use', ['@count' => $grant_count]);
- }
- else {
- $value = t('Disabled');
- }
-
- $requirements['node_access'] = [
- 'title' => t('Node Access Permissions'),
- 'value' => $value,
- 'description' => t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Rebuilding will remove all privileges to content and replace them with permissions based on the current modules and settings. Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed, content will automatically use the new permissions. <a href=":rebuild">Rebuild permissions</a>', [
- ':rebuild' => Url::fromRoute('node.configure_rebuild_confirm')->toString(),
- ]),
- ];
-
- // Report when the "Published status or admin user" has no impact on the
- // result of dependent views due to active node access modules.
- // @see https://www.drupal.org/node/3472976
- if ($has_node_grants_implementations && \Drupal::moduleHandler()->moduleExists('views')) {
- $node_status_filter_problematic_views = [];
- $active_view_ids = \Drupal::entityQuery('view')
- ->condition('status', TRUE)
- ->accessCheck(FALSE)
- ->execute();
-
- $views_storage = \Drupal::entityTypeManager()->getStorage('view');
- foreach ($views_storage->loadMultiple($active_view_ids) as $view) {
- foreach ($view->get('display') as $display_id => $display) {
- if (array_key_exists('filters', $display['display_options'])) {
- foreach ($display['display_options']['filters'] as $filter) {
- if (array_key_exists('plugin_id', $filter) && $filter['plugin_id'] === 'node_status') {
- $node_status_filter_problematic_views[$view->id()][$display_id] = [
- 'view_label' => $view->label(),
- 'display_name' => $display['display_title'] ?? $display_id,
- ];
- break;
- }
- }
- }
- }
- }
-
- if ($node_status_filter_problematic_views !== []) {
- $node_access_implementations = [];
- $module_data = \Drupal::service('extension.list.module')->getAllInstalledInfo();
- foreach (['node_grants', 'node_grants_alter'] as $hook) {
- \Drupal::moduleHandler()->invokeAllWith(
- $hook,
- static function (callable $hook, string $module) use (&$node_access_implementations, $module_data) {
- $node_access_implementations[$module] = $module_data[$module]['name'];
- }
- );
- }
- uasort($node_access_implementations, 'strnatcasecmp');
- $views_ui_enabled = \Drupal::moduleHandler()->moduleExists('views_ui');
- $node_status_filter_problematic_views_list = [];
- foreach ($node_status_filter_problematic_views as $view_id => $displays) {
- foreach ($displays as $display_id => $info) {
- $text = "{$info['view_label']} ({$info['display_name']})";
- if ($views_ui_enabled) {
- $url = Url::fromRoute('entity.view.edit_display_form', [
- 'view' => $view_id,
- 'display_id' => $display_id,
- ]);
- if ($url->access()) {
- $node_status_filter_problematic_views_list[] = Link::fromTextAndUrl($text, $url)->toString();
- }
- else {
- $node_status_filter_problematic_views_list[] = $text;
- }
- }
- else {
- $node_status_filter_problematic_views_list[] = $text;
- }
- }
- }
-
- $node_status_filter_problematic_views_count = count($node_status_filter_problematic_views_list);
- $node_status_filter_description_arguments = [
- '%modules' => implode(', ', $node_access_implementations),
- '%status_filter' => t('Published status or admin user'),
- ];
-
- if ($node_status_filter_problematic_views_count > 1) {
- $node_status_filter_problematic_views_list = [
- '#theme' => 'item_list',
- '#items' => $node_status_filter_problematic_views_list,
- ];
- $node_status_filter_description_arguments['@views'] = \Drupal::service('renderer')->renderInIsolation($node_status_filter_problematic_views_list);
- }
- else {
- $node_status_filter_description_arguments['%view'] = reset($node_status_filter_problematic_views_list);
- }
-
- $node_status_filter_description = new PluralTranslatableMarkup(
- $node_status_filter_problematic_views_count,
- 'The %view view uses the %status_filter filter but it has no effect because the following module(s) control access: %modules. Review and consider removing the filter.',
- 'The following views use the %status_filter filter but it has no effect because the following module(s) control access: %modules. Review and consider removing the filter from these views: @views',
- $node_status_filter_description_arguments,
- );
-
- $requirements['node_status_filter'] = [
- 'title' => t('Content status filter'),
- 'value' => t('Redundant filters detected'),
- 'description' => $node_status_filter_description,
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
- }
- }
- return $requirements;
-}
-
-/**
* Implements hook_schema().
*/
function node_schema(): array {
diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml
index a70ae7faa576..83eb19f7355d 100644
--- a/core/modules/node/node.services.yml
+++ b/core/modules/node/node.services.yml
@@ -1,3 +1,16 @@
+parameters:
+ node.moved_classes:
+ 'Drupal\node\NodeForm':
+ class: 'Drupal\node\Form\NodeForm'
+ deprecation_version: drupal:11.2.0
+ removed_version: drupal:12.0.0
+ change_record: https://www.drupal.org/node/3517871
+ 'Drupal\node\NodeTypeForm':
+ class: 'Drupal\node\Form\NodeTypeForm'
+ deprecation_version: drupal:11.2.0
+ removed_version: drupal:12.0.0
+ change_record: https://www.drupal.org/node/3517871
+
services:
_defaults:
autoconfigure: true
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index f4c519a43c8a..0f61a74f1c01 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -11,8 +11,8 @@ use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\node\Form\DeleteMultiple;
use Drupal\node\Form\NodeDeleteForm;
+use Drupal\node\Form\NodeForm;
use Drupal\node\NodeAccessControlHandler;
-use Drupal\node\NodeForm;
use Drupal\node\NodeInterface;
use Drupal\node\NodeListBuilder;
use Drupal\node\NodeStorage;
diff --git a/core/modules/node/src/Entity/NodeType.php b/core/modules/node/src/Entity/NodeType.php
index 35b911d7ffb7..48d295635f3d 100644
--- a/core/modules/node/src/Entity/NodeType.php
+++ b/core/modules/node/src/Entity/NodeType.php
@@ -9,7 +9,7 @@ use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\node\Form\NodeTypeDeleteConfirm;
use Drupal\node\NodeTypeAccessControlHandler;
-use Drupal\node\NodeTypeForm;
+use Drupal\node\Form\NodeTypeForm;
use Drupal\node\NodeTypeInterface;
use Drupal\node\NodeTypeListBuilder;
use Drupal\user\Entity\EntityPermissionsRouteProvider;
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/Form/NodeForm.php
index ab81a4e3f928..d5afa396568c 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/Form/NodeForm.php
@@ -1,6 +1,6 @@
<?php
-namespace Drupal\node;
+namespace Drupal\node\Form;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
@@ -163,7 +163,7 @@ class NodeForm extends ContentEntityForm {
$form['meta']['author'] = [
'#type' => 'item',
'#title' => $this->t('Author'),
- '#markup' => $node->getOwner()->getAccountName(),
+ '#markup' => $node->getOwner()?->getAccountName(),
'#wrapper_attributes' => ['class' => ['entity-meta__author']],
];
diff --git a/core/modules/node/src/NodeTypeForm.php b/core/modules/node/src/Form/NodeTypeForm.php
index 3328ade970da..93d510d4387e 100644
--- a/core/modules/node/src/NodeTypeForm.php
+++ b/core/modules/node/src/Form/NodeTypeForm.php
@@ -1,6 +1,6 @@
<?php
-namespace Drupal\node;
+namespace Drupal\node\Form;
use Drupal\Core\Entity\BundleEntityFormBase;
use Drupal\Core\Entity\EntityFieldManagerInterface;
diff --git a/core/modules/node/src/Hook/NodeRequirements.php b/core/modules/node/src/Hook/NodeRequirements.php
new file mode 100644
index 000000000000..aa8b39d5682f
--- /dev/null
+++ b/core/modules/node/src/Hook/NodeRequirements.php
@@ -0,0 +1,154 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\node\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\ModuleExtensionList;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Link;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Url;
+
+/**
+ * Requirements for the Node module.
+ */
+class NodeRequirements {
+
+ use StringTranslationTrait;
+
+ public function __construct(
+ protected readonly EntityTypeManagerInterface $entityTypeManager,
+ protected readonly ModuleHandlerInterface $moduleHandler,
+ protected readonly TranslationInterface $translation,
+ protected readonly ModuleExtensionList $moduleExtensionList,
+ ) {}
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+ // Only show rebuild button if there are either 0, or 2 or more, rows
+ // in the {node_access} table, or if there are modules that
+ // implement hook_node_grants().
+ $grant_count = $this->entityTypeManager->getAccessControlHandler('node')->countGrants();
+ $has_node_grants_implementations = $this->moduleHandler->hasImplementations('node_grants');
+ if ($grant_count != 1 || $has_node_grants_implementations) {
+ $value = $this->translation->formatPlural($grant_count, 'One permission in use', '@count permissions in use', ['@count' => $grant_count]);
+ }
+ else {
+ $value = $this->t('Disabled');
+ }
+
+ $requirements['node_access'] = [
+ 'title' => $this->t('Node Access Permissions'),
+ 'value' => $value,
+ 'description' => $this->t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Rebuilding will remove all privileges to content and replace them with permissions based on the current modules and settings. Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed, content will automatically use the new permissions. <a href=":rebuild">Rebuild permissions</a>', [
+ ':rebuild' => Url::fromRoute('node.configure_rebuild_confirm')->toString(),
+ ]),
+ ];
+
+ // Report when the "Published status or admin user" has no impact on the
+ // result of dependent views due to active node access modules.
+ // @see https://www.drupal.org/node/3472976
+ if ($has_node_grants_implementations && $this->moduleHandler->moduleExists('views')) {
+ $node_status_filter_problematic_views = [];
+ $query = $this->entityTypeManager->getStorage('view')->getQuery();
+ $query->condition('status', TRUE);
+ $query->accessCheck(FALSE);
+ $active_view_ids = $query->execute();
+
+ $views_storage = $this->entityTypeManager->getStorage('view');
+ foreach ($views_storage->loadMultiple($active_view_ids) as $view) {
+ foreach ($view->get('display') as $display_id => $display) {
+ if (array_key_exists('filters', $display['display_options'])) {
+ foreach ($display['display_options']['filters'] as $filter) {
+ if (array_key_exists('plugin_id', $filter) && $filter['plugin_id'] === 'node_status') {
+ $node_status_filter_problematic_views[$view->id()][$display_id] = [
+ 'view_label' => $view->label(),
+ 'display_name' => $display['display_title'] ?? $display_id,
+ ];
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if ($node_status_filter_problematic_views !== []) {
+ $node_access_implementations = [];
+ $module_data = $this->moduleExtensionList->getAllInstalledInfo();
+ foreach (['node_grants', 'node_grants_alter'] as $hook) {
+ $this->moduleHandler->invokeAllWith(
+ $hook,
+ static function (callable $hook, string $module) use (&$node_access_implementations, $module_data) {
+ $node_access_implementations[$module] = $module_data[$module]['name'];
+ }
+ );
+ }
+ uasort($node_access_implementations, 'strnatcasecmp');
+ $views_ui_enabled = $this->moduleHandler->moduleExists('views_ui');
+ $node_status_filter_problematic_views_list = [];
+ foreach ($node_status_filter_problematic_views as $view_id => $displays) {
+ foreach ($displays as $display_id => $info) {
+ $text = "{$info['view_label']} ({$info['display_name']})";
+ if ($views_ui_enabled) {
+ $url = Url::fromRoute('entity.view.edit_display_form', [
+ 'view' => $view_id,
+ 'display_id' => $display_id,
+ ]);
+ if ($url->access()) {
+ $node_status_filter_problematic_views_list[] = Link::fromTextAndUrl($text, $url)->toString();
+ }
+ else {
+ $node_status_filter_problematic_views_list[] = $text;
+ }
+ }
+ else {
+ $node_status_filter_problematic_views_list[] = $text;
+ }
+ }
+ }
+
+ $node_status_filter_problematic_views_count = count($node_status_filter_problematic_views_list);
+ $node_status_filter_description_arguments = [
+ '%modules' => implode(', ', $node_access_implementations),
+ '%status_filter' => $this->t('Published status or admin user'),
+ ];
+
+ if ($node_status_filter_problematic_views_count > 1) {
+ $node_status_filter_problematic_views_list = [
+ '#theme' => 'item_list',
+ '#items' => $node_status_filter_problematic_views_list,
+ ];
+ $node_status_filter_description_arguments['@views'] = \Drupal::service('renderer')->renderInIsolation($node_status_filter_problematic_views_list);
+ }
+ else {
+ $node_status_filter_description_arguments['%view'] = reset($node_status_filter_problematic_views_list);
+ }
+
+ $node_status_filter_description = new PluralTranslatableMarkup(
+ $node_status_filter_problematic_views_count,
+ 'The %view view uses the %status_filter filter but it has no effect because the following module(s) control access: %modules. Review and consider removing the filter.',
+ 'The following views use the %status_filter filter but it has no effect because the following module(s) control access: %modules. Review and consider removing the filter from these views: @views',
+ $node_status_filter_description_arguments,
+ );
+
+ $requirements['node_status_filter'] = [
+ 'title' => $this->t('Content status filter'),
+ 'value' => $this->t('Redundant filters detected'),
+ 'description' => $node_status_filter_description,
+ 'severity' => REQUIREMENT_WARNING,
+ ];
+ }
+ }
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/node/src/Plugin/views/wizard/Node.php b/core/modules/node/src/Plugin/views/wizard/Node.php
index 84a21c3f7f2e..d66fa956f9aa 100644
--- a/core/modules/node/src/Plugin/views/wizard/Node.php
+++ b/core/modules/node/src/Plugin/views/wizard/Node.php
@@ -89,11 +89,7 @@ class Node extends WizardPluginBase {
}
/**
- * Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::getAvailableSorts().
- *
- * @return array
- * An array whose keys are the available sort options and whose
- * corresponding values are human readable labels.
+ * {@inheritdoc}
*/
public function getAvailableSorts() {
// You can't execute functions in properties, so override the method
@@ -238,9 +234,7 @@ class Node extends WizardPluginBase {
}
/**
- * Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::buildFilters().
- *
- * Add some options for filter by taxonomy terms.
+ * {@inheritdoc}
*/
protected function buildFilters(&$form, FormStateInterface $form_state) {
parent::buildFilters($form, $form_state);
diff --git a/core/modules/node/src/Plugin/views/wizard/NodeRevision.php b/core/modules/node/src/Plugin/views/wizard/NodeRevision.php
index c10389cc2392..96422504cf5e 100644
--- a/core/modules/node/src/Plugin/views/wizard/NodeRevision.php
+++ b/core/modules/node/src/Plugin/views/wizard/NodeRevision.php
@@ -29,11 +29,10 @@ class NodeRevision extends WizardPluginBase {
protected $createdColumn = 'changed';
/**
- * Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::rowStyleOptions().
- *
- * Node revisions do not support full posts or teasers, so remove them.
+ * {@inheritdoc}
*/
protected function rowStyleOptions() {
+ // Node revisions do not support full posts or teasers, so remove them.
$options = parent::rowStyleOptions();
unset($options['teasers']);
unset($options['full_posts']);
diff --git a/core/modules/node/tests/modules/node_no_default_author/node_no_default_author.info.yml b/core/modules/node/tests/modules/node_no_default_author/node_no_default_author.info.yml
new file mode 100644
index 000000000000..c4f56344370f
--- /dev/null
+++ b/core/modules/node/tests/modules/node_no_default_author/node_no_default_author.info.yml
@@ -0,0 +1,5 @@
+name: 'Node no default author'
+type: module
+description: 'Disables the default value callback for the uid field on node.'
+package: Testing
+version: VERSION
diff --git a/core/modules/node/tests/modules/node_no_default_author/src/Hook/NodeNoDefaultAuthorHooks.php b/core/modules/node/tests/modules/node_no_default_author/src/Hook/NodeNoDefaultAuthorHooks.php
new file mode 100644
index 000000000000..700e82236adc
--- /dev/null
+++ b/core/modules/node/tests/modules/node_no_default_author/src/Hook/NodeNoDefaultAuthorHooks.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\node_no_default_author\Hook;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for node_no_default_author.
+ */
+class NodeNoDefaultAuthorHooks {
+
+ /**
+ * Implements hook_entity_base_field_info_alter().
+ */
+ #[Hook('entity_base_field_info_alter')]
+ public function entityBaseFieldInfoAlter(&$fields, EntityTypeInterface $entity_type): void {
+ if ($entity_type->id() === 'node') {
+ $fields['uid']->setDefaultValueCallback(static::class . '::noDefaultAuthor');
+ }
+ }
+
+ /**
+ * An empty callback to set for the default value callback of uid.
+ */
+ public static function noDefaultAuthor(): void {
+ }
+
+}
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml
index 766f656fc2bf..65ca46658711 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml
@@ -68,6 +68,16 @@ display:
quantity: 9
style:
type: table
+ options:
+ grouping: { }
+ class: ''
+ row_class: ''
+ default_row_class: true
+ override: true
+ sticky: false
+ caption: ''
+ summary: ''
+ description: ''
row:
type: fields
fields:
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
index d7a3fa080fed..107165a27fb6 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
@@ -19,6 +19,8 @@ display:
display_options:
style:
type: table
+ options:
+ class: ''
row:
type: fields
fields:
diff --git a/core/modules/node/tests/src/Functional/NodeEditFormTest.php b/core/modules/node/tests/src/Functional/NodeEditFormTest.php
index bc8cfa927e43..dc47998c9093 100644
--- a/core/modules/node/tests/src/Functional/NodeEditFormTest.php
+++ b/core/modules/node/tests/src/Functional/NodeEditFormTest.php
@@ -258,6 +258,16 @@ class NodeEditFormTest extends NodeTestBase {
}
/**
+ * Tests the node form when the author is NULL.
+ */
+ public function testNodeFormNullAuthor(): void {
+ \Drupal::service('module_installer')->install(['node_no_default_author']);
+ $this->drupalLogin($this->adminUser);
+ $this->drupalGet('node/add/page');
+ $this->assertSession()->statusCodeEquals(200);
+ }
+
+ /**
* Checks that the "authored by" works correctly with various values.
*
* @param \Drupal\node\NodeInterface $node
diff --git a/core/modules/node/tests/src/Kernel/NodeRequirementsStatusFilterWarningTest.php b/core/modules/node/tests/src/Kernel/NodeRequirementsStatusFilterWarningTest.php
index 6ddb8c8c987d..60ce5c7cdb04 100644
--- a/core/modules/node/tests/src/Kernel/NodeRequirementsStatusFilterWarningTest.php
+++ b/core/modules/node/tests/src/Kernel/NodeRequirementsStatusFilterWarningTest.php
@@ -197,8 +197,7 @@ class NodeRequirementsStatusFilterWarningTest extends KernelTestBase {
* The requirements raised by the Node module.
*/
private function getRequirements(): array {
- $this->container->get('module_handler')->loadInclude('node', 'install');
- return node_requirements('runtime');
+ return $this->container->get('module_handler')->invoke('node', 'runtime_requirements');
}
/**
diff --git a/core/modules/package_manager/package_manager.install b/core/modules/package_manager/package_manager.install
deleted file mode 100644
index 9c5c65773dde..000000000000
--- a/core/modules/package_manager/package_manager.install
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains install and update functions for Package Manager.
- */
-
-declare(strict_types=1);
-
-use Drupal\Core\Site\Settings;
-use Drupal\package_manager\ComposerInspector;
-use Drupal\package_manager\Exception\FailureMarkerExistsException;
-use Drupal\package_manager\FailureMarker;
-use PhpTuf\ComposerStager\API\Exception\ExceptionInterface;
-use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
-
-/**
- * Implements hook_requirements().
- */
-function package_manager_requirements(string $phase): array {
- $requirements = [];
-
- if (Settings::get('testing_package_manager', FALSE) === FALSE) {
- $requirements['testing_package_manager'] = [
- 'title' => 'Package Manager',
- 'description' => t("Package Manager is available for early testing. To install the module set the value of 'testing_package_manager' to TRUE in your settings.php file."),
- 'severity' => REQUIREMENT_ERROR,
- ];
- return $requirements;
- }
-
- // If we're able to check for the presence of the failure marker at all, do it
- // irrespective of the current run phase. If the failure marker is there, the
- // site is in an indeterminate state and should be restored from backup ASAP.
- $service_id = FailureMarker::class;
- if (\Drupal::hasService($service_id)) {
- try {
- \Drupal::service($service_id)->assertNotExists(NULL);
- }
- catch (FailureMarkerExistsException $exception) {
- $requirements['package_manager_failure_marker'] = [
- 'title' => t('Failed Package Manager update detected'),
- 'description' => $exception->getMessage(),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- }
-
- if ($phase !== 'runtime') {
- return $requirements;
- }
- /** @var \PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface $executable_finder */
- $executable_finder = \Drupal::service(ExecutableFinderInterface::class);
-
- // Report the Composer version in use, as well as its path.
- $title = t('Composer version');
- try {
- $requirements['package_manager_composer'] = [
- 'title' => $title,
- 'description' => t('@version (<code>@path</code>)', [
- '@version' => \Drupal::service(ComposerInspector::class)->getVersion(),
- '@path' => $executable_finder->find('composer'),
- ]),
- 'severity' => REQUIREMENT_INFO,
- ];
- }
- catch (\Throwable $e) {
- // All Composer Stager exceptions are translatable.
- $message = $e instanceof ExceptionInterface
- ? $e->getTranslatableMessage()
- : $e->getMessage();
-
- $requirements['package_manager_composer'] = [
- 'title' => $title,
- 'description' => t('Composer was not found. The error message was: @message', [
- '@message' => $message,
- ]),
- 'severity' => REQUIREMENT_ERROR,
- ];
- }
- return $requirements;
-}
diff --git a/core/modules/package_manager/src/Hook/PackageManagerRequirementsHooks.php b/core/modules/package_manager/src/Hook/PackageManagerRequirementsHooks.php
new file mode 100644
index 000000000000..4d315a94330f
--- /dev/null
+++ b/core/modules/package_manager/src/Hook/PackageManagerRequirementsHooks.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Drupal\package_manager\Hook;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Site\Settings;
+use Drupal\package_manager\ComposerInspector;
+use Drupal\package_manager\Exception\FailureMarkerExistsException;
+use Drupal\package_manager\FailureMarker;
+use PhpTuf\ComposerStager\API\Exception\ExceptionInterface;
+use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
+
+/**
+ * Requirements checks for Package Manager.
+ */
+class PackageManagerRequirementsHooks {
+
+ use StringTranslationTrait;
+
+ public function __construct(
+ protected readonly ComposerInspector $composerInspector,
+ protected ExecutableFinderInterface $executableFinder,
+ ) {}
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+ $requirements = $this->checkSettings($requirements);
+ $requirements = $this->checkFailure($requirements);
+
+ // Report the Composer version in use, as well as its path.
+ $title = $this->t('Composer version');
+ try {
+ $requirements['package_manager_composer'] = [
+ 'title' => $title,
+ 'description' => $this->t('@version (<code>@path</code>)', [
+ '@version' => $this->composerInspector->getVersion(),
+ '@path' => $this->executableFinder->find('composer'),
+ ]),
+ 'severity' => REQUIREMENT_INFO,
+ ];
+ }
+ catch (\Throwable $e) {
+ // All Composer Stager exceptions are translatable.
+ $message = $e instanceof ExceptionInterface
+ ? $e->getTranslatableMessage()
+ : $e->getMessage();
+
+ $requirements['package_manager_composer'] = [
+ 'title' => $title,
+ 'description' => $this->t('Composer was not found. The error message was: @message', [
+ '@message' => $message,
+ ]),
+ 'severity' => REQUIREMENT_ERROR,
+ ];
+ }
+
+ return $requirements;
+ }
+
+ /**
+ * Implements hook_update_requirements().
+ */
+ #[Hook('update_requirements')]
+ public function update(): array {
+ $requirements = [];
+ $requirements = $this->checkSettings($requirements);
+ $requirements = $this->checkFailure($requirements);
+ return $requirements;
+ }
+
+ /**
+ * Check that package manager has an explicit setting to allow installation.
+ *
+ * @param array $requirements
+ * The requirements array that has been processed so far.
+ *
+ * @return array
+ * Requirements array.
+ *
+ * @see hook_runtime_requirements
+ * @see hook_update_requirements
+ */
+ public function checkSettings($requirements): array {
+ if (Settings::get('testing_package_manager', FALSE) === FALSE) {
+ $requirements['testing_package_manager'] = [
+ 'title' => 'Package Manager',
+ 'description' => $this->t("Package Manager is available for early testing. To install the module set the value of 'testing_package_manager' to TRUE in your settings.php file."),
+ 'severity' => REQUIREMENT_ERROR,
+ ];
+ }
+
+ return $requirements;
+ }
+
+ /**
+ * Check for a failed update.
+ *
+ * This is run during requirements to allow restoring from backup.
+ *
+ * @param array $requirements
+ * The requirements array that has been processed so far.
+ *
+ * @return array
+ * Requirements array.
+ *
+ * @see hook_runtime_requirements
+ * @see hook_update_requirements
+ */
+ public function checkFailure(array $requirements): array {
+ // If we're able to check for the presence of the failure marker at all, do
+ // it irrespective of the current run phase. If the failure marker is there,
+ // the site is in an indeterminate state and should be restored from backup
+ // ASAP.
+ $service_id = FailureMarker::class;
+ if (\Drupal::hasService($service_id)) {
+ try {
+ \Drupal::service($service_id)->assertNotExists(NULL);
+ }
+ catch (FailureMarkerExistsException $exception) {
+ $requirements['package_manager_failure_marker'] = [
+ 'title' => $this->t('Failed Package Manager update detected'),
+ 'description' => $exception->getMessage(),
+ 'severity' => REQUIREMENT_ERROR,
+ ];
+ }
+ }
+
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/package_manager/src/Install/Requirements/PackageManagerRequirements.php b/core/modules/package_manager/src/Install/Requirements/PackageManagerRequirements.php
new file mode 100644
index 000000000000..45e0166ed878
--- /dev/null
+++ b/core/modules/package_manager/src/Install/Requirements/PackageManagerRequirements.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\package_manager\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+use Drupal\Core\Site\Settings;
+use Drupal\package_manager\Exception\FailureMarkerExistsException;
+use Drupal\package_manager\FailureMarker;
+
+/**
+ * Install time requirements for the package_manager module.
+ */
+class PackageManagerRequirements implements InstallRequirementsInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getRequirements(): array {
+ $requirements = [];
+
+ if (Settings::get('testing_package_manager', FALSE) === FALSE) {
+ $requirements['testing_package_manager'] = [
+ 'title' => 'Package Manager',
+ 'description' => t("Package Manager is available for early testing. To install the module set the value of 'testing_package_manager' to TRUE in your settings.php file."),
+ 'severity' => REQUIREMENT_ERROR,
+ ];
+ }
+
+ // If we're able to check for the presence of the failure marker at all, do
+ // it irrespective of the current run phase. If the failure marker is there,
+ // the site is in an indeterminate state and should be restored from backup
+ // ASAP.
+ $service_id = FailureMarker::class;
+ if (\Drupal::hasService($service_id)) {
+ try {
+ \Drupal::service($service_id)->assertNotExists(NULL);
+ }
+ catch (FailureMarkerExistsException $exception) {
+ $requirements['package_manager_failure_marker'] = [
+ 'title' => t('Failed Package Manager update detected'),
+ 'description' => $exception->getMessage(),
+ 'severity' => REQUIREMENT_ERROR,
+ ];
+ }
+ }
+
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/package_manager/src/Validator/ComposerPluginsValidator.php b/core/modules/package_manager/src/Validator/ComposerPluginsValidator.php
index ba6d32f29fec..1297e6a711ed 100644
--- a/core/modules/package_manager/src/Validator/ComposerPluginsValidator.php
+++ b/core/modules/package_manager/src/Validator/ComposerPluginsValidator.php
@@ -78,6 +78,7 @@ final class ComposerPluginsValidator implements EventSubscriberInterface {
'composer/installers' => '^2.0',
'dealerdirect/phpcodesniffer-composer-installer' => '^0.7.1 || ^1.0.0',
'drupal/core-composer-scaffold' => '*',
+ 'drupal/core-recipe-unpack' => '*',
'drupal/core-project-message' => '*',
'phpstan/extension-installer' => '^1.1',
PhpTufValidator::PLUGIN_NAME => '^1',
diff --git a/core/modules/pgsql/pgsql.install b/core/modules/pgsql/pgsql.install
index 3adeb4f5dff3..682ef7d605b5 100644
--- a/core/modules/pgsql/pgsql.install
+++ b/core/modules/pgsql/pgsql.install
@@ -5,42 +5,6 @@
* Install, update and uninstall functions for the pgsql module.
*/
-use Drupal\Core\Database\Database;
-
-/**
- * Implements hook_requirements().
- */
-function pgsql_requirements(): array {
- $requirements = [];
- // Test with PostgreSQL databases for the status of the pg_trgm extension.
- if (Database::isActiveConnection()) {
- $connection = Database::getConnection();
-
- // Set the requirement just for postgres.
- if ($connection->driver() == 'pgsql') {
- $requirements['pgsql_extension_pg_trgm'] = [
- 'severity' => REQUIREMENT_OK,
- 'title' => t('PostgreSQL pg_trgm extension'),
- 'value' => t('Available'),
- 'description' => 'The pg_trgm PostgreSQL extension is present.',
- ];
-
- // If the extension is not available, set the requirement error.
- if (!$connection->schema()->extensionExists('pg_trgm')) {
- $requirements['pgsql_extension_pg_trgm']['severity'] = REQUIREMENT_ERROR;
- $requirements['pgsql_extension_pg_trgm']['value'] = t('Not created');
- $requirements['pgsql_extension_pg_trgm']['description'] = t('The <a href=":pg_trgm">pg_trgm</a> PostgreSQL extension is not present. The extension is required by Drupal 10 to improve performance when using PostgreSQL. See <a href=":requirements">Drupal database server requirements</a> for more information.', [
- ':pg_trgm' => 'https://www.postgresql.org/docs/current/pgtrgm.html',
- ':requirements' => 'https://www.drupal.org/docs/system-requirements/database-server-requirements',
- ]);
- }
-
- }
- }
-
- return $requirements;
-}
-
/**
* Implements hook_update_last_removed().
*/
diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php
index a4585e15da7a..440dc7179dbf 100644
--- a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php
+++ b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php
@@ -1072,7 +1072,8 @@ EOD;
/**
* Calculates a base-64 encoded PostgreSQL-safe sha-256 hash.
*
- * The hash is modified to according to @link https://www.postgresql.org/docs/current/sql-syntax-lexical.html PostgreSQL Lexical Structure@endlink.
+ * The hash is modified to according to PostgreSQL Lexical Structure. See
+ * https://www.postgresql.org/docs/current/sql-syntax-lexical.html.
*
* @param string $data
* String to be hashed.
diff --git a/core/modules/pgsql/src/Hook/PgsqlRequirementsHooks.php b/core/modules/pgsql/src/Hook/PgsqlRequirementsHooks.php
new file mode 100644
index 000000000000..66b8e2dfea09
--- /dev/null
+++ b/core/modules/pgsql/src/Hook/PgsqlRequirementsHooks.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\pgsql\Hook;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for pgsql module.
+ */
+class PgsqlRequirementsHooks {
+
+ use StringTranslationTrait;
+
+ /**
+ * Implements hook_update_requirements().
+ *
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('update_requirements')]
+ #[Hook('runtime_requirements')]
+ public function checkRequirements(): array {
+ $requirements = [];
+ // Test with PostgreSQL databases for the status of the pg_trgm extension.
+ if (Database::isActiveConnection()) {
+ $connection = Database::getConnection();
+
+ // Set the requirement just for postgres.
+ if ($connection->driver() == 'pgsql') {
+ $requirements['pgsql_extension_pg_trgm'] = [
+ 'severity' => REQUIREMENT_OK,
+ 'title' => $this->t('PostgreSQL pg_trgm extension'),
+ 'value' => $this->t('Available'),
+ 'description' => $this->t('The pg_trgm PostgreSQL extension is present.'),
+ ];
+
+ // If the extension is not available, set the requirement error.
+ if (!$connection->schema()->extensionExists('pg_trgm')) {
+ $requirements['pgsql_extension_pg_trgm']['severity'] = REQUIREMENT_ERROR;
+ $requirements['pgsql_extension_pg_trgm']['value'] = $this->t('Not created');
+ $requirements['pgsql_extension_pg_trgm']['description'] = $this->t('The <a href=":pg_trgm">pg_trgm</a> PostgreSQL extension is not present. The extension is required by Drupal to improve performance when using PostgreSQL. See <a href=":requirements">Drupal database server requirements</a> for more information.', [
+ ':pg_trgm' => 'https://www.postgresql.org/docs/current/pgtrgm.html',
+ ':requirements' => 'https://www.drupal.org/docs/system-requirements/database-server-requirements',
+ ]);
+ }
+
+ }
+ }
+
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/pgsql/src/Install/Requirements/PgsqlRequirements.php b/core/modules/pgsql/src/Install/Requirements/PgsqlRequirements.php
new file mode 100644
index 000000000000..a2f7771575eb
--- /dev/null
+++ b/core/modules/pgsql/src/Install/Requirements/PgsqlRequirements.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\pgsql\Install\Requirements;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Extension\InstallRequirementsInterface;
+
+/**
+ * Install time requirements for the pgsql module.
+ */
+class PgsqlRequirements implements InstallRequirementsInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getRequirements(): array {
+ $requirements = [];
+ // Test with PostgreSQL databases for the status of the pg_trgm extension.
+ if (Database::isActiveConnection()) {
+ $connection = Database::getConnection();
+
+ // Set the requirement just for postgres.
+ if ($connection->driver() == 'pgsql') {
+ $requirements['pgsql_extension_pg_trgm'] = [
+ 'severity' => REQUIREMENT_OK,
+ 'title' => t('PostgreSQL pg_trgm extension'),
+ 'value' => t('Available'),
+ 'description' => t('The pg_trgm PostgreSQL extension is present.'),
+ ];
+
+ // If the extension is not available, set the requirement error.
+ if (!$connection->schema()->extensionExists('pg_trgm')) {
+ $requirements['pgsql_extension_pg_trgm']['severity'] = REQUIREMENT_ERROR;
+ $requirements['pgsql_extension_pg_trgm']['value'] = t('Not created');
+ $requirements['pgsql_extension_pg_trgm']['description'] = t('The <a href=":pg_trgm">pg_trgm</a> PostgreSQL extension is not present. The extension is required by Drupal to improve performance when using PostgreSQL. See <a href=":requirements">Drupal database server requirements</a> for more information.', [
+ ':pg_trgm' => 'https://www.postgresql.org/docs/current/pgtrgm.html',
+ ':requirements' => 'https://www.drupal.org/docs/system-requirements/database-server-requirements',
+ ]);
+ }
+
+ }
+ }
+
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/search/search.install b/core/modules/search/search.install
index 8873e1fbbf99..6e3e2691aa0f 100644
--- a/core/modules/search/search.install
+++ b/core/modules/search/search.install
@@ -122,35 +122,3 @@ function search_schema(): array {
return $schema;
}
-
-/**
- * Implements hook_requirements().
- *
- * For the Status Report, return information about search index status.
- */
-function search_requirements($phase): array {
- $requirements = [];
-
- if ($phase == 'runtime') {
- $remaining = 0;
- $total = 0;
- $search_page_repository = \Drupal::service('search.search_page_repository');
- foreach ($search_page_repository->getIndexableSearchPages() as $entity) {
- $status = $entity->getPlugin()->indexStatus();
- $remaining += $status['remaining'];
- $total += $status['total'];
- }
-
- $done = $total - $remaining;
- // Use floor() to calculate the percentage, so if it is not quite 100%, it
- // will show as 99%, to indicate "almost done".
- $percent = ($total > 0 ? floor(100 * $done / $total) : 100);
- $requirements['search_status'] = [
- 'title' => t('Search index progress'),
- 'value' => t('@percent% (@remaining remaining)', ['@percent' => $percent, '@remaining' => $remaining]),
- 'severity' => REQUIREMENT_INFO,
- ];
- }
-
- return $requirements;
-}
diff --git a/core/modules/search/src/Entity/SearchPage.php b/core/modules/search/src/Entity/SearchPage.php
index 894da73bbac0..f6c87fce2c66 100644
--- a/core/modules/search/src/Entity/SearchPage.php
+++ b/core/modules/search/src/Entity/SearchPage.php
@@ -214,7 +214,9 @@ class SearchPage extends ConfigEntityBase implements SearchPageInterface, Entity
}
/**
- * Helper callback for uasort() to sort search page entities by status, weight and label.
+ * Sorts search page entities by status, weight and label.
+ *
+ * Callback for uasort().
*/
public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) {
/** @var \Drupal\search\SearchPageInterface $a */
diff --git a/core/modules/search/src/Hook/SearchRequirements.php b/core/modules/search/src/Hook/SearchRequirements.php
new file mode 100644
index 000000000000..4fd79e640312
--- /dev/null
+++ b/core/modules/search/src/Hook/SearchRequirements.php
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\search\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\search\SearchPageRepositoryInterface;
+
+/**
+ * Requirements for the Search module.
+ */
+class SearchRequirements {
+
+ use StringTranslationTrait;
+
+ public function __construct(
+ protected readonly SearchPageRepositoryInterface $searchPageRepository,
+ ) {}
+
+ /**
+ * Implements hook_runtime_requirements().
+ *
+ * For the Status Report, return information about search index status.
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+ $remaining = 0;
+ $total = 0;
+ foreach ($this->searchPageRepository->getIndexableSearchPages() as $entity) {
+ $status = $entity->getPlugin()->indexStatus();
+ $remaining += $status['remaining'];
+ $total += $status['total'];
+ }
+
+ $done = $total - $remaining;
+ // Use floor() to calculate the percentage, so if it is not quite 100%, it
+ // will show as 99%, to indicate "almost done".
+ $percent = ($total > 0 ? floor(100 * $done / $total) : 100);
+ $requirements['search_status'] = [
+ 'title' => $this->t('Search index progress'),
+ 'value' => $this->t('@percent% (@remaining remaining)', ['@percent' => $percent, '@remaining' => $remaining]),
+ 'severity' => REQUIREMENT_INFO,
+ ];
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php b/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php
index 3779e7625195..cbcdffcd2f95 100644
--- a/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php
+++ b/core/modules/sqlite/src/Driver/Database/sqlite/Schema.php
@@ -149,7 +149,7 @@ class Schema extends DatabaseSchema {
}
/**
- * Create an SQL string for a field to be used in table creation or alteration.
+ * Create an SQL string for a field to be used in table create or alter.
*
* Before passing a field out of a schema definition into this function it has
* to be processed by self::processField().
@@ -647,7 +647,7 @@ class Schema extends DatabaseSchema {
}
/**
- * Utility method: rename columns in an index definition according to a new mapping.
+ * Renames columns in an index definition according to a new mapping.
*
* @param array $key_definition
* The key definition.
diff --git a/core/modules/system/src/EventSubscriber/AccessRouteAlterSubscriber.php b/core/modules/system/src/EventSubscriber/AccessRouteAlterSubscriber.php
index c9250047f1fa..777efab6b0c5 100644
--- a/core/modules/system/src/EventSubscriber/AccessRouteAlterSubscriber.php
+++ b/core/modules/system/src/EventSubscriber/AccessRouteAlterSubscriber.php
@@ -23,7 +23,7 @@ class AccessRouteAlterSubscriber implements EventSubscriberInterface {
}
/**
- * Adds _access_admin_menu_block_page requirement to routes pointing to SystemController::systemAdminMenuBlockPage.
+ * Adds requirements to some System Controller routes.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The event to process.
diff --git a/core/modules/system/src/Plugin/migrate/process/d6/SystemUpdate7000.php b/core/modules/system/src/Plugin/migrate/process/d6/SystemUpdate7000.php
index ee1550984f77..2fbfd980d344 100644
--- a/core/modules/system/src/Plugin/migrate/process/d6/SystemUpdate7000.php
+++ b/core/modules/system/src/Plugin/migrate/process/d6/SystemUpdate7000.php
@@ -16,7 +16,7 @@ class SystemUpdate7000 extends ProcessPluginBase {
/**
* {@inheritdoc}
*
- * Rename blog and forum permissions to be consistent with other content types.
+ * Makes blog and forum permissions to be consistent with other content types.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$value = preg_replace('/(?<=^|,\ )create\ blog\ entries(?=,|$)/', 'create blog content', $value);
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index c7ed1808206f..dbda1e1b6b4b 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -223,7 +223,7 @@ function template_preprocess_entity_add_list(&$variables): void {
* Optional string to use as the page title once redirected to authorize.php.
*/
function system_authorized_init($callback, $file, $arguments = [], $page_title = NULL): void {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
$session = \Drupal::request()->getSession();
// First, figure out what file transfer backends the site supports, and put
@@ -255,7 +255,7 @@ function system_authorized_init($callback, $file, $arguments = [], $page_title =
* @see system_authorized_init()
*/
function system_authorized_get_url(array $options = []) {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
// core/authorize.php is an unrouted URL, so using the base: scheme is
// the correct usage for this case.
@@ -275,7 +275,7 @@ function system_authorized_get_url(array $options = []) {
* The full URL for the authorize.php script with batch processing options.
*/
function system_authorized_batch_processing_url(array $options = []) {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
$options['query'] = ['batch' => '1'];
return system_authorized_get_url($options);
@@ -287,7 +287,7 @@ function system_authorized_batch_processing_url(array $options = []) {
* @see system_authorized_init()
*/
function system_authorized_run($callback, $file, $arguments = [], $page_title = NULL) {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
system_authorized_init($callback, $file, $arguments, $page_title);
return new RedirectResponse(system_authorized_get_url()->toString());
@@ -299,7 +299,7 @@ function system_authorized_run($callback, $file, $arguments = [], $page_title =
* @see batch_process()
*/
function system_authorized_batch_process() {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
$finish_url = system_authorized_get_url();
$process_url = system_authorized_batch_processing_url();
diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php
index b1ed3dbe019f..ce110e2c2f36 100644
--- a/core/modules/system/system.post_update.php
+++ b/core/modules/system/system.post_update.php
@@ -108,7 +108,7 @@ function system_post_update_remove_path_key(): void {
}
/**
- * Updates all entity_form_mode configuration entities to set description from empty string to null.
+ * Updates entity_form_mode descriptions from empty string to null.
*/
function system_post_update_convert_empty_description_entity_form_modes_to_null(array &$sandbox): void {
\Drupal::classResolver(ConfigEntityUpdater::class)
diff --git a/core/modules/system/templates/authorize-report.html.twig b/core/modules/system/templates/authorize-report.html.twig
index 914458684775..f6f443c58075 100644
--- a/core/modules/system/templates/authorize-report.html.twig
+++ b/core/modules/system/templates/authorize-report.html.twig
@@ -12,6 +12,11 @@
* @see template_preprocess_authorize_report()
*
* @ingroup themeable
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement. Use composer to manage the code for your site.
+ *
+ * @see https://www.drupal.org/node/3522119
*/
#}
{% if messages %}
diff --git a/core/modules/system/tests/modules/element_info_test/src/Element/DeprecatedExtendsFormElement.php b/core/modules/system/tests/modules/element_info_test/src/Element/DeprecatedExtendsFormElement.php
index 44308c3f6a9c..33dba9b0bf6f 100644
--- a/core/modules/system/tests/modules/element_info_test/src/Element/DeprecatedExtendsFormElement.php
+++ b/core/modules/system/tests/modules/element_info_test/src/Element/DeprecatedExtendsFormElement.php
@@ -7,13 +7,14 @@ namespace Drupal\element_info_test\Element;
use Drupal\Core\Render\Attribute\FormElement;
use Drupal\Core\Render\Element\FormElement as FormElementDeprecated;
+// @phpcs:disable
/**
* Provides render element that extends deprecated FormElement for testing.
- *
- * @phpstan-ignore class.extendsDeprecatedClass
*/
#[FormElement('deprecated_extends_form')]
+// @phpstan-ignore class.extendsDeprecatedClass
class DeprecatedExtendsFormElement extends FormElementDeprecated {
+// @phpcs:enable
/**
* {@inheritdoc}
diff --git a/core/modules/system/tests/modules/element_info_test/src/Element/DeprecatedExtendsRenderElement.php b/core/modules/system/tests/modules/element_info_test/src/Element/DeprecatedExtendsRenderElement.php
index 514a3b01a4bc..72a608bfc5e1 100644
--- a/core/modules/system/tests/modules/element_info_test/src/Element/DeprecatedExtendsRenderElement.php
+++ b/core/modules/system/tests/modules/element_info_test/src/Element/DeprecatedExtendsRenderElement.php
@@ -7,13 +7,14 @@ namespace Drupal\element_info_test\Element;
use Drupal\Core\Render\Attribute\RenderElement;
use Drupal\Core\Render\Element\RenderElement as RenderElementDeprecated;
+// @phpcs:disable
/**
* Provides render element that extends deprecated RenderElement for testing.
- *
- * @phpstan-ignore class.extendsDeprecatedClass
*/
#[RenderElement('deprecated_extends_render')]
+// @phpstan-ignore class.extendsDeprecatedClass
class DeprecatedExtendsRenderElement extends RenderElementDeprecated {
+// @phpcs:enable
/**
* {@inheritdoc}
diff --git a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml
index 39d5a0f81252..25c13b4cd4cd 100644
--- a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml
+++ b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml
@@ -77,6 +77,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php b/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
index 1528a0839816..6877871e2d29 100644
--- a/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
+++ b/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
@@ -112,7 +112,9 @@ class RenderPlaceholderMessageTestController implements TrustedCallbackInterface
$reordered = [];
foreach ($placeholder_order as $placeholder) {
- $reordered[$placeholder] = $build['#attached']['placeholders'][$placeholder];
+ if (isset($build['#attached']['placeholders'][$placeholder])) {
+ $reordered[$placeholder] = $build['#attached']['placeholders'][$placeholder];
+ }
}
$build['#attached']['placeholders'] = $reordered;
diff --git a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
index 70767c16544d..fb84be133cd7 100644
--- a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
+++ b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
@@ -9,6 +9,8 @@ declare(strict_types=1);
/**
* Implements hook_requirements().
+ *
+ * This tests the procedural implementations for this hook.
*/
function requirements1_test_requirements($phase): array {
$requirements = [];
diff --git a/core/modules/system/tests/modules/update_script_test/src/Hook/UpdateScriptTestRequirements.php b/core/modules/system/tests/modules/update_script_test/src/Hook/UpdateScriptTestRequirements.php
new file mode 100644
index 000000000000..5927e31e460b
--- /dev/null
+++ b/core/modules/system/tests/modules/update_script_test/src/Hook/UpdateScriptTestRequirements.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\update_script_test\Hook;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Requirements for the Update Script Test module.
+ */
+class UpdateScriptTestRequirements {
+
+ public function __construct(
+ protected readonly ConfigFactoryInterface $configFactory,
+ ) {}
+
+ /**
+ * Implements hook_update_requirements().
+ */
+ #[Hook('update_requirements')]
+ public function update(): array {
+ $requirements = [];
+ // Set a requirements warning or error when the test requests it.
+ $requirement_type = $this->configFactory->get('update_script_test.settings')->get('requirement_type');
+ switch ($requirement_type) {
+ case REQUIREMENT_WARNING:
+ $requirements['update_script_test'] = [
+ 'title' => 'Update script test',
+ 'value' => 'Warning',
+ 'description' => 'This is a requirements warning provided by the update_script_test module.',
+ 'severity' => REQUIREMENT_WARNING,
+ ];
+ break;
+
+ case REQUIREMENT_ERROR:
+ $requirements['update_script_test'] = [
+ 'title' => 'Update script test',
+ 'value' => 'Error',
+ 'description' => 'This is a (buggy description fixed in update_script_test_requirements_alter()) requirements error provided by the update_script_test module.',
+ 'severity' => REQUIREMENT_ERROR,
+ ];
+ break;
+ }
+ return $requirements;
+ }
+
+ /**
+ * Implements hook_update_requirements_alter().
+ */
+ #[Hook('update_requirements_alter')]
+ public function updateAlter(array &$requirements): void {
+ if (isset($requirements['update_script_test']) && $requirements['update_script_test']['severity'] === REQUIREMENT_ERROR) {
+ $requirements['update_script_test']['description'] = 'This is a requirements error provided by the update_script_test module.';
+ }
+ }
+
+}
diff --git a/core/modules/system/tests/modules/update_script_test/update_script_test.install b/core/modules/system/tests/modules/update_script_test/update_script_test.install
index 5a641fd95644..c380aabd7ff7 100644
--- a/core/modules/system/tests/modules/update_script_test/update_script_test.install
+++ b/core/modules/system/tests/modules/update_script_test/update_script_test.install
@@ -8,48 +8,6 @@
declare(strict_types=1);
/**
- * Implements hook_requirements().
- */
-function update_script_test_requirements($phase): array {
- $requirements = [];
-
- if ($phase == 'update') {
- // Set a requirements warning or error when the test requests it.
- $requirement_type = \Drupal::config('update_script_test.settings')->get('requirement_type');
- switch ($requirement_type) {
- case REQUIREMENT_WARNING:
- $requirements['update_script_test'] = [
- 'title' => 'Update script test',
- 'value' => 'Warning',
- 'description' => 'This is a requirements warning provided by the update_script_test module.',
- 'severity' => REQUIREMENT_WARNING,
- ];
- break;
-
- case REQUIREMENT_ERROR:
- $requirements['update_script_test'] = [
- 'title' => 'Update script test',
- 'value' => 'Error',
- 'description' => 'This is a (buggy description fixed in update_script_test_requirements_alter()) requirements error provided by the update_script_test module.',
- 'severity' => REQUIREMENT_ERROR,
- ];
- break;
- }
- }
-
- return $requirements;
-}
-
-/**
- * Implements hook_requirements_alter().
- */
-function update_script_test_requirements_alter(array &$requirements): void {
- if (isset($requirements['update_script_test']) && $requirements['update_script_test']['severity'] === REQUIREMENT_ERROR) {
- $requirements['update_script_test']['description'] = 'This is a requirements error provided by the update_script_test module.';
- }
-}
-
-/**
* Implements hook_update_last_removed().
*/
function update_script_test_update_last_removed(): int {
diff --git a/core/modules/system/tests/modules/update_test_schema/src/Hook/UpdateTestSchemaRequirements.php b/core/modules/system/tests/modules/update_test_schema/src/Hook/UpdateTestSchemaRequirements.php
new file mode 100644
index 000000000000..de96ce3e36a5
--- /dev/null
+++ b/core/modules/system/tests/modules/update_test_schema/src/Hook/UpdateTestSchemaRequirements.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\update_test_schema\Hook;
+
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Url;
+
+/**
+ * Requirements for the Update Test Schema module.
+ */
+class UpdateTestSchemaRequirements {
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+ $requirements['path_alias_test'] = [
+ 'title' => 'Path alias test',
+ 'value' => 'Check a path alias for the admin page',
+ 'severity' => REQUIREMENT_INFO,
+ 'description' => new FormattableMarkup('Visit <a href=":link">the structure page</a> to do many useful things.', [
+ ':link' => Url::fromRoute('system.admin_structure')->toString(),
+ ]),
+ ];
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/system/tests/modules/update_test_schema/update_test_schema.install b/core/modules/system/tests/modules/update_test_schema/update_test_schema.install
index 305d2cc7913e..0ad88c618bd0 100644
--- a/core/modules/system/tests/modules/update_test_schema/update_test_schema.install
+++ b/core/modules/system/tests/modules/update_test_schema/update_test_schema.install
@@ -12,25 +12,6 @@ use Drupal\Core\Url;
use Drupal\Component\Render\FormattableMarkup;
/**
- * Implements hook_requirements().
- */
-function update_test_schema_requirements($phase): array {
- $requirements = [];
- if ($phase === 'runtime') {
- $requirements['path_alias_test'] = [
- 'title' => 'Path alias test',
- 'value' => 'Check a path alias for the admin page',
- 'severity' => REQUIREMENT_INFO,
- 'description' => new FormattableMarkup('Visit <a href=":link">the structure page</a> to do many useful things.', [
- ':link' => Url::fromRoute('system.admin_structure')->toString(),
- ]),
- ];
- }
-
- return $requirements;
-}
-
-/**
* Implements hook_schema().
*
* The schema defined here will vary on state to allow for update hook testing.
diff --git a/core/modules/system/tests/src/Functional/FileTransfer/TestFileTransfer.php b/core/modules/system/tests/src/Functional/FileTransfer/TestFileTransfer.php
index 42605c114ffa..dcf46af35c9d 100644
--- a/core/modules/system/tests/src/Functional/FileTransfer/TestFileTransfer.php
+++ b/core/modules/system/tests/src/Functional/FileTransfer/TestFileTransfer.php
@@ -50,9 +50,11 @@ class TestFileTransfer extends FileTransfer {
* Establishes a mock connection for file transfer.
*/
public function connect() {
+ // @phpstan-ignore property.deprecatedClass
$this->connection = new MockTestConnection();
// Access the connection via the property. The property used to be set via a
// magic method and this can cause problems if coded incorrectly.
+ // @phpstan-ignore property.deprecatedClass
$this->connection->connectionString = 'test://' . urlencode($this->username) . ':' . urlencode($this->password) . "@$this->host:$this->port/";
}
@@ -60,6 +62,7 @@ class TestFileTransfer extends FileTransfer {
* Copies a file within the jailed environment.
*/
public function copyFileJailed($source, $destination) {
+ // @phpstan-ignore property.deprecatedClass
$this->connection->run("copyFile $source $destination");
}
@@ -67,6 +70,7 @@ class TestFileTransfer extends FileTransfer {
* Removes a directory within the jailed environment.
*/
protected function removeDirectoryJailed($directory) {
+ // @phpstan-ignore property.deprecatedClass
$this->connection->run("rmdir $directory");
}
@@ -74,6 +78,7 @@ class TestFileTransfer extends FileTransfer {
* Creates a directory within the jailed environment.
*/
public function createDirectoryJailed($directory) {
+ // @phpstan-ignore property.deprecatedClass
$this->connection->run("mkdir $directory");
}
@@ -81,6 +86,7 @@ class TestFileTransfer extends FileTransfer {
* Removes a file within the jailed environment.
*/
public function removeFileJailed($destination) {
+ // @phpstan-ignore property.deprecatedClass
$this->connection->run("rm $destination");
}
diff --git a/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php b/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php
index 4b2779ead48a..63e325c51111 100644
--- a/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php
+++ b/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php
@@ -17,7 +17,10 @@ class PlaceholderMessageTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
- protected static $modules = ['render_placeholder_message_test'];
+ protected static $modules = [
+ 'render_placeholder_message_test',
+ 'big_pipe_messages_test',
+ ];
/**
* {@inheritdoc}
diff --git a/core/modules/update/src/Hook/UpdateHooks.php b/core/modules/update/src/Hook/UpdateHooks.php
index 293882f01246..2502d4bb1715 100644
--- a/core/modules/update/src/Hook/UpdateHooks.php
+++ b/core/modules/update/src/Hook/UpdateHooks.php
@@ -77,8 +77,15 @@ class UpdateHooks {
$verbose = TRUE;
break;
}
+ // This loadInclude() is to ensure that the install API is available.
+ // Since we're loading an include of type 'install', this will also
+ // include core/includes/install.inc for us, which is where the
+ // REQUIREMENTS* constants are currently defined.
+ // @todo Remove this once those constants live in a better place.
+ // @see https://www.drupal.org/project/drupal/issues/2909480
+ // @see https://www.drupal.org/project/drupal/issues/3410938
\Drupal::moduleHandler()->loadInclude('update', 'install');
- $status = update_requirements('runtime');
+ $status = \Drupal::moduleHandler()->invoke('update', 'runtime_requirements');
foreach (['core', 'contrib'] as $report_type) {
$type = 'update_' . $report_type;
// hook_requirements() supports render arrays therefore we need to
@@ -178,8 +185,6 @@ class UpdateHooks {
\Drupal::moduleHandler()->loadInclude('update', 'inc', 'update.fetch');
_update_cron_notify();
}
- // Clear garbage from disk.
- update_clear_update_disk_cache();
}
/**
diff --git a/core/modules/update/src/Hook/UpdateRequirements.php b/core/modules/update/src/Hook/UpdateRequirements.php
new file mode 100644
index 000000000000..4aa5ccc1826f
--- /dev/null
+++ b/core/modules/update/src/Hook/UpdateRequirements.php
@@ -0,0 +1,161 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\update\Hook;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Link;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+use Drupal\update\ProjectSecurityData;
+use Drupal\update\ProjectSecurityRequirement;
+use Drupal\update\UpdateFetcherInterface;
+use Drupal\update\UpdateManagerInterface;
+
+/**
+ * Requirements for the update module.
+ */
+class UpdateRequirements {
+
+ use StringTranslationTrait;
+
+ public function __construct(
+ protected readonly ModuleHandlerInterface $moduleHandler,
+ ) {}
+
+ /**
+ * Implements hook_runtime_requirements().
+ *
+ * Describes the status of the site regarding available updates. If
+ * there is no update data, only one record will be returned, indicating that
+ * the status of core can't be determined. If data is available, there will
+ * be two records: one for core, and another for all of contrib (assuming
+ * there are any contributed modules or themes installed on the site). In
+ * addition to the fields expected by hook_requirements ('value', 'severity',
+ * and optionally 'description'), this array will contain a 'reason'
+ * attribute, which is an integer constant to indicate why the given status
+ * is being returned (UPDATE_NOT_SECURE, UPDATE_NOT_CURRENT, or
+ * UPDATE_UNKNOWN). This is used for generating the appropriate email
+ * notification messages during update_cron(), and might be useful for other
+ * modules that invoke update_runtime_requirements() to find out if the site
+ * is up to date or not.
+ *
+ * @see _update_message_text()
+ * @see _update_cron_notify()
+ * @see \Drupal\update\UpdateManagerInterface
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+ if ($available = update_get_available(FALSE)) {
+ $this->moduleHandler->loadInclude('update', 'inc', 'update.compare');
+ $data = update_calculate_project_data($available);
+ // First, populate the requirements for core:
+ $requirements['update_core'] = $this->requirementCheck($data['drupal'], 'core');
+ if (!empty($available['drupal']['releases'])) {
+ $security_data = ProjectSecurityData::createFromProjectDataAndReleases($data['drupal'], $available['drupal']['releases'])->getCoverageInfo();
+ if ($core_coverage_requirement = ProjectSecurityRequirement::createFromProjectDataAndSecurityCoverageInfo($data['drupal'], $security_data)->getRequirement()) {
+ $requirements['coverage_core'] = $core_coverage_requirement;
+ }
+ }
+
+ // We don't want to check drupal a second time.
+ unset($data['drupal']);
+ if (!empty($data)) {
+ // Now, sort our $data array based on each project's status. The
+ // status constants are numbered in the right order of precedence, so
+ // we just need to make sure the projects are sorted in ascending
+ // order of status, and we can look at the first project we find.
+ uasort($data, '_update_project_status_sort');
+ $first_project = reset($data);
+ $requirements['update_contrib'] = $this->requirementCheck($first_project, 'contrib');
+ }
+ }
+ else {
+ $requirements['update_core']['title'] = $this->t('Drupal core update status');
+ $requirements['update_core']['value'] = $this->t('No update data available');
+ $requirements['update_core']['severity'] = REQUIREMENT_WARNING;
+ $requirements['update_core']['reason'] = UpdateFetcherInterface::UNKNOWN;
+ $requirements['update_core']['description'] = _update_no_data();
+ }
+ return $requirements;
+ }
+
+ /**
+ * Fills in the requirements array.
+ *
+ * This is shared for both core and contrib to generate the right elements in
+ * the array for hook_runtime_requirements().
+ *
+ * @param array $project
+ * Array of information about the project we're testing as returned by
+ * update_calculate_project_data().
+ * @param string $type
+ * What kind of project this is ('core' or 'contrib').
+ *
+ * @return array
+ * An array to be included in the nested $requirements array.
+ *
+ * @see hook_requirements()
+ * @see update_requirements()
+ * @see update_calculate_project_data()
+ */
+ protected function requirementCheck($project, $type): array {
+ $requirement = [];
+ if ($type == 'core') {
+ $requirement['title'] = $this->t('Drupal core update status');
+ }
+ else {
+ $requirement['title'] = $this->t('Module and theme update status');
+ }
+ $status = $project['status'];
+ if ($status != UpdateManagerInterface::CURRENT) {
+ $requirement['reason'] = $status;
+ $requirement['severity'] = REQUIREMENT_ERROR;
+ // When updates are available, append the available updates link to the
+ // message from _update_message_text(), and format the two translated
+ // strings together in a single paragraph.
+ $requirement['description'][] = ['#markup' => _update_message_text($type, $status)];
+ if (!in_array($status, [UpdateFetcherInterface::UNKNOWN, UpdateFetcherInterface::NOT_CHECKED, UpdateFetcherInterface::NOT_FETCHED, UpdateFetcherInterface::FETCH_PENDING])) {
+ $requirement['description'][] = ['#prefix' => ' ', '#markup' => $this->t('See the <a href=":available_updates">available updates</a> page for more information.', [':available_updates' => Url::fromRoute('update.status')->toString()])];
+ }
+ }
+ switch ($status) {
+ case UpdateManagerInterface::NOT_SECURE:
+ $requirement_label = $this->t('Not secure!');
+ break;
+
+ case UpdateManagerInterface::REVOKED:
+ $requirement_label = $this->t('Revoked!');
+ break;
+
+ case UpdateManagerInterface::NOT_SUPPORTED:
+ $requirement_label = $this->t('Unsupported release');
+ break;
+
+ case UpdateManagerInterface::NOT_CURRENT:
+ $requirement_label = $this->t('Out of date');
+ $requirement['severity'] = REQUIREMENT_WARNING;
+ break;
+
+ case UpdateFetcherInterface::UNKNOWN:
+ case UpdateFetcherInterface::NOT_CHECKED:
+ case UpdateFetcherInterface::NOT_FETCHED:
+ case UpdateFetcherInterface::FETCH_PENDING:
+ $requirement_label = $project['reason'] ?? $this->t('Can not determine status');
+ $requirement['severity'] = REQUIREMENT_WARNING;
+ break;
+
+ default:
+ $requirement_label = $this->t('Up to date');
+ }
+ if ($status != UpdateManagerInterface::CURRENT && $type == 'core' && isset($project['recommended'])) {
+ $requirement_label .= ' ' . $this->t('(version @version available)', ['@version' => $project['recommended']]);
+ }
+ $requirement['value'] = Link::fromTextAndUrl($requirement_label, Url::fromRoute('update.status'))->toString();
+ return $requirement;
+ }
+
+}
diff --git a/core/modules/update/src/UpdateRoot.php b/core/modules/update/src/UpdateRoot.php
index a2f1619d6ea6..604e9ff0f529 100644
--- a/core/modules/update/src/UpdateRoot.php
+++ b/core/modules/update/src/UpdateRoot.php
@@ -6,7 +6,12 @@ use Drupal\Core\DrupalKernelInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
- * Gets the root path used by the Update Manager to install or update projects.
+ * Gets the root path used by the legacy Update Manager.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement. Use composer to manage the code for your site.
+ *
+ * @see https://www.drupal.org/node/3522119
*/
class UpdateRoot {
@@ -32,7 +37,7 @@ class UpdateRoot {
protected $updateRoot;
/**
- * Constructs an UpdateRootFactory instance.
+ * Constructs an UpdateRoot instance.
*
* @param \Drupal\Core\DrupalKernelInterface $drupal_kernel
* The Drupal kernel.
@@ -40,6 +45,7 @@ class UpdateRoot {
* The request stack.
*/
public function __construct(DrupalKernelInterface $drupal_kernel, RequestStack $request_stack) {
+ @trigger_error(__CLASS__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3522119', E_USER_DEPRECATED);
$this->drupalKernel = $drupal_kernel;
$this->requestStack = $request_stack;
}
diff --git a/core/modules/update/tests/src/Functional/UpdateManagerTest.php b/core/modules/update/tests/src/Functional/UpdateManagerTest.php
new file mode 100644
index 000000000000..a69e06a72fdd
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateManagerTest.php
@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\update\Functional;
+
+/**
+ * Tests legacy Update Manager functionality of the Update Status module.
+ *
+ * @group legacy
+ * @group update
+ */
+class UpdateManagerTest extends UpdateTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $defaultTheme = 'stark';
+
+ /**
+ * Checks that clearing the disk cache works.
+ */
+ public function testClearDiskCache(): void {
+ $directories = [
+ _update_manager_cache_directory(FALSE),
+ _update_manager_extract_directory(FALSE),
+ ];
+ // Check that update directories does not exists.
+ foreach ($directories as $directory) {
+ $this->assertDirectoryDoesNotExist($directory);
+ }
+
+ // Method must not fail if update directories do not exists.
+ update_clear_update_disk_cache();
+ }
+
+}
diff --git a/core/modules/update/tests/src/Functional/UpdateMiscTest.php b/core/modules/update/tests/src/Functional/UpdateMiscTest.php
index 5b544ea36b0e..3a06d965ee5e 100644
--- a/core/modules/update/tests/src/Functional/UpdateMiscTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateMiscTest.php
@@ -41,23 +41,6 @@ class UpdateMiscTest extends UpdateTestBase {
}
/**
- * Checks that clearing the disk cache works.
- */
- public function testClearDiskCache(): void {
- $directories = [
- _update_manager_cache_directory(FALSE),
- _update_manager_extract_directory(FALSE),
- ];
- // Check that update directories does not exists.
- foreach ($directories as $directory) {
- $this->assertDirectoryDoesNotExist($directory);
- }
-
- // Method must not fail if update directories do not exists.
- update_clear_update_disk_cache();
- }
-
- /**
* Tests the Update Status module when the update server returns 503 errors.
*/
public function testServiceUnavailable(): void {
diff --git a/core/modules/update/tests/src/Kernel/UpdateDeleteFileIfStaleTest.php b/core/modules/update/tests/src/Kernel/UpdateDeleteFileIfStaleTest.php
index ff5a8b02d457..1e33d81d6141 100644
--- a/core/modules/update/tests/src/Kernel/UpdateDeleteFileIfStaleTest.php
+++ b/core/modules/update/tests/src/Kernel/UpdateDeleteFileIfStaleTest.php
@@ -10,6 +10,7 @@ use Drupal\KernelTests\KernelTestBase;
* Tests the update_delete_file_if_stale() function.
*
* @group update
+ * @group legacy
*/
class UpdateDeleteFileIfStaleTest extends KernelTestBase {
diff --git a/core/modules/update/update.authorize.inc b/core/modules/update/update.authorize.inc
index 64f8fe7b014a..cab253303bbb 100644
--- a/core/modules/update/update.authorize.inc
+++ b/core/modules/update/update.authorize.inc
@@ -33,7 +33,7 @@ use Drupal\Core\Url;
*/
#[ProceduralHookScanStop]
function update_authorize_run_update($filetransfer, $projects) {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
$batch_builder = (new BatchBuilder())
->setFile(\Drupal::service('extension.list.module')->getPath('update') . '/update.authorize.inc')
@@ -79,7 +79,7 @@ function update_authorize_run_update($filetransfer, $projects) {
* Reference to an array used for Batch API storage.
*/
function update_authorize_batch_copy_project($project, $updater_name, $local_url, $filetransfer, &$context): void {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
// Initialize some variables in the Batch API $context array.
if (!isset($context['results']['log'])) {
@@ -100,6 +100,7 @@ function update_authorize_batch_copy_project($project, $updater_name, $local_url
// though the connection itself is now gone. So, although it's ugly, we have
// to unset the connection variable at this point so that the FileTransfer
// object will re-initiate the actual connection.
+ // @phpstan-ignore property.deprecatedClass
unset($filetransfer->connection);
if (!empty($context['results']['log'][$project]['#abort'])) {
@@ -107,6 +108,7 @@ function update_authorize_batch_copy_project($project, $updater_name, $local_url
return;
}
+ // @phpstan-ignore getDeprecatedService.deprecated
$updater = new $updater_name($local_url, \Drupal::getContainer()->get('update.root'));
try {
@@ -149,7 +151,7 @@ function update_authorize_batch_copy_project($project, $updater_name, $local_url
* An associative array of results from the batch operation.
*/
function update_authorize_update_batch_finished($success, $results): void {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
foreach ($results['log'] as $messages) {
if (!empty($messages['#abort'])) {
@@ -233,7 +235,7 @@ function update_authorize_update_batch_finished($success, $results): void {
* if there were errors. Defaults to TRUE.
*/
function _update_batch_create_message(&$project_results, $message, $success = TRUE): void {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
$project_results[] = ['message' => $message, 'success' => $success];
}
@@ -252,7 +254,7 @@ function _update_batch_create_message(&$project_results, $message, $success = TR
* @see update_storage_clear()
*/
function _update_authorize_clear_update_status(): void {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
\Drupal::keyValueExpirable('update')->deleteAll();
\Drupal::keyValueExpirable('update_available_release')->deleteAll();
diff --git a/core/modules/update/update.fetch.inc b/core/modules/update/update.fetch.inc
index 296dfbd3d06d..a0d2a22e5624 100644
--- a/core/modules/update/update.fetch.inc
+++ b/core/modules/update/update.fetch.inc
@@ -19,8 +19,15 @@ use Drupal\update\UpdateManagerInterface;
#[ProceduralHookScanStop]
function _update_cron_notify(): void {
$update_config = \Drupal::config('update.settings');
+ // This loadInclude() is to ensure that the install API is available.
+ // Since we're loading an include of type 'install', this will also
+ // include core/includes/install.inc for us, which is where the
+ // REQUIREMENTS* constants are currently defined.
+ // @todo Remove this once those constants live in a better place.
+ // @see https://www.drupal.org/project/drupal/issues/2909480
+ // @see https://www.drupal.org/project/drupal/issues/3410938
\Drupal::moduleHandler()->loadInclude('update', 'install');
- $status = update_requirements('runtime');
+ $status = \Drupal::moduleHandler()->invoke('update', 'runtime_requirements');
$params = [];
$notify_all = ($update_config->get('notification.threshold') == 'all');
foreach (['core', 'contrib'] as $report_type) {
diff --git a/core/modules/update/update.install b/core/modules/update/update.install
index 3ad55265aab1..ccd73e993bc6 100644
--- a/core/modules/update/update.install
+++ b/core/modules/update/update.install
@@ -6,70 +6,6 @@
*/
use Drupal\Core\Hook\Attribute\ProceduralHookScanStop;
-use Drupal\Core\Link;
-use Drupal\Core\Url;
-use Drupal\update\ProjectSecurityData;
-use Drupal\update\ProjectSecurityRequirement;
-use Drupal\update\UpdateFetcherInterface;
-use Drupal\update\UpdateManagerInterface;
-
-/**
- * Implements hook_requirements().
- *
- * Describes the status of the site regarding available updates. If
- * there is no update data, only one record will be returned, indicating that
- * the status of core can't be determined. If data is available, there will be
- * two records: one for core, and another for all of contrib (assuming there
- * are any contributed modules or themes installed on the site). In addition to
- * the fields expected by hook_requirements ('value', 'severity', and
- * optionally 'description'), this array will contain a 'reason' attribute,
- * which is an integer constant to indicate why the given status is being
- * returned (UPDATE_NOT_SECURE, UPDATE_NOT_CURRENT, or UPDATE_UNKNOWN). This
- * is used for generating the appropriate email notification messages during
- * update_cron(), and might be useful for other modules that invoke
- * update_requirements() to find out if the site is up to date or not.
- *
- * @see _update_message_text()
- * @see _update_cron_notify()
- * @see \Drupal\update\UpdateManagerInterface
- */
-function update_requirements($phase): array {
- $requirements = [];
- if ($phase == 'runtime') {
- if ($available = update_get_available(FALSE)) {
- \Drupal::moduleHandler()->loadInclude('update', 'inc', 'update.compare');
- $data = update_calculate_project_data($available);
- // First, populate the requirements for core:
- $requirements['update_core'] = _update_requirement_check($data['drupal'], 'core');
- if (!empty($available['drupal']['releases'])) {
- $security_data = ProjectSecurityData::createFromProjectDataAndReleases($data['drupal'], $available['drupal']['releases'])->getCoverageInfo();
- if ($core_coverage_requirement = ProjectSecurityRequirement::createFromProjectDataAndSecurityCoverageInfo($data['drupal'], $security_data)->getRequirement()) {
- $requirements['coverage_core'] = $core_coverage_requirement;
- }
- }
-
- // We don't want to check drupal a second time.
- unset($data['drupal']);
- if (!empty($data)) {
- // Now, sort our $data array based on each project's status. The
- // status constants are numbered in the right order of precedence, so
- // we just need to make sure the projects are sorted in ascending
- // order of status, and we can look at the first project we find.
- uasort($data, '_update_project_status_sort');
- $first_project = reset($data);
- $requirements['update_contrib'] = _update_requirement_check($first_project, 'contrib');
- }
- }
- else {
- $requirements['update_core']['title'] = t('Drupal core update status');
- $requirements['update_core']['value'] = t('No update data available');
- $requirements['update_core']['severity'] = REQUIREMENT_WARNING;
- $requirements['update_core']['reason'] = UpdateFetcherInterface::UNKNOWN;
- $requirements['update_core']['description'] = _update_no_data();
- }
- }
- return $requirements;
-}
/**
* Implements hook_install().
@@ -92,81 +28,6 @@ function update_uninstall(): void {
}
/**
- * Fills in the requirements array.
- *
- * This is shared for both core and contrib to generate the right elements in
- * the array for hook_requirements().
- *
- * @param array $project
- * Array of information about the project we're testing as returned by
- * update_calculate_project_data().
- * @param string $type
- * What kind of project this is ('core' or 'contrib').
- *
- * @return array
- * An array to be included in the nested $requirements array.
- *
- * @see hook_requirements()
- * @see update_requirements()
- * @see update_calculate_project_data()
- */
-function _update_requirement_check($project, $type): array {
- $requirement = [];
- if ($type == 'core') {
- $requirement['title'] = t('Drupal core update status');
- }
- else {
- $requirement['title'] = t('Module and theme update status');
- }
- $status = $project['status'];
- if ($status != UpdateManagerInterface::CURRENT) {
- $requirement['reason'] = $status;
- $requirement['severity'] = REQUIREMENT_ERROR;
- // When updates are available, append the available updates link to the
- // message from _update_message_text(), and format the two translated
- // strings together in a single paragraph.
- $requirement['description'][] = ['#markup' => _update_message_text($type, $status)];
- if (!in_array($status, [UpdateFetcherInterface::UNKNOWN, UpdateFetcherInterface::NOT_CHECKED, UpdateFetcherInterface::NOT_FETCHED, UpdateFetcherInterface::FETCH_PENDING])) {
- $requirement['description'][] = ['#prefix' => ' ', '#markup' => t('See the <a href=":available_updates">available updates</a> page for more information.', [':available_updates' => Url::fromRoute('update.status')->toString()])];
- }
- }
- switch ($status) {
- case UpdateManagerInterface::NOT_SECURE:
- $requirement_label = t('Not secure!');
- break;
-
- case UpdateManagerInterface::REVOKED:
- $requirement_label = t('Revoked!');
- break;
-
- case UpdateManagerInterface::NOT_SUPPORTED:
- $requirement_label = t('Unsupported release');
- break;
-
- case UpdateManagerInterface::NOT_CURRENT:
- $requirement_label = t('Out of date');
- $requirement['severity'] = REQUIREMENT_WARNING;
- break;
-
- case UpdateFetcherInterface::UNKNOWN:
- case UpdateFetcherInterface::NOT_CHECKED:
- case UpdateFetcherInterface::NOT_FETCHED:
- case UpdateFetcherInterface::FETCH_PENDING:
- $requirement_label = $project['reason'] ?? t('Can not determine status');
- $requirement['severity'] = REQUIREMENT_WARNING;
- break;
-
- default:
- $requirement_label = t('Up to date');
- }
- if ($status != UpdateManagerInterface::CURRENT && $type == 'core' && isset($project['recommended'])) {
- $requirement_label .= ' ' . t('(version @version available)', ['@version' => $project['recommended']]);
- }
- $requirement['value'] = Link::fromTextAndUrl($requirement_label, Url::fromRoute('update.status'))->toString();
- return $requirement;
-}
-
-/**
* Implements hook_update_last_removed().
*/
function update_update_last_removed(): int {
diff --git a/core/modules/update/update.manager.inc b/core/modules/update/update.manager.inc
index f4b18321002b..de29e229dd46 100644
--- a/core/modules/update/update.manager.inc
+++ b/core/modules/update/update.manager.inc
@@ -26,7 +26,7 @@ use Psr\Http\Client\ClientExceptionInterface;
*/
#[ProceduralHookScanStop]
function _update_manager_check_backends(&$form, $operation) {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
// If file transfers will be performed locally, we do not need to display any
// warnings or notices to the user and should automatically continue the
@@ -94,7 +94,7 @@ function _update_manager_check_backends(&$form, $operation) {
* @throws Exception
*/
function update_manager_archive_extract($file, $directory) {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
/** @var \Drupal\Core\Archiver\ArchiverInterface $archiver */
$archiver = \Drupal::service('plugin.manager.archiver')->getInstance([
@@ -145,7 +145,7 @@ function update_manager_archive_extract($file, $directory) {
* are no errors, it will be an empty array.
*/
function update_manager_archive_verify($project, $archive_file, $directory) {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
return \Drupal::moduleHandler()->invokeAllDeprecated('There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', 'verify_update_archive', [$project, $archive_file, $directory]);
}
@@ -162,7 +162,7 @@ function update_manager_archive_verify($project, $archive_file, $directory) {
* Path to local file, or FALSE if it could not be retrieved.
*/
function update_manager_file_get($url) {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
$parsed_url = parse_url($url);
$remote_schemes = ['http', 'https', 'ftp', 'ftps', 'smb', 'nfs'];
@@ -172,9 +172,11 @@ function update_manager_file_get($url) {
}
// Check the cache and download the file if needed.
+ // @phpstan-ignore function.deprecated
$cache_directory = _update_manager_cache_directory();
$local = $cache_directory . '/' . \Drupal::service('file_system')->basename($parsed_url['path']);
+ // @phpstan-ignore function.deprecated
if (!file_exists($local) || update_delete_file_if_stale($local)) {
try {
$data = (string) \Drupal::httpClient()->get($url)->getBody();
@@ -212,7 +214,7 @@ function update_manager_file_get($url) {
* @see update_manager_download_page()
*/
function update_manager_batch_project_get($project, $url, &$context): void {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
// This is here to show the user that we are in the process of downloading.
if (!isset($context['sandbox']['started'])) {
@@ -229,6 +231,7 @@ function update_manager_batch_project_get($project, $url, &$context): void {
}
// Extract it.
+ // @phpstan-ignore function.deprecated
$extract_directory = _update_manager_extract_directory();
try {
update_manager_archive_extract($local_cache, $extract_directory);
@@ -274,7 +277,7 @@ function update_manager_batch_project_get($project, $url, &$context): void {
* @see install_check_requirements()
*/
function update_manager_local_transfers_allowed() {
- @trigger_error(__METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
$file_system = \Drupal::service('file_system');
// Compare the owner of a webserver-created temporary file to the owner of
diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index e06ac21ea317..89e94fe7b1d7 100644
--- a/core/modules/update/update.module
+++ b/core/modules/update/update.module
@@ -276,8 +276,15 @@ function update_storage_clear(): void {
*
* @return string
* An eight character string uniquely identifying this Drupal installation.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement. Use composer to manage the code for your site.
+ *
+ * @see https://www.drupal.org/node/3522119
*/
function _update_manager_unique_identifier() {
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3522119', E_USER_DEPRECATED);
+
static $id;
if (!isset($id)) {
$id = substr(hash('sha256', Settings::getHashSalt()), 0, 8);
@@ -295,8 +302,15 @@ function _update_manager_unique_identifier() {
* @return string
* The full path to the temporary directory where update file archives should
* be extracted.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement. Use composer to manage the code for your site.
+ *
+ * @see https://www.drupal.org/node/3522119
*/
function _update_manager_extract_directory($create = TRUE) {
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3522119', E_USER_DEPRECATED);
+
static $directory;
if (!isset($directory)) {
$directory = 'temporary://update-extraction-' . _update_manager_unique_identifier();
@@ -317,8 +331,15 @@ function _update_manager_extract_directory($create = TRUE) {
* @return string
* The full path to the temporary directory where update file archives should
* be cached.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement. Use composer to manage the code for your site.
+ *
+ * @see https://www.drupal.org/node/3522119
*/
function _update_manager_cache_directory($create = TRUE) {
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3522119', E_USER_DEPRECATED);
+
static $directory;
if (!isset($directory)) {
$directory = 'temporary://update-cache-' . _update_manager_unique_identifier();
@@ -331,8 +352,15 @@ function _update_manager_cache_directory($create = TRUE) {
/**
* Clears the temporary files and directories based on file age from disk.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement. Use composer to manage the code for your site.
+ *
+ * @see https://www.drupal.org/node/3522119
*/
function update_clear_update_disk_cache(): void {
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3522119', E_USER_DEPRECATED);
+
// List of update module cache directories. Do not create the directories if
// they do not exist.
$directories = [
@@ -368,8 +396,15 @@ function update_clear_update_disk_cache(): void {
*
* @return bool
* TRUE if the file is stale and deleted successfully, FALSE otherwise.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement. Use composer to manage the code for your site.
+ *
+ * @see https://www.drupal.org/node/3522119
*/
function update_delete_file_if_stale($path) {
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3522119', E_USER_DEPRECATED);
+
if (file_exists($path)) {
$filectime = filectime($path);
$max_age = \Drupal::config('system.file')->get('temporary_maximum_age');
diff --git a/core/modules/update/update.post_update.php b/core/modules/update/update.post_update.php
index 3462899d989b..34ddba396858 100644
--- a/core/modules/update/update.post_update.php
+++ b/core/modules/update/update.post_update.php
@@ -5,6 +5,8 @@
* Post update functions for Update Status.
*/
+use Drupal\Core\Site\Settings;
+
/**
* Implements hook_removed_post_updates().
*/
@@ -14,3 +16,24 @@ function update_remove_post_updates() {
'update_post_update_set_blank_fetch_url_to_null' => '11.0.0',
];
}
+
+/**
+ * Removes the legacy 'Update Manager' disk cache.
+ */
+function update_post_update_clear_disk_cache(): void {
+ // @see _update_manager_unique_id()
+ $id = substr(hash('sha256', Settings::getHashSalt()), 0, 8);
+ // List of legacy 'Update Manager' cache directories.
+ $directories = [
+ // @see _update_manager_cache_directory()
+ "temporary://update-cache-$id",
+ // @see _update_manager_extract_directory()
+ "temporary://update-extraction-$id",
+ ];
+ foreach ($directories as $directory) {
+ if (is_dir($directory)) {
+ \Drupal::service('file_system')->deleteRecursive($directory);
+ }
+ }
+
+}
diff --git a/core/modules/update/update.services.yml b/core/modules/update/update.services.yml
index 465df135529a..bebf0398151c 100644
--- a/core/modules/update/update.services.yml
+++ b/core/modules/update/update.services.yml
@@ -21,6 +21,7 @@ services:
update.root:
class: Drupal\update\UpdateRoot
arguments: ['@kernel', '@request_stack']
+ deprecated: The "%service_id%" service is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3522119
logger.channel.update:
parent: logger.channel_base
arguments: [ 'update' ]
diff --git a/core/modules/user/config/optional/views.view.user_admin_people.yml b/core/modules/user/config/optional/views.view.user_admin_people.yml
index 5f9c0667cace..001c3e20cfb9 100644
--- a/core/modules/user/config/optional/views.view.user_admin_people.yml
+++ b/core/modules/user/config/optional/views.view.user_admin_people.yml
@@ -851,6 +851,7 @@ display:
sticky: false
summary: ''
empty_table: true
+ class: ''
row:
type: fields
query:
diff --git a/core/modules/user/src/Hook/UserRequirements.php b/core/modules/user/src/Hook/UserRequirements.php
new file mode 100644
index 000000000000..186ce12285f1
--- /dev/null
+++ b/core/modules/user/src/Hook/UserRequirements.php
@@ -0,0 +1,67 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\user\Hook;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Requirements for the User module.
+ */
+class UserRequirements {
+
+ use StringTranslationTrait;
+
+ public function __construct(
+ protected readonly EntityTypeManagerInterface $entityTypeManager,
+ protected readonly Connection $connection,
+ ) {}
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+
+ $result = (bool) $this->entityTypeManager->getStorage('user')->getQuery()
+ ->accessCheck(FALSE)
+ ->condition('uid', 0)
+ ->range(0, 1)
+ ->execute();
+
+ if ($result === FALSE) {
+ $requirements['anonymous user'] = [
+ 'title' => $this->t('Anonymous user'),
+ 'description' => $this->t('The anonymous user does not exist. See the <a href=":url">restore the anonymous (user ID 0) user record</a> for more information', [
+ ':url' => 'https://www.drupal.org/node/1029506',
+ ]),
+ 'severity' => REQUIREMENT_WARNING,
+ ];
+ }
+
+ $query = $this->connection->select('users_field_data');
+ $query->addExpression('LOWER(mail)', 'lower_mail');
+ $query->isNotNull('mail');
+ $query->groupBy('lower_mail');
+ $query->having('COUNT(uid) > :matches', [':matches' => 1]);
+ $conflicts = $query->countQuery()->execute()->fetchField();
+
+ if ($conflicts > 0) {
+ $requirements['conflicting emails'] = [
+ 'title' => $this->t('Conflicting user emails'),
+ 'description' => $this->t('Some user accounts have email addresses that differ only by case. For example, one account might have alice@example.com and another might have Alice@Example.com. See <a href=":url">Conflicting User Emails</a> for more information.', [
+ ':url' => 'https://www.drupal.org/node/3486109',
+ ]),
+ 'severity' => REQUIREMENT_WARNING,
+ ];
+ }
+
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_bulk_form.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_bulk_form.yml
index 7858370e137d..24e851e3f2a7 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_bulk_form.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_bulk_form.yml
@@ -19,6 +19,8 @@ display:
display_options:
style:
type: table
+ options:
+ class: ''
row:
type: fields
fields:
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml
index ce16bacc3aae..c0b9b9d7879e 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml
@@ -63,6 +63,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/user/tests/src/Kernel/UserRequirementsTest.php b/core/modules/user/tests/src/Kernel/UserRequirementsTest.php
index 88b406bc30e4..146ab9c8b904 100644
--- a/core/modules/user/tests/src/Kernel/UserRequirementsTest.php
+++ b/core/modules/user/tests/src/Kernel/UserRequirementsTest.php
@@ -22,12 +22,20 @@ class UserRequirementsTest extends KernelTestBase {
protected static $modules = ['user'];
/**
+ * Module handler for invoking user requirements.
+ *
+ * @var \Drupal\Core\Extension\ModuleHandlerInterface
+ */
+ protected $moduleHandler;
+
+ /**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
- $this->container->get('module_handler')->loadInclude('user', 'install');
+ $this->moduleHandler = $this->container->get('module_handler');
$this->installEntitySchema('user');
+ include_once $this->root . '/core/includes/install.inc';
}
/**
@@ -37,13 +45,13 @@ class UserRequirementsTest extends KernelTestBase {
*/
public function testConflictingUserEmails(): void {
- $output = \user_requirements('runtime');
+ $output = $this->moduleHandler->invoke('user', 'runtime_requirements');
$this->assertArrayNotHasKey('conflicting emails', $output);
$this->createUser([], 'User A', FALSE, ['mail' => 'unique@example.com']);
$this->createUser([], 'User B', FALSE, ['mail' => 'UNIQUE@example.com']);
- $output = \user_requirements('runtime');
+ $output = $this->moduleHandler->invoke('user', 'runtime_requirements');
$this->assertArrayHasKey('conflicting emails', $output);
}
@@ -52,13 +60,13 @@ class UserRequirementsTest extends KernelTestBase {
*/
public function testBlankUserEmails(): void {
- $output = \user_requirements('runtime');
+ $output = $this->moduleHandler->invoke('user', 'runtime_requirements');
$this->assertArrayNotHasKey('conflicting emails', $output);
$this->createUser([], 'User A', FALSE, ['mail' => '']);
$this->createUser([], 'User B', FALSE, ['mail' => '']);
- $output = \user_requirements('runtime');
+ $output = $this->moduleHandler->invoke('user', 'runtime_requirements');
$this->assertArrayNotHasKey('conflicting emails', $output);
}
diff --git a/core/modules/user/user.install b/core/modules/user/user.install
index 99ede9e59b76..747e61253226 100644
--- a/core/modules/user/user.install
+++ b/core/modules/user/user.install
@@ -92,51 +92,6 @@ function user_install(): void {
}
/**
- * Implements hook_requirements().
- */
-function user_requirements($phase): array {
- if ($phase !== 'runtime') {
- return [];
- }
- $return = [];
-
- $result = (bool) \Drupal::entityQuery('user')
- ->accessCheck(FALSE)
- ->condition('uid', 0)
- ->range(0, 1)
- ->execute();
-
- if ($result === FALSE) {
- $return['anonymous user'] = [
- 'title' => t('Anonymous user'),
- 'description' => t('The anonymous user does not exist. See the <a href=":url">restore the anonymous (user ID 0) user record</a> for more information', [
- ':url' => 'https://www.drupal.org/node/1029506',
- ]),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
-
- $query = \Drupal::database()->select('users_field_data');
- $query->addExpression('LOWER(mail)', 'lower_mail');
- $query->isNotNull('mail');
- $query->groupBy('lower_mail');
- $query->having('COUNT(uid) > :matches', [':matches' => 1]);
- $conflicts = $query->countQuery()->execute()->fetchField();
-
- if ($conflicts > 0) {
- $return['conflicting emails'] = [
- 'title' => t('Conflicting user emails'),
- 'description' => t('Some user accounts have email addresses that differ only by case. For example, one account might have alice@example.com and another might have Alice@Example.com. See <a href=":url">Conflicting User Emails</a> for more information.', [
- ':url' => 'https://www.drupal.org/node/3486109',
- ]),
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
-
- return $return;
-}
-
-/**
* Implements hook_update_last_removed().
*/
function user_update_last_removed(): int {
diff --git a/core/modules/views/config/schema/views.schema.yml b/core/modules/views/config/schema/views.schema.yml
index 8ddd73931f2c..12187c6b9465 100644
--- a/core/modules/views/config/schema/views.schema.yml
+++ b/core/modules/views/config/schema/views.schema.yml
@@ -140,16 +140,29 @@ views.view.*:
views_block:
type: block_settings
label: 'View block'
+ constraints:
+ FullyValidatable: ~
mapping:
views_label:
type: label
label: 'Title'
+ requiredKey: false
items_per_page:
- type: string
+ type: integer
label: 'Items per block'
+ constraints:
+ Range:
+ min: 1
+ # Will only be respected if the associated View is configured to allow this to be overridden.
+ # @see \Drupal\views\Plugin\views\display\Block::blockForm()
+ requiredKey: false
+ # NULL to use the default defined by the view.
+ nullable: true
block.settings.views_block:*:
type: views_block
+ constraints:
+ FullyValidatable: ~
block.settings.views_exposed_filter_block:*:
type: views_block
diff --git a/core/modules/views/src/FieldViewsDataProvider.php b/core/modules/views/src/FieldViewsDataProvider.php
index c0b9d50b2d9c..fad34ff460e2 100644
--- a/core/modules/views/src/FieldViewsDataProvider.php
+++ b/core/modules/views/src/FieldViewsDataProvider.php
@@ -139,8 +139,8 @@ class FieldViewsDataProvider {
if (!empty($translatable_configs) && empty($untranslatable_configs)) {
$translation_join_type = 'language';
}
- // If the field is translatable only on certain bundles, there will be a join
- // on langcode OR bundle name.
+ // If the field is translatable only on certain bundles, there will be a
+ // join on langcode OR bundle name.
elseif (!empty($translatable_configs) && !empty($untranslatable_configs)) {
foreach ($untranslatable_configs as $config) {
$untranslatable_config_bundles[] = $config->getTargetBundle();
@@ -268,8 +268,8 @@ class FieldViewsDataProvider {
'help' => $this->t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
];
- // Go through and create a list of aliases for all possible combinations of
- // entity type + name.
+ // Go through and create a list of aliases for all possible combinations
+ // of entity type + name.
$aliases = [];
$also_known = [];
foreach ($all_labels as $label_name => $true) {
@@ -296,15 +296,15 @@ class FieldViewsDataProvider {
}
if ($aliases) {
$data[$table_alias][$field_alias]['aliases'] = $aliases;
- // The $also_known variable contains markup that is HTML escaped and that
- // loses safeness when imploded. The help text is used in #description
- // and therefore XSS admin filtered by default. Escaped HTML is not
- // altered by XSS filtering, therefore it is safe to just concatenate the
- // strings. Afterwards we mark the entire string as safe, so it won't be
- // escaped, no matter where it is used.
+ // The $also_known variable contains markup that is HTML escaped and
+ // that loses safeness when imploded. The help text is used in
+ // #description and therefore XSS admin filtered by default. Escaped
+ // HTML is not altered by XSS filtering, therefore it is safe to just
+ // concatenate the strings. Afterwards we mark the entire string as
+ // safe, so it won't be escaped, no matter where it is used.
// Considering the dual use of this help data (both as metadata and as
- // help text), other patterns such as use of #markup would not be correct
- // here.
+ // help text), other patterns such as use of #markup would not be
+ // correct here.
$data[$table_alias][$field_alias]['help'] = Markup::create($data[$table_alias][$field_alias]['help'] . ' ' . $this->t('Also known as:') . ' ' . implode(', ', $also_known));
}
@@ -328,7 +328,8 @@ class FieldViewsDataProvider {
foreach ($field_columns as $column => $attributes) {
$allow_sort = TRUE;
- // Identify likely filters and arguments for each column based on field type.
+ // Identify likely filters and arguments for each column based on field
+ // type.
switch ($attributes['type']) {
case 'int':
case 'mediumint':
@@ -387,8 +388,8 @@ class FieldViewsDataProvider {
'help' => $this->t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
];
- // Go through and create a list of aliases for all possible combinations of
- // entity type + name.
+ // Go through and create a list of aliases for all possible combinations
+ // of entity type + name.
$aliases = [];
$also_known = [];
foreach ($all_labels as $label_name => $true) {
diff --git a/core/modules/views/src/Hook/ViewsHooks.php b/core/modules/views/src/Hook/ViewsHooks.php
index 6facc63de6de..b309887723c3 100644
--- a/core/modules/views/src/Hook/ViewsHooks.php
+++ b/core/modules/views/src/Hook/ViewsHooks.php
@@ -2,6 +2,7 @@
namespace Drupal\views\Hook;
+use Drupal\block\BlockInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\views\ViewsConfigUpdater;
use Drupal\views\ViewEntityInterface;
@@ -378,4 +379,19 @@ class ViewsHooks {
$config_updater->updateAll($view);
}
+ /**
+ * Implements hook_ENTITY_TYPE_presave() for blocks.
+ */
+ #[Hook('block_presave')]
+ public function blockPresave(BlockInterface $block): void {
+ if (str_starts_with($block->getPluginId(), 'views_block:')) {
+ $settings = $block->get('settings');
+ if (isset($settings['items_per_page']) && $settings['items_per_page'] === 'none') {
+ @trigger_error('Saving a views block with "none" items per page is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. To use the items per page defined by the view, use NULL. See https://www.drupal.org/node/3522240', E_USER_DEPRECATED);
+ $settings['items_per_page'] = NULL;
+ $block->set('settings', $settings);
+ }
+ }
+ }
+
}
diff --git a/core/modules/views/src/Hook/ViewsViewsHooks.php b/core/modules/views/src/Hook/ViewsViewsHooks.php
index 531f6c754fa2..4f10f689646c 100644
--- a/core/modules/views/src/Hook/ViewsViewsHooks.php
+++ b/core/modules/views/src/Hook/ViewsViewsHooks.php
@@ -143,8 +143,9 @@ class ViewsViewsHooks {
}
}
// Registers an action bulk form per entity.
+ $all_actions = \Drupal::entityTypeManager()->getStorage('action')->loadMultiple();
foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type => $entity_info) {
- $actions = array_filter(\Drupal::entityTypeManager()->getStorage('action')->loadMultiple(), function (ActionConfigEntityInterface $action) use ($entity_type) {
+ $actions = array_filter($all_actions, function (ActionConfigEntityInterface $action) use ($entity_type) {
return $action->getType() == $entity_type;
});
if (empty($actions)) {
@@ -183,6 +184,7 @@ class ViewsViewsHooks {
if (is_array($result)) {
$data = NestedArray::mergeDeep($result, $data);
}
+ \Drupal::moduleHandler()->invoke($field_storage->getTypeProvider(), 'field_views_data_views_data_alter', [&$data, $field_storage]);
}
}
}
@@ -190,28 +192,6 @@ class ViewsViewsHooks {
}
/**
- * Implements hook_views_data_alter().
- *
- * Field modules can implement hook_field_views_data_views_data_alter() to
- * alter the views data on a per field basis. This is weirdly named so as not
- * to conflict with the \Drupal::moduleHandler()->alter('field_views_data') in
- * views_views_data().
- */
- #[Hook('views_data_alter')]
- public function viewsDataAlter(&$data): void {
- $entity_type_manager = \Drupal::entityTypeManager();
- if (!$entity_type_manager->hasDefinition('field_storage_config')) {
- return;
- }
- /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
- foreach ($entity_type_manager->getStorage('field_storage_config')->loadMultiple() as $field_storage) {
- if (\Drupal::service('views.field_data_provider')->getSqlStorageForField($field_storage)) {
- \Drupal::moduleHandler()->invoke($field_storage->getTypeProvider(), 'field_views_data_views_data_alter', [&$data, $field_storage]);
- }
- }
- }
-
- /**
* Implements hook_field_views_data().
*
* The function implements the hook on behalf of 'core' because it adds a
diff --git a/core/modules/views/src/Plugin/views/argument/LanguageArgument.php b/core/modules/views/src/Plugin/views/argument/LanguageArgument.php
index 23980568f764..81a2181dd5a2 100644
--- a/core/modules/views/src/Plugin/views/argument/LanguageArgument.php
+++ b/core/modules/views/src/Plugin/views/argument/LanguageArgument.php
@@ -15,21 +15,19 @@ use Drupal\views\Attribute\ViewsArgument;
class LanguageArgument extends ArgumentPluginBase {
/**
- * Overrides \Drupal\views\Plugin\views\argument\ArgumentPluginBase::summaryName().
- *
- * Gets the user-friendly version of the language name.
+ * {@inheritdoc}
*/
public function summaryName($data) {
+ // Gets the user-friendly version of the language name.
return $this->language($data->{$this->name_alias});
}
/**
- * Overrides \Drupal\views\Plugin\views\argument\ArgumentPluginBase::title().
- *
- * Gets the user friendly version of the language name for display as a
- * title placeholder.
+ * {@inheritdoc}
*/
public function title() {
+ // Gets the user friendly version of the language name for display as a
+ // title placeholder.
return $this->language($this->argument);
}
diff --git a/core/modules/views/src/Plugin/views/display/Block.php b/core/modules/views/src/Plugin/views/display/Block.php
index 6532990b8d3e..5692ee1df013 100644
--- a/core/modules/views/src/Plugin/views/display/Block.php
+++ b/core/modules/views/src/Plugin/views/display/Block.php
@@ -120,7 +120,7 @@ class Block extends DisplayPluginBase {
* @see \Drupal\views\Plugin\Block\ViewsBlock::defaultConfiguration()
*/
public function blockSettings(array $settings) {
- $settings['items_per_page'] = 'none';
+ $settings['items_per_page'] = NULL;
return $settings;
}
@@ -315,7 +315,7 @@ class Block extends DisplayPluginBase {
40 => 40,
48 => 48,
],
- '#default_value' => $block_configuration['items_per_page'],
+ '#default_value' => $block_configuration['items_per_page'] ?? 'none',
];
break;
}
@@ -353,7 +353,7 @@ class Block extends DisplayPluginBase {
*/
public function blockSubmit(ViewsBlock $block, $form, FormStateInterface $form_state) {
if ($items_per_page = $form_state->getValue(['override', 'items_per_page'])) {
- $block->setConfigurationValue('items_per_page', $items_per_page);
+ $block->setConfigurationValue('items_per_page', $items_per_page === 'none' ? NULL : intval($items_per_page));
}
$form_state->unsetValue(['override', 'items_per_page']);
}
@@ -366,8 +366,9 @@ class Block extends DisplayPluginBase {
*/
public function preBlockBuild(ViewsBlock $block) {
$config = $block->getConfiguration();
- if ($config['items_per_page'] !== 'none') {
- $this->view->setItemsPerPage($config['items_per_page']);
+ if (is_numeric($config['items_per_page']) && $config['items_per_page'] > 0) {
+ // @todo Delete the intval() in https://www.drupal.org/project/drupal/issues/3521221
+ $this->view->setItemsPerPage(intval($config['items_per_page']));
}
}
diff --git a/core/modules/views/src/Plugin/views/field/Boolean.php b/core/modules/views/src/Plugin/views/field/Boolean.php
index f2eb8f639b87..0c91fdc59509 100644
--- a/core/modules/views/src/Plugin/views/field/Boolean.php
+++ b/core/modules/views/src/Plugin/views/field/Boolean.php
@@ -16,8 +16,9 @@ use Drupal\views\Plugin\views\display\DisplayPluginBase;
* Allows for display of true/false, yes/no, on/off, enabled/disabled.
*
* Definition terms:
- * - output formats: An array where the first entry is displayed on boolean true
- * and the second is displayed on boolean false. An example for sticky is:
+ * - output formats: An array where the first entry is displayed on boolean
+ * true and the second is displayed on boolean false. An example for sticky
+ * is:
* @code
* 'output formats' => [
* 'sticky' => [t('Sticky'), ''],
diff --git a/core/modules/views/src/Plugin/views/field/Url.php b/core/modules/views/src/Plugin/views/field/Url.php
index 18e40a61f0e9..7f5dd0a33653 100644
--- a/core/modules/views/src/Plugin/views/field/Url.php
+++ b/core/modules/views/src/Plugin/views/field/Url.php
@@ -9,7 +9,7 @@ use Drupal\views\Attribute\ViewsField;
use Drupal\views\ResultRow;
/**
- * Field handler to provide simple renderer that turns a URL into a clickable link.
+ * Field handler to provide a renderer that turns a URL into a clickable link.
*
* @ingroup views_field_handlers
*/
diff --git a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
index e3b5b87ac2ef..0f526735d6e3 100644
--- a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
+++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
@@ -889,7 +889,7 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
* (optional) The form element to set any errors on.
*
* @return string
- * Returns an error message if validation fails, or NULL if validation passes.
+ * The error message if validation fails, or NULL if validation passes.
*/
protected function validateIdentifier($identifier, ?FormStateInterface $form_state = NULL, &$form_group = []) {
$error = '';
@@ -1226,9 +1226,11 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
continue;
}
// Each rows contains three widgets:
- // a) The title, where users define how they identify a pair of operator | value
- // b) The operator
- // c) The value (or values) to use in the filter with the selected operator
+ // - The title, where users define how they identify a pair of
+ // operator | value.
+ // - The operator.
+ // - The value (or values) to use in the filter with the selected
+ // operator.
// In each row, we have to display the operator form and the value from
// $row acts as a fake form to render each widget in a row.
diff --git a/core/modules/views/src/Plugin/views/style/Table.php b/core/modules/views/src/Plugin/views/style/Table.php
index 48adbf427ede..561628ac6820 100644
--- a/core/modules/views/src/Plugin/views/style/Table.php
+++ b/core/modules/views/src/Plugin/views/style/Table.php
@@ -71,7 +71,7 @@ class Table extends StylePluginBase implements CacheableDependencyInterface {
$options = parent::defineOptions();
$options['columns'] = ['default' => []];
- $options['class'] = ['default' => []];
+ $options['class'] = ['default' => ''];
$options['default'] = ['default' => ''];
$options['info'] = ['default' => []];
$options['override'] = ['default' => TRUE];
diff --git a/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
index ef8568203cd4..d53e93cecec1 100644
--- a/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
+++ b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
@@ -208,7 +208,8 @@ abstract class WizardPluginBase extends PluginBase implements WizardInterface {
* Gets the availableSorts property.
*
* @return array
- * An array of available sorts, keyed by sort ID, containing sort information.
+ * An array whose keys are the available sort options and whose
+ * corresponding values are human readable labels.
*/
public function getAvailableSorts() {
return $this->availableSorts;
@@ -483,7 +484,7 @@ abstract class WizardPluginBase extends PluginBase implements WizardInterface {
}
/**
- * Gets the current value of a #select element, from within a form constructor function.
+ * Gets the current value of a #select element.
*
* This function is intended for use in highly dynamic forms (in particular
* the add view wizard) which are rebuilt in different ways depending on which
diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php
index 55e3a8d91453..4867ba48f9a6 100644
--- a/core/modules/views/src/ViewExecutable.php
+++ b/core/modules/views/src/ViewExecutable.php
@@ -348,7 +348,7 @@ class ViewExecutable {
public $footer;
/**
- * Stores the area handlers for the empty text which are initialized on this view.
+ * The area handlers for the empty text which are initialized on this view.
*
* An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
*
diff --git a/core/modules/views/src/ViewsConfigUpdater.php b/core/modules/views/src/ViewsConfigUpdater.php
index 3b2709cc60c2..9c5d38e10ac5 100644
--- a/core/modules/views/src/ViewsConfigUpdater.php
+++ b/core/modules/views/src/ViewsConfigUpdater.php
@@ -134,6 +134,9 @@ class ViewsConfigUpdater implements ContainerInjectionInterface {
if ($this->processRememberRolesUpdate($handler, $handler_type)) {
$changed = TRUE;
}
+ if ($this->processTableCssClassUpdate($view)) {
+ $changed = TRUE;
+ }
return $changed;
});
}
@@ -335,6 +338,7 @@ class ViewsConfigUpdater implements ContainerInjectionInterface {
if (
isset($display['display_options']['style']) &&
$display['display_options']['style']['type'] === 'table' &&
+ isset($display['display_options']['style']['options']) &&
!isset($display['display_options']['style']['options']['class'])
) {
$display['display_options']['style']['options']['class'] = '';
@@ -346,6 +350,12 @@ class ViewsConfigUpdater implements ContainerInjectionInterface {
$view->set('display', $displays);
}
+ $deprecations_triggered = &$this->triggeredDeprecations['table_css_class'][$view->id()];
+ if ($this->deprecationsEnabled && $changed && !$deprecations_triggered) {
+ $deprecations_triggered = TRUE;
+ @trigger_error(sprintf('The update to add a default table CSS class for view "%s" is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Profile, module and theme provided configuration should be updated. See https://www.drupal.org/node/3499943', $view->id()), E_USER_DEPRECATED);
+ }
+
return $changed;
}
diff --git a/core/modules/views/tests/fixtures/update/views-block-items-per-page.php b/core/modules/views/tests/fixtures/update/views-block-items-per-page.php
new file mode 100644
index 000000000000..542992a24df2
--- /dev/null
+++ b/core/modules/views/tests/fixtures/update/views-block-items-per-page.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Creates a Views block with an `items_per_page` setting of `none`.
+ */
+
+declare(strict_types=1);
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Serialization\Yaml;
+
+$block_data = Yaml::decode(<<<END
+uuid: ecdad54d-8165-4ed3-a678-8ad20b388282
+langcode: en
+status: true
+dependencies:
+ config:
+ - views.view.who_s_online
+ module:
+ - views
+ theme:
+ - olivero
+id: olivero_who_s_online
+theme: olivero
+region: header
+weight: 0
+provider: null
+plugin: 'views_block:who_s_online-who_s_online_block'
+settings:
+ id: 'views_block:who_s_online-who_s_online_block'
+ label: ''
+ label_display: visible
+ provider: views
+ views_label: ''
+ items_per_page: none
+visibility: { }
+END
+);
+
+Database::getConnection()
+ ->insert('config')
+ ->fields([
+ 'collection' => '',
+ 'name' => 'block.block.olivero_who_s_online',
+ 'data' => serialize($block_data),
+ ])
+ ->execute();
diff --git a/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml b/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml
index cba11476ae8b..573705f502da 100644
--- a/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml
+++ b/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml
@@ -126,6 +126,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
columns:
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml
index 3ab5ecb66894..077b4bb4ce16 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml
@@ -42,6 +42,7 @@ display:
style:
type: table
options:
+ class: ''
info:
id:
sortable: true
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml
index bf65e0eaf379..1157a4e60bca 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml
@@ -43,6 +43,15 @@ display:
style:
type: table
options:
+ grouping: { }
+ class: ''
+ row_class: ''
+ default_row_class: true
+ override: true
+ sticky: true
+ caption: ''
+ summary: ''
+ description: ''
info:
id:
sortable: true
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml
index db3fd0693bf7..2036ee77c5fb 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_content_ajax.yml
@@ -57,6 +57,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_distinct_click_sorting.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_distinct_click_sorting.yml
index 55813bc9c49a..fe6c14f744bf 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_distinct_click_sorting.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_distinct_click_sorting.yml
@@ -65,6 +65,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml
index a19cea01b9f3..5ed73c2a1178 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml
@@ -133,6 +133,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml
index 091c4375635e..a8c84a7a6497 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml
@@ -20,6 +20,8 @@ display:
element_label_type: h2
style:
type: table
+ options:
+ class: ''
display_extenders: { }
display_plugin: default
display_title: Default
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_placeholder_text.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_placeholder_text.yml
index 1f33e75b61fb..1217b6c86eba 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_placeholder_text.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_placeholder_text.yml
@@ -52,6 +52,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml
index ddb305fb162e..dffcbe3c0b72 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml
@@ -40,6 +40,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml
index 21ac6945808e..aa3e387ee905 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml
@@ -286,6 +286,7 @@ display:
override: true
sticky: false
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
uses_fields: false
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml
index b52621ee9908..a4bda56b5437 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml
@@ -68,6 +68,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache_none.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache_none.yml
index aea7b9040134..91382cd03900 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache_none.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache_none.yml
@@ -68,6 +68,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml
index 10d215d29b51..77cca5f518f3 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml
@@ -63,6 +63,7 @@ display:
type: table
options:
grouping: { }
+ class: ''
row_class: ''
default_row_class: true
override: true
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml
index cec43f7c7dc7..98fa2836be96 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml
@@ -48,6 +48,8 @@ display:
plugin_id: standard
style:
type: table
+ options:
+ class: ''
display_plugin: default
display_title: Default
id: default
diff --git a/core/modules/views/tests/src/Functional/Plugin/ContextualFiltersBlockContextTest.php b/core/modules/views/tests/src/Functional/Plugin/ContextualFiltersBlockContextTest.php
index 955226497f95..0384c917083e 100644
--- a/core/modules/views/tests/src/Functional/Plugin/ContextualFiltersBlockContextTest.php
+++ b/core/modules/views/tests/src/Functional/Plugin/ContextualFiltersBlockContextTest.php
@@ -122,7 +122,7 @@ class ContextualFiltersBlockContextTest extends ViewTestBase {
'provider' => 'views',
'label_display' => 'visible',
'views_label' => '',
- 'items_per_page' => 'none',
+ 'items_per_page' => NULL,
'context_mapping' => ['nid' => '@node.node_route_context:node'],
];
$this->assertEquals($expected_settings, $block->getPlugin()->getConfiguration(), 'Block settings are correct.');
diff --git a/core/modules/views/tests/src/Functional/Update/BlockItemsPerPageUpdateTest.php b/core/modules/views/tests/src/Functional/Update/BlockItemsPerPageUpdateTest.php
new file mode 100644
index 000000000000..bd250cc2736e
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/Update/BlockItemsPerPageUpdateTest.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\views\Functional\Update;
+
+use Drupal\block\Entity\Block;
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+
+/**
+ * @group Update
+ * @covers views_post_update_block_items_per_page
+ */
+final class BlockItemsPerPageUpdateTest extends UpdatePathTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setDatabaseDumpFiles(): void {
+ $this->databaseDumpFiles = [
+ __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-10.3.0.filled.standard.php.gz',
+ __DIR__ . '/../../../fixtures/update/views-block-items-per-page.php',
+ ];
+ }
+
+ /**
+ * Tests changing an `items_per_page` setting of `none` to NULL.
+ */
+ public function testUpdateItemsPerPage(): void {
+ $settings = Block::load('olivero_who_s_online')?->get('settings');
+ $this->assertIsArray($settings);
+ $this->assertSame('none', $settings['items_per_page']);
+
+ $this->runUpdates();
+
+ $settings = Block::load('olivero_who_s_online')?->get('settings');
+ $this->assertIsArray($settings);
+ $this->assertNull($settings['items_per_page']);
+ }
+
+}
diff --git a/core/modules/views/tests/src/Functional/Wizard/ItemsPerPageTest.php b/core/modules/views/tests/src/Functional/Wizard/ItemsPerPageTest.php
index 5f9cd364dac8..d290a27cd191 100644
--- a/core/modules/views/tests/src/Functional/Wizard/ItemsPerPageTest.php
+++ b/core/modules/views/tests/src/Functional/Wizard/ItemsPerPageTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Drupal\Tests\views\Functional\Wizard;
+use Drupal\views\Entity\View;
+
/**
* Tests that the views wizard can specify the number of items per page.
*
@@ -19,6 +21,16 @@ class ItemsPerPageTest extends WizardTestBase {
/**
* {@inheritdoc}
*/
+ protected static $configSchemaCheckerExclusions = [
+ // To be able to test with the now invalid:
+ // - `items_per_page: 'none'`
+ // - `items_per_page: '5'`
+ 'block.block.views_block_items_per_page_test_with_historical_override',
+ ];
+
+ /**
+ * {@inheritdoc}
+ */
protected function setUp($import_test_views = TRUE, $modules = []): void {
parent::setUp($import_test_views, $modules);
@@ -27,6 +39,12 @@ class ItemsPerPageTest extends WizardTestBase {
/**
* Tests the number of items per page.
+ *
+ * This should be removed from the `legacy` group in
+ * https://drupal.org/i/3521221; see
+ * \Drupal\views\Hook\ViewsHooks::blockPresave().
+ *
+ * @group legacy
*/
public function testItemsPerPage(): void {
$this->drupalCreateContentType(['type' => 'article']);
@@ -83,7 +101,7 @@ class ItemsPerPageTest extends WizardTestBase {
$this->drupalGet($view['page[path]']);
$this->assertSession()->statusCodeEquals(200);
- // Make sure the page display shows the nodes we expect, and that they
+ // Make sure the page display shows the 4 nodes we expect, and that they
// appear in the expected order.
$this->assertSession()->addressEquals($view['page[path]']);
$this->assertSession()->pageTextContains($view['page[title]']);
@@ -109,21 +127,94 @@ class ItemsPerPageTest extends WizardTestBase {
// Place the block, visit a page that displays the block, and check that the
// nodes we expect appear in the correct order.
- $this->drupalPlaceBlock("views_block:{$view['id']}-block_1");
-
+ $block = $this->drupalPlaceBlock("views_block:{$view['id']}-block_1");
+
+ // Asserts that the 3 newest articles are listed, which is the configuration
+ // for the `block` display in the view. In other words: the `items_per_page`
+ // setting in the `View` config entity is respected.
+ $assert_3_newest_nodes = function () use ($node5, $node4, $node3, $node2, $node1, $page_node) {
+ $this->drupalGet('user');
+ $content = $this->getSession()->getPage()->getContent();
+ $this->assertSession()->pageTextContains($node5->label());
+ $this->assertSession()->pageTextContains($node4->label());
+ $this->assertSession()->pageTextContains($node3->label());
+ $this->assertSession()->pageTextNotContains($node2->label());
+ $this->assertSession()->pageTextNotContains($node1->label());
+ $this->assertSession()->pageTextNotContains($page_node->label());
+ $pos5 = strpos($content, $node5->label());
+ $pos4 = strpos($content, $node4->label());
+ $pos3 = strpos($content, $node3->label());
+ $this->assertGreaterThan($pos5, $pos4);
+ $this->assertGreaterThan($pos4, $pos3);
+ };
+ self::assertSame(4, View::load($view['id'])->toArray()['display']['default']['display_options']['pager']['options']['items_per_page']);
+ self::assertSame(3, View::load($view['id'])->toArray()['display']['block_1']['display_options']['pager']['options']['items_per_page']);
+ self::assertArrayNotHasKey('items_per_page', $block->get('settings'));
+ $assert_3_newest_nodes();
+ $block->delete();
+
+ // Because the `allow[items_per_page]` checkbox is checked, it is allowed to
+ // override the `items_per_page` setting for the Views's `block` display,
+ // and is actually respected. Valid values are `null` ("do not override")
+ // and a positive integer.
+ $block = $this->drupalPlaceBlock("views_block:{$view['id']}-block_1", [
+ 'items_per_page' => NULL,
+ ]);
+ self::assertSame(4, View::load($view['id'])->toArray()['display']['default']['display_options']['pager']['options']['items_per_page']);
+ self::assertSame(3, View::load($view['id'])->toArray()['display']['block_1']['display_options']['pager']['options']['items_per_page']);
+ self::assertNull($block->get('settings')['items_per_page']);
+ $assert_3_newest_nodes();
+ $block->delete();
+
+ $block = $this->drupalPlaceBlock("views_block:{$view['id']}-block_1", [
+ 'items_per_page' => 5,
+ ]);
+ self::assertSame(4, View::load($view['id'])->toArray()['display']['default']['display_options']['pager']['options']['items_per_page']);
+ self::assertSame(3, View::load($view['id'])->toArray()['display']['block_1']['display_options']['pager']['options']['items_per_page']);
+ self::assertSame(5, $block->get('settings')['items_per_page']);
$this->drupalGet('user');
- $content = $this->getSession()->getPage()->getContent();
- $this->assertSession()->pageTextContains($node5->label());
- $this->assertSession()->pageTextContains($node4->label());
- $this->assertSession()->pageTextContains($node3->label());
- $this->assertSession()->pageTextNotContains($node2->label());
- $this->assertSession()->pageTextNotContains($node1->label());
- $this->assertSession()->pageTextNotContains($page_node->label());
- $pos5 = strpos($content, $node5->label());
- $pos4 = strpos($content, $node4->label());
- $pos3 = strpos($content, $node3->label());
- $this->assertGreaterThan($pos5, $pos4);
- $this->assertGreaterThan($pos4, $pos3);
+ foreach ([$node5, $node4, $node3, $node2, $node1] as $node) {
+ $this->assertSession()->pageTextContains($node->label());
+ }
+ $block->delete();
+
+ // Finally: set `items_per_page: 'none'`, which is the predecessor of
+ // `items_per_page: null`. This must continue to work as before even if the
+ // configuration is no longer considered valid, because otherwise we risk
+ // breaking e.g. blocks placed using Layout Builder.
+ // @todo Delete in https://www.drupal.org/project/drupal/issues/3521221.
+ $block = $this->drupalPlaceBlock("views_block:{$view['id']}-block_1", [
+ 'id' => 'views_block_items_per_page_test_with_historical_override',
+ ]);
+ // Explicitly set the `items_per_page` setting to a string without casting.
+ // It should be changed to NULL by the pre-save hook.
+ // @see \Drupal\views\Hook\ViewsHooks::blockPresave()
+ $block->set('settings', [
+ 'items_per_page' => 'none',
+ ])->trustData()->save();
+ $this->expectDeprecation('Saving a views block with "none" items per page is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. To use the items per page defined by the view, use NULL. See https://www.drupal.org/node/3522240');
+ self::assertNull($block->get('settings')['items_per_page']);
+ self::assertSame(4, View::load($view['id'])->toArray()['display']['default']['display_options']['pager']['options']['items_per_page']);
+ self::assertSame(3, View::load($view['id'])->toArray()['display']['block_1']['display_options']['pager']['options']['items_per_page']);
+ $assert_3_newest_nodes();
+ $block->delete();
+
+ // Truly finally: set `items_per_page: '5'`, because for the same reason as
+ // above, blocks placed using Layout Builder may still have stale settings.
+ $block = $this->drupalPlaceBlock("views_block:{$view['id']}-block_1", [
+ 'id' => 'views_block_items_per_page_test_with_historical_override',
+ ]);
+ // Explicitly set the `items_per_page` setting to a string without casting.
+ $block->set('settings', [
+ 'items_per_page' => '5',
+ ])->trustData()->save();
+ self::assertSame('5', $block->get('settings')['items_per_page']);
+ self::assertSame(4, View::load($view['id'])->toArray()['display']['default']['display_options']['pager']['options']['items_per_page']);
+ self::assertSame(3, View::load($view['id'])->toArray()['display']['block_1']['display_options']['pager']['options']['items_per_page']);
+ $this->drupalGet('user');
+ foreach ([$node5, $node4, $node3, $node2, $node1] as $node) {
+ $this->assertSession()->pageTextContains($node->label());
+ }
}
}
diff --git a/core/modules/views/tests/src/Kernel/Plugin/ViewsBlockTest.php b/core/modules/views/tests/src/Kernel/Plugin/ViewsBlockTest.php
index b336584c74d6..ebd9df73e01d 100644
--- a/core/modules/views/tests/src/Kernel/Plugin/ViewsBlockTest.php
+++ b/core/modules/views/tests/src/Kernel/Plugin/ViewsBlockTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Drupal\Tests\views\Kernel\Plugin;
+use Drupal\Core\Extension\ThemeInstallerInterface;
+use Drupal\Tests\block\Traits\BlockCreationTrait;
use Drupal\views\Plugin\Block\ViewsBlock;
use Drupal\views\Tests\ViewTestData;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
@@ -16,6 +18,8 @@ use Drupal\views\Views;
*/
class ViewsBlockTest extends ViewsKernelTestBase {
+ use BlockCreationTrait;
+
/**
* {@inheritdoc}
*/
@@ -142,4 +146,22 @@ class ViewsBlockTest extends ViewsKernelTestBase {
$this->assertEquals('"test_view_block::block_1" views block', $views_block->getPreviewFallbackString());
}
+ /**
+ * Tests that saving a Views block with items_per_page = `none` is deprecated.
+ *
+ * @covers \Drupal\views\Hook\ViewsHooks::blockPresave
+ *
+ * @group legacy
+ */
+ public function testSaveBlockWithDeprecatedItemsPerPageSetting(): void {
+ $this->container->get(ThemeInstallerInterface::class)->install(['stark']);
+
+ $this->expectDeprecation('Saving a views block with "none" items per page is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. To use the items per page defined by the view, use NULL. See https://www.drupal.org/node/3522240');
+ $block = $this->placeBlock('views_block:test_view_block-block_1', [
+ 'items_per_page' => 'none',
+ ]);
+ $settings = $block->get('settings');
+ $this->assertNull($settings['items_per_page']);
+ }
+
}
diff --git a/core/modules/views/tests/src/Unit/Plugin/views/display/BlockTest.php b/core/modules/views/tests/src/Unit/Plugin/views/display/BlockTest.php
index 0d72bf27b4ac..6988a04d8b48 100644
--- a/core/modules/views/tests/src/Unit/Plugin/views/display/BlockTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/views/display/BlockTest.php
@@ -62,29 +62,37 @@ class BlockTest extends UnitTestCase {
/**
* Tests the build method with no overriding.
+ *
+ * @testWith [null]
+ * ["none"]
+ * [0]
+ * @todo Delete the last two cases in https://www.drupal.org/project/drupal/issues/3521221. The last one is `intval('none')`.
*/
- public function testBuildNoOverride(): void {
+ public function testBuildNoOverride($items_per_page_setting): void {
$this->executable->expects($this->never())
->method('setItemsPerPage');
$this->blockPlugin->expects($this->once())
->method('getConfiguration')
- ->willReturn(['items_per_page' => 'none']);
+ ->willReturn(['items_per_page' => $items_per_page_setting]);
$this->blockDisplay->preBlockBuild($this->blockPlugin);
}
/**
* Tests the build method with overriding items per page.
+ *
+ * @testWith [5, 5]
+ * ["5", 5]
*/
- public function testBuildOverride(): void {
+ public function testBuildOverride(mixed $input, int $expected): void {
$this->executable->expects($this->once())
->method('setItemsPerPage')
- ->with(5);
+ ->with($expected);
$this->blockPlugin->expects($this->once())
->method('getConfiguration')
- ->willReturn(['items_per_page' => 5]);
+ ->willReturn(['items_per_page' => $input]);
$this->blockDisplay->preBlockBuild($this->blockPlugin);
}
diff --git a/core/modules/views/views.api.php b/core/modules/views/views.api.php
index 4fbcad6730ea..6579f93199bb 100644
--- a/core/modules/views/views.api.php
+++ b/core/modules/views/views.api.php
@@ -532,8 +532,9 @@ function hook_views_data_alter(array &$data) {
* When collecting the views data, views_views_data() invokes this hook for each
* field storage definition, on the module that provides the field storage
* definition. If the return value is empty, the result of
- * FieldViewsDataProvider::defaultFieldImplementation() is used instead. Then the result is altered
- * by invoking hook_field_views_data_alter() on all modules.
+ * FieldViewsDataProvider::defaultFieldImplementation() is used instead. Then
+ * the result is altered by invoking hook_field_views_data_alter() on all
+ * modules.
*
* If no hook implementation exists, hook_views_data() falls back to
* FieldViewsDataProvider::defaultFieldImplementation().
diff --git a/core/modules/views/views.post_update.php b/core/modules/views/views.post_update.php
index c3f352e99a42..48623fc593e0 100644
--- a/core/modules/views/views.post_update.php
+++ b/core/modules/views/views.post_update.php
@@ -5,6 +5,7 @@
* Post update functions for Views.
*/
+use Drupal\block\BlockInterface;
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
use Drupal\views\ViewEntityInterface;
use Drupal\views\ViewsConfigUpdater;
@@ -83,7 +84,29 @@ function views_post_update_update_remember_role_empty(?array &$sandbox = NULL):
function views_post_update_table_css_class(?array &$sandbox = NULL): void {
/** @var \Drupal\views\ViewsConfigUpdater $view_config_updater */
$view_config_updater = \Drupal::classResolver(ViewsConfigUpdater::class);
+ $view_config_updater->setDeprecationsEnabled(FALSE);
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function (ViewEntityInterface $view) use ($view_config_updater): bool {
return $view_config_updater->needsTableCssClassUpdate($view);
});
}
+
+/**
+ * Defaults `items_per_page` to NULL in Views blocks.
+ */
+function views_post_update_block_items_per_page(?array &$sandbox = NULL): void {
+ if (!\Drupal::moduleHandler()->moduleExists('block')) {
+ return;
+ }
+ \Drupal::classResolver(ConfigEntityUpdater::class)
+ ->update($sandbox, 'block', function (BlockInterface $block): bool {
+ if (str_starts_with($block->getPluginId(), 'views_block:')) {
+ $settings = $block->get('settings');
+ if ($settings['items_per_page'] === 'none') {
+ $settings['items_per_page'] = NULL;
+ $block->set('settings', $settings);
+ return TRUE;
+ }
+ }
+ return FALSE;
+ });
+}
diff --git a/core/modules/views_ui/admin.inc b/core/modules/views_ui/admin.inc
index b866e9ed62cc..3da4ebaed854 100644
--- a/core/modules/views_ui/admin.inc
+++ b/core/modules/views_ui/admin.inc
@@ -116,7 +116,7 @@ function views_ui_add_ajax_trigger(&$wrapping_element, $trigger_key, $refresh_pa
}
/**
- * Processes a non-JavaScript fallback submit button to limit its validation errors.
+ * Limits validation errors for a non-JavaScript fallback submit button.
*/
function views_ui_add_limited_validation($element, FormStateInterface $form_state) {
// Retrieve the AJAX triggering element so we can determine its parents. (We
@@ -148,7 +148,7 @@ function views_ui_add_limited_validation($element, FormStateInterface $form_stat
}
/**
- * After-build function that adds a wrapper to a form region (for AJAX refreshes).
+ * Adds a wrapper to a form region (for AJAX refreshes) after the build.
*
* This function inserts a wrapper around the region of the form that needs to
* be refreshed by AJAX, based on information stored in the corresponding
diff --git a/core/modules/workspaces/src/EntityQuery/Tables.php b/core/modules/workspaces/src/EntityQuery/Tables.php
index 199d5cc15597..7b397c9580c4 100644
--- a/core/modules/workspaces/src/EntityQuery/Tables.php
+++ b/core/modules/workspaces/src/EntityQuery/Tables.php
@@ -126,7 +126,7 @@ class Tables extends BaseTables {
}
/**
- * Adds a new join to the 'workspace_association' table for an entity base table.
+ * Adds a join to the 'workspace_association' table for an entity base table.
*
* This method assumes that the active workspace has already been determined
* to be a non-default workspace.
diff --git a/core/modules/workspaces/src/Install/Requirements/WorkspacesRequirements.php b/core/modules/workspaces/src/Install/Requirements/WorkspacesRequirements.php
new file mode 100644
index 000000000000..a54148215af1
--- /dev/null
+++ b/core/modules/workspaces/src/Install/Requirements/WorkspacesRequirements.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\workspaces\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+
+/**
+ * Install time requirements for the workspaces module.
+ */
+class WorkspacesRequirements implements InstallRequirementsInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getRequirements(): array {
+ $requirements = [];
+ if (\Drupal::moduleHandler()->moduleExists('workspace')) {
+ $requirements['workspace_incompatibility'] = [
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => t('Workspaces can not be installed when the contributed Workspace module is also installed. See the <a href=":link">upgrade path</a> page for more information on how to upgrade.', [
+ ':link' => 'https://www.drupal.org/node/2987783',
+ ]),
+ ];
+ }
+
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php
index bf2fc243f6f4..180cfa9a8a1b 100644
--- a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php
+++ b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityReferenceSupportedNewEntitiesConstraintValidator.php
@@ -36,9 +36,6 @@ class EntityReferenceSupportedNewEntitiesConstraintValidator extends ConstraintV
*/
protected $workspaceInfo;
- /**
- * Creates a new EntityReferenceSupportedNewEntitiesConstraintValidator instance.
- */
public function __construct(WorkspaceManagerInterface $workspaceManager, EntityTypeManagerInterface $entityTypeManager, WorkspaceInformationInterface $workspace_information) {
$this->workspaceManager = $workspaceManager;
$this->entityTypeManager = $entityTypeManager;
diff --git a/core/modules/workspaces/workspaces.install b/core/modules/workspaces/workspaces.install
index a798b4a2c22b..6c9c99f29a3c 100644
--- a/core/modules/workspaces/workspaces.install
+++ b/core/modules/workspaces/workspaces.install
@@ -9,25 +9,6 @@ use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\workspaces\Entity\Workspace;
/**
- * Implements hook_requirements().
- */
-function workspaces_requirements($phase): array {
- $requirements = [];
- if ($phase === 'install') {
- if (\Drupal::moduleHandler()->moduleExists('workspace')) {
- $requirements['workspace_incompatibility'] = [
- 'severity' => REQUIREMENT_ERROR,
- 'description' => t('Workspaces can not be installed when the contributed Workspace module is also installed. See the <a href=":link">upgrade path</a> page for more information on how to upgrade.', [
- ':link' => 'https://www.drupal.org/node/2987783',
- ]),
- ];
- }
- }
-
- return $requirements;
-}
-
-/**
* Implements hook_install().
*/
function workspaces_install(): void {