From 6826875cdbc195419c8fce12b5117cd222460bdd Mon Sep 17 00:00:00 2001
From: Michael Lee <lee.5151@osu.edu>
Date: Mon, 8 Nov 2021 15:03:04 -0500
Subject: [PATCH] update core 9.2.7 => 9.2.8

---
 composer.lock                                 |   26 +-
 vendor/composer/installed.json                |   30 +-
 vendor/composer/installed.php                 |  240 ++--
 web/core/lib/Drupal.php                       |    2 +-
 .../Core/Asset/CssCollectionOptimizer.php     |    8 +-
 .../lib/Drupal/Core/Asset/CssOptimizer.php    |    6 +-
 .../Drupal/Core/Logger/LogMessageParser.php   |    2 +-
 .../Core/Render/Element/MachineName.php       |   10 +-
 .../HtmlResponseAttachmentsProcessor.php      |   10 +-
 .../lib/Drupal/Core/Test/TestRunnerKernel.php |    4 +-
 web/core/misc/cspell/dictionary.txt           |    1 +
 .../block/src/Plugin/migrate/source/Block.php |    5 +-
 .../migrate/source/d6/BlockTranslation.php    |    5 +-
 .../migrate/source/d7/BlockTranslation.php    |    5 +-
 .../src/Plugin/migrate/source/d6/Box.php      |    3 +-
 .../migrate/source/d6/BoxTranslation.php      |    5 +-
 .../Plugin/migrate/source/d7/BlockCustom.php  |    3 +-
 .../source/d7/BlockCustomTranslation.php      |    5 +-
 .../src/Plugin/CKEditorPlugin/Internal.php    |   11 +-
 .../source/d6/ProfileFieldTranslation.php     |    3 +-
 .../d7_language_content_comment_settings.yml  |   15 +-
 .../d7_taxonomy_term_translation.yml          |   12 -
 .../dblog/tests/src/Functional/DbLogTest.php  |   17 +
 web/core/modules/editor/editor.module         |   12 +-
 .../editor/tests/modules/editor_test.module   |   36 +
 .../FunctionalJavascript/EditorAdminTest.php  |    1 +
 .../tests/src/Kernel/EntityUpdateTest.php     |   68 +
 .../src/Plugin/migrate/source/d6/Field.php    |    3 +-
 .../migrate/source/d6/FieldInstance.php       |    3 +-
 .../d6/FieldInstanceOptionTranslation.php     |    3 +-
 .../source/d6/FieldInstancePerFormDisplay.php |    3 +-
 .../source/d6/FieldInstancePerViewMode.php    |    3 +-
 .../d6/FieldLabelDescriptionTranslation.php   |    3 +-
 .../source/d6/FieldOptionTranslation.php      |    3 +-
 .../src/Plugin/migrate/source/d7/Field.php    |    3 +-
 .../migrate/source/d7/FieldInstance.php       |    3 +-
 .../source/d7/FieldInstancePerFormDisplay.php |    3 +-
 .../source/d7/FieldInstancePerViewMode.php    |    3 +-
 .../d7/FieldLabelDescriptionTranslation.php   |    3 +-
 .../source/d7/FieldOptionTranslation.php      |    3 +-
 .../jsonapi/src/Controller/EntityResource.php |   20 +-
 .../JsonApiFunctionalDateFieldTest.php        |  182 +++
 .../jsonapi/tests/src/Functional/UserTest.php |  197 ++-
 .../src/Plugin/migrate/source/Language.php    |    5 +-
 .../source/d6/LanguageContentSettings.php     |    3 +-
 ...guageContentSettingsTaxonomyVocabulary.php |    3 +-
 .../source/d7/LanguageContentSettings.php     |    3 +-
 ...guageContentSettingsTaxonomyVocabulary.php |    3 +-
 ...CommentSettingsNoEntityTranslationTest.php |   13 +
 ...rateLanguageContentCommentSettingsTest.php |   13 +
 .../BlockComponentRenderArray.php             |   25 +-
 .../layout_builder_test.links.contextual.yml  |    4 +
 .../src/Plugin/Block/TestAttributesBlock.php  |   43 +
 .../src/Functional/LayoutBuilderTest.php      |   32 +
 .../src/Plugin/migrate/process/FieldLink.php  |    7 +
 .../Plugin/migrate/process/FieldLinkTest.php  |    4 +
 web/core/modules/media/media.install          |   33 +-
 .../Plugin/Field/FieldWidget/OEmbedWidget.php |    2 +-
 .../FieldWidget/OEmbedFieldWidgetTest.php     |   43 +
 .../src/Functional/MediaRequirementsTest.php  |    3 +
 .../CKEditorIntegrationTest.php               |    2 -
 .../src/Plugin/migrate/source/MenuLink.php    |    2 +-
 .../migrate/source/d6/MenuLinkTranslation.php |    2 +-
 .../migrate/source/d7/MenuLinkLocalized.php   |    2 +-
 .../migrate/source/d7/MenuLinkTranslation.php |    2 +-
 .../modules/menu_ui/src/MenuListBuilder.php   |   14 +
 .../tests/src/Functional/MenuUiTest.php       |   63 +-
 .../EntityReferenceTranslationDeriver.php     |   13 +-
 .../Plugin/migrate/source/DrupalSqlBase.php   |    3 +-
 .../src/Plugin/migrate/source/Variable.php    |    5 +-
 .../migrate/source/VariableMultiRow.php       |    5 +-
 .../migrate/source/d6/VariableTranslation.php |    5 +-
 .../migrate/source/d7/FieldableEntity.php     |   12 +-
 .../migrate/source/d7/VariableTranslation.php |    5 +-
 .../src/Plugin/migrate/source/d8/Config.php   |    3 +-
 .../migrate_drupal/tests/fixtures/drupal7.php |   24 +-
 .../src/Kernel/d7/FollowUpMigrationsTest.php  |   26 +-
 .../tests/src/Functional/d7/Upgrade7Test.php  |    4 +-
 .../src/Plugin/migrate/source/d6/Node.php     |    4 +
 .../source/d7/NodeEntityTranslation.php       |    2 +-
 .../Migrate/d7/MigrateNodeCompleteTest.php    |   10 +
 .../Plugin/migrate/source/d6/NodeTest.php     |   24 +
 .../RenderAttachedTestController.php          |    1 +
 .../Render/HtmlResponseAttachmentsTest.php    |    5 +
 .../source/d6/TermLocalizedTranslation.php    |    2 +-
 .../source/d6/VocabularyTranslation.php       |    2 +-
 .../source/d7/TermLocalizedTranslation.php    |    2 +-
 .../migrate/source/d7/TermTranslation.php     |   10 +-
 .../migrate/source/d7/TermTranslationTest.php |  361 +++++
 .../src/Plugin/migrate/field/d7/TextField.php |    2 +-
 .../Plugin/migrate/field/d7/TextFieldTest.php |    5 +-
 .../modules/toolbar_test/toolbar_test.module  |   11 +
 web/core/modules/toolbar/toolbar.module       |    2 +-
 .../Plugin/migrate/source/ProfileField.php    |    5 +-
 .../migrate/source/UserPictureInstance.php    |    5 +-
 .../d6/ProfileFieldOptionTranslation.php      |    3 +-
 .../migrate/source/d6/ProfileFieldValues.php  |    3 +-
 .../src/Plugin/migrate/source/d6/Role.php     |    3 +-
 .../src/Plugin/migrate/source/d6/User.php     |    3 +-
 .../Plugin/migrate/source/d6/UserPicture.php  |    3 +-
 .../migrate/source/d6/UserPictureFile.php     |    3 +-
 .../src/Plugin/migrate/source/d7/Role.php     |    3 +-
 .../src/Plugin/migrate/source/d7/User.php     |    3 +-
 .../source/d7/UserEntityTranslation.php       |    5 +-
 .../views/src/Plugin/views/query/Sql.php      |   10 +-
 .../Functional/Plugin/QueryOptionsTest.php    |   70 +
 .../src/Kernel/Entity/EntityViewsDataTest.php |  821 +++++++++++
 .../tests/src/Unit/EntityViewsDataTest.php    | 1197 -----------------
 .../Core/Entity/EntityQueryAggregateTest.php  |    2 +-
 .../Core/Render/Element/MachineNameTest.php   |   74 +
 .../Core/Theme/ClaroVerticalTabsTest.php      |   10 +-
 .../Functional/ManageGitIgnoreTest.php        |    9 +-
 .../fixtures/scripts/disable-git-bin/git      |    2 -
 .../Asset/CssCollectionOptimizerUnitTest.php  |   82 ++
 .../Tests/Core/Asset/CssOptimizerUnitTest.php |    4 +-
 .../css_test_files/css_input_with_import.css  |   19 +-
 ...t_with_import.css.optimized.aggregated.css |   14 +
 .../css_input_with_import.css.optimized.css   |    4 +-
 .../Core/Logger/LogMessageParserTest.php      |    4 +-
 web/core/themes/claro/claro.theme             |   10 +-
 web/core/themes/claro/src/ClaroPreRender.php  |   11 +-
 .../olivero/css/components/fieldset.css       |    8 +-
 .../olivero/css/components/fieldset.pcss.css  |    8 +-
 .../olivero/css/components/form-select.css    |   53 +-
 .../css/components/form-select.pcss.css       |   23 +-
 .../css/components/header-search-wide.css     |   11 +
 .../components/header-search-wide.pcss.css    |    8 +
 .../olivero/css/components/site-header.css    |    1 +
 .../css/components/site-header.pcss.css       |    1 +
 .../olivero/css/components/vertical-tabs.css  |    1 +
 .../css/components/vertical-tabs.pcss.css     |    1 +
 131 files changed, 2820 insertions(+), 1534 deletions(-)
 create mode 100644 web/core/modules/editor/tests/src/Kernel/EntityUpdateTest.php
 create mode 100644 web/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalDateFieldTest.php
 create mode 100644 web/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.links.contextual.yml
 create mode 100644 web/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Block/TestAttributesBlock.php
 create mode 100644 web/core/modules/media/tests/src/Functional/FieldWidget/OEmbedFieldWidgetTest.php
 create mode 100644 web/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTranslationTest.php
 create mode 100644 web/core/modules/views/tests/src/Functional/Plugin/QueryOptionsTest.php
 create mode 100644 web/core/modules/views/tests/src/Kernel/Entity/EntityViewsDataTest.php
 delete mode 100644 web/core/modules/views/tests/src/Unit/EntityViewsDataTest.php
 create mode 100644 web/core/tests/Drupal/KernelTests/Core/Render/Element/MachineNameTest.php
 delete mode 100755 web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scripts/disable-git-bin/git
 create mode 100644 web/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerUnitTest.php
 create mode 100644 web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css.optimized.aggregated.css

diff --git a/composer.lock b/composer.lock
index 06555cc036..2e4118d494 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2958,16 +2958,16 @@
         },
         {
             "name": "drupal/core",
-            "version": "9.2.7",
+            "version": "9.2.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core.git",
-                "reference": "ce3220458c7a744bb00e9436e48d8e644e134576"
+                "reference": "b40ae04a0a8a8a0952e77da7545de5ee8fa40757"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core/zipball/ce3220458c7a744bb00e9436e48d8e644e134576",
-                "reference": "ce3220458c7a744bb00e9436e48d8e644e134576",
+                "url": "https://api.github.com/repos/drupal/core/zipball/b40ae04a0a8a8a0952e77da7545de5ee8fa40757",
+                "reference": "b40ae04a0a8a8a0952e77da7545de5ee8fa40757",
                 "shasum": ""
             },
             "require": {
@@ -3206,9 +3206,9 @@
             ],
             "description": "Drupal is an open source content management platform powering millions of websites and applications.",
             "support": {
-                "source": "https://github.com/drupal/core/tree/9.2.7"
+                "source": "https://github.com/drupal/core/tree/9.2.8"
             },
-            "time": "2021-10-06T10:34:39+00:00"
+            "time": "2021-11-03T17:25:16+00:00"
         },
         {
             "name": "drupal/core-composer-scaffold",
@@ -3262,16 +3262,16 @@
         },
         {
             "name": "drupal/core-recommended",
-            "version": "9.2.7",
+            "version": "9.2.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core-recommended.git",
-                "reference": "87c998e8d60d6b2452b21827fb7b16f77d02a38a"
+                "reference": "c77ac58a8ef159065a7d28853e41f758a9cb057d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core-recommended/zipball/87c998e8d60d6b2452b21827fb7b16f77d02a38a",
-                "reference": "87c998e8d60d6b2452b21827fb7b16f77d02a38a",
+                "url": "https://api.github.com/repos/drupal/core-recommended/zipball/c77ac58a8ef159065a7d28853e41f758a9cb057d",
+                "reference": "c77ac58a8ef159065a7d28853e41f758a9cb057d",
                 "shasum": ""
             },
             "require": {
@@ -3280,7 +3280,7 @@
                 "doctrine/annotations": "1.13.1",
                 "doctrine/lexer": "1.2.1",
                 "doctrine/reflection": "1.2.2",
-                "drupal/core": "9.2.7",
+                "drupal/core": "9.2.8",
                 "egulias/email-validator": "2.1.25",
                 "guzzlehttp/guzzle": "6.5.5",
                 "guzzlehttp/promises": "1.4.1",
@@ -3343,9 +3343,9 @@
             ],
             "description": "Locked core dependencies; require this project INSTEAD OF drupal/core.",
             "support": {
-                "source": "https://github.com/drupal/core-recommended/tree/9.2.7"
+                "source": "https://github.com/drupal/core-recommended/tree/9.2.8"
             },
-            "time": "2021-10-06T10:34:39+00:00"
+            "time": "2021-11-03T17:25:16+00:00"
         },
         {
             "name": "drupal/crop",
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index f2c3cba795..c3d3fbae53 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -3039,17 +3039,17 @@
         },
         {
             "name": "drupal/core",
-            "version": "9.2.7",
-            "version_normalized": "9.2.7.0",
+            "version": "9.2.8",
+            "version_normalized": "9.2.8.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core.git",
-                "reference": "ce3220458c7a744bb00e9436e48d8e644e134576"
+                "reference": "b40ae04a0a8a8a0952e77da7545de5ee8fa40757"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core/zipball/ce3220458c7a744bb00e9436e48d8e644e134576",
-                "reference": "ce3220458c7a744bb00e9436e48d8e644e134576",
+                "url": "https://api.github.com/repos/drupal/core/zipball/b40ae04a0a8a8a0952e77da7545de5ee8fa40757",
+                "reference": "b40ae04a0a8a8a0952e77da7545de5ee8fa40757",
                 "shasum": ""
             },
             "require": {
@@ -3214,7 +3214,7 @@
                 "drupal/workflows": "self.version",
                 "drupal/workspaces": "self.version"
             },
-            "time": "2021-10-06T10:34:39+00:00",
+            "time": "2021-11-03T17:25:16+00:00",
             "type": "drupal-core",
             "extra": {
                 "drupal-scaffold": {
@@ -3294,7 +3294,7 @@
             ],
             "description": "Drupal is an open source content management platform powering millions of websites and applications.",
             "support": {
-                "source": "https://github.com/drupal/core/tree/9.2.7"
+                "source": "https://github.com/drupal/core/tree/9.2.8"
             },
             "install-path": "../../web/core"
         },
@@ -3350,17 +3350,17 @@
         },
         {
             "name": "drupal/core-recommended",
-            "version": "9.2.7",
-            "version_normalized": "9.2.7.0",
+            "version": "9.2.8",
+            "version_normalized": "9.2.8.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core-recommended.git",
-                "reference": "87c998e8d60d6b2452b21827fb7b16f77d02a38a"
+                "reference": "c77ac58a8ef159065a7d28853e41f758a9cb057d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core-recommended/zipball/87c998e8d60d6b2452b21827fb7b16f77d02a38a",
-                "reference": "87c998e8d60d6b2452b21827fb7b16f77d02a38a",
+                "url": "https://api.github.com/repos/drupal/core-recommended/zipball/c77ac58a8ef159065a7d28853e41f758a9cb057d",
+                "reference": "c77ac58a8ef159065a7d28853e41f758a9cb057d",
                 "shasum": ""
             },
             "require": {
@@ -3369,7 +3369,7 @@
                 "doctrine/annotations": "1.13.1",
                 "doctrine/lexer": "1.2.1",
                 "doctrine/reflection": "1.2.2",
-                "drupal/core": "9.2.7",
+                "drupal/core": "9.2.8",
                 "egulias/email-validator": "2.1.25",
                 "guzzlehttp/guzzle": "6.5.5",
                 "guzzlehttp/promises": "1.4.1",
@@ -3425,7 +3425,7 @@
             "conflict": {
                 "webflo/drupal-core-strict": "*"
             },
-            "time": "2021-10-06T10:34:39+00:00",
+            "time": "2021-11-03T17:25:16+00:00",
             "type": "metapackage",
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -3433,7 +3433,7 @@
             ],
             "description": "Locked core dependencies; require this project INSTEAD OF drupal/core.",
             "support": {
-                "source": "https://github.com/drupal/core-recommended/tree/9.2.7"
+                "source": "https://github.com/drupal/core-recommended/tree/9.2.8"
             },
             "install-path": null
         },
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 1e63755e08..ddc65fc6a3 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -5,7 +5,7 @@
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
-        'reference' => '5baa0f240fa9db4ac35dde696ec3fe32e6e3eec2',
+        'reference' => 'df61ac4c80357a6ff9e6e56957f55630f42d3212',
         'name' => 'osu-asc-webservices/d8-upstream',
         'dev' => true,
     ),
@@ -271,7 +271,7 @@
         'drupal/action' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/addtocalendar' => array(
@@ -304,7 +304,7 @@
         'drupal/aggregator' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/allowed_formats' => array(
@@ -328,25 +328,25 @@
         'drupal/automated_cron' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/ban' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/bartik' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/basic_auth' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/better_exposed_filters' => array(
@@ -361,19 +361,19 @@
         'drupal/big_pipe' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/block' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/block_content' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/block_field' => array(
@@ -406,7 +406,7 @@
         'drupal/book' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/bootstrap' => array(
@@ -421,7 +421,7 @@
         'drupal/breakpoint' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/cache_control_override' => array(
@@ -445,7 +445,7 @@
         'drupal/ckeditor' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/ckeditor_indentblock' => array(
@@ -460,31 +460,31 @@
         'drupal/claro' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/classy' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/color' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/comment' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/config' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/config_direct_save' => array(
@@ -517,7 +517,7 @@
         'drupal/config_translation' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/config_update' => array(
@@ -568,7 +568,7 @@
         'drupal/contact' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/content_access' => array(
@@ -583,52 +583,52 @@
         'drupal/content_moderation' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/content_translation' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/contextual' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core' => array(
-            'pretty_version' => '9.2.7',
-            'version' => '9.2.7.0',
+            'pretty_version' => '9.2.8',
+            'version' => '9.2.8.0',
             'type' => 'drupal-core',
             'install_path' => __DIR__ . '/../../web/core',
             'aliases' => array(),
-            'reference' => 'ce3220458c7a744bb00e9436e48d8e644e134576',
+            'reference' => 'b40ae04a0a8a8a0952e77da7545de5ee8fa40757',
             'dev_requirement' => false,
         ),
         'drupal/core-annotation' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-assertion' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-bridge' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-class-finder' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-composer-scaffold' => array(
@@ -643,136 +643,136 @@
         'drupal/core-datetime' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-dependency-injection' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-diff' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-discovery' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-event-dispatcher' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-file-cache' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-file-security' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-filesystem' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-front-matter' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-gettext' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-graph' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-http-foundation' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-php-storage' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-plugin' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-proxy-builder' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-recommended' => array(
-            'pretty_version' => '9.2.7',
-            'version' => '9.2.7.0',
+            'pretty_version' => '9.2.8',
+            'version' => '9.2.8.0',
             'type' => 'metapackage',
             'install_path' => NULL,
             'aliases' => array(),
-            'reference' => '87c998e8d60d6b2452b21827fb7b16f77d02a38a',
+            'reference' => 'c77ac58a8ef159065a7d28853e41f758a9cb057d',
             'dev_requirement' => false,
         ),
         'drupal/core-render' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-serialization' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-transliteration' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-utility' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-uuid' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/core-version' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/crop' => array(
@@ -796,19 +796,19 @@
         'drupal/datetime' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/datetime_range' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/dblog' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/dropzonejs' => array(
@@ -832,13 +832,13 @@
         'drupal/dynamic_page_cache' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/editor' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/editor_advanced_link' => array(
@@ -907,7 +907,7 @@
         'drupal/entity_reference' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/entity_reference_revisions' => array(
@@ -931,7 +931,7 @@
         'drupal/field' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/field_group' => array(
@@ -946,7 +946,7 @@
         'drupal/field_layout' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/field_permissions' => array(
@@ -961,13 +961,13 @@
         'drupal/field_ui' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/file' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/file_browser' => array(
@@ -982,7 +982,7 @@
         'drupal/filter' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/focal_point' => array(
@@ -997,7 +997,7 @@
         'drupal/forum' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/google_analytics' => array(
@@ -1021,25 +1021,25 @@
         'drupal/hal' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/help' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/help_topics' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/history' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/honeypot' => array(
@@ -1054,7 +1054,7 @@
         'drupal/image' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/inline_entity_form' => array(
@@ -1069,7 +1069,7 @@
         'drupal/inline_form_errors' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/jquery_ui' => array(
@@ -1111,25 +1111,25 @@
         'drupal/jsonapi' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/language' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/layout_builder' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/layout_discovery' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/libraries' => array(
@@ -1144,7 +1144,7 @@
         'drupal/link' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/link_attributes' => array(
@@ -1168,7 +1168,7 @@
         'drupal/locale' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/mathjax' => array(
@@ -1183,7 +1183,7 @@
         'drupal/media' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/media_entity_browser' => array(
@@ -1207,7 +1207,7 @@
         'drupal/media_library' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/menu_block' => array(
@@ -1240,13 +1240,13 @@
         'drupal/menu_link_content' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/menu_ui' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/metatag' => array(
@@ -1261,7 +1261,7 @@
         'drupal/migrate' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/migrate_devel' => array(
@@ -1276,19 +1276,19 @@
         'drupal/migrate_drupal' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/migrate_drupal_multilingual' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/migrate_drupal_ui' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/migrate_plus' => array(
@@ -1312,7 +1312,7 @@
         'drupal/minimal' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/mobile_detect' => array(
@@ -1354,25 +1354,25 @@
         'drupal/node' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/olivero' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/options' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/page_cache' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/pantheon_advanced_page_cache' => array(
@@ -1396,13 +1396,13 @@
         'drupal/path' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/path_alias' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/pathauto' => array(
@@ -1426,13 +1426,13 @@
         'drupal/quickedit' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/rdf' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/rebuild_cache_access' => array(
@@ -1474,13 +1474,13 @@
         'drupal/responsive_image' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/rest' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/roleassign' => array(
@@ -1504,7 +1504,7 @@
         'drupal/search' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/search_api' => array(
@@ -1528,25 +1528,25 @@
         'drupal/serialization' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/settings_tray' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/seven' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/shortcut' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/simple_gmap' => array(
@@ -1615,19 +1615,19 @@
         'drupal/standard' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/stark' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/statistics' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/superfish' => array(
@@ -1651,31 +1651,31 @@
         'drupal/syslog' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/system' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/taxonomy' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/telephone' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/text' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/token' => array(
@@ -1690,19 +1690,19 @@
         'drupal/toolbar' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/tour' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/tracker' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/twig_tweak' => array(
@@ -1735,13 +1735,13 @@
         'drupal/update' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/user' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/userprotect' => array(
@@ -1774,7 +1774,7 @@
         'drupal/views' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/views_ajax_history' => array(
@@ -1834,7 +1834,7 @@
         'drupal/views_ui' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/webform' => array(
@@ -1849,13 +1849,13 @@
         'drupal/workflows' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drupal/workspaces' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '9.2.7',
+                0 => '9.2.8',
             ),
         ),
         'drush-ops/behat-drush-endpoint' => array(
@@ -2104,7 +2104,7 @@
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
-            'reference' => '5baa0f240fa9db4ac35dde696ec3fe32e6e3eec2',
+            'reference' => 'df61ac4c80357a6ff9e6e56957f55630f42d3212',
             'dev_requirement' => false,
         ),
         'pantheon-systems/quicksilver-pushback' => array(
diff --git a/web/core/lib/Drupal.php b/web/core/lib/Drupal.php
index 286a13668d..b4b1be906e 100644
--- a/web/core/lib/Drupal.php
+++ b/web/core/lib/Drupal.php
@@ -75,7 +75,7 @@ class Drupal {
   /**
    * The current system version.
    */
-  const VERSION = '9.2.7';
+  const VERSION = '9.2.8';
 
   /**
    * Core API compatibility.
diff --git a/web/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php b/web/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
index 62dff2c7f9..7d47dad86e 100644
--- a/web/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
+++ b/web/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
@@ -123,8 +123,12 @@ public function optimize(array $css_assets) {
               // Per the W3C specification at
               // http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, @import
               // rules must precede any other style, so we move those to the
-              // top.
-              $regexp = '/@import[^;]+;/i';
+              // top. The regular expression is expressed in NOWDOC since it is
+              // detecting backslashes as well as single and double quotes. It
+              // is difficult to read when represented as a quoted string.
+              $regexp = <<<'REGEXP'
+/@import\s*(?:'(?:\\'|.)*'|"(?:\\"|.)*"|url\(\s*(?:\\[\)\'\"]|[^'")])*\s*\)|url\(\s*'(?:\'|.)*'\s*\)|url\(\s*"(?:\"|.)*"\s*\)).*;/iU
+REGEXP;
               preg_match_all($regexp, $data, $matches);
               $data = preg_replace($regexp, '', $data);
               $data = implode('', $matches[0]) . $data;
diff --git a/web/core/lib/Drupal/Core/Asset/CssOptimizer.php b/web/core/lib/Drupal/Core/Asset/CssOptimizer.php
index e939b3d054..0aec443f98 100644
--- a/web/core/lib/Drupal/Core/Asset/CssOptimizer.php
+++ b/web/core/lib/Drupal/Core/Asset/CssOptimizer.php
@@ -166,7 +166,7 @@ protected function loadNestedFile($matches) {
     // the url() path.
     $directory = $directory == '.' ? '' : $directory . '/';
 
-    // Alter all internal url() paths. Leave external paths alone. We don't need
+    // Alter all internal asset paths. Leave external paths alone. We don't need
     // to normalize absolute paths here because that will be done later.
     return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)([^\'")]+)([\'"]?)\s*\)/i', 'url(\1' . $directory . '\2\3)', $file);
   }
@@ -230,7 +230,9 @@ protected function processCss($contents, $optimize = FALSE) {
     }
 
     // Replaces @import commands with the actual stylesheet content.
-    // This happens recursively but omits external files.
+    // This happens recursively but omits external files and local files
+    // with supports- or media-query qualifiers, as those are conditionally
+    // loaded depending on the user agent.
     $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)(?!\/\/)([^\'"\()]+)[\'"]?\s*\)?\s*;/', [$this, 'loadNestedFile'], $contents);
 
     return $contents;
diff --git a/web/core/lib/Drupal/Core/Logger/LogMessageParser.php b/web/core/lib/Drupal/Core/Logger/LogMessageParser.php
index 690847e7d5..dc0ba4fdc5 100644
--- a/web/core/lib/Drupal/Core/Logger/LogMessageParser.php
+++ b/web/core/lib/Drupal/Core/Logger/LogMessageParser.php
@@ -17,7 +17,7 @@ public function parseMessagePlaceholders(&$message, array &$context) {
       $has_psr3 = TRUE;
       // Transform PSR3 style messages containing placeholders to
       // \Drupal\Component\Render\FormattableMarkup style.
-      $message = preg_replace('/\{(.*)\}/U', '@$1', $message);
+      $message = preg_replace('/\{([^\{}]*)\}/U', '@$1', $message);
     }
     foreach ($context as $key => $variable) {
       // PSR3 style placeholders.
diff --git a/web/core/lib/Drupal/Core/Render/Element/MachineName.php b/web/core/lib/Drupal/Core/Render/Element/MachineName.php
index 9e05326468..555a044c0e 100644
--- a/web/core/lib/Drupal/Core/Render/Element/MachineName.php
+++ b/web/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -44,7 +44,8 @@
  *   - error: (optional) A custom form error message string to show, if the
  *     machine name contains disallowed characters.
  *   - standalone: (optional) Whether the live preview should stay in its own
- *     form element rather than in the suffix of the source element. Defaults
+ *     form element rather than in the suffix of the source element. The source
+ *     element must appear in the form structure before this element. Defaults
  *     to FALSE.
  * - #maxlength: (optional) Maximum allowed length of the machine name. Defaults
  *   to 64.
@@ -183,6 +184,13 @@ public static function processMachineName(&$element, FormStateInterface $form_st
       return $element;
     }
 
+    // The source element must be defined before the machine name element.
+    if (!isset($source['#id'])) {
+      $element_parents = implode('][', $element['#array_parents']);
+      $source_parents = implode('][', $element['#machine_name']['source']);
+      throw new \LogicException(sprintf('The machine name element "%s" is defined before the source element "%s", it must be defined after or the source element must specify an id.', $element_parents, $source_parents));
+    }
+
     $suffix_id = $source['#id'] . '-machine-name-suffix';
     $element['#machine_name']['suffix'] = '#' . $suffix_id;
 
diff --git a/web/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php b/web/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
index d698604b15..2dc416f4bd 100644
--- a/web/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
+++ b/web/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
@@ -436,7 +436,15 @@ protected function processHtmlHeadLink(array $html_head_link) {
         '#attributes' => $attributes,
       ];
       $href = $attributes['href'];
-      $attached['html_head'][] = [$element, 'html_head_link:' . $attributes['rel'] . ':' . $href];
+      $rel = $attributes['rel'];
+
+      // Allow multiple hreflang tags to use the same href.
+      if (isset($attributes['hreflang'])) {
+        $attached['html_head'][] = [$element, 'html_head_link:' . $rel . ':' . $attributes['hreflang'] . ':' . $href];
+      }
+      else {
+        $attached['html_head'][] = [$element, 'html_head_link:' . $rel . ':' . $href];
+      }
 
       if ($should_add_header) {
         // Also add a HTTP header "Link:".
diff --git a/web/core/lib/Drupal/Core/Test/TestRunnerKernel.php b/web/core/lib/Drupal/Core/Test/TestRunnerKernel.php
index 85d84b75a7..2315693eac 100644
--- a/web/core/lib/Drupal/Core/Test/TestRunnerKernel.php
+++ b/web/core/lib/Drupal/Core/Test/TestRunnerKernel.php
@@ -87,8 +87,8 @@ public function boot() {
 
     // Create the build/artifacts directory if necessary.
     include_once $this->getAppRoot() . '/core/includes/file.inc';
-    if (!is_dir('public://simpletest')) {
-      mkdir('public://simpletest', 0777, TRUE);
+    if (!is_dir('public://simpletest') && !@mkdir('public://simpletest', 0777, TRUE) && !is_dir('public://simpletest')) {
+      throw new \RuntimeException('Unable to create directory: public://simpletest');
     }
   }
 
diff --git a/web/core/misc/cspell/dictionary.txt b/web/core/misc/cspell/dictionary.txt
index 80d6cb2fc6..9a23b6260e 100644
--- a/web/core/misc/cspell/dictionary.txt
+++ b/web/core/misc/cspell/dictionary.txt
@@ -1045,6 +1045,7 @@ nothere
 notnull
 notsimpletest
 nourriture
+nowdoc
 nplurals
 nresponse
 ntfs
diff --git a/web/core/modules/block/src/Plugin/migrate/source/Block.php b/web/core/modules/block/src/Plugin/migrate/source/Block.php
index 20b128c3a0..16fd6cd9aa 100644
--- a/web/core/modules/block/src/Plugin/migrate/source/Block.php
+++ b/web/core/modules/block/src/Plugin/migrate/source/Block.php
@@ -6,9 +6,10 @@
 use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
 
 /**
- * Drupal block source from database.
+ * Drupal 6/7 block source from database.
+ *
+ * For available configuration keys, refer to the parent classes.
  *
- * For available configuration keys, refer to the parent classes:
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/block/src/Plugin/migrate/source/d6/BlockTranslation.php b/web/core/modules/block/src/Plugin/migrate/source/d6/BlockTranslation.php
index 007c25d289..6cfa1d6ffc 100644
--- a/web/core/modules/block/src/Plugin/migrate/source/d6/BlockTranslation.php
+++ b/web/core/modules/block/src/Plugin/migrate/source/d6/BlockTranslation.php
@@ -7,9 +7,10 @@
 use Drupal\migrate\Row;
 
 /**
- * Gets i18n block data from source database.
+ * Drupal 6 i18n block data from database.
+ *
+ * For available configuration keys, refer to the parent classes.
  *
- * For available configuration keys, refer to the parent classes:
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/block/src/Plugin/migrate/source/d7/BlockTranslation.php b/web/core/modules/block/src/Plugin/migrate/source/d7/BlockTranslation.php
index 132599357b..8eb01c8015 100644
--- a/web/core/modules/block/src/Plugin/migrate/source/d7/BlockTranslation.php
+++ b/web/core/modules/block/src/Plugin/migrate/source/d7/BlockTranslation.php
@@ -5,9 +5,10 @@
 use Drupal\block\Plugin\migrate\source\Block;
 
 /**
- * Gets i18n block data from source database.
+ * Drupal 7 i18n block data from database.
+ *
+ * For available configuration keys, refer to the parent classes.
  *
- * For available configuration keys, refer to the parent classes:
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/block_content/src/Plugin/migrate/source/d6/Box.php b/web/core/modules/block_content/src/Plugin/migrate/source/d6/Box.php
index 1e8a88d471..f264f20892 100644
--- a/web/core/modules/block_content/src/Plugin/migrate/source/d6/Box.php
+++ b/web/core/modules/block_content/src/Plugin/migrate/source/d6/Box.php
@@ -7,7 +7,8 @@
 /**
  * Drupal 6 block source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/block_content/src/Plugin/migrate/source/d6/BoxTranslation.php b/web/core/modules/block_content/src/Plugin/migrate/source/d6/BoxTranslation.php
index 50685e3b3d..234af073c0 100644
--- a/web/core/modules/block_content/src/Plugin/migrate/source/d6/BoxTranslation.php
+++ b/web/core/modules/block_content/src/Plugin/migrate/source/d6/BoxTranslation.php
@@ -5,9 +5,10 @@
 use Drupal\block_content\Plugin\migrate\source\d7\BlockCustomTranslation as D7BlockCustomTranslation;
 
 /**
- * Gets Drupal 6 i18n custom block translations from database.
+ * Drupal 6 i18n custom block translations source from database.
+ *
+ * For available configuration keys, refer to the parent classes.
  *
- * For available configuration keys, refer to the parent classes:
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustom.php b/web/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustom.php
index 4b9c5e89cc..70a0711f40 100644
--- a/web/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustom.php
+++ b/web/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustom.php
@@ -7,7 +7,8 @@
 /**
  * Drupal 7 custom block source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php b/web/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php
index a297ff3cea..8298a0ee93 100644
--- a/web/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php
+++ b/web/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php
@@ -7,9 +7,10 @@
 use Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait;
 
 /**
- * Gets Drupal 7 i18n custom block translations from database.
+ * Drupal 7 i18n custom block translations source from database.
+ *
+ * For available configuration keys, refer to the parent classes.
  *
- * For available configuration keys, refer to the parent classes:
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php b/web/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php
index 8b4dbe0980..135cdee290 100644
--- a/web/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php
+++ b/web/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php
@@ -116,8 +116,8 @@ public function getConfig(Editor $editor) {
 
     // Add the format_tags setting, if its button is enabled.
     $toolbar_buttons = CKEditorPluginManager::getEnabledButtons($editor);
-    if (in_array('Format', $toolbar_buttons)) {
-      $config['format_tags'] = $this->generateFormatTagsSetting($editor);
+    if (in_array('Format', $toolbar_buttons) && $format_string = $this->generateFormatTagsSetting($editor)) {
+      $config['format_tags'] = $format_string;
     }
 
     return $config;
@@ -344,14 +344,15 @@ public function getButtons() {
    * @param \Drupal\editor\Entity\Editor $editor
    *   A configured text editor object.
    *
-   * @return array
-   *   An array containing the "format_tags" configuration.
+   * @return string|false
+   *   A string containing the "format_tags" configuration or FALSE if the
+   *   editor has not an associated filter format.
    */
   protected function generateFormatTagsSetting(Editor $editor) {
     // When no text format is associated yet, assume no tag is allowed.
     // @see \Drupal\editor\EditorInterface::hasAssociatedFilterFormat()
     if (!$editor->hasAssociatedFilterFormat()) {
-      return [];
+      return FALSE;
     }
 
     $format = $editor->getFilterFormat();
diff --git a/web/core/modules/config_translation/src/Plugin/migrate/source/d6/ProfileFieldTranslation.php b/web/core/modules/config_translation/src/Plugin/migrate/source/d6/ProfileFieldTranslation.php
index 2ccb43cb61..11215b4403 100644
--- a/web/core/modules/config_translation/src/Plugin/migrate/source/d6/ProfileFieldTranslation.php
+++ b/web/core/modules/config_translation/src/Plugin/migrate/source/d6/ProfileFieldTranslation.php
@@ -7,7 +7,8 @@
 /**
  * Drupal 6 i18n strings profile field source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/content_translation/migrations/d7_language_content_comment_settings.yml b/web/core/modules/content_translation/migrations/d7_language_content_comment_settings.yml
index c40173d4fc..4515535d13 100644
--- a/web/core/modules/content_translation/migrations/d7_language_content_comment_settings.yml
+++ b/web/core/modules/content_translation/migrations/d7_language_content_comment_settings.yml
@@ -8,22 +8,17 @@ source:
   plugin: d7_language_content_settings
   constants:
     target_type: comment
-    id_prefix: comment_node_
-    label_suffix: comment
 process:
   target_bundle:
     -
-      plugin: concat
+      plugin: migration_lookup
+      migration: d7_comment_type
+      no_stub: true
       source:
-        - constants/id_prefix
         - type
     -
-      plugin: static_map
-      bypass: true
-      # The Forum module provides its own comment type (comment_forum), which we
-      # want to reuse if it exists.
-      map:
-        comment_node_forum: comment_forum
+      plugin: skip_on_empty
+      method: row
   target_entity_type_id: constants/target_type
   default_langcode:
     -
diff --git a/web/core/modules/content_translation/migrations/d7_taxonomy_term_translation.yml b/web/core/modules/content_translation/migrations/d7_taxonomy_term_translation.yml
index a964a26f31..10091a8e76 100644
--- a/web/core/modules/content_translation/migrations/d7_taxonomy_term_translation.yml
+++ b/web/core/modules/content_translation/migrations/d7_taxonomy_term_translation.yml
@@ -8,18 +8,6 @@ source:
   plugin: d7_taxonomy_term_translation
   translations: true
 process:
-  skip:
-    -
-      plugin: static_map
-      source: i18n_mode
-      default_value: 0
-      map:
-        1: 0
-        2: 2
-        4: 4
-    -
-      plugin: skip_on_empty
-      method: row
   # If you are using this file to build a custom migration consider removing
   # the tid field to allow incremental migrations.
   tid: tid
diff --git a/web/core/modules/dblog/tests/src/Functional/DbLogTest.php b/web/core/modules/dblog/tests/src/Functional/DbLogTest.php
index e3b7931519..1e57cde952 100644
--- a/web/core/modules/dblog/tests/src/Functional/DbLogTest.php
+++ b/web/core/modules/dblog/tests/src/Functional/DbLogTest.php
@@ -245,6 +245,23 @@ public function testLogEventPageWithMissingInfo() {
     $this->assertSession()->linkNotExists($request_uri);
   }
 
+  /**
+   * Test that twig errors are displayed correctly.
+   */
+  public function testMessageParsing() {
+    $this->drupalLogin($this->adminUser);
+    // Log a common twig error with {{ }} and { } variables.
+    \Drupal::service('logger.factory')->get("php")
+      ->error('Incorrect parameter {{foo}} in path {path}: {value}',
+        ['foo' => 'bar', 'path' => '/baz', 'value' => 'horse']
+      );
+    // View the log page to verify it's correct.
+    $wid = \Drupal::database()->query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
+    $this->drupalGet('admin/reports/dblog/event/' . $wid);
+    $this->assertSession()
+      ->responseContains('Incorrect parameter {bar} in path /baz: horse');
+  }
+
   /**
    * Verifies setting of the database log row limit.
    *
diff --git a/web/core/modules/editor/editor.module b/web/core/modules/editor/editor.module
index 2dd2ed9760..f9dbec1804 100644
--- a/web/core/modules/editor/editor.module
+++ b/web/core/modules/editor/editor.module
@@ -396,13 +396,19 @@ function editor_entity_update(EntityInterface $entity) {
   // File references that existed both in the previous version of the revision
   // and in the new one don't need their usage to be updated.
   else {
-    $original_uuids_by_field = _editor_get_file_uuids_by_field($entity->original);
+    $original_uuids_by_field = empty($entity->original) ? [] :
+      _editor_get_file_uuids_by_field($entity->original);
+
     $uuids_by_field = _editor_get_file_uuids_by_field($entity);
 
     // Detect file usages that should be incremented.
     foreach ($uuids_by_field as $field => $uuids) {
-      $added_files = array_diff($uuids_by_field[$field], $original_uuids_by_field[$field]);
-      _editor_record_file_usage($added_files, $entity);
+      $original_uuids = isset($original_uuids_by_field[$field]) ?
+        $original_uuids_by_field[$field] : [];
+
+      if ($added_files = array_diff($uuids_by_field[$field], $original_uuids)) {
+        _editor_record_file_usage($added_files, $entity);
+      }
     }
 
     // Detect file usages that should be decremented.
diff --git a/web/core/modules/editor/tests/modules/editor_test.module b/web/core/modules/editor/tests/modules/editor_test.module
index 70fe59fa41..bdb7292d62 100644
--- a/web/core/modules/editor/tests/modules/editor_test.module
+++ b/web/core/modules/editor/tests/modules/editor_test.module
@@ -5,9 +5,45 @@
  * Helper module for the Text Editor tests.
  */
 
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\node\NodeInterface;
 use Drupal\filter\FilterFormatInterface;
 use Drupal\file\FileInterface;
 
+/**
+ * Implements hook_entity_update().
+ *
+ * @see \Drupal\Tests\editor\Kernel\EntityUpdateTest
+ */
+function editor_test_entity_update(EntityInterface $entity) {
+  // Only act on nodes.
+  if (!$entity instanceof NodeInterface) {
+    return;
+  }
+
+  // Avoid infinite loop by only going through our post save logic once.
+  if (!empty($entity->editor_test_updating)) {
+    return;
+  }
+
+  // Set flag for whether or not the entity needs to be resaved.
+  $needs_update = FALSE;
+
+  // Perform our post save logic.
+  if ($entity->title->value == 'test updated') {
+    // Change the node title.
+    $entity->title->value = 'test updated 2';
+    $needs_update = TRUE;
+  }
+
+  if ($needs_update) {
+    // Set flag on entity that our logic was already executed.
+    $entity->editor_test_updating = TRUE;
+    // And resave entity.
+    $entity->save();
+  }
+}
+
 /**
  * Implements hook_editor_js_settings_alter().
  */
diff --git a/web/core/modules/editor/tests/src/FunctionalJavascript/EditorAdminTest.php b/web/core/modules/editor/tests/src/FunctionalJavascript/EditorAdminTest.php
index 51aee88140..3d19d3e599 100644
--- a/web/core/modules/editor/tests/src/FunctionalJavascript/EditorAdminTest.php
+++ b/web/core/modules/editor/tests/src/FunctionalJavascript/EditorAdminTest.php
@@ -54,6 +54,7 @@ public function testEditorSelection() {
     $this->assertNotEmpty($assert_session->waitForText('sulaco'));
     $page->selectFieldOption('editor[editor]', 'ckeditor');
     $this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', 'ul.ckeditor-toolbar-group-buttons'));
+    $this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '#ckeditor-plugin-settings'));
     $page->pressButton('Save configuration');
 
     // Test that toggling the editor selection off and back on works.
diff --git a/web/core/modules/editor/tests/src/Kernel/EntityUpdateTest.php b/web/core/modules/editor/tests/src/Kernel/EntityUpdateTest.php
new file mode 100644
index 0000000000..1a454ea349
--- /dev/null
+++ b/web/core/modules/editor/tests/src/Kernel/EntityUpdateTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\Tests\editor\Kernel;
+
+use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
+use Drupal\node\Entity\NodeType;
+use Drupal\node\Entity\Node;
+
+/**
+ * Tests updating an entity.
+ *
+ * @group editor
+ */
+class EntityUpdateTest extends EntityKernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  protected static $modules = ['editor', 'editor_test', 'node'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->installSchema('node', ['node_access']);
+    $this->installConfig(['node']);
+
+    // Create a node type for testing.
+    $type = NodeType::create(['type' => 'page', 'name' => 'page']);
+    $type->save();
+
+    // Set editor_test module weight to be lower than editor module's weight so
+    // that editor_test_entity_update() is called before editor_entity_update().
+    $extension_config = \Drupal::configFactory()->get('core.extension');
+    $editor_module_weight = $extension_config->get('module.editor');
+    module_set_weight('editor_test', $editor_module_weight - 1);
+  }
+
+  /**
+   * Tests updating an existing entity.
+   *
+   * @see editor_test_entity_update()
+   */
+  public function testEntityUpdate() {
+    // Create a node.
+    $node = Node::create([
+      'type' => 'page',
+      'title' => 'test',
+    ]);
+    $node->save();
+
+    // Update the node.
+    // What happens is the following:
+    // 1. \Drupal\Core\Entity\EntityStorageBase::doPostSave() gets called.
+    // 2. editor_test_entity_update() gets called.
+    // 3. A resave of the updated entity gets triggered (second save call).
+    // 4. \Drupal\Core\Entity\EntityStorageBase::doPostSave() gets called.
+    // 5. editor_test_entity_update() gets called.
+    // 6. editor_entity_update() gets called (caused by the second save call).
+    // 7. editor_entity_update() gets called (caused by the first save call).
+    $node->title->value = 'test updated';
+    $node->save();
+  }
+
+}
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d6/Field.php b/web/core/modules/field/src/Plugin/migrate/source/d6/Field.php
index d2c6392ae2..c3fba78ecc 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d6/Field.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d6/Field.php
@@ -8,7 +8,8 @@
 /**
  * Drupal 6 field source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstance.php b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstance.php
index ed302ea6db..66f24fd223 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstance.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstance.php
@@ -26,7 +26,8 @@
  * In this example field instances of type page are retrieved from the source
  * database.
  *
- * For additional configuration keys, refer to the parent classes:
+ * For additional configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstanceOptionTranslation.php b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstanceOptionTranslation.php
index 505aa1c9aa..c0f58b78e5 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstanceOptionTranslation.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstanceOptionTranslation.php
@@ -5,7 +5,8 @@
 /**
  * Drupal 6 i18n field instance option labels source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php
index 438b2198e6..9112e9a63e 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php
@@ -7,7 +7,8 @@
 /**
  * Drupal 6 field instance per form display source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php
index b571991084..62b4e3d368 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerViewMode.php
@@ -7,7 +7,8 @@
 /**
  * Drupal 6 field instance per view mode source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldLabelDescriptionTranslation.php b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldLabelDescriptionTranslation.php
index 81edff9209..51382e6cc6 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldLabelDescriptionTranslation.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldLabelDescriptionTranslation.php
@@ -7,7 +7,8 @@
 /**
  * Drupal 6 i18n field label and description source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldOptionTranslation.php b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldOptionTranslation.php
index 94aaeb4368..325a27e8ba 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d6/FieldOptionTranslation.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d6/FieldOptionTranslation.php
@@ -5,7 +5,8 @@
 /**
  * Drupal 6 i18n field option labels source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d7/Field.php b/web/core/modules/field/src/Plugin/migrate/source/d7/Field.php
index ae71d7dde0..f978146afb 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d7/Field.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d7/Field.php
@@ -12,7 +12,8 @@
  * migrated. The values of those fields will be migrated to the base fields they
  * were replacing.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php b/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php
index d23c1b52c4..079139b688 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php
@@ -41,7 +41,8 @@
  * In this example field instances of page content type are retrieved from the
  * source database.
  *
- * For additional configuration keys, refer to the parent classes:
+ * For additional configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstancePerFormDisplay.php b/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstancePerFormDisplay.php
index 0ea30440a3..e1f999718f 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstancePerFormDisplay.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstancePerFormDisplay.php
@@ -5,7 +5,8 @@
 /**
  * Drupal 7 field instance per form display source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\field\Plugin\migrate\source\d7\FieldInstance
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstancePerViewMode.php b/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstancePerViewMode.php
index 9e38bb247d..44a0b42ec4 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstancePerViewMode.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d7/FieldInstancePerViewMode.php
@@ -5,7 +5,8 @@
 /**
  * Drupal 7 field instance per view mode source class.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\field\Plugin\migrate\source\d7\FieldInstance
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d7/FieldLabelDescriptionTranslation.php b/web/core/modules/field/src/Plugin/migrate/source/d7/FieldLabelDescriptionTranslation.php
index 1ea718af50..bec150fe78 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d7/FieldLabelDescriptionTranslation.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d7/FieldLabelDescriptionTranslation.php
@@ -7,7 +7,8 @@
 /**
  * Drupal 7 i18n field label and description source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/field/src/Plugin/migrate/source/d7/FieldOptionTranslation.php b/web/core/modules/field/src/Plugin/migrate/source/d7/FieldOptionTranslation.php
index 5569541d2d..b7159b31d4 100644
--- a/web/core/modules/field/src/Plugin/migrate/source/d7/FieldOptionTranslation.php
+++ b/web/core/modules/field/src/Plugin/migrate/source/d7/FieldOptionTranslation.php
@@ -5,7 +5,8 @@
 /**
  * Drupal 7 i18n field option label source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/jsonapi/src/Controller/EntityResource.php b/web/core/modules/jsonapi/src/Controller/EntityResource.php
index 47bc9c056d..ca588d12f7 100644
--- a/web/core/modules/jsonapi/src/Controller/EntityResource.php
+++ b/web/core/modules/jsonapi/src/Controller/EntityResource.php
@@ -369,7 +369,25 @@ public function patchIndividual(ResourceType $resource_type, EntityInterface $en
    *   The response.
    */
   public function deleteIndividual(EntityInterface $entity) {
-    $entity->delete();
+    // @todo Replace with entity handlers in: https://www.drupal.org/project/drupal/issues/3230434
+    if ($entity->getEntityTypeId() === 'user') {
+      $cancel_method = \Drupal::service('config.factory')->get('user.settings')->get('cancel_method');
+
+      // Allow other modules to act.
+
+      user_cancel([], $entity->id(), $cancel_method);
+      // Since user_cancel() is not invoked via Form API, batch processing
+      // needs to be invoked manually.
+      $batch =& batch_get();
+      // Mark this batch as non-progressive to bypass the progress bar and
+      // redirect.
+      $batch['progressive'] = FALSE;
+      batch_process();
+    }
+    else {
+      $entity->delete();
+    }
+
     return new ResourceResponse(NULL, 204);
   }
 
diff --git a/web/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalDateFieldTest.php b/web/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalDateFieldTest.php
new file mode 100644
index 0000000000..21b2a7b9a3
--- /dev/null
+++ b/web/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalDateFieldTest.php
@@ -0,0 +1,182 @@
+<?php
+
+namespace Drupal\Tests\jsonapi\Functional;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\node\NodeInterface;
+
+/**
+ * JSON:API integration test for the "Date" field.
+ *
+ * @group jsonapi
+ */
+class JsonApiFunctionalDateFieldTest extends JsonApiFunctionalTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'basic_auth',
+    'datetime',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    FieldStorageConfig::create([
+      'field_name' => 'field_datetime',
+      'entity_type' => 'node',
+      'type' => 'datetime',
+      'settings' => [
+        'datetime_type' => 'datetime',
+      ],
+      'cardinality' => 1,
+    ])->save();
+
+    FieldConfig::create([
+      'field_name' => 'field_datetime',
+      'label' => 'Date and time',
+      'entity_type' => 'node',
+      'bundle' => 'article',
+      'required' => FALSE,
+      'settings' => [],
+      'description' => '',
+    ])->save();
+  }
+
+  /**
+   * Tests the GET method.
+   */
+  public function testRead() {
+    /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
+    $date_formatter = $this->container->get('date.formatter');
+
+    $timestamp_1 = '5000000';
+    $timestamp_2 = '6000000';
+    $timestamp_3 = '7000000';
+    // Expected: node 1.
+    $timestamp_smaller_than_value = $timestamp_2;
+    // Expected: node 1 and node 2.
+    $timestamp_smaller_than_or_equal_value = $timestamp_2;
+    // Expected: node 3.
+    $timestamp_greater_than_value = $timestamp_2;
+    // Expected: node 2 and node 3.
+    $timestamp_greater_than_or_equal_value = $timestamp_2;
+
+    $node_1 = $this->createNode([
+      'type' => 'article',
+      'uuid' => 'es_test_1',
+      'status' => NodeInterface::PUBLISHED,
+      'field_datetime' => $date_formatter->format($timestamp_1, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT),
+    ]);
+    $node_2 = $this->createNode([
+      'type' => 'article',
+      'uuid' => 'es_test_2',
+      'status' => NodeInterface::PUBLISHED,
+      'field_datetime' => $date_formatter->format($timestamp_2, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT),
+    ]);
+    $node_3 = $this->createNode([
+      'type' => 'article',
+      'uuid' => 'es_test_3',
+      'status' => NodeInterface::PUBLISHED,
+      'field_datetime' => $date_formatter->format($timestamp_3, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT),
+    ]);
+
+    // Checks whether the date is greater than the given timestamp.
+    $filter = [
+      'filter_datetime' => [
+        'condition' => [
+          'path' => 'field_datetime',
+          'operator' => '>',
+          'value' => date(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $timestamp_greater_than_value),
+        ],
+      ],
+    ];
+    $output = Json::decode($this->drupalGet('/jsonapi/node/article', [
+      'query' => ['filter' => $filter],
+    ]));
+    $this->assertSession()->statusCodeEquals(200);
+    $output_uuids = array_map(function ($result) {
+      return $result['id'];
+    }, $output['data']);
+    $this->assertCount(1, $output_uuids);
+    $this->assertSame([
+      $node_3->uuid(),
+    ], $output_uuids);
+
+    // Checks whether the date is greater than or equal to the given timestamp.
+    $filter = [
+      'filter_datetime' => [
+        'condition' => [
+          'path' => 'field_datetime',
+          'operator' => '>=',
+          'value' => date(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $timestamp_greater_than_or_equal_value),
+        ],
+      ],
+    ];
+    $output = Json::decode($this->drupalGet('/jsonapi/node/article', [
+      'query' => ['filter' => $filter],
+    ]));
+    $this->assertSession()->statusCodeEquals(200);
+    $output_uuids = array_map(function ($result) {
+      return $result['id'];
+    }, $output['data']);
+    $this->assertCount(2, $output_uuids);
+    $this->assertSame([
+      $node_2->uuid(),
+      $node_3->uuid(),
+    ], $output_uuids);
+
+    // Checks whether the date is less than the given timestamp.
+    $filter = [
+      'filter_datetime' => [
+        'condition' => [
+          'path' => 'field_datetime',
+          'operator' => '<',
+          'value' => date(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $timestamp_smaller_than_value),
+        ],
+      ],
+    ];
+    $output = Json::decode($this->drupalGet('/jsonapi/node/article', [
+      'query' => ['filter' => $filter],
+    ]));
+    $this->assertSession()->statusCodeEquals(200);
+    $output_uuids = array_map(function ($result) {
+      return $result['id'];
+    }, $output['data']);
+    $this->assertCount(1, $output_uuids);
+    $this->assertSame([
+      $node_1->uuid(),
+    ], $output_uuids);
+
+    // Checks whether the date is less than or equal to the given timestamp.
+    $filter = [
+      'filter_datetime' => [
+        'condition' => [
+          'path' => 'field_datetime',
+          'operator' => '<=',
+          'value' => date(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $timestamp_smaller_than_or_equal_value),
+        ],
+      ],
+    ];
+    $output = Json::decode($this->drupalGet('/jsonapi/node/article', [
+      'query' => ['filter' => $filter],
+    ]));
+    $this->assertSession()->statusCodeEquals(200);
+    $output_uuids = array_map(function ($result) {
+      return $result['id'];
+    }, $output['data']);
+    $this->assertCount(2, $output_uuids);
+    $this->assertSame([
+      $node_1->uuid(),
+      $node_2->uuid(),
+    ], $output_uuids);
+  }
+
+}
diff --git a/web/core/modules/jsonapi/tests/src/Functional/UserTest.php b/web/core/modules/jsonapi/tests/src/Functional/UserTest.php
index bc6b453b43..2184c3bcb4 100644
--- a/web/core/modules/jsonapi/tests/src/Functional/UserTest.php
+++ b/web/core/modules/jsonapi/tests/src/Functional/UserTest.php
@@ -10,6 +10,7 @@
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\node\Entity\Node;
 use Drupal\user\Entity\User;
+use Drupal\user\UserInterface;
 use GuzzleHttp\RequestOptions;
 
 /**
@@ -19,10 +20,12 @@
  */
 class UserTest extends ResourceTestBase {
 
+  const BATCH_TEST_NODE_COUNT = 15;
+
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['user', 'jsonapi_test_user'];
+  protected static $modules = ['user', 'jsonapi_test_user', 'node'];
 
   /**
    * {@inheritdoc}
@@ -119,6 +122,15 @@ protected function createAnotherEntity($key) {
     return $user;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function testDeleteIndividual() {
+    $this->config('user.settings')->set('cancel_method', 'user_cancel_delete')->save(TRUE);
+
+    parent::testDeleteIndividual();
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -597,6 +609,175 @@ public function testResaveAccountName() {
     $this->assertEquals($original_name, $updated_user->get('name')->value);
   }
 
+  /**
+   * Tests if JSON:API respects user.settings.cancel_method: user_cancel_block.
+   */
+  public function testDeleteRespectsUserCancelBlock() {
+    $cancel_method = 'user_cancel_block';
+    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
+    $this->config('user.settings')->set('cancel_method', $cancel_method)->save(TRUE);
+
+    $account = $this->createAnotherEntity($cancel_method);
+    $node = $this->drupalCreateNode(['uid' => $account->id()]);
+
+    $this->sendDeleteRequestForUser($account, $cancel_method);
+
+    $user_storage = $this->container->get('entity_type.manager')
+      ->getStorage('user');
+    $user_storage->resetCache([$account->id()]);
+    $account = $user_storage->load($account->id());
+
+    $this->assertNotNull($account, 'User is not deleted after JSON:API DELETE operation with user.settings.cancel_method: ' . $cancel_method);
+    $this->assertTrue($account->isBlocked(), 'User is blocked after JSON:API DELETE operation with user.settings.cancel_method: ' . $cancel_method);
+
+    $node_storage = $this->container->get('entity_type.manager')->getStorage('node');
+    $node_storage->resetCache([$node->id()]);
+    $test_node = $node_storage->load($node->id());
+    $this->assertNotNull($test_node, 'Node of the user is not deleted.');
+    $this->assertTrue($test_node->isPublished(), 'Node of the user is published.');
+    $test_node = node_revision_load($node->getRevisionId());
+    $this->assertTrue($test_node->isPublished(), 'Node revision of the user is published.');
+  }
+
+  /**
+   * Tests if JSON:API respects user.settings.cancel_method: user_cancel_block_unpublish.
+   */
+  public function testDeleteRespectsUserCancelBlockUnpublish() {
+    $cancel_method = 'user_cancel_block_unpublish';
+    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
+    $this->config('user.settings')->set('cancel_method', $cancel_method)->save(TRUE);
+
+    $account = $this->createAnotherEntity($cancel_method);
+    $node = $this->drupalCreateNode(['uid' => $account->id()]);
+
+    $this->sendDeleteRequestForUser($account, $cancel_method);
+
+    $user_storage = $this->container->get('entity_type.manager')
+      ->getStorage('user');
+    $user_storage->resetCache([$account->id()]);
+    $account = $user_storage->load($account->id());
+
+    $this->assertNotNull($account, 'User is not deleted after JSON:API DELETE operation with user.settings.cancel_method: ' . $cancel_method);
+    $this->assertTrue($account->isBlocked(), 'User is blocked after JSON:API DELETE operation with user.settings.cancel_method: ' . $cancel_method);
+
+    $node_storage = $this->container->get('entity_type.manager')->getStorage('node');
+    $node_storage->resetCache([$node->id()]);
+    $test_node = $node_storage->load($node->id());
+    $this->assertNotNull($test_node, 'Node of the user is not deleted.');
+    $this->assertFalse($test_node->isPublished(), 'Node of the user is no longer published.');
+    $test_node = node_revision_load($node->getRevisionId());
+    $this->assertFalse($test_node->isPublished(), 'Node revision of the user is no longer published.');
+  }
+
+  /**
+   * Tests if JSON:API respects user.settings.cancel_method: user_cancel_block_unpublish.
+   * @group jsonapi
+   */
+  public function testDeleteRespectsUserCancelBlockUnpublishAndProcessesBatches() {
+    $cancel_method = 'user_cancel_block_unpublish';
+    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
+    $this->config('user.settings')->set('cancel_method', $cancel_method)->save(TRUE);
+
+    $account = $this->createAnotherEntity($cancel_method);
+
+    $nodeCount = self::BATCH_TEST_NODE_COUNT;
+    $node_ids = [];
+    $nodes = [];
+    while ($nodeCount-- > 0) {
+      $node = $this->drupalCreateNode(['uid' => $account->id()]);
+      $nodes[] = $node;
+      $node_ids[] = $node->id();
+    }
+
+    $this->sendDeleteRequestForUser($account, $cancel_method);
+
+    $user_storage = $this->container->get('entity_type.manager')
+      ->getStorage('user');
+    $user_storage->resetCache([$account->id()]);
+    $account = $user_storage->load($account->id());
+
+    $this->assertNotNull($account, 'User is not deleted after JSON:API DELETE operation with user.settings.cancel_method: ' . $cancel_method);
+    $this->assertTrue($account->isBlocked(), 'User is blocked after JSON:API DELETE operation with user.settings.cancel_method: ' . $cancel_method);
+
+    $node_storage = $this->container->get('entity_type.manager')->getStorage('node');
+    $node_storage->resetCache($node_ids);
+
+    $test_nodes = $node_storage->loadMultiple($node_ids);
+
+    $this->assertCount(self::BATCH_TEST_NODE_COUNT, $test_nodes, 'Nodes of the user are not deleted.');
+
+    foreach ($test_nodes as $test_node) {
+      $this->assertFalse($test_node->isPublished(), 'Node of the user is no longer published.');
+    }
+
+    foreach ($nodes as $node) {
+      $test_node = node_revision_load($node->getRevisionId());
+      $this->assertFalse($test_node->isPublished(), 'Node revision of the user is no longer published.');
+    }
+  }
+
+  /**
+   * Tests if JSON:API respects user.settings.cancel_method: user_cancel_reassign.
+   */
+  public function testDeleteRespectsUserCancelReassign() {
+    $cancel_method = 'user_cancel_reassign';
+    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
+    $this->config('user.settings')->set('cancel_method', $cancel_method)->save(TRUE);
+
+    $account = $this->createAnotherEntity($cancel_method);
+    $node = $this->drupalCreateNode(['uid' => $account->id()]);
+
+    $this->sendDeleteRequestForUser($account, $cancel_method);
+
+    $user_storage = $this->container->get('entity_type.manager')
+      ->getStorage('user');
+    $user_storage->resetCache([$account->id()]);
+    $account = $user_storage->load($account->id());
+
+    $this->assertNull($account, 'User is deleted after JSON:API DELETE operation with user.settings.cancel_method: ' . $cancel_method);
+
+    $node_storage = $this->container->get('entity_type.manager')->getStorage('node');
+    $node_storage->resetCache([$node->id()]);
+    $test_node = $node_storage->load($node->id());
+    $this->assertNotNull($test_node, 'Node of the user is not deleted.');
+    $this->assertTrue($test_node->isPublished(), 'Node of the user is still published.');
+    $this->assertEquals(0, $test_node->getOwnerId(), 'Node of the user has been attributed to anonymous user.');
+    $test_node = node_revision_load($node->getRevisionId());
+    $this->assertTrue($test_node->isPublished(), 'Node revision of the user is still published.');
+    $this->assertEquals(0, $test_node->getRevisionUser()->id(), 'Node revision of the user has been attributed to anonymous user.');
+  }
+
+  /**
+   * Tests if JSON:API respects user.settings.cancel_method: user_cancel_delete.
+   */
+  public function testDeleteRespectsUserCancelDelete() {
+    $cancel_method = 'user_cancel_delete';
+    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
+    $this->config('user.settings')->set('cancel_method', $cancel_method)->save(TRUE);
+
+    $account = $this->createAnotherEntity($cancel_method);
+    $node = $this->drupalCreateNode(['uid' => $account->id()]);
+
+    $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $account->uuid()]);
+    $request_options = [];
+    $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
+    $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
+    $this->setUpAuthorization('DELETE');
+    $response = $this->request('DELETE', $url, $request_options);
+    $this->assertResourceResponse(204, NULL, $response);
+
+    $node_storage = $this->container->get('entity_type.manager')->getStorage('node');
+    $user_storage = $this->container->get('entity_type.manager')->getStorage('user');
+
+    $user_storage->resetCache([$account->id()]);
+    $account = $user_storage->load($account->id());
+    $this->assertNull($account, 'User is deleted after JSON:API DELETE operation with user.settings.cancel_method: ' . $cancel_method);
+
+    $node_storage->resetCache([$node->id()]);
+    $test_node = $node_storage->load($node->id());
+    $this->assertNull($test_node, 'Node of the user is deleted.');
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -620,4 +801,18 @@ protected function makeNormalizationInvalid(array $document, $entity_key) {
     return parent::makeNormalizationInvalid($document, $entity_key);
   }
 
+  /**
+   * @param \Drupal\user\UserInterface $account
+   * @param string $cancel_method
+   */
+  private function sendDeleteRequestForUser(UserInterface $account, string $cancel_method) {
+    $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $account->uuid()]);
+    $request_options = [];
+    $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
+    $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
+    $this->setUpAuthorization('DELETE');
+    $response = $this->request('DELETE', $url, $request_options);
+    $this->assertResourceResponse(204, NULL, $response);
+  }
+
 }
diff --git a/web/core/modules/language/src/Plugin/migrate/source/Language.php b/web/core/modules/language/src/Plugin/migrate/source/Language.php
index 9576cf7a79..870f9790e6 100644
--- a/web/core/modules/language/src/Plugin/migrate/source/Language.php
+++ b/web/core/modules/language/src/Plugin/migrate/source/Language.php
@@ -6,7 +6,7 @@
 use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
 
 /**
- * Drupal language source from database.
+ * Drupal 6/7 language source from database.
  *
  * Available configuration keys:
  * - fetch_all: (optional) If not empty, all source languages are retrieved and
@@ -28,7 +28,8 @@
  * Given that fetch_all and domain_negotiation are specified, each row also
  * contains all languages and the domain negotiation status, if enabled.
  *
- * For additional configuration keys, refer to the parent classes:
+ * For additional configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/language/src/Plugin/migrate/source/d6/LanguageContentSettings.php b/web/core/modules/language/src/Plugin/migrate/source/d6/LanguageContentSettings.php
index 9fbe85c831..d2f4c8b342 100644
--- a/web/core/modules/language/src/Plugin/migrate/source/d6/LanguageContentSettings.php
+++ b/web/core/modules/language/src/Plugin/migrate/source/d6/LanguageContentSettings.php
@@ -8,7 +8,8 @@
 /**
  * Drupal 6 i18n node settings from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBas
  *
diff --git a/web/core/modules/language/src/Plugin/migrate/source/d6/LanguageContentSettingsTaxonomyVocabulary.php b/web/core/modules/language/src/Plugin/migrate/source/d6/LanguageContentSettingsTaxonomyVocabulary.php
index 246e635ddf..4eddde832e 100644
--- a/web/core/modules/language/src/Plugin/migrate/source/d6/LanguageContentSettingsTaxonomyVocabulary.php
+++ b/web/core/modules/language/src/Plugin/migrate/source/d6/LanguageContentSettingsTaxonomyVocabulary.php
@@ -8,7 +8,8 @@
 /**
  * Drupal 6 i18n vocabularies source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBas
  *
diff --git a/web/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettings.php b/web/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettings.php
index 6ce9283c51..ae0b11409d 100644
--- a/web/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettings.php
+++ b/web/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettings.php
@@ -8,7 +8,8 @@
 /**
  * Drupal 7 i18n node settings from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBas
  *
diff --git a/web/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettingsTaxonomyVocabulary.php b/web/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettingsTaxonomyVocabulary.php
index 9017a9e461..89622ab5b0 100644
--- a/web/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettingsTaxonomyVocabulary.php
+++ b/web/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettingsTaxonomyVocabulary.php
@@ -7,7 +7,8 @@
 /**
  * Drupal 7 i18n vocabularies source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBas
  *
diff --git a/web/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsNoEntityTranslationTest.php b/web/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsNoEntityTranslationTest.php
index 287a98e2fd..29cb946832 100644
--- a/web/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsNoEntityTranslationTest.php
+++ b/web/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsNoEntityTranslationTest.php
@@ -31,6 +31,7 @@ class MigrateLanguageContentCommentSettingsNoEntityTranslationTest extends Migra
    */
   protected function setUp(): void {
     parent::setUp();
+    $this->startCollectingMessages();
     $this->migrateCommentTypes();
     $this->executeMigrations([
       'language',
@@ -57,6 +58,9 @@ public static function migrateDumpAlter(KernelTestBase $test) {
    * Tests migration of content language settings.
    */
   public function testLanguageCommentSettings() {
+    // Confirm there is no message about a missing bundle.
+    $this->assertEmpty($this->migrateMessages, $this->migrateMessages['error'][0] ?? '');
+
     // Article and Blog content type have multilingual settings of 'Enabled,
     // with Translation'. Assert that comments are translatable and the default
     // language is 'current_interface'.
@@ -88,6 +92,15 @@ public function testLanguageCommentSettings() {
     $this->assertTrue($config->isLanguageAlterable());
     $this->assertSame($third_party_settings, $config->get('third_party_settings'));
 
+    // The content type with a long name has multilingual settings of 'Enabled'.
+    $config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_a_thirty_two_char');
+    $this->assertFalse($config->isNew());
+    $this->assertSame('comment', $config->getTargetEntityTypeId());
+    $this->assertSame('comment_node_a_thirty_two_char', $config->getTargetBundle());
+    $this->assertSame('current_interface', $config->getDefaultLangcode());
+    $this->assertTrue($config->isLanguageAlterable());
+    $this->assertSame($third_party_settings, $config->get('third_party_settings'));
+
     // Test content type has multilingual settings of 'Enabled, with field
     // translation'.
     $config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_test_content_type');
diff --git a/web/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php b/web/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php
index 4032c9dd05..db157140ce 100644
--- a/web/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php
+++ b/web/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentCommentSettingsTest.php
@@ -28,6 +28,7 @@ class MigrateLanguageContentCommentSettingsTest extends MigrateDrupal7TestBase {
    */
   protected function setUp(): void {
     parent::setUp();
+    $this->startCollectingMessages();
     $this->migrateCommentTypes();
     $this->executeMigrations([
       'language',
@@ -39,6 +40,9 @@ protected function setUp(): void {
    * Tests migration of content language settings.
    */
   public function testLanguageCommentSettings() {
+    // Confirm there is no message about a missing bundle.
+    $this->assertEmpty($this->migrateMessages, $this->migrateMessages['error'][0] ?? '');
+
     // Article and Blog content type have multilingual settings of 'Enabled,
     // with Translation'. Assert that comments are translatable and the default
     // language is 'current_interface'.
@@ -70,6 +74,15 @@ public function testLanguageCommentSettings() {
     $this->assertTrue($config->isLanguageAlterable());
     $this->assertSame($third_party_settings, $config->get('third_party_settings'));
 
+    // The content type with a long name has multilingual settings of 'Enabled'.
+    $config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_a_thirty_two_char');
+    $this->assertFalse($config->isNew());
+    $this->assertSame('comment', $config->getTargetEntityTypeId());
+    $this->assertSame('comment_node_a_thirty_two_char', $config->getTargetBundle());
+    $this->assertSame('current_interface', $config->getDefaultLangcode());
+    $this->assertTrue($config->isLanguageAlterable());
+    $this->assertSame($third_party_settings, $config->get('third_party_settings'));
+
     // Test content type has multilingual settings of 'Enabled, with field
     // translation'.
     $config = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_node_test_content_type');
diff --git a/web/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php b/web/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php
index d9218b5387..8ed8137f25 100644
--- a/web/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php
+++ b/web/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php
@@ -12,6 +12,7 @@
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\layout_builder\Access\LayoutPreviewAccessAllowed;
 use Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent;
+use Drupal\layout_builder\Plugin\Block\InlineBlock;
 use Drupal\layout_builder\LayoutBuilderEvents;
 use Drupal\views\Plugin\Block\ViewsBlock;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -124,9 +125,31 @@ public function onBuildRender(SectionComponentBuildRenderArrayEvent $event) {
         '#base_plugin_id' => $block->getBaseId(),
         '#derivative_plugin_id' => $block->getDerivativeId(),
         '#weight' => $event->getComponent()->getWeight(),
-        'content' => $content,
       ];
 
+      // Place the $content returned by the block plugin into a 'content' child
+      // element, as a way to allow the plugin to have complete control of its
+      // properties and rendering (for instance, its own #theme) without
+      // conflicting with the properties used above, or alternate ones used by
+      // alternate block rendering approaches in contributed modules. However,
+      // the use of a child element is an implementation detail of this
+      // particular block rendering approach. Semantically, the content returned
+      // by the block plugin, and in particular, attributes and contextual links
+      // are information that belong to the entire block. Therefore, we must
+      // move these properties from $content and merge them into the top-level
+      // element.
+      if (isset($content['#attributes'])) {
+        $build['#attributes'] = $content['#attributes'];
+        unset($content['#attributes']);
+      }
+      // Hide contextual links for inline blocks until the UX issues surrounding
+      // editing them directly are resolved.
+      // @see https://www.drupal.org/project/drupal/issues/3075308
+      if (!$block instanceof InlineBlock && !empty($content['#contextual_links'])) {
+        $build['#contextual_links'] = $content['#contextual_links'];
+      }
+      $build['content'] = $content;
+
       if ($event->inPreview()) {
         if ($block instanceof PreviewFallbackInterface) {
           $preview_fallback_string = $block->getPreviewFallbackString();
diff --git a/web/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.links.contextual.yml b/web/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.links.contextual.yml
new file mode 100644
index 0000000000..62a4a509dd
--- /dev/null
+++ b/web/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.links.contextual.yml
@@ -0,0 +1,4 @@
+layout_builder_test:
+  title: 'Test contextual link'
+  route_name: '<front>'
+  group: 'layout_builder_test'
diff --git a/web/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Block/TestAttributesBlock.php b/web/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Block/TestAttributesBlock.php
new file mode 100644
index 0000000000..877fc3bfe7
--- /dev/null
+++ b/web/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Block/TestAttributesBlock.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\layout_builder_test\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a 'TestAttributes' block.
+ *
+ * @Block(
+ *   id = "layout_builder_test_test_attributes",
+ *   admin_label = @Translation("Test Attributes"),
+ *   category = @Translation("Test")
+ * )
+ */
+class TestAttributesBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockForm($form, FormStateInterface $form_state) {
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $build = [
+      '#attributes' => [
+        'class' => ['attribute-test-class'],
+        'custom-attribute' => 'test',
+      ],
+      '#markup' => $this->t('Example block providing its own attributes.'),
+      '#contextual_links' => [
+        'layout_builder_test' => ['route_parameters' => []],
+      ],
+    ];
+    return $build;
+  }
+
+}
diff --git a/web/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/web/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
index ba6f5bc41c..8f121c75b4 100644
--- a/web/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
+++ b/web/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
@@ -25,6 +25,7 @@ class LayoutBuilderTest extends BrowserTestBase {
     'layout_test',
     'block',
     'block_test',
+    'contextual',
     'node',
     'layout_builder_test',
   ];
@@ -639,6 +640,37 @@ public function testPluginDependencies() {
     $assert_session->elementNotExists('css', '.block.menu--my-menu');
   }
 
+  /**
+   * Tests that block plugins can define custom attributes and contextual links.
+   */
+  public function testPluginsProvidingCustomAttributesAndContextualLinks() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'access contextual links',
+      'configure any layout',
+      'administer node display',
+    ]));
+
+    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
+    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
+    $page->clickLink('Manage layout');
+    $page->clickLink('Add section');
+    $page->clickLink('Layout Builder Test Plugin');
+    $page->pressButton('Add section');
+    $page->clickLink('Add block');
+    $page->clickLink('Test Attributes');
+    $page->pressButton('Add block');
+    $page->pressButton('Save layout');
+
+    $this->drupalGet('node/1');
+
+    $assert_session->elementExists('css', '.attribute-test-class');
+    $assert_session->elementExists('css', '[custom-attribute=test]');
+    $assert_session->elementExists('css', 'div[data-contextual-id*="layout_builder_test"]');
+  }
+
   /**
    * Tests the interaction between full and default view modes.
    *
diff --git a/web/core/modules/link/src/Plugin/migrate/process/FieldLink.php b/web/core/modules/link/src/Plugin/migrate/process/FieldLink.php
index 232ed903f9..b118f449b1 100644
--- a/web/core/modules/link/src/Plugin/migrate/process/FieldLink.php
+++ b/web/core/modules/link/src/Plugin/migrate/process/FieldLink.php
@@ -59,6 +59,13 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
    * @see \Drupal\link\Plugin\Field\FieldWidget\LinkWidget::getUserEnteredStringAsUri()
    */
   protected function canonicalizeUri($uri) {
+    // If the path starts with 2 slashes then it is always considered an
+    // external URL without an explicit protocol part.
+    // @todo Remove this when https://www.drupal.org/node/2744729 lands.
+    if (strpos($uri, '//') === 0) {
+      return $this->configuration['uri_scheme'] . ltrim($uri, '/');
+    }
+
     // If we already have a scheme, we're fine.
     if (parse_url($uri, PHP_URL_SCHEME)) {
       return $uri;
diff --git a/web/core/modules/link/tests/src/Unit/Plugin/migrate/process/FieldLinkTest.php b/web/core/modules/link/tests/src/Unit/Plugin/migrate/process/FieldLinkTest.php
index d6721e51f2..1142ec0c20 100644
--- a/web/core/modules/link/tests/src/Unit/Plugin/migrate/process/FieldLinkTest.php
+++ b/web/core/modules/link/tests/src/Unit/Plugin/migrate/process/FieldLinkTest.php
@@ -66,6 +66,10 @@ public function canonicalizeUriDataProvider() {
         'https://yahoo.com',
         ['uri_scheme' => 'https://'],
       ],
+      'Absolute URL without explicit protocol (protocol-relative)' => [
+        '//example.com',
+        'http://example.com',
+      ],
       'Absolute URL with non-standard characters' => [
         'http://www.ßÀÑÐ¥ƒå¢ë.com',
         'http://www.ßÀÑÐ¥ƒå¢ë.com',
diff --git a/web/core/modules/media/media.install b/web/core/modules/media/media.install
index f9fddb2511..a2c5eed95d 100644
--- a/web/core/modules/media/media.install
+++ b/web/core/modules/media/media.install
@@ -120,22 +120,37 @@ function media_requirements($phase) {
       }
     }
 
-    // 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
     $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) || !is_a($source_field_definition->getItemDefinition()->getClass(), ImageItem::class, TRUE)) {
+      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;
       }
 
diff --git a/web/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php b/web/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php
index 1252265e45..9ca270c6a1 100644
--- a/web/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php
+++ b/web/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php
@@ -36,7 +36,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     $source = $items->getEntity()->getSource();
     $message = $this->t('You can link to media from the following services: @providers', ['@providers' => implode(', ', $source->getProviders())]);
 
-    if (!empty($element['#value']['#description'])) {
+    if (!empty($element['value']['#description'])) {
       $element['value']['#description'] = [
         '#theme' => 'item_list',
         '#items' => [$element['value']['#description'], $message],
diff --git a/web/core/modules/media/tests/src/Functional/FieldWidget/OEmbedFieldWidgetTest.php b/web/core/modules/media/tests/src/Functional/FieldWidget/OEmbedFieldWidgetTest.php
new file mode 100644
index 0000000000..5d1caf11ae
--- /dev/null
+++ b/web/core/modules/media/tests/src/Functional/FieldWidget/OEmbedFieldWidgetTest.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\Tests\media\Functional\FieldWidget;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\Tests\media\Functional\MediaFunctionalTestBase;
+
+/**
+ * @covers \Drupal\media\Plugin\Field\FieldWidget\OEmbedWidget
+ *
+ * @group media
+ */
+class OEmbedFieldWidgetTest extends MediaFunctionalTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * Tests that the oEmbed field widget shows the configured help text.
+   */
+  public function testFieldWidgetHelpText() {
+    $account = $this->drupalCreateUser(['create media']);
+    $this->drupalLogin($account);
+
+    $media_type = $this->createMediaType('oembed:video');
+    $source_field = $media_type->getSource()
+      ->getSourceFieldDefinition($media_type)
+      ->getName();
+
+    /** @var \Drupal\field\Entity\FieldConfig $field */
+    $field = FieldConfig::loadByName('media', $media_type->id(), $source_field);
+    $field->setDescription('This is help text for oEmbed field.')
+      ->save();
+
+    $this->drupalGet('media/add/' . $media_type->id());
+    $assert_session = $this->assertSession();
+    $assert_session->pageTextContains('This is help text for oEmbed field.');
+    $assert_session->pageTextContains('You can link to media from the following services: YouTube, Vimeo');
+  }
+
+}
diff --git a/web/core/modules/media/tests/src/Functional/MediaRequirementsTest.php b/web/core/modules/media/tests/src/Functional/MediaRequirementsTest.php
index e8ff550cbc..db12de6150 100644
--- a/web/core/modules/media/tests/src/Functional/MediaRequirementsTest.php
+++ b/web/core/modules/media/tests/src/Functional/MediaRequirementsTest.php
@@ -26,10 +26,13 @@ public function testMissingSourceFieldDefinition() {
     $field_storage_definition = $field_definition->getFieldStorageDefinition();
     $field_definition->delete();
     $field_storage_definition->delete();
+    $valid_media_type = $this->createMediaType('test');
 
     $this->drupalLogin($this->rootUser);
     $this->drupalGet('/admin/reports/status');
     $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains("The source field definition for the {$media_type->label()} media type is missing.");
+    $this->assertSession()->pageTextNotContains("The source field definition for the {$valid_media_type->label()} media type is missing.");
   }
 
 }
diff --git a/web/core/modules/media_library/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/web/core/modules/media_library/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
index ca95070bc7..40fe5f7f53 100644
--- a/web/core/modules/media_library/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
+++ b/web/core/modules/media_library/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
@@ -236,8 +236,6 @@ public function testButton() {
     $this->assignNameToCkeditorIframe();
     $this->getSession()->switchToIFrame('ckeditor');
     $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia drupal-media .media'));
-    // @todo Inserting media embed should enable undo.
-    // @see https://www.drupal.org/project/drupal/issues/3073294
     $this->pressEditorButton('source');
     $value = $assert_session->elementExists('css', 'textarea.cke_source')->getValue();
     $dom = Html::load($value);
diff --git a/web/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php b/web/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
index 1b186a6e04..ff413abf8a 100644
--- a/web/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
+++ b/web/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
@@ -7,7 +7,7 @@
 use Drupal\migrate\Row;
 
 /**
- * Drupal menu link source from database.
+ * Drupal 6/7 menu link source from database.
  *
  * @MigrateSource(
  *   id = "menu_link",
diff --git a/web/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php b/web/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php
index 54b2068734..863e5bee49 100644
--- a/web/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php
+++ b/web/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php
@@ -7,7 +7,7 @@
 use Drupal\menu_link_content\Plugin\migrate\source\MenuLink;
 
 /**
- * Gets Menu link translations from source database.
+ * Drupal 6 i18n menu link translations source from database.
  *
  * @MigrateSource(
  *   id = "d6_menu_link_translation",
diff --git a/web/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkLocalized.php b/web/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkLocalized.php
index b683186cea..0b9e5ce30f 100644
--- a/web/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkLocalized.php
+++ b/web/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkLocalized.php
@@ -6,7 +6,7 @@
 use Drupal\migrate\Row;
 
 /**
- * Gets localized menu link translations from source database.
+ * Drupal 7 localized menu link translations source from database.
  *
  * @MigrateSource(
  *   id = "d7_menu_link_localized",
diff --git a/web/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php b/web/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php
index b55a1a218f..267e896b9b 100644
--- a/web/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php
+++ b/web/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php
@@ -7,7 +7,7 @@
 use Drupal\menu_link_content\Plugin\migrate\source\MenuLink;
 
 /**
- * Gets Menu link translations from source database.
+ * Drupal 7 i18n menu link translations source from database.
  *
  * @MigrateSource(
  *   id = "d7_menu_link_translation",
diff --git a/web/core/modules/menu_ui/src/MenuListBuilder.php b/web/core/modules/menu_ui/src/MenuListBuilder.php
index c6efe07222..59af3090bd 100644
--- a/web/core/modules/menu_ui/src/MenuListBuilder.php
+++ b/web/core/modules/menu_ui/src/MenuListBuilder.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Url;
 
 /**
  * Defines a class to build a listing of menu entities.
@@ -49,6 +50,9 @@ public function getDefaultOperations(EntityInterface $entity) {
         'title' => t('Add link'),
         'weight' => 20,
         'url' => $entity->toUrl('add-link-form'),
+        'query' => [
+          'destination' => $entity->toUrl('edit-form')->toString(),
+        ],
       ];
     }
     if (isset($operations['delete'])) {
@@ -57,6 +61,16 @@ public function getDefaultOperations(EntityInterface $entity) {
     return $operations;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function ensureDestination(Url $url) {
+    // We don't want to add the destination URL here, as it means we get
+    // redirected back to the list-builder after adding/deleting menu links from
+    // a menu.
+    return $url;
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/web/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php b/web/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php
index 2ded666bff..d565e53203 100644
--- a/web/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php
+++ b/web/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php
@@ -85,6 +85,10 @@ protected function setUp(): void {
 
     $this->drupalPlaceBlock('page_title_block');
     $this->drupalPlaceBlock('system_menu_block:main');
+    $this->drupalPlaceBlock('local_actions_block', [
+      'region' => 'content',
+      'weight' => -100,
+    ]);
 
     $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
 
@@ -101,7 +105,7 @@ protected function setUp(): void {
   /**
    * Tests menu functionality using the admin and user interfaces.
    */
-  public function testMenu() {
+  public function testMenuAdministration() {
     // Log in the user.
     $this->drupalLogin($this->adminUser);
     $this->items = [];
@@ -195,6 +199,16 @@ public function addCustomMenuCRUD() {
     $menu->save();
     $this->drupalGet('admin/structure/menu/manage/' . $menu_name);
     $this->assertSession()->pageTextContains($new_label);
+
+    // Delete the custom menu via the UI to testing destination handling.
+    $this->drupalGet('admin/structure/menu');
+    $this->assertSession()->pageTextContains($new_label);
+    // Click the "Delete menu" operation in the Tools row.
+    $links = $this->xpath('//*/td[contains(text(),:menu_label)]/following::a[normalize-space()=:link_label]', [':menu_label' => $new_label, ':link_label' => 'Delete menu']);
+    $links[0]->click();
+    $this->submitForm([], 'Delete');
+    $this->assertSession()->addressEquals('admin/structure/menu');
+    $this->assertSession()->responseContains("The menu <em class=\"placeholder\">$new_label</em> has been deleted.");
   }
 
   /**
@@ -259,6 +273,7 @@ public function deleteCustomMenu() {
     $this->drupalGet("admin/structure/menu/manage/{$menu_name}/delete");
     $this->submitForm([], 'Delete');
     $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->addressEquals("admin/structure/menu");
     $this->assertSession()->pageTextContains("The menu $label has been deleted.");
     $this->assertNull(Menu::load($menu_name), 'Custom menu was deleted');
     // Test if all menu links associated with the menu were removed from
@@ -279,11 +294,37 @@ public function deleteCustomMenu() {
    * Tests menu functionality.
    */
   public function doMenuTests() {
+    // Add a link to the tools menu first, to test cacheability metadata of the
+    // destination query string.
+    $this->drupalGet('admin/structure/menu/manage/tools');
+    $this->clickLink('Add link');
+    $link_title = $this->randomString();
+    $this->submitForm(['link[0][uri]' => '/', 'title[0][value]' => $link_title], 'Save');
+    $this->assertSession()->linkExists($link_title);
+    $this->assertSession()->addressEquals('admin/structure/menu/manage/tools');
+
+    // Test adding a menu link direct from the menus listing page.
+    $this->drupalGet('admin/structure/menu');
+    // Click the "Add link" operation in the Tools row.
+    $links = $this->xpath('//*/td[contains(text(),:menu_label)]/following::a[normalize-space()=:link_label]', [':menu_label' => 'Tools', ':link_label' => 'Add link']);
+    $links[0]->click();
+    $this->assertMatchesRegularExpression('#admin/structure/menu/manage/tools/add\?destination=(/[^/]*)*/admin/structure/menu/manage/tools$#', $this->getSession()->getCurrentUrl());
+    $link_title = $this->randomString();
+    $this->submitForm(['link[0][uri]' => '/', 'title[0][value]' => $link_title], 'Save');
+    $this->assertSession()->linkExists($link_title);
+    $this->assertSession()->addressEquals('admin/structure/menu/manage/tools');
+
     $menu_name = $this->menu->id();
 
-    // Test the 'Add link' local action.
-    $this->drupalGet(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
+    // Access the menu via the overview form to ensure it does not add a
+    // destination that breaks the user interface.
+    $this->drupalGet('admin/structure/menu');
 
+    // Select the edit menu link for our menu.
+    $links = $this->xpath('//*/td[contains(text(),:menu_label)]/following::a[normalize-space()=:link_label]', [':menu_label' => (string) $this->menu->label(), ':link_label' => 'Edit menu']);
+    $links[0]->click();
+
+    // Test the 'Add link' local action.
     $this->clickLink('Add link');
     $link_title = $this->randomString();
     $this->submitForm(['link[0][uri]' => '/', 'title[0][value]' => $link_title], 'Save');
@@ -300,6 +341,22 @@ public function doMenuTests() {
     $this->submitForm([], 'Delete');
     $this->assertSession()->addressEquals(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
 
+    // Clear the cache to ensure that recent caches aren't preventing us from
+    // seeing a broken add link.
+    $this->resetAll();
+    $this->drupalGet('admin/structure/menu');
+
+    // Select the edit menu link for our menu.
+    $links = $this->xpath('//*/td[contains(text(),:menu_label)]/following::a[normalize-space()=:link_label]', [':menu_label' => (string) $this->menu->label(), ':link_label' => 'Edit menu']);
+    $links[0]->click();
+
+    // Test the 'Add link' local action.
+    $this->clickLink('Add link');
+    $link_title = $this->randomString();
+    $this->submitForm(['link[0][uri]' => '/', 'title[0][value]' => $link_title], 'Save');
+    $this->assertSession()->linkExists($link_title);
+    $this->assertSession()->addressEquals(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
+
     // Add nodes to use as links for menu links.
     $node1 = $this->drupalCreateNode(['type' => 'article']);
     $node2 = $this->drupalCreateNode(['type' => 'article']);
diff --git a/web/core/modules/migrate_drupal/src/Plugin/migrate/EntityReferenceTranslationDeriver.php b/web/core/modules/migrate_drupal/src/Plugin/migrate/EntityReferenceTranslationDeriver.php
index 603cbbe70a..e66c8a170b 100644
--- a/web/core/modules/migrate_drupal/src/Plugin/migrate/EntityReferenceTranslationDeriver.php
+++ b/web/core/modules/migrate_drupal/src/Plugin/migrate/EntityReferenceTranslationDeriver.php
@@ -153,7 +153,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
             'plugin' => 'sub_process',
             'source' => $field_name,
             'process' => [
-              'target_id' => [
+              'translation_target_id' => [
                 [
                   'plugin' => 'migration_lookup',
                   'source' => 'target_id',
@@ -162,13 +162,22 @@ public function getDerivativeDefinitions($base_plugin_definition) {
                 ],
                 [
                   'plugin' => 'skip_on_empty',
-                  'method' => 'row',
+                  'method' => 'process',
                 ],
                 [
                   'plugin' => 'extract',
                   'index' => [0],
                 ],
               ],
+              'target_id' => [
+                [
+                  'plugin' => 'null_coalesce',
+                  'source' => [
+                    '@translation_target_id',
+                    'target_id',
+                  ],
+                ],
+              ],
             ],
           ];
 
diff --git a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php
index 54cce94105..428bfd2b42 100644
--- a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php
+++ b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php
@@ -22,7 +22,8 @@
  *
  * For a full list, refer to the methods of this class.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  */
diff --git a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php
index 1b7679ea0d..aed5245d0a 100644
--- a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php
+++ b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php
@@ -7,7 +7,7 @@
 use Drupal\migrate\Plugin\MigrationInterface;
 
 /**
- * Drupal variable source from database.
+ * Drupal 6/7 variable source from database.
  *
  * This source class fetches variables from the source Drupal database.
  * Depending on the configuration, this returns zero or a single row and as such
@@ -70,7 +70,8 @@
  *     - book_allowed_types
  * @endcode
  *
- * For additional configuration keys, refer to the parent classes:
+ * For additional configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php
index 27d820fb9e..98f22edbda 100644
--- a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php
+++ b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php
@@ -5,7 +5,7 @@
 use Drupal\migrate\Row;
 
 /**
- * Multiple variables source from database.
+ * Drupal 6/7 multiple variables source from database.
  *
  * Unlike the variable source plugin, this one returns one row per
  * variable.
@@ -27,7 +27,8 @@
  * In this example the specified variables are retrieved from the source
  * database one row per variable.
  *
- * For additional configuration keys, refer to the parent classes:
+ * For additional configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php
index eb897ee385..c41bc5d2e2 100644
--- a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php
+++ b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php
@@ -9,7 +9,7 @@
 use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
 
 /**
- * Drupal i18n_variable source from database.
+ * Drupal 6 i18n_variable source from database.
  *
  * Available configuration keys:
  * - variables: (required) The list of variable translations to retrieve from
@@ -25,7 +25,8 @@
  * In this example the translations for site_offline_message variable are
  * retrieved from the source database.
  *
- * For additional configuration keys, refer to the parent classes:
+ * For additional configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
index dbb5ed67d6..176749480b 100644
--- a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
+++ b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
@@ -12,7 +12,8 @@
  * @see \Drupal\node\Plugin\migrate\source\d7\Node
  * @see \Drupal\user\Plugin\migrate\source\d7\User
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  */
@@ -21,6 +22,12 @@ abstract class FieldableEntity extends DrupalSqlBase {
   /**
    * Returns all non-deleted field instances attached to a specific entity type.
    *
+   * Typically, getFields() is used in the prepareRow method of a source plugin
+   * to get a list of all the field instances of the entity. A source plugin can
+   * then loop through the list of fields to do any other preparation before
+   * processing the row. Typically, a source plugin will use getFieldValues()
+   * to get the values of each field.
+   *
    * @param string $entity_type
    *   The entity type ID.
    * @param string|null $bundle
@@ -47,6 +54,9 @@ protected function getFields($entity_type, $bundle = NULL) {
   /**
    * Retrieves field values for a single field of a single entity.
    *
+   * Typically, getFieldValues() is used in the prepareRow method of a source
+   * plugin where the return values are placed on the row source.
+   *
    * @param string $entity_type
    *   The entity type.
    * @param string $field
diff --git a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php
index eb71189a44..32bdfb2eb9 100644
--- a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php
+++ b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php
@@ -8,7 +8,7 @@
 use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
 
 /**
- * Gets Drupal variable_store source from database.
+ * Drupal 7 variable_store source from database.
  *
  * Available configuration keys:
  * - variables: (required) The list of variable translations to retrieve from
@@ -25,7 +25,8 @@
  * In this example the translations for site_name and site_slogan variables are
  * retrieved from the source database.
  *
- * For additional configuration keys, refer to the parent classes:
+ * For additional configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php
index 5aba90f2ac..16b545c0fc 100644
--- a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php
+++ b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php
@@ -42,7 +42,8 @@
  * As a result, French versions of specified configuration objects are retrieved
  * from the source database.
  *
- * For additional configuration keys, refer to the parent classes:
+ * For additional configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/migrate_drupal/tests/fixtures/drupal7.php b/web/core/modules/migrate_drupal/tests/fixtures/drupal7.php
index 2412555aaf..0662f876dc 100644
--- a/web/core/modules/migrate_drupal/tests/fixtures/drupal7.php
+++ b/web/core/modules/migrate_drupal/tests/fixtures/drupal7.php
@@ -4,7 +4,7 @@
  * @file
  * A database agnostic dump for testing purposes.
  *
- * This file was generated by the Drupal 8.0 db-tools.php script.
+ * This file was generated by the Drupal 9.3.0-dev db-tools.php script.
  */
 
 use Drupal\Core\Database\Database;
@@ -4312,7 +4312,7 @@
   'storage_module' => 'field_sql_storage',
   'storage_active' => '1',
   'locked' => '0',
-  'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:11:"target_type";s:4:"node";s:7:"handler";s:4:"base";s:16:"handler_settings";a:2:{s:14:"target_bundles";a:1:{s:7:"article";s:7:"article";}s:4:"sort";a:1:{s:4:"type";s:4:"none";}}}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:28:"field_data_field_reference_2";a:1:{s:9:"target_id";s:27:"field_reference_2_target_id";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:32:"field_revision_field_reference_2";a:1:{s:9:"target_id";s:27:"field_reference_2_target_id";}}}}}s:12:"foreign keys";a:1:{s:4:"node";a:2:{s:5:"table";s:4:"node";s:7:"columns";a:1:{s:9:"target_id";s:3:"nid";}}}s:7:"indexes";a:1:{s:9:"target_id";a:1:{i:0;s:9:"target_id";}}s:2:"id";s:2:"39";}',
+  'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:11:"target_type";s:4:"node";s:7:"handler";s:4:"base";s:16:"handler_settings";a:2:{s:14:"target_bundles";a:2:{s:7:"article";s:7:"article";s:5:"forum";s:5:"forum";}s:4:"sort";a:1:{s:4:"type";s:4:"none";}}}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:28:"field_data_field_reference_2";a:1:{s:9:"target_id";s:27:"field_reference_2_target_id";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:32:"field_revision_field_reference_2";a:1:{s:9:"target_id";s:27:"field_reference_2_target_id";}}}}}s:12:"foreign keys";a:1:{s:4:"node";a:2:{s:5:"table";s:4:"node";s:7:"columns";a:1:{s:9:"target_id";s:3:"nid";}}}s:7:"indexes";a:1:{s:9:"target_id";a:1:{i:0;s:9:"target_id";}}s:2:"id";s:2:"39";}',
   'cardinality' => '1',
   'translatable' => '0',
   'deleted' => '0',
@@ -8181,7 +8181,7 @@
   'bundle' => 'article',
   'deleted' => '0',
   'entity_id' => '2',
-  'revision_id' => '2',
+  'revision_id' => '11',
   'language' => 'und',
   'delta' => '0',
   'field_node_reference_nid' => '5',
@@ -8768,7 +8768,7 @@
   'revision_id' => '11',
   'language' => 'und',
   'delta' => '0',
-  'field_reference_2_target_id' => '5',
+  'field_reference_2_target_id' => '6',
 ))
 ->values(array(
   'entity_type' => 'node',
@@ -14731,6 +14731,16 @@
   'delta' => '0',
   'field_node_reference_nid' => '5',
 ))
+->values(array(
+  'entity_type' => 'node',
+  'bundle' => 'article',
+  'deleted' => '0',
+  'entity_id' => '2',
+  'revision_id' => '11',
+  'language' => 'und',
+  'delta' => '0',
+  'field_node_reference_nid' => '5',
+))
 ->execute();
 $connection->schema()->createTable('field_revision_field_phone', array(
   'fields' => array(
@@ -15378,7 +15388,7 @@
   'revision_id' => '11',
   'language' => 'und',
   'delta' => '0',
-  'field_reference_2_target_id' => '5',
+  'field_reference_2_target_id' => '6',
 ))
 ->values(array(
   'entity_type' => 'node',
@@ -59872,6 +59882,10 @@
   'name' => 'language_content_type_article',
   'value' => 's:1:"2";',
 ))
+->values(array(
+  'name' => 'language_content_type_a_thirty_two_character_type_name',
+  'value' => 's:1:"1";',
+))
 ->values(array(
   'name' => 'language_content_type_blog',
   'value' => 's:1:"2";',
diff --git a/web/core/modules/migrate_drupal/tests/src/Kernel/d7/FollowUpMigrationsTest.php b/web/core/modules/migrate_drupal/tests/src/Kernel/d7/FollowUpMigrationsTest.php
index 94ef1d68f7..b67b3f3358 100644
--- a/web/core/modules/migrate_drupal/tests/src/Kernel/d7/FollowUpMigrationsTest.php
+++ b/web/core/modules/migrate_drupal/tests/src/Kernel/d7/FollowUpMigrationsTest.php
@@ -51,8 +51,6 @@ protected function setUp(): void {
       'language',
       'd7_language_content_settings',
       'd7_taxonomy_vocabulary',
-      'd7_node',
-      'd7_node_translation',
     ]);
   }
 
@@ -70,12 +68,16 @@ protected function getFileMigrationInfo() {
 
   /**
    * Tests entity reference translations.
+   *
+   * @dataProvider providerTestEntityReferenceTranslations
    */
-  public function testEntityReferenceTranslations() {
+  public function testEntityReferenceTranslations($node_migrations) {
+    $this->executeMigrations($node_migrations);
+
     // Test the entity reference field before the follow-up migrations.
     $node = Node::load(2);
     $this->assertSame('5', $node->get('field_reference')->target_id);
-    $this->assertSame('5', $node->get('field_reference_2')->target_id);
+    $this->assertSame('6', $node->get('field_reference_2')->target_id);
     $translation = $node->getTranslation('is');
     $this->assertSame('4', $translation->get('field_reference')->target_id);
     $this->assertSame('4', $translation->get('field_reference_2')->target_id);
@@ -99,7 +101,7 @@ public function testEntityReferenceTranslations() {
     // Test the entity reference field after the follow-up migrations.
     $node = Node::load(2);
     $this->assertSame('4', $node->get('field_reference')->target_id);
-    $this->assertSame('4', $node->get('field_reference_2')->target_id);
+    $this->assertSame('6', $node->get('field_reference_2')->target_id);
     $translation = $node->getTranslation('is');
     $this->assertSame('4', $translation->get('field_reference')->target_id);
     $this->assertSame('4', $translation->get('field_reference_2')->target_id);
@@ -115,4 +117,18 @@ public function testEntityReferenceTranslations() {
     $this->assertSame('2', $user->get('field_reference')->target_id);
   }
 
+  /**
+   * Data provider for testEntityReferenceTranslations().
+   */
+  public function providerTestEntityReferenceTranslations() {
+    return [
+      [
+        ['d7_node', 'd7_node_translation'],
+      ],
+      [
+        ['d7_node_complete'],
+      ],
+    ];
+  }
+
 }
diff --git a/web/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php b/web/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php
index 2652e6db23..b29adb07a8 100644
--- a/web/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php
+++ b/web/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php
@@ -89,7 +89,7 @@ protected function getEntityCounts() {
       'file' => 3,
       'filter_format' => 7,
       'image_style' => 7,
-      'language_content_settings' => 22,
+      'language_content_settings' => 24,
       'node' => 7,
       'node_type' => 8,
       'rdf_mapping' => 8,
@@ -237,7 +237,7 @@ public function testUpgradeAndIncremental() {
   protected function assertFollowUpMigrationResults() {
     $node = Node::load(2);
     $this->assertSame('4', $node->get('field_reference')->target_id);
-    $this->assertSame('4', $node->get('field_reference_2')->target_id);
+    $this->assertSame('6', $node->get('field_reference_2')->target_id);
     $translation = $node->getTranslation('is');
     $this->assertSame('4', $translation->get('field_reference')->target_id);
     $this->assertSame('4', $translation->get('field_reference_2')->target_id);
diff --git a/web/core/modules/node/src/Plugin/migrate/source/d6/Node.php b/web/core/modules/node/src/Plugin/migrate/source/d6/Node.php
index 4fe426d890..e120ef9cc6 100644
--- a/web/core/modules/node/src/Plugin/migrate/source/d6/Node.php
+++ b/web/core/modules/node/src/Plugin/migrate/source/d6/Node.php
@@ -297,6 +297,10 @@ protected function getFieldData(array $field, Row $node) {
 
     if (isset($query)) {
       $columns = array_keys($field['db_columns']);
+      // If there are no columns then there are no values to return.
+      if (empty($columns)) {
+        return [];
+      }
 
       // Add every column in the field's schema.
       foreach ($columns as $column) {
diff --git a/web/core/modules/node/src/Plugin/migrate/source/d7/NodeEntityTranslation.php b/web/core/modules/node/src/Plugin/migrate/source/d7/NodeEntityTranslation.php
index ca61c6c563..d9ea90c373 100644
--- a/web/core/modules/node/src/Plugin/migrate/source/d7/NodeEntityTranslation.php
+++ b/web/core/modules/node/src/Plugin/migrate/source/d7/NodeEntityTranslation.php
@@ -6,7 +6,7 @@
 use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
 
 /**
- * Provides Drupal 7 node entity translations source plugin.
+ * Drupal 7 node entity translations source from database.
  *
  * Available configuration keys:
  * - node_type: The node_types to get from the source - can be a string or
diff --git a/web/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php b/web/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php
index 1d366d3414..2c3acd8cf2 100644
--- a/web/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php
+++ b/web/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\migrate\MigrateExecutable;
 use Drupal\migrate_drupal\NodeMigrateType;
+use Drupal\node\Entity\Node;
 use Drupal\node\NodeInterface;
 use Drupal\Tests\file\Kernel\Migrate\d7\FileMigrationSetupTrait;
 use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
@@ -136,6 +137,15 @@ public function testNodeCompleteMigration() {
       $this->assertSame('Bob', $revision->field_user_reference[0]->entity->getAccountName());
     }
 
+    // Test the translated node reference in the latest revision of node 2. This
+    // references the legacy site node 4 instead of node 2. The reference is
+    // fixed by the followup migrations, 'd7_entity_reference_translation' and
+    // tested in \Drupal\Tests\migrate_drupal\Kernel\d7\FollowUpMigrationsTest.
+    $node = Node::load(2);
+    $this->assertSame('6', $node->get('field_reference_2')->target_id);
+    $translation = $node->getTranslation('is');
+    $this->assertSame('4', $translation->get('field_reference_2')->target_id);
+
     // Test the order in multi-value fields.
     $revision = $this->nodeStorage->loadRevision(1);
     $this->assertSame([
diff --git a/web/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTest.php b/web/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTest.php
index 9a78d61da3..c6f72ee3ec 100644
--- a/web/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTest.php
+++ b/web/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTest.php
@@ -38,6 +38,18 @@ public function providerSource() {
         'active' => '1',
         'locked' => '0',
       ],
+      [
+        'field_name' => 'field_test_empty_db_columns',
+        'type' => 'empty_db_columns',
+        'global_settings' => 'a:0:{}',
+        'required' => '0',
+        'multiple' => '0',
+        'db_storage' => '1',
+        'module' => 'empty_db_columns',
+        'db_columns' => 'a:0:{}',
+        'active' => '1',
+        'locked' => '0',
+      ],
     ];
     $tests[0]['source_data']['content_node_field_instance'] = [
       [
@@ -52,6 +64,18 @@ public function providerSource() {
         'widget_module' => 'number',
         'widget_active' => '1',
       ],
+      [
+        'field_name' => 'field_test_empty_db_columns',
+        'type_name' => 'story',
+        'weight' => '33',
+        'label' => 'Empty db_columns Field',
+        'widget_type' => 'empty_db_columns',
+        'widget_settings' => 'a:0:{}',
+        'display_settings' => 'a:0:{}',
+        'description' => 'An example field with empty db_columns.',
+        'widget_module' => 'empty_db_columns',
+        'widget_active' => '1',
+      ],
     ];
     $tests[0]['source_data']['content_type_story'] = [
       [
diff --git a/web/core/modules/system/tests/modules/render_attached_test/src/Controller/RenderAttachedTestController.php b/web/core/modules/system/tests/modules/render_attached_test/src/Controller/RenderAttachedTestController.php
index 1bbf8ebe65..955e3daa22 100644
--- a/web/core/modules/system/tests/modules/render_attached_test/src/Controller/RenderAttachedTestController.php
+++ b/web/core/modules/system/tests/modules/render_attached_test/src/Controller/RenderAttachedTestController.php
@@ -80,6 +80,7 @@ public function htmlHeaderLink() {
     $render['#attached']['html_head_link'][] = [['href' => '/foo?bar=<baz>&baz=false', 'rel' => 'alternate'], TRUE];
     $render['#attached']['html_head_link'][] = [['href' => '/not-added-to-http-headers', 'rel' => 'alternate'], FALSE];
     $render['#attached']['html_head_link'][] = [['href' => '/foo/bar', 'hreflang' => 'nl', 'rel' => 'alternate'], TRUE];
+    $render['#attached']['html_head_link'][] = [['href' => '/foo/bar', 'hreflang' => 'de', 'rel' => 'alternate'], TRUE];
     return $render;
   }
 
diff --git a/web/core/modules/system/tests/src/Functional/Render/HtmlResponseAttachmentsTest.php b/web/core/modules/system/tests/src/Functional/Render/HtmlResponseAttachmentsTest.php
index f07424b455..9921fe04a9 100644
--- a/web/core/modules/system/tests/src/Functional/Render/HtmlResponseAttachmentsTest.php
+++ b/web/core/modules/system/tests/src/Functional/Render/HtmlResponseAttachmentsTest.php
@@ -65,8 +65,13 @@ public function testAttachments() {
     $expected_link_headers = [
       '</foo?bar=&lt;baz&gt;&amp;baz=false>; rel="alternate"',
       '</foo/bar>; hreflang="nl"; rel="alternate"',
+      '</foo/bar>; hreflang="de"; rel="alternate"',
     ];
     $this->assertEquals($expected_link_headers, $this->getSession()->getResponseHeaders()['Link']);
+
+    // Check that duplicate alternate URLs with different hreflangs are allowed.
+    $test_link = $this->xpath('//head/link[@rel="alternate"][@href="/foo/bar"]');
+    $this->assertEquals(2, count($test_link), 'Duplicate alternate URLs are allowed.');
   }
 
   /**
diff --git a/web/core/modules/taxonomy/src/Plugin/migrate/source/d6/TermLocalizedTranslation.php b/web/core/modules/taxonomy/src/Plugin/migrate/source/d6/TermLocalizedTranslation.php
index 6fd6a1ce4e..57397c62bc 100644
--- a/web/core/modules/taxonomy/src/Plugin/migrate/source/d6/TermLocalizedTranslation.php
+++ b/web/core/modules/taxonomy/src/Plugin/migrate/source/d6/TermLocalizedTranslation.php
@@ -5,7 +5,7 @@
 use Drupal\migrate\Row;
 
 /**
- * Drupal 6 i18n taxonomy terms from source database.
+ * Drupal 6 i18n taxonomy terms source from database.
  *
  * For available configuration keys, refer to the parent classes.
  *
diff --git a/web/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyTranslation.php b/web/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyTranslation.php
index 294da00e31..617daf7e47 100644
--- a/web/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyTranslation.php
+++ b/web/core/modules/taxonomy/src/Plugin/migrate/source/d6/VocabularyTranslation.php
@@ -6,7 +6,7 @@
 use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
 
 /**
- * Drupal 6 i18n vocabulary translations from source database.
+ * Drupal 6 i18n vocabulary translations source from database.
  *
  * For available configuration keys, refer to the parent classes.
  *
diff --git a/web/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php b/web/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php
index a06ec0d138..43d5cfaaf4 100644
--- a/web/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php
+++ b/web/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php
@@ -6,7 +6,7 @@
 use Drupal\migrate\Row;
 
 /**
- * Drupal 7 i18n taxonomy terms from source database.
+ * Drupal 7 i18n taxonomy terms source from database.
  *
  * For available configuration keys, refer to the parent classes.
  *
diff --git a/web/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermTranslation.php b/web/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermTranslation.php
index 50fb58161a..c58dba0731 100644
--- a/web/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermTranslation.php
+++ b/web/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermTranslation.php
@@ -5,7 +5,7 @@
 use Drupal\migrate\Row;
 
 /**
- * Drupal 7 i18n taxonomy terms from source database.
+ * Drupal 7 i18n taxonomy terms source from database.
  *
  * For available configuration keys, refer to the parent classes.
  *
@@ -28,9 +28,17 @@ public function query() {
     if ($this->database->schema()->fieldExists('taxonomy_term_data', 'language')) {
       $query->addField('td', 'language', 'td_language');
     }
+    // Get data when the i18n_mode column exists and it is not the Drupal 7
+    // value I18N_MODE_NONE or I18N_MODE_LOCALIZE. Otherwise, return no data.
+    // @see https://git.drupalcode.org/project/i18n/-/blob/7.x-1.x/i18n.module#L26
     if ($this->database->schema()->fieldExists('taxonomy_vocabulary', 'i18n_mode')) {
       $query->addField('tv', 'i18n_mode');
+      $query->condition('tv.i18n_mode', ['0', '1'], 'NOT IN');
     }
+    else {
+      $query->alwaysFalse();
+    }
+
     return $query;
   }
 
diff --git a/web/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTranslationTest.php b/web/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTranslationTest.php
new file mode 100644
index 0000000000..a218b3ec3e
--- /dev/null
+++ b/web/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTranslationTest.php
@@ -0,0 +1,361 @@
+<?php
+
+namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
+
+/**
+ * Tests D7 i18n term localized source plugin.
+ *
+ * @covers \Drupal\taxonomy\Plugin\migrate\source\d7\TermTranslation
+ * @group taxonomy
+ */
+class TermTranslationTest extends TermTest {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['taxonomy', 'migrate_drupal'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function providerSource() {
+    $tests = [];
+
+    // Ignore i18_modes 0 and 1, get i18n_mode 4.
+    $tests[0]['source_data']['taxonomy_term_data'] = [
+      [
+        'tid' => 1,
+        'vid' => 5,
+        'name' => 'fr - name 1',
+        'description' => 'desc 1',
+        'weight' => 0,
+        'is_container' => FALSE,
+        'language' => 'fr',
+        'i18n_tsid' => '1',
+      ],
+      [
+        'tid' => 2,
+        'vid' => 5,
+        'name' => 'name 2',
+        'description' => 'desc 2',
+        'weight' => 0,
+        'is_container' => TRUE,
+        'language' => 'en',
+        'i18n_tsid' => '1',
+      ],
+      [
+        'tid' => 3,
+        'vid' => 6,
+        'name' => 'name 3',
+        'description' => 'desc 3',
+        'weight' => 0,
+        'is_container' => FALSE,
+        'language' => '',
+        'i18n_tsid' => '',
+      ],
+      [
+        'tid' => 4,
+        'vid' => 5,
+        'name' => 'is - name 4',
+        'description' => 'desc 4',
+        'weight' => 1,
+        'is_container' => FALSE,
+        'language' => 'is',
+        'i18n_tsid' => '1',
+      ],
+      [
+        'tid' => 5,
+        'vid' => 6,
+        'name' => 'name 5',
+        'description' => 'desc 5',
+        'weight' => 1,
+        'is_container' => FALSE,
+        'language' => '',
+        'i18n_tsid' => '',
+      ],
+      [
+        'tid' => 6,
+        'vid' => 6,
+        'name' => 'name 6',
+        'description' => 'desc 6',
+        'weight' => 0,
+        'is_container' => TRUE,
+        'language' => '',
+        'i18n_tsid' => '',
+      ],
+      [
+        'tid' => 7,
+        'vid' => 7,
+        'name' => 'is - captains',
+        'description' => 'desc 7',
+        'weight' => 0,
+        'is_container' => TRUE,
+        'language' => 'is',
+        'i18n_tsid' => '',
+      ],
+    ];
+    $tests[0]['source_data']['taxonomy_term_hierarchy'] = [
+      [
+        'tid' => 1,
+        'parent' => 0,
+      ],
+      [
+        'tid' => 2,
+        'parent' => 0,
+      ],
+      [
+        'tid' => 3,
+        'parent' => 0,
+      ],
+      [
+        'tid' => 4,
+        'parent' => 1,
+      ],
+      [
+        'tid' => 5,
+        'parent' => 2,
+      ],
+      [
+        'tid' => 6,
+        'parent' => 3,
+      ],
+      [
+        'tid' => 6,
+        'parent' => 2,
+      ],
+      [
+        'tid' => 7,
+        'parent' => 0,
+      ],
+    ];
+
+    $tests[0]['source_data']['taxonomy_vocabulary'] = [
+      [
+        'vid' => 3,
+        'machine_name' => 'foo',
+        'language' => 'und',
+        'i18n_mode' => '0',
+      ],
+      [
+        'vid' => 5,
+        'machine_name' => 'tags',
+        'language' => 'und',
+        'i18n_mode' => '4',
+      ],
+      [
+        'vid' => 6,
+        'machine_name' => 'categories',
+        'language' => 'is',
+        'i18n_mode' => '1',
+      ],
+    ];
+
+    $tests[0]['source_data']['field_config'] = [
+      [
+        'id' => '3',
+        'translatable' => '0',
+      ],
+      [
+        'id' => '4',
+        'translatable' => '1',
+      ],
+      [
+        'id' => '5',
+        'translatable' => '1',
+      ],
+    ];
+    $tests[0]['source_data']['field_config_instance'] = [
+      [
+        'id' => '2',
+        'field_id' => 3,
+        'field_name' => 'field_term_field',
+        'entity_type' => 'taxonomy_term',
+        'bundle' => 'tags',
+        'data' => 'a:0:{}',
+        'deleted' => 0,
+      ],
+      [
+        'id' => '3',
+        'field_id' => 3,
+        'field_name' => 'field_term_field',
+        'entity_type' => 'taxonomy_term',
+        'bundle' => 'categories',
+        'data' => 'a:0:{}',
+        'deleted' => 0,
+      ],
+      [
+        'id' => '4',
+        'field_id' => '4',
+        'field_name' => 'name_field',
+        'entity_type' => 'taxonomy_term',
+        'bundle' => 'tags',
+        'data' => 'a:0:{}',
+        'deleted' => '0',
+      ],
+      [
+        'id' => '5',
+        'field_id' => '5',
+        'field_name' => 'description_field',
+        'entity_type' => 'taxonomy_term',
+        'bundle' => 'tags',
+        'data' => 'a:0:{}',
+        'deleted' => '0',
+      ],
+    ];
+    $tests[0]['source_data']['field_data_field_term_field'] = [
+      [
+        'entity_type' => 'taxonomy_term',
+        'bundle' => 'tags',
+        'deleted' => 0,
+        'entity_id' => 1,
+        'delta' => 0,
+      ],
+      [
+        'entity_type' => 'taxonomy_term',
+        'bundle' => 'categories',
+        'deleted' => 0,
+        'entity_id' => 1,
+        'delta' => 0,
+      ],
+    ];
+    $tests[0]['source_data']['field_data_name_field'] = [
+      [
+        'entity_type' => 'taxonomy_term',
+        'bundle' => 'tags',
+        'deleted' => '0',
+        'entity_id' => '1',
+        'revision_id' => '1',
+        'language' => 'und',
+        'delta' => '0',
+        'name_field_value' => 'fr - name 1',
+        'name_field_format' => NULL,
+      ],
+      [
+        'entity_type' => 'taxonomy_term',
+        'bundle' => 'tags',
+        'deleted' => '0',
+        'entity_id' => '4',
+        'revision_id' => '4',
+        'language' => 'und',
+        'delta' => '0',
+        'name_field_value' => 'is - name 4',
+        'name_field_format' => NULL,
+      ],
+    ];
+    $tests[0]['source_data']['field_data_description_field'] = [
+      [
+        'entity_type' => 'taxonomy_term',
+        'bundle' => 'tags',
+        'deleted' => '0',
+        'entity_id' => '1',
+        'revision_id' => '1',
+        'language' => 'und',
+        'delta' => '0',
+        'description_field_value' => 'desc 1',
+        'description_field_format' => NULL,
+      ],
+      [
+        'entity_type' => 'taxonomy_term',
+        'bundle' => 'tags',
+        'deleted' => '0',
+        'entity_id' => '4',
+        'revision_id' => '4',
+        'language' => 'und',
+        'delta' => '0',
+        'description_field_value' => 'desc 4',
+        'description_field_format' => NULL,
+      ],
+    ];
+
+    // The expected results.
+    $tests[0]['expected_data'] = [
+      [
+        'tid' => 1,
+        'vid' => 5,
+        'name' => 'fr - name 1',
+        'description' => 'desc 1',
+        'weight' => 0,
+        'is_container' => '',
+        'language' => 'fr',
+        'i18n_tsid' => '1',
+        'machine_name' => 'tags',
+        'i18n_mode' => '4',
+        'td_language' => 'fr',
+        'tv_i18n_mode' => '4',
+      ],
+      [
+        'tid' => 2,
+        'vid' => 5,
+        'name' => 'name 2',
+        'description' => 'desc 2',
+        'weight' => 0,
+        'is_container' => '',
+        'language' => 'en',
+        'i18n_tsid' => '1',
+        'machine_name' => 'tags',
+        'i18n_mode' => '4',
+        'td_language' => 'en',
+        'tv_i18n_mode' => '4',
+      ],
+      [
+        'tid' => 4,
+        'vid' => 5,
+        'name' => 'is - name 4',
+        'description' => 'desc 4',
+        'weight' => 1,
+        'is_container' => '',
+        'language' => 'is',
+        'i18n_tsid' => '1',
+        'machine_name' => 'tags',
+        'i18n_mode' => '4',
+        'td_language' => 'is',
+        'tv_i18n_mode' => '4',
+      ],
+    ];
+
+    $tests[0]['expected_count'] = NULL;
+    // Get translations for the tags bundle.
+    $tests[0]['configuration']['bundle'] = ['tags'];
+
+    // Ignore i18_modes 0. get i18n_mode 2 and 4.
+    $tests[1] = $tests[0];
+    // Change a vocabulary to using fixed translation.
+    $tests[1]['source_data']['taxonomy_vocabulary'][2] = [
+      'vid' => 7,
+      'machine_name' => 'categories',
+      'language' => 'is',
+      'i18n_mode' => '2',
+    ];
+
+    // Add the term with fixed translation.
+    $tests[1]['expected_data'][] = [
+      'tid' => 7,
+      'vid' => 7,
+      'name' => 'is - captains',
+      'description' => 'desc 7',
+      'weight' => 0,
+      'is_container' => '',
+      'language' => 'is',
+      'i18n_tsid' => '',
+      'machine_name' => 'categories',
+      'i18n_mode' => '2',
+      'td_language' => 'is',
+      'tv_i18n_mode' => '2',
+    ];
+
+    $tests[1]['expected_count'] = NULL;
+    $tests[1]['configuration']['bundle'] = NULL;
+
+    // No data returned when there is no i18n_mode column.
+    $tests[2] = [];
+    $tests[2]['source_data'] = $tests[0]['source_data'];
+    foreach ($tests[2]['source_data']['taxonomy_vocabulary'] as &$table) {
+      unset($table['i18n_mode']);
+    }
+    $tests[2]['expected_data'] = [0];
+    $tests[2]['expected_count'] = 0;
+
+    return $tests;
+  }
+
+}
diff --git a/web/core/modules/text/src/Plugin/migrate/field/d7/TextField.php b/web/core/modules/text/src/Plugin/migrate/field/d7/TextField.php
index 2d2396ed87..21bcd74d87 100644
--- a/web/core/modules/text/src/Plugin/migrate/field/d7/TextField.php
+++ b/web/core/modules/text/src/Plugin/migrate/field/d7/TextField.php
@@ -34,7 +34,7 @@ public function getFieldFormatterType(Row $row) {
         break;
 
       case 'string_long':
-        $formatter_type = str_replace('text_default', 'basic_string', $formatter_type);
+        $formatter_type = str_replace(['text_default', 'text_plain'], 'basic_string', $formatter_type);
         break;
     }
 
diff --git a/web/core/modules/text/tests/src/Unit/Plugin/migrate/field/d7/TextFieldTest.php b/web/core/modules/text/tests/src/Unit/Plugin/migrate/field/d7/TextFieldTest.php
index 6923a2ec5e..71adb4e59d 100644
--- a/web/core/modules/text/tests/src/Unit/Plugin/migrate/field/d7/TextFieldTest.php
+++ b/web/core/modules/text/tests/src/Unit/Plugin/migrate/field/d7/TextFieldTest.php
@@ -25,12 +25,13 @@ protected function setUp(): void {
   }
 
   /**
-   * Data provider for getFieldFormatterType().
+   * Data provider for testGetFieldFormatterType().
    */
   public function getFieldFormatterTypeProvider() {
     return [
       ['text', 'text_plain', 'string'],
       ['text_long', 'text_default', 'basic_string'],
+      ['text_long', 'text_plain', 'basic_string'],
     ];
   }
 
@@ -39,7 +40,7 @@ public function getFieldFormatterTypeProvider() {
    * @covers ::getFieldType
    * @dataProvider getFieldFormatterTypeProvider
    */
-  public function testGetFieldType($type, $formatter_type, $expected) {
+  public function testGetFieldFormatterType($type, $formatter_type, $expected) {
     $row = new Row();
     $row->setSourceProperty('type', $type);
     $row->setSourceProperty('formatter/type', $formatter_type);
diff --git a/web/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module b/web/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module
index 1f919a1e0b..cbc014054d 100644
--- a/web/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module
+++ b/web/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module
@@ -48,3 +48,14 @@ function toolbar_test_toolbar() {
 
   return $items;
 }
+
+/**
+ * Implements hook_preprocess_HOOK().
+ */
+function toolbar_test_preprocess_menu(&$variables) {
+  // All the standard hook_theme variables should be populated when the
+  // Toolbar module is rendering a menu.
+  foreach (['menu_name', 'items', 'attributes'] as $variable) {
+    $variables[$variable];
+  }
+}
diff --git a/web/core/modules/toolbar/toolbar.module b/web/core/modules/toolbar/toolbar.module
index 483a30b76a..5451dcb751 100644
--- a/web/core/modules/toolbar/toolbar.module
+++ b/web/core/modules/toolbar/toolbar.module
@@ -41,7 +41,7 @@ function toolbar_theme($existing, $type, $theme, $path) {
   ];
   $items['menu__toolbar'] = [
     'base hook' => 'menu',
-    'variables' => ['items' => [], 'attributes' => []],
+    'variables' => ['menu_name' => NULL, 'items' => [], 'attributes' => []],
   ];
 
   return $items;
diff --git a/web/core/modules/user/src/Plugin/migrate/source/ProfileField.php b/web/core/modules/user/src/Plugin/migrate/source/ProfileField.php
index ce40c399af..db87a26fc1 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/ProfileField.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/ProfileField.php
@@ -7,9 +7,10 @@
 use Drupal\migrate\Row;
 
 /**
- * Profile field source from database.
+ * Drupal 6/7 profile field source from database.
+ *
+ * For available configuration keys, refer to the parent classes.
  *
- * For available configuration keys, refer to the parent classes:
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/user/src/Plugin/migrate/source/UserPictureInstance.php b/web/core/modules/user/src/Plugin/migrate/source/UserPictureInstance.php
index 591cc441e5..cd4f0b00ac 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/UserPictureInstance.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/UserPictureInstance.php
@@ -6,9 +6,10 @@
 use Drupal\migrate\Plugin\migrate\source\DummyQueryTrait;
 
 /**
- * User picture field instance source from database.
+ * Drupal 6/7 user picture field instance source from database.
+ *
+ * For available configuration keys, refer to the parent classes.
  *
- * For available configuration keys, refer to the parent classes:
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldOptionTranslation.php b/web/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldOptionTranslation.php
index b74fb5e260..41c3848765 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldOptionTranslation.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldOptionTranslation.php
@@ -7,7 +7,8 @@
 /**
  * Drupal 6 i18n profile field option labels source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php b/web/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php
index f73aa059d4..8445be5cc3 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php
@@ -8,7 +8,8 @@
 /**
  * Drupal 6 profile fields values source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/user/src/Plugin/migrate/source/d6/Role.php b/web/core/modules/user/src/Plugin/migrate/source/d6/Role.php
index 6be90ea8e7..26cdc54082 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/d6/Role.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/d6/Role.php
@@ -8,7 +8,8 @@
 /**
  * Drupal 6 role source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/user/src/Plugin/migrate/source/d6/User.php b/web/core/modules/user/src/Plugin/migrate/source/d6/User.php
index 99cdcdebdf..6c2349f210 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/d6/User.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/d6/User.php
@@ -8,7 +8,8 @@
 /**
  * Drupal 6 user source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/user/src/Plugin/migrate/source/d6/UserPicture.php b/web/core/modules/user/src/Plugin/migrate/source/d6/UserPicture.php
index c32a77385d..cd52c7bd20 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/d6/UserPicture.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/d6/UserPicture.php
@@ -7,7 +7,8 @@
 /**
  * Drupal 6 user picture source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/user/src/Plugin/migrate/source/d6/UserPictureFile.php b/web/core/modules/user/src/Plugin/migrate/source/d6/UserPictureFile.php
index 60e38e8e4d..02ba6163f6 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/d6/UserPictureFile.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/d6/UserPictureFile.php
@@ -12,7 +12,8 @@
  * - site_path: (optional) The path to the site directory relative to Drupal
  *   root. Defaults to 'sites/default'.
  *
- * For additional configuration keys, refer to the parent classes:
+ * For additional configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/user/src/Plugin/migrate/source/d7/Role.php b/web/core/modules/user/src/Plugin/migrate/source/d7/Role.php
index 2939cd9549..d07842058a 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/d7/Role.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/d7/Role.php
@@ -8,7 +8,8 @@
 /**
  * Drupal 7 role source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/user/src/Plugin/migrate/source/d7/User.php b/web/core/modules/user/src/Plugin/migrate/source/d7/User.php
index e92ae14e5f..286857a6b6 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/d7/User.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/d7/User.php
@@ -8,7 +8,8 @@
 /**
  * Drupal 7 user source from database.
  *
- * For available configuration keys, refer to the parent classes:
+ * For available configuration keys, refer to the parent classes.
+ *
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/user/src/Plugin/migrate/source/d7/UserEntityTranslation.php b/web/core/modules/user/src/Plugin/migrate/source/d7/UserEntityTranslation.php
index 067f86b695..04c6cead82 100644
--- a/web/core/modules/user/src/Plugin/migrate/source/d7/UserEntityTranslation.php
+++ b/web/core/modules/user/src/Plugin/migrate/source/d7/UserEntityTranslation.php
@@ -6,9 +6,10 @@
 use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
 
 /**
- * Provides Drupal 7 user entity translations source plugin.
+ * Drupal 7 user entity translations source from database.
+ *
+ * For available configuration keys, refer to the parent classes.
  *
- * For available configuration keys, refer to the parent classes:
  * @see \Drupal\migrate\Plugin\migrate\source\SqlBase
  * @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
  *
diff --git a/web/core/modules/views/src/Plugin/views/query/Sql.php b/web/core/modules/views/src/Plugin/views/query/Sql.php
index c6200bc16a..53a9ac037c 100644
--- a/web/core/modules/views/src/Plugin/views/query/Sql.php
+++ b/web/core/modules/views/src/Plugin/views/query/Sql.php
@@ -326,8 +326,14 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    */
   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
     $element = ['#parents' => ['query', 'options', 'query_tags']];
-    $value = explode(',', NestedArray::getValue($form_state->getValues(), $element['#parents']));
-    $value = array_filter(array_map('trim', $value));
+    $value = NestedArray::getValue($form_state->getValues(), $element['#parents']);
+    // When toggling a display to override defaults or vice-versa the submit
+    // handler gets invoked twice, and we don't want to bash the values from the
+    // original call.
+    if (is_array($value)) {
+      return;
+    }
+    $value = array_filter(array_map('trim', explode(',', $value)));
     $form_state->setValueForElement($element, $value);
   }
 
diff --git a/web/core/modules/views/tests/src/Functional/Plugin/QueryOptionsTest.php b/web/core/modules/views/tests/src/Functional/Plugin/QueryOptionsTest.php
new file mode 100644
index 0000000000..cb027c06e1
--- /dev/null
+++ b/web/core/modules/views/tests/src/Functional/Plugin/QueryOptionsTest.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\Tests\views\Functional\Plugin;
+
+use Drupal\Tests\views\Functional\ViewTestBase;
+
+/**
+ * Tests setting the query options.
+ *
+ * @group views
+ */
+class QueryOptionsTest extends ViewTestBase {
+
+  /**
+   * Views used by this test.
+   *
+   * @var array
+   */
+  public static $testViews = ['test_view'];
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  protected static $modules = ['node', 'views_ui'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * Test that query overrides are stored.
+   */
+  public function testStoreQuerySettingsOverride() {
+    // Show the default display so the override selection is shown.
+    \Drupal::configFactory()->getEditable('views.settings')->set('ui.show.default_display', TRUE)->save();
+
+    $admin_user = $this->drupalCreateUser([
+      'administer views',
+      'administer site configuration',
+    ]);
+    $this->drupalLogin($admin_user);
+
+    $edit = [];
+    $this->drupalGet('admin/structure/views/view/test_view/edit');
+    $this->submitForm($edit, 'Add Page');
+
+    $this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/query');
+    $this->assertSession()->checkboxNotChecked('query[options][distinct]');
+    $edit = [
+      'override[dropdown]' => 'page_1',
+      'query[options][distinct]' => 1,
+    ];
+    $this->submitForm($edit, 'Apply');
+    $this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/query');
+    $this->assertSession()->checkboxChecked('query[options][distinct]');
+    $edit = [
+      'query[options][query_comment]' => 'comment',
+      'query[options][query_tags]' => 'query_tag, another_tag',
+    ];
+    $this->submitForm($edit, 'Apply');
+    $this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/query');
+    $this->assertSession()->checkboxChecked('query[options][distinct]');
+    $this->assertSession()->fieldValueEquals('query[options][query_comment]', 'comment');
+    $this->assertSession()->fieldValueEquals('query[options][query_tags]', 'query_tag, another_tag');
+  }
+
+}
diff --git a/web/core/modules/views/tests/src/Kernel/Entity/EntityViewsDataTest.php b/web/core/modules/views/tests/src/Kernel/Entity/EntityViewsDataTest.php
new file mode 100644
index 0000000000..6bab4cfdbd
--- /dev/null
+++ b/web/core/modules/views/tests/src/Kernel/Entity/EntityViewsDataTest.php
@@ -0,0 +1,821 @@
+<?php
+
+namespace Drupal\Tests\views\Kernel\Entity;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Config\Entity\ConfigEntityType;
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\ContentEntityType;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\views\EntityViewsData;
+
+/**
+ * Tests entity views data.
+ *
+ * @coversDefaultClass \Drupal\views\EntityViewsData
+ * @group views
+ */
+class EntityViewsDataTest extends KernelTestBase {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The base entity type definition, which some tests modify.
+   *
+   * Uses a custom class which allows changing entity keys.
+   *
+   * @var \Drupal\Tests\views\Kernel\Entity\TestEntityType
+   */
+  protected $baseEntityType;
+
+  /**
+   * The common base fields for test entity types.
+   *
+   * @var \Drupal\Core\Field\BaseFieldDefinition[]
+   */
+  protected $commonBaseFields;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  protected static $modules = [
+    'user',
+    'system',
+    'field',
+    'text',
+    'filter',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->entityTypeManager = $this->container->get('entity_type.manager');
+
+    // A common entity type definition. Tests may change this prior to passing
+    // it to setUpEntityType().
+    $this->baseEntityType = new TestEntityType([
+      // A normal entity type would have its class picked up during discovery,
+      // but as we're mocking this without an annotation we have to specify it.
+      'class' => ViewsTestEntity::class,
+      'base_table' => 'entity_test',
+      'id' => 'entity_test',
+      'label' => 'Entity test',
+      'entity_keys' => [
+        'uuid' => 'uuid',
+        'id' => 'id',
+        'langcode' => 'langcode',
+        'bundle' => 'type',
+        'revision' => 'revision_id',
+      ],
+      'handlers' => [
+        'views_data' => EntityViewsData::class,
+      ],
+      'provider' => 'entity_test',
+      'list_cache_contexts' => ['entity_test_list_cache_context'],
+    ]);
+
+    // Base fields for the test entity types.
+    $this->commonBaseFields['name'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Name'))
+      ->setDescription(t('The name of the test entity.'))
+      ->setTranslatable(TRUE)
+      ->setSetting('max_length', 32);
+
+    $this->commonBaseFields['created'] = BaseFieldDefinition::create('created')
+      ->setLabel(t('Authored on'))
+      ->setDescription(t('Time the entity was created'))
+      ->setTranslatable(TRUE);
+
+    $this->commonBaseFields['user_id'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('User ID'))
+      ->setDescription(t('The ID of the associated user.'))
+      ->setSetting('target_type', 'user')
+      ->setSetting('handler', 'default')
+      // Default EntityTest entities to have the root user as the owner, to
+      // simplify testing.
+      ->setDefaultValue([0 => ['target_id' => 1]])
+      ->setTranslatable(TRUE);
+
+    // Add a description field. This example comes from the taxonomy Term
+    // entity.
+    $this->commonBaseFields['description'] = BaseFieldDefinition::create('text_long')
+      ->setLabel('Description')
+      ->setDescription('A description of the term.')
+      ->setTranslatable(TRUE);
+
+    // Add a URL field; this example is from the Comment entity.
+    $this->commonBaseFields['homepage'] = BaseFieldDefinition::create('uri')
+      ->setLabel('Homepage')
+      ->setDescription("The comment author's home page address.")
+      ->setTranslatable(TRUE)
+      ->setSetting('max_length', 255);
+
+    // A base field with cardinality > 1
+    $this->commonBaseFields['string'] = BaseFieldDefinition::create('string')
+      ->setLabel('Strong')
+      ->setTranslatable(TRUE)
+      ->setCardinality(2);
+
+    // Set up the basic 'entity_test' entity type. This is used by several
+    // tests; others customize it and the base fields.
+    $this->setUpEntityType($this->baseEntityType, $this->commonBaseFields);
+  }
+
+  /**
+   * Mocks an entity type and its base fields.
+   *
+   * This works by:
+   * - inserting the entity type definition into the entity type manager's cache
+   * - setting the base fields on the ViewsTestEntity class as a static property
+   *   for its baseFieldsDefinitions() method to use.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $definition
+   *   An entity type definition to add to the entity type manager.
+   * @param \Drupal\Core\Field\BaseFieldDefinition[] $base_fields
+   *   An array of base field definitions
+   */
+  protected function setUpEntityType(EntityTypeInterface $definition, array $base_fields = []) {
+    // Replace the cache backend in the entity type manager so it returns
+    // our test entity type in addition to the existing ones.
+    $definitions = $this->entityTypeManager->getDefinitions();
+    $definitions[$definition->id()] = $definition;
+
+    $cache_backend = $this->prophesize(CacheBackendInterface::class);
+    $cache_data = new \StdClass();
+    $cache_data->data = $definitions;
+    $cache_backend->get('entity_type')->willReturn($cache_data);
+    $this->entityTypeManager->setCacheBackend($cache_backend->reveal(), 'entity_type', ['entity_types']);
+    $this->entityTypeManager->clearCachedDefinitions();
+
+    if ($base_fields) {
+      ViewsTestEntity::setMockedBaseFieldDefinitions($definition->id(), $base_fields);
+    }
+  }
+
+  /**
+   * Tests base tables.
+   */
+  public function testBaseTables() {
+    $data = $this->entityTypeManager->getHandler('entity_test', 'views_data')->getViewsData();
+
+    $this->assertEquals('entity_test', $data['entity_test']['table']['entity type']);
+    $this->assertEquals(FALSE, $data['entity_test']['table']['entity revision']);
+    $this->assertEquals('Entity test', $data['entity_test']['table']['group']);
+    $this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
+
+    $this->assertEquals('id', $data['entity_test']['table']['base']['field']);
+    $this->assertEquals(['entity_test_list_cache_context'], $data['entity_test']['table']['base']['cache_contexts']);
+    $this->assertEquals('Entity test', $data['entity_test']['table']['base']['title']);
+
+    // TODO: change these to assertArrayNotHasKey().
+    $this->assertFalse(isset($data['entity_test']['table']['defaults']));
+
+    $this->assertFalse(isset($data['entity_test_mul_property_data']));
+    $this->assertFalse(isset($data['revision_table']));
+    $this->assertFalse(isset($data['revision_data_table']));
+  }
+
+  /**
+   * Tests data_table support.
+   */
+  public function testDataTable() {
+    $entity_type = $this->baseEntityType
+      ->set('data_table', 'entity_test_mul_property_data')
+      ->set('id', 'entity_test_mul')
+      ->set('translatable', TRUE)
+      ->setKey('label', 'label');
+
+    $this->setUpEntityType($entity_type);
+
+    // Tests the join definition between the base and the data table.
+    $data = $this->entityTypeManager->getHandler('entity_test_mul', 'views_data')->getViewsData();
+    // TODO: change the base table in the entity type definition to match the
+    // changed entity ID.
+    $base_views_data = $data['entity_test'];
+
+    // Ensure that the base table is set to the data table.
+    $this->assertEquals('id', $data['entity_test_mul_property_data']['table']['base']['field']);
+    $this->assertEquals('Entity test', $data['entity_test_mul_property_data']['table']['base']['title']);
+    $this->assertFalse(isset($data['entity_test']['table']['base']));
+
+    $this->assertEquals('entity_test_mul', $data['entity_test_mul_property_data']['table']['entity type']);
+    $this->assertEquals(FALSE, $data['entity_test_mul_property_data']['table']['entity revision']);
+    $this->assertEquals('Entity test', $data['entity_test_mul_property_data']['table']['group']);
+    $this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
+    $this->assertEquals(['field' => 'label', 'table' => 'entity_test_mul_property_data'], $data['entity_test_mul_property_data']['table']['base']['defaults']);
+
+    // Ensure the join information is set up properly.
+    $this->assertCount(1, $base_views_data['table']['join']);
+    $this->assertEquals(['entity_test_mul_property_data' => ['left_field' => 'id', 'field' => 'id', 'type' => 'INNER']], $base_views_data['table']['join']);
+    $this->assertFalse(isset($data['revision_table']));
+    $this->assertFalse(isset($data['revision_data_table']));
+  }
+
+  /**
+   * Tests revision table without data table support.
+   */
+  public function testRevisionTableWithoutDataTable() {
+    $entity_type = $this->baseEntityType
+      ->set('revision_table', 'entity_test_mulrev_revision')
+      ->set('revision_data_table', NULL)
+      ->set('id', 'entity_test_mulrev')
+      ->setKey('revision', 'revision_id');
+
+    $this->setUpEntityType($entity_type);
+
+    $data = $this->entityTypeManager->getHandler('entity_test_mulrev', 'views_data')->getViewsData();
+
+    $this->assertEquals('Entity test revisions', $data['entity_test_mulrev_revision']['table']['base']['title']);
+    $this->assertEquals('revision_id', $data['entity_test_mulrev_revision']['table']['base']['field']);
+
+    $this->assertEquals(FALSE, $data['entity_test']['table']['entity revision']);
+    $this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_revision']['table']['entity type']);
+    $this->assertEquals(TRUE, $data['entity_test_mulrev_revision']['table']['entity revision']);
+    $this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_revision']['table']['entity type']);
+    $this->assertEquals(TRUE, $data['entity_test_mulrev_revision']['table']['entity revision']);
+
+    $this->assertEquals('Entity test revision', $data['entity_test_mulrev_revision']['table']['group']);
+    $this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
+
+    // Ensure the join information is set up properly.
+    // Tests the join definition between the base and the revision table.
+    $revision_data = $data['entity_test_mulrev_revision'];
+    $this->assertCount(1, $revision_data['table']['join']);
+    $this->assertEquals(['entity_test' => ['left_field' => 'revision_id', 'field' => 'revision_id', 'type' => 'INNER']], $revision_data['table']['join']);
+    $this->assertFalse(isset($data['data_table']));
+  }
+
+  /**
+   * Tests revision table with data table support.
+   */
+  public function testRevisionTableWithRevisionDataTableAndDataTable() {
+    $entity_type = $this->baseEntityType
+      ->set('data_table', 'entity_test_mul_property_data')
+      ->set('revision_table', 'entity_test_mulrev_revision')
+      ->set('revision_data_table', 'entity_test_mulrev_property_revision')
+      ->set('id', 'entity_test_mulrev')
+      ->set('translatable', TRUE)
+      ->setKey('revision', 'revision_id');
+    $this->setUpEntityType($entity_type);
+
+    $data = $this->entityTypeManager->getHandler('entity_test_mulrev', 'views_data')->getViewsData();
+
+    $this->assertEquals('Entity test revisions', $data['entity_test_mulrev_property_revision']['table']['base']['title']);
+    $this->assertEquals('revision_id', $data['entity_test_mulrev_property_revision']['table']['base']['field']);
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['table']['base']));
+
+    $this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_property_revision']['table']['entity type']);
+    $this->assertEquals('Entity test revision', $data['entity_test_mulrev_revision']['table']['group']);
+    $this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
+
+    // Ensure the join information is set up properly.
+    // Tests the join definition between the base and the revision table.
+    $revision_field_data = $data['entity_test_mulrev_property_revision'];
+    $this->assertCount(1, $revision_field_data['table']['join']);
+    $this->assertEquals([
+      'entity_test_mul_property_data' => [
+        'left_field' => 'revision_id',
+        'field' => 'revision_id',
+        'type' => 'INNER',
+      ],
+    ], $revision_field_data['table']['join']);
+
+    $revision_base_data = $data['entity_test_mulrev_revision'];
+    $this->assertCount(1, $revision_base_data['table']['join']);
+    $this->assertEquals([
+      'entity_test_mulrev_property_revision' => [
+        'left_field' => 'revision_id',
+        'field' => 'revision_id',
+        'type' => 'INNER',
+      ],
+    ], $revision_base_data['table']['join']);
+
+    $this->assertFalse(isset($data['data_table']));
+  }
+
+  /**
+   * Tests revision table with data table support.
+   */
+  public function testRevisionTableWithRevisionDataTable() {
+    $entity_type = $this->baseEntityType
+      ->set('revision_table', 'entity_test_mulrev_revision')
+      ->set('revision_data_table', 'entity_test_mulrev_property_revision')
+      ->set('id', 'entity_test_mulrev')
+      ->set('translatable', TRUE)
+      ->setKey('revision', 'revision_id');
+    $this->setUpEntityType($entity_type);
+
+    $data = $this->entityTypeManager->getHandler('entity_test_mulrev', 'views_data')->getViewsData();
+
+    $this->assertEquals('Entity test revisions', $data['entity_test_mulrev_property_revision']['table']['base']['title']);
+    $this->assertEquals('revision_id', $data['entity_test_mulrev_property_revision']['table']['base']['field']);
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['table']['base']));
+
+    $this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_property_revision']['table']['entity type']);
+    $this->assertEquals('Entity test revision', $data['entity_test_mulrev_revision']['table']['group']);
+    $this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
+
+    // Ensure the join information is set up properly.
+    // Tests the join definition between the base and the revision table.
+    $revision_field_data = $data['entity_test_mulrev_property_revision'];
+    $this->assertCount(1, $revision_field_data['table']['join']);
+    $this->assertEquals([
+      'entity_test_mulrev_field_data' => [
+        'left_field' => 'revision_id',
+        'field' => 'revision_id',
+        'type' => 'INNER',
+      ],
+    ], $revision_field_data['table']['join']);
+
+    $revision_base_data = $data['entity_test_mulrev_revision'];
+    $this->assertCount(1, $revision_base_data['table']['join']);
+    $this->assertEquals([
+      'entity_test_mulrev_property_revision' => [
+        'left_field' => 'revision_id',
+        'field' => 'revision_id',
+        'type' => 'INNER',
+      ],
+    ], $revision_base_data['table']['join']);
+    $this->assertFalse(isset($data['data_table']));
+  }
+
+  /**
+   * Tests fields on the base table.
+   */
+  public function testBaseTableFields() {
+    $data = $this->entityTypeManager->getHandler('entity_test', 'views_data')->getViewsData();
+
+    $this->assertNumericField($data['entity_test']['id']);
+    $this->assertViewsDataField($data['entity_test']['id'], 'id');
+    $this->assertUuidField($data['entity_test']['uuid']);
+    $this->assertViewsDataField($data['entity_test']['uuid'], 'uuid');
+    $this->assertStringField($data['entity_test']['type']);
+    $this->assertEquals('type', $data['entity_test']['type']['entity field']);
+
+    $this->assertLanguageField($data['entity_test']['langcode']);
+    $this->assertViewsDataField($data['entity_test']['langcode'], 'langcode');
+    $this->assertEquals('Original language', $data['entity_test']['langcode']['title']);
+
+    $this->assertStringField($data['entity_test']['name']);
+    $this->assertViewsDataField($data['entity_test']['name'], 'name');
+
+    $this->assertLongTextField($data['entity_test'], 'description');
+    $this->assertViewsDataField($data['entity_test']['description__value'], 'description');
+    $this->assertViewsDataField($data['entity_test']['description__format'], 'description');
+
+    $this->assertUriField($data['entity_test']['homepage']);
+    $this->assertViewsDataField($data['entity_test']['homepage'], 'homepage');
+
+    $this->assertEntityReferenceField($data['entity_test']['user_id']);
+    $this->assertViewsDataField($data['entity_test']['user_id'], 'user_id');
+
+    $relationship = $data['entity_test']['user_id']['relationship'];
+    $this->assertEquals('users_field_data', $relationship['base']);
+    $this->assertEquals('uid', $relationship['base field']);
+
+    // The string field name should be used as the 'entity field' but the actual
+    // field should reflect what the column mapping is using for multi-value
+    // base fields NOT just the field name. The actual column name returned from
+    // mappings in the test mocks is 'value'.
+    $this->assertStringField($data['entity_test__string']['string_value']);
+    $this->assertViewsDataField($data['entity_test__string']['string_value'], 'string');
+    $this->assertEquals([
+      'left_field' => 'id',
+      'field' => 'entity_id',
+      'extra' => [[
+          'field' => 'deleted',
+          'value' => 0,
+          'numeric' => TRUE,
+        ],
+      ],
+    ], $data['entity_test__string']['table']['join']['entity_test']);
+  }
+
+  /**
+   * Tests fields on the data table.
+   */
+  public function testDataTableFields() {
+    $entity_test_type = new ConfigEntityType([
+      'class' => ConfigEntityBase::class,
+      'id' => 'entity_test_bundle',
+      'entity_keys' => [
+        'id' => 'type',
+        'label' => 'name',
+      ],
+    ]);
+    $this->setUpEntityType($entity_test_type);
+
+    $entity_type = $this->baseEntityType
+      ->set('data_table', 'entity_test_mul_property_data')
+      ->set('base_table', 'entity_test_mul')
+      ->set('translatable', TRUE)
+      ->set('id', 'entity_test_mul')
+      ->set('bundle_entity_type', 'entity_test_bundle')
+      ->setKey('bundle', 'type');
+
+    $base_field_definitions = $this->commonBaseFields;
+    $base_field_definitions['type'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel('entity test type')
+      ->setSetting('target_type', 'entity_test_bundle');
+
+    $this->setUpEntityType($entity_type, $base_field_definitions);
+
+    $data = $this->entityTypeManager->getHandler('entity_test_mul', 'views_data')->getViewsData();
+
+    // Check the base fields.
+    $this->assertFalse(isset($data['entity_test_mul']['id']));
+    $this->assertFalse(isset($data['entity_test_mul']['type']));
+    $this->assertUuidField($data['entity_test_mul']['uuid']);
+    $this->assertViewsDataField($data['entity_test_mul']['uuid'], 'uuid');
+
+    $this->assertFalse(isset($data['entity_test_mul']['type']['relationship']));
+
+    // Also ensure that field_data only fields don't appear on the base table.
+    $this->assertFalse(isset($data['entity_test_mul']['name']));
+    $this->assertFalse(isset($data['entity_test_mul']['description']));
+    $this->assertFalse(isset($data['entity_test_mul']['description__value']));
+    $this->assertFalse(isset($data['entity_test_mul']['description__format']));
+    $this->assertFalse(isset($data['entity_test_mul']['user_id']));
+    $this->assertFalse(isset($data['entity_test_mul']['homepage']));
+
+    // Check the data fields.
+    $this->assertNumericField($data['entity_test_mul_property_data']['id']);
+    $this->assertViewsDataField($data['entity_test_mul_property_data']['id'], 'id');
+
+    $this->assertBundleField($data['entity_test_mul_property_data']['type']);
+    $this->assertViewsDataField($data['entity_test_mul_property_data']['type'], 'type');
+
+    $this->assertLanguageField($data['entity_test_mul_property_data']['langcode']);
+    $this->assertViewsDataField($data['entity_test_mul_property_data']['langcode'], 'langcode');
+    $this->assertEquals('Translation language', $data['entity_test_mul_property_data']['langcode']['title']);
+
+    $this->assertStringField($data['entity_test_mul_property_data']['name']);
+    $this->assertViewsDataField($data['entity_test_mul_property_data']['name'], 'name');
+
+    $this->assertLongTextField($data['entity_test_mul_property_data'], 'description');
+    $this->assertViewsDataField($data['entity_test_mul_property_data']['description__value'], 'description');
+    $this->assertViewsDataField($data['entity_test_mul_property_data']['description__format'], 'description');
+
+    $this->assertUriField($data['entity_test_mul_property_data']['homepage']);
+    $this->assertViewsDataField($data['entity_test_mul_property_data']['homepage'], 'homepage');
+
+    $this->assertEntityReferenceField($data['entity_test_mul_property_data']['user_id']);
+    $this->assertViewsDataField($data['entity_test_mul_property_data']['user_id'], 'user_id');
+    $relationship = $data['entity_test_mul_property_data']['user_id']['relationship'];
+    $this->assertEquals('users_field_data', $relationship['base']);
+    $this->assertEquals('uid', $relationship['base field']);
+
+    $this->assertStringField($data['entity_test_mul__string']['string_value']);
+    $this->assertViewsDataField($data['entity_test_mul__string']['string_value'], 'string');
+    $this->assertEquals([
+      'left_field' => 'id',
+      'field' => 'entity_id',
+      'extra' => [[
+          'field' => 'deleted',
+          'value' => 0,
+          'numeric' => TRUE,
+        ],
+      ],
+    ], $data['entity_test_mul__string']['table']['join']['entity_test_mul_property_data']);
+  }
+
+  /**
+   * Tests fields on the revision table.
+   */
+  public function testRevisionTableFields() {
+    $entity_type = $this->baseEntityType
+      ->set('id', 'entity_test_mulrev')
+      ->set('base_table', 'entity_test_mulrev')
+      ->set('revision_table', 'entity_test_mulrev_revision')
+      ->set('data_table', 'entity_test_mulrev_property_data')
+      ->set('revision_data_table', 'entity_test_mulrev_property_revision')
+      ->set('translatable', TRUE);
+
+    $base_field_definitions = $this->commonBaseFields;
+
+    $base_field_definitions['name']->setRevisionable(TRUE);
+    $base_field_definitions['description']->setRevisionable(TRUE);
+    $base_field_definitions['homepage']->setRevisionable(TRUE);
+    $base_field_definitions['user_id']->setRevisionable(TRUE);
+
+    $base_field_definitions['non_rev_field'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Non Revisionable Field'))
+      ->setDescription(t('A non-revisionable test field.'))
+      ->setRevisionable(FALSE)
+      ->setTranslatable(TRUE)
+      ->setCardinality(1)
+      ->setReadOnly(TRUE);
+
+    $base_field_definitions['non_mul_field'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Non translatable'))
+      ->setDescription(t('A non-translatable string field'))
+      ->setRevisionable(TRUE);
+
+    $this->setUpEntityType($entity_type, $base_field_definitions);
+
+    $data = $this->entityTypeManager->getHandler('entity_test_mulrev', 'views_data')->getViewsData();
+
+    // Check the base fields.
+    $this->assertFalse(isset($data['entity_test_mulrev']['id']));
+    $this->assertFalse(isset($data['entity_test_mulrev']['type']));
+    $this->assertFalse(isset($data['entity_test_mulrev']['revision_id']));
+    $this->assertUuidField($data['entity_test_mulrev']['uuid']);
+    $this->assertViewsDataField($data['entity_test_mulrev']['uuid'], 'uuid');
+
+    // Also ensure that field_data only fields don't appear on the base table.
+    $this->assertFalse(isset($data['entity_test_mulrev']['name']));
+    $this->assertFalse(isset($data['entity_test_mul']['description']));
+    $this->assertFalse(isset($data['entity_test_mul']['description__value']));
+    $this->assertFalse(isset($data['entity_test_mul']['description__format']));
+    $this->assertFalse(isset($data['entity_test_mul']['homepage']));
+    // $this->assertFalse(isset($data['entity_test_mulrev']['langcode']));
+    $this->assertFalse(isset($data['entity_test_mulrev']['user_id']));
+
+    // Check the revision fields. The revision ID should only appear in the data
+    // table.
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['revision_id']));
+
+    // Also ensure that field_data only fields don't appear on the revision table.
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['id']));
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['name']));
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['description']));
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['description__value']));
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['description__format']));
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['homepage']));
+    $this->assertFalse(isset($data['entity_test_mulrev_revision']['user_id']));
+
+    // Check the data fields.
+    $this->assertNumericField($data['entity_test_mulrev_property_data']['id']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_data']['id'], 'id');
+    $this->assertNumericField($data['entity_test_mulrev_property_data']['revision_id']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_data']['revision_id'], 'revision_id');
+    $this->assertLanguageField($data['entity_test_mulrev_property_data']['langcode']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_data']['langcode'], 'langcode');
+    $this->assertStringField($data['entity_test_mulrev_property_data']['name']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_data']['name'], 'name');
+
+    $this->assertLongTextField($data['entity_test_mulrev_property_data'], 'description');
+    $this->assertViewsDataField($data['entity_test_mulrev_property_data']['description__value'], 'description');
+    $this->assertViewsDataField($data['entity_test_mulrev_property_data']['description__format'], 'description');
+    $this->assertUriField($data['entity_test_mulrev_property_data']['homepage']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_data']['homepage'], 'homepage');
+
+    $this->assertEntityReferenceField($data['entity_test_mulrev_property_data']['user_id']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_data']['user_id'], 'user_id');
+    $relationship = $data['entity_test_mulrev_property_data']['user_id']['relationship'];
+    $this->assertEquals('users_field_data', $relationship['base']);
+    $this->assertEquals('uid', $relationship['base field']);
+
+    // Check the property data fields.
+    $this->assertNumericField($data['entity_test_mulrev_property_revision']['id']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_revision']['id'], 'id');
+
+    $this->assertLanguageField($data['entity_test_mulrev_property_revision']['langcode']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_revision']['langcode'], 'langcode');
+    $this->assertEquals('Translation language', $data['entity_test_mulrev_property_revision']['langcode']['title']);
+
+    $this->assertStringField($data['entity_test_mulrev_property_revision']['name']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_revision']['name'], 'name');
+
+    $this->assertLongTextField($data['entity_test_mulrev_property_revision'], 'description');
+    $this->assertViewsDataField($data['entity_test_mulrev_property_revision']['description__value'], 'description');
+    $this->assertViewsDataField($data['entity_test_mulrev_property_revision']['description__format'], 'description');
+
+    $this->assertUriField($data['entity_test_mulrev_property_revision']['homepage']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_revision']['homepage'], 'homepage');
+
+    $this->assertEntityReferenceField($data['entity_test_mulrev_property_revision']['user_id']);
+    $this->assertViewsDataField($data['entity_test_mulrev_property_revision']['user_id'], 'user_id');
+    $relationship = $data['entity_test_mulrev_property_revision']['user_id']['relationship'];
+    $this->assertEquals('users_field_data', $relationship['base']);
+    $this->assertEquals('uid', $relationship['base field']);
+
+    $this->assertStringField($data['entity_test_mulrev__string']['string_value']);
+    $this->assertViewsDataField($data['entity_test_mulrev__string']['string_value'], 'string');
+    $this->assertEquals([
+      'left_field' => 'id',
+      'field' => 'entity_id',
+      'extra' => [[
+          'field' => 'deleted',
+          'value' => 0,
+          'numeric' => TRUE,
+        ],
+      ],
+    ], $data['entity_test_mulrev__string']['table']['join']['entity_test_mulrev_property_data']);
+
+    $this->assertStringField($data['entity_test_mulrev_revision__string']['string_value']);
+    $this->assertViewsDataField($data['entity_test_mulrev_revision__string']['string_value'], 'string');
+    $this->assertEquals([
+      'left_field' => 'revision_id',
+      'field' => 'entity_id',
+      'extra' => [[
+          'field' => 'deleted',
+          'value' => 0,
+          'numeric' => TRUE,
+        ],
+      ],
+    ], $data['entity_test_mulrev_revision__string']['table']['join']['entity_test_mulrev_property_revision']);
+  }
+
+  /**
+   * Tests generic stuff per field.
+   *
+   * @param array $data
+   *   The views data to check.
+   * @param string $field_name
+   *   The entity field name.
+   */
+  protected function assertViewsDataField($data, $field_name) {
+    $this->assertEquals($field_name, $data['entity field']);
+  }
+
+  /**
+   * Tests views data for a string field.
+   *
+   * @param $data
+   *   The views data to check.
+   */
+  protected function assertStringField($data) {
+    $this->assertEquals('field', $data['field']['id']);
+    $this->assertEquals('string', $data['filter']['id']);
+    $this->assertEquals('string', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  /**
+   * Tests views data for a URI field.
+   *
+   * @param $data
+   *   The views data to check.
+   */
+  protected function assertUriField($data) {
+    $this->assertEquals('field', $data['field']['id']);
+    $this->assertEquals('string', $data['field']['default_formatter']);
+    $this->assertEquals('string', $data['filter']['id']);
+    $this->assertEquals('string', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  /**
+   * Tests views data for a long text field.
+   *
+   * @param $data
+   *   The views data for the table this field is in.
+   * @param $field_name
+   *   The name of the field being checked.
+   */
+  protected function assertLongTextField($data, $field_name) {
+    $value_field = $data[$field_name . '__value'];
+    $this->assertEquals('field', $value_field['field']['id']);
+    $this->assertEquals($field_name . '__format', $value_field['field']['format']);
+    $this->assertEquals('string', $value_field['filter']['id']);
+    $this->assertEquals('string', $value_field['argument']['id']);
+    $this->assertEquals('standard', $value_field['sort']['id']);
+
+    $this->assertStringField($data[$field_name . '__format']);
+  }
+
+  /**
+   * Tests views data for a UUID field.
+   *
+   * @param array $data
+   *   The views data to check.
+   */
+  protected function assertUuidField($data) {
+    // @todo Can we provide additional support for UUIDs in views?
+    $this->assertEquals('field', $data['field']['id']);
+    $this->assertFalse($data['field']['click sortable']);
+    $this->assertEquals('string', $data['filter']['id']);
+    $this->assertEquals('string', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  /**
+   * Tests views data for a numeric field.
+   *
+   * @param array $data
+   *   The views data to check.
+   */
+  protected function assertNumericField($data) {
+    $this->assertEquals('field', $data['field']['id']);
+    $this->assertEquals('numeric', $data['filter']['id']);
+    $this->assertEquals('numeric', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  /**
+   * Tests views data for a language field.
+   *
+   * @param array $data
+   *   The views data to check.
+   */
+  protected function assertLanguageField($data) {
+    $this->assertEquals('field', $data['field']['id']);
+    $this->assertEquals('language', $data['filter']['id']);
+    $this->assertEquals('language', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  /**
+   * Tests views data for an entity reference field.
+   */
+  protected function assertEntityReferenceField($data) {
+    $this->assertEquals('field', $data['field']['id']);
+    $this->assertEquals('numeric', $data['filter']['id']);
+    $this->assertEquals('numeric', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+  /**
+   * Tests views data for a bundle field.
+   */
+  protected function assertBundleField($data) {
+    $this->assertEquals('field', $data['field']['id']);
+    $this->assertEquals('bundle', $data['filter']['id']);
+    $this->assertEquals('string', $data['argument']['id']);
+    $this->assertEquals('standard', $data['sort']['id']);
+  }
+
+}
+
+/**
+ * Entity type class which allows changing the entity keys.
+ */
+class TestEntityType extends ContentEntityType {
+
+  /**
+   * Sets a specific entity key.
+   *
+   * @param string $key
+   *   The name of the entity key.
+   * @param string $value
+   *   The new value of the key.
+   *
+   * @return $this
+   */
+  public function setKey($key, $value) {
+    $this->entity_keys[$key] = $value;
+    return $this;
+  }
+
+}
+
+/**
+ * Generic entity class for our test entity types.
+ *
+ * Allows mocked base field definitions.
+ */
+class ViewsTestEntity extends ContentEntityBase {
+
+  /**
+   * The mocked base fields for test entity types.
+   *
+   * An array keyed by entity type ID, whose values are arrays of base field
+   * definitions.
+   *
+   * @var array
+   */
+  protected static $mockedBaseFieldDefinitions = [];
+
+  /**
+   * Sets up the mocked base field definitions.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param array $definitions
+   *   The array of base field definitions to mock. These are added to the
+   *   defaults ones from the parent class.
+   */
+  public static function setMockedBaseFieldDefinitions(string $entity_type_id, array $definitions) {
+    static::$mockedBaseFieldDefinitions[$entity_type_id] = $definitions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = parent::baseFieldDefinitions($entity_type);
+
+    if (isset(static::$mockedBaseFieldDefinitions[$entity_type->id()])) {
+      $mocked_fields = static::$mockedBaseFieldDefinitions[$entity_type->id()];
+      // Mocked fields take priority over ones from the base class.
+      $fields = $mocked_fields + $fields;
+    }
+
+    return $fields;
+  }
+
+}
diff --git a/web/core/modules/views/tests/src/Unit/EntityViewsDataTest.php b/web/core/modules/views/tests/src/Unit/EntityViewsDataTest.php
deleted file mode 100644
index 6e0621e40f..0000000000
--- a/web/core/modules/views/tests/src/Unit/EntityViewsDataTest.php
+++ /dev/null
@@ -1,1197 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Tests\views\Unit\EntityViewsDataTest.
- */
-
-namespace Drupal\Tests\views\Unit;
-
-use Drupal\Core\Config\Entity\ConfigEntityType;
-use Drupal\Core\Entity\ContentEntityType;
-use Drupal\Core\Entity\EntityFieldManagerInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Entity\Sql\DefaultTableMapping;
-use Drupal\Core\Field\BaseFieldDefinition;
-use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
-use Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem;
-use Drupal\Core\Field\Plugin\Field\FieldType\LanguageItem;
-use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
-use Drupal\Core\Field\Plugin\Field\FieldType\UriItem;
-use Drupal\Core\Field\Plugin\Field\FieldType\UuidItem;
-use Drupal\Core\State\StateInterface;
-use Drupal\Core\TypedData\TypedDataManagerInterface;
-use Drupal\text\Plugin\Field\FieldType\TextLongItem;
-use Drupal\entity_test\Entity\EntityTest;
-use Drupal\entity_test\Entity\EntityTestMul;
-use Drupal\entity_test\Entity\EntityTestMulRev;
-use Drupal\Tests\UnitTestCase;
-use Drupal\views\EntityViewsData;
-use Prophecy\Argument;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-
-/**
- * @coversDefaultClass \Drupal\views\EntityViewsData
- * @group Views
- */
-class EntityViewsDataTest extends UnitTestCase {
-
-  /**
-   * Entity info to use in this test.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeInterface|\Drupal\Tests\views\Unit\TestEntityType
-   */
-  protected $baseEntityType;
-
-  /**
-   * The mocked entity storage.
-   *
-   * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage|\PHPUnit\Framework\MockObject\MockObject
-   */
-  protected $entityStorage;
-
-  /**
-   * The mocked entity field manager.
-   *
-   * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\PHPUnit\Framework\MockObject\MockObject
-   */
-  protected $entityFieldManager;
-
-
-  /**
-   * The mocked entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject
-   */
-  protected $entityTypeManager;
-
-  /**
-   * The mocked module handler.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit\Framework\MockObject\MockObject
-   */
-  protected $moduleHandler;
-
-  /**
-   * The mocked translation manager.
-   *
-   * @var \Drupal\Core\StringTranslation\TranslationInterface|\PHPUnit\Framework\MockObject\MockObject
-   */
-  protected $translationManager;
-
-  /**
-   * The tested entity views controller.
-   *
-   * @var \Drupal\Tests\views\Unit\TestEntityViewsData
-   */
-  protected $viewsData;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    $this->entityStorage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
-    $this->entityFieldManager = $this->createMock(EntityFieldManagerInterface::class);
-
-    $typed_data_manager = $this->createMock(TypedDataManagerInterface::class);
-    $typed_data_manager->expects($this->any())
-      ->method('createDataDefinition')
-      ->willReturn($this->createMock('Drupal\Core\TypedData\DataDefinitionInterface'));
-
-    $typed_data_manager->expects($this->any())
-      ->method('getDefinition')
-      ->will($this->returnValueMap([
-        'entity:user' => ['class' => '\Drupal\Core\TypedData\DataDefinitionInterface'],
-        'field_item:string_long' => ['class' => '\Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem'],
-      ]));
-
-    $this->baseEntityType = new TestEntityType([
-      'base_table' => 'entity_test',
-      'id' => 'entity_test',
-      'label' => 'Entity test',
-      'entity_keys' => [
-        'uuid' => 'uuid',
-        'id' => 'id',
-        'langcode' => 'langcode',
-        'bundle' => 'type',
-        'revision' => 'revision_id',
-      ],
-      'provider' => 'entity_test',
-      'list_cache_contexts' => ['entity_test_list_cache_context'],
-    ]);
-
-    $this->translationManager = $this->getStringTranslationStub();
-    $this->baseEntityType->setStringTranslation($this->translationManager);
-    $this->moduleHandler = $this->createMock('Drupal\Core\Extension\ModuleHandlerInterface');
-
-    $this->viewsData = new TestEntityViewsData($this->baseEntityType, $this->entityStorage, $this->entityTypeManager, $this->moduleHandler, $this->translationManager, $this->entityFieldManager);
-
-    $field_type_manager = $this->getMockBuilder('Drupal\Core\Field\FieldTypePluginManager')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $field_type_manager->expects($this->any())
-      ->method('getDefaultStorageSettings')
-      ->willReturn([]);
-    $field_type_manager->expects($this->any())
-      ->method('getDefaultFieldSettings')
-      ->willReturn([]);
-
-    $state = $this->prophesize(StateInterface::class);
-    $state->get(Argument::any(), [])->willReturn([]);
-
-    $container = new ContainerBuilder();
-    $container->set('plugin.manager.field.field_type', $field_type_manager);
-    $container->set('entity_type.manager', $this->entityTypeManager);
-    $container->set('entity_field.manager', $this->entityFieldManager);
-    $container->set('typed_data_manager', $typed_data_manager);
-    $container->set('state', $state->reveal());
-    \Drupal::setContainer($container);
-  }
-
-  /**
-   * Helper method to setup base fields.
-   *
-   * @param \Drupal\Core\Field\BaseFieldDefinition[] $base_fields
-   *   The base fields which are adapted.
-   *
-   * @return \Drupal\Core\Field\BaseFieldDefinition[]
-   *   The setup base fields.
-   */
-  protected function setupBaseFields(array $base_fields) {
-    // Add a description field to the fields supplied by the EntityTest
-    // classes. This example comes from the taxonomy Term entity.
-    $base_fields['description'] = BaseFieldDefinition::create('text_long')
-      ->setLabel('Description')
-      ->setDescription('A description of the term.')
-      ->setTranslatable(TRUE)
-      ->setDisplayOptions('view', [
-          'label' => 'hidden',
-          'type' => 'text_default',
-          'weight' => 0,
-        ])
-      ->setDisplayConfigurable('view', TRUE)
-      ->setDisplayOptions('form', [
-          'type' => 'text_textfield',
-          'weight' => 0,
-        ])
-      ->setDisplayConfigurable('form', TRUE);
-
-    // Add a URL field; this example is from the Comment entity.
-    $base_fields['homepage'] = BaseFieldDefinition::create('uri')
-      ->setLabel('Homepage')
-      ->setDescription("The comment author's home page address.")
-      ->setTranslatable(TRUE)
-      ->setSetting('max_length', 255);
-
-    // A base field with cardinality > 1
-    $base_fields['string'] = BaseFieldDefinition::create('string')
-      ->setLabel('Strong')
-      ->setTranslatable(TRUE)
-      ->setCardinality(2);
-
-    foreach ($base_fields as $name => $base_field) {
-      $base_field->setName($name);
-    }
-    return $base_fields;
-  }
-
-  /**
-   * Tests base tables.
-   */
-  public function testBaseTables() {
-    $data = $this->viewsData->getViewsData();
-
-    $this->assertEquals('entity_test', $data['entity_test']['table']['entity type']);
-    $this->assertEquals(FALSE, $data['entity_test']['table']['entity revision']);
-    $this->assertEquals('Entity test', $data['entity_test']['table']['group']);
-    $this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
-
-    $this->assertEquals('id', $data['entity_test']['table']['base']['field']);
-    $this->assertEquals(['entity_test_list_cache_context'], $data['entity_test']['table']['base']['cache_contexts']);
-    $this->assertEquals('Entity test', $data['entity_test']['table']['base']['title']);
-
-    $this->assertFalse(isset($data['entity_test']['table']['defaults']));
-
-    $this->assertFalse(isset($data['entity_test_mul_property_data']));
-    $this->assertFalse(isset($data['revision_table']));
-    $this->assertFalse(isset($data['revision_data_table']));
-  }
-
-  /**
-   * Tests data_table support.
-   */
-  public function testDataTable() {
-    $entity_type = $this->baseEntityType
-      ->set('data_table', 'entity_test_mul_property_data')
-      ->set('id', 'entity_test_mul')
-      ->set('translatable', TRUE)
-      ->setKey('label', 'label');
-
-    $this->viewsData->setEntityType($entity_type);
-
-    // Tests the join definition between the base and the data table.
-    $data = $this->viewsData->getViewsData();
-    $base_views_data = $data['entity_test'];
-
-    // Ensure that the base table is set to the data table.
-    $this->assertEquals('id', $data['entity_test_mul_property_data']['table']['base']['field']);
-    $this->assertEquals('Entity test', $data['entity_test_mul_property_data']['table']['base']['title']);
-    $this->assertFalse(isset($data['entity_test']['table']['base']));
-
-    $this->assertEquals('entity_test_mul', $data['entity_test_mul_property_data']['table']['entity type']);
-    $this->assertEquals(FALSE, $data['entity_test_mul_property_data']['table']['entity revision']);
-    $this->assertEquals('Entity test', $data['entity_test_mul_property_data']['table']['group']);
-    $this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
-    $this->assertEquals(['field' => 'label', 'table' => 'entity_test_mul_property_data'], $data['entity_test_mul_property_data']['table']['base']['defaults']);
-
-    // Ensure the join information is set up properly.
-    $this->assertCount(1, $base_views_data['table']['join']);
-    $this->assertEquals(['entity_test_mul_property_data' => ['left_field' => 'id', 'field' => 'id', 'type' => 'INNER']], $base_views_data['table']['join']);
-    $this->assertFalse(isset($data['revision_table']));
-    $this->assertFalse(isset($data['revision_data_table']));
-  }
-
-  /**
-   * Tests revision table without data table support.
-   */
-  public function testRevisionTableWithoutDataTable() {
-    $entity_type = $this->baseEntityType
-      ->set('revision_table', 'entity_test_mulrev_revision')
-      ->set('revision_data_table', NULL)
-      ->set('id', 'entity_test_mulrev')
-      ->setKey('revision', 'revision_id');
-    $this->viewsData->setEntityType($entity_type);
-
-    $data = $this->viewsData->getViewsData();
-
-    $this->assertEquals('Entity test revisions', $data['entity_test_mulrev_revision']['table']['base']['title']);
-    $this->assertEquals('revision_id', $data['entity_test_mulrev_revision']['table']['base']['field']);
-
-    $this->assertEquals(FALSE, $data['entity_test']['table']['entity revision']);
-    $this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_revision']['table']['entity type']);
-    $this->assertEquals(TRUE, $data['entity_test_mulrev_revision']['table']['entity revision']);
-    $this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_revision']['table']['entity type']);
-    $this->assertEquals(TRUE, $data['entity_test_mulrev_revision']['table']['entity revision']);
-
-    $this->assertEquals('Entity test revision', $data['entity_test_mulrev_revision']['table']['group']);
-    $this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
-
-    // Ensure the join information is set up properly.
-    // Tests the join definition between the base and the revision table.
-    $revision_data = $data['entity_test_mulrev_revision'];
-    $this->assertCount(1, $revision_data['table']['join']);
-    $this->assertEquals(['entity_test' => ['left_field' => 'revision_id', 'field' => 'revision_id', 'type' => 'INNER']], $revision_data['table']['join']);
-    $this->assertFalse(isset($data['data_table']));
-  }
-
-  /**
-   * Tests revision table with data table support.
-   */
-  public function testRevisionTableWithRevisionDataTableAndDataTable() {
-    $entity_type = $this->baseEntityType
-      ->set('data_table', 'entity_test_mul_property_data')
-      ->set('revision_table', 'entity_test_mulrev_revision')
-      ->set('revision_data_table', 'entity_test_mulrev_property_revision')
-      ->set('id', 'entity_test_mulrev')
-      ->set('translatable', TRUE)
-      ->setKey('revision', 'revision_id');
-    $this->viewsData->setEntityType($entity_type);
-
-    $data = $this->viewsData->getViewsData();
-
-    $this->assertEquals('Entity test revisions', $data['entity_test_mulrev_property_revision']['table']['base']['title']);
-    $this->assertEquals('revision_id', $data['entity_test_mulrev_property_revision']['table']['base']['field']);
-    $this->assertFalse(isset($data['entity_test_mulrev_revision']['table']['base']));
-
-    $this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_property_revision']['table']['entity type']);
-    $this->assertEquals('Entity test revision', $data['entity_test_mulrev_revision']['table']['group']);
-    $this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
-
-    // Ensure the join information is set up properly.
-    // Tests the join definition between the base and the revision table.
-    $revision_field_data = $data['entity_test_mulrev_property_revision'];
-    $this->assertCount(1, $revision_field_data['table']['join']);
-    $this->assertEquals([
-      'entity_test_mul_property_data' => [
-        'left_field' => 'revision_id',
-        'field' => 'revision_id',
-        'type' => 'INNER',
-      ],
-    ], $revision_field_data['table']['join']);
-
-    $revision_base_data = $data['entity_test_mulrev_revision'];
-    $this->assertCount(1, $revision_base_data['table']['join']);
-    $this->assertEquals([
-      'entity_test_mulrev_property_revision' => [
-        'left_field' => 'revision_id',
-        'field' => 'revision_id',
-        'type' => 'INNER',
-      ],
-    ], $revision_base_data['table']['join']);
-
-    $this->assertFalse(isset($data['data_table']));
-  }
-
-  /**
-   * Tests revision table with data table support.
-   */
-  public function testRevisionTableWithRevisionDataTable() {
-    $entity_type = $this->baseEntityType
-      ->set('revision_table', 'entity_test_mulrev_revision')
-      ->set('revision_data_table', 'entity_test_mulrev_property_revision')
-      ->set('id', 'entity_test_mulrev')
-      ->set('translatable', TRUE)
-      ->setKey('revision', 'revision_id');
-    $this->viewsData->setEntityType($entity_type);
-
-    $data = $this->viewsData->getViewsData();
-
-    $this->assertEquals('Entity test revisions', $data['entity_test_mulrev_property_revision']['table']['base']['title']);
-    $this->assertEquals('revision_id', $data['entity_test_mulrev_property_revision']['table']['base']['field']);
-    $this->assertFalse(isset($data['entity_test_mulrev_revision']['table']['base']));
-
-    $this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_property_revision']['table']['entity type']);
-    $this->assertEquals('Entity test revision', $data['entity_test_mulrev_revision']['table']['group']);
-    $this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
-
-    // Ensure the join information is set up properly.
-    // Tests the join definition between the base and the revision table.
-    $revision_field_data = $data['entity_test_mulrev_property_revision'];
-    $this->assertCount(1, $revision_field_data['table']['join']);
-    $this->assertEquals([
-      'entity_test_mulrev_field_data' => [
-        'left_field' => 'revision_id',
-        'field' => 'revision_id',
-        'type' => 'INNER',
-      ],
-    ], $revision_field_data['table']['join']);
-
-    $revision_base_data = $data['entity_test_mulrev_revision'];
-    $this->assertCount(1, $revision_base_data['table']['join']);
-    $this->assertEquals([
-      'entity_test_mulrev_property_revision' => [
-        'left_field' => 'revision_id',
-        'field' => 'revision_id',
-        'type' => 'INNER',
-      ],
-    ], $revision_base_data['table']['join']);
-    $this->assertFalse(isset($data['data_table']));
-  }
-
-  /**
-   * Helper method to mock all store definitions.
-   */
-  protected function setupFieldStorageDefinition() {
-    $id_field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $id_field_storage_definition->expects($this->any())
-      ->method('getSchema')
-      ->willReturn(IntegerItem::schema($id_field_storage_definition));
-    $uuid_field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $uuid_field_storage_definition->expects($this->any())
-      ->method('getSchema')
-      ->willReturn(UuidItem::schema($uuid_field_storage_definition));
-    $type_field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $type_field_storage_definition->expects($this->any())
-      ->method('getSchema')
-      ->willReturn(StringItem::schema($type_field_storage_definition));
-    $langcode_field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $langcode_field_storage_definition->expects($this->any())
-      ->method('getSchema')
-      ->willReturn(LanguageItem::schema($langcode_field_storage_definition));
-    $name_field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $name_field_storage_definition->expects($this->any())
-      ->method('getSchema')
-      ->willReturn(StringItem::schema($name_field_storage_definition));
-    $description_field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $description_field_storage_definition->expects($this->any())
-      ->method('getSchema')
-      ->willReturn(TextLongItem::schema($description_field_storage_definition));
-    $homepage_field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $homepage_field_storage_definition->expects($this->any())
-      ->method('getSchema')
-      ->willReturn(UriItem::schema($homepage_field_storage_definition));
-    $string_field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $string_field_storage_definition->expects($this->any())
-      ->method('getSchema')
-      ->willReturn(StringItem::schema($string_field_storage_definition));
-
-    // Setup the user_id entity reference field.
-    $this->entityTypeManager->expects($this->any())
-      ->method('getDefinition')
-      ->willReturnMap([
-          ['user', TRUE, static::userEntityInfo()],
-        ]
-      );
-    $this->entityTypeManager->expects($this->any())
-      ->method('getDefinition')
-      ->willReturnMap([
-          ['user', TRUE, static::userEntityInfo()],
-        ]
-      );
-    $user_id_field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $user_id_field_storage_definition->expects($this->any())
-      ->method('getSetting')
-      ->with('target_type')
-      ->willReturn('user');
-    $user_id_field_storage_definition->expects($this->any())
-      ->method('getSettings')
-      ->willReturn(['target_type' => 'user']);
-    $user_id_field_storage_definition->expects($this->any())
-      ->method('getSchema')
-      ->willReturn(EntityReferenceItem::schema($user_id_field_storage_definition));
-
-    $revision_id_field_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $revision_id_field_storage_definition->expects($this->any())
-      ->method('getSchema')
-      ->willReturn(IntegerItem::schema($revision_id_field_storage_definition));
-
-    $this->entityFieldManager->expects($this->any())
-      ->method('getFieldStorageDefinitions')
-      ->willReturn([
-        'id' => $id_field_storage_definition,
-        'uuid' => $uuid_field_storage_definition,
-        'type' => $type_field_storage_definition,
-        'langcode' => $langcode_field_storage_definition,
-        'name' => $name_field_storage_definition,
-        'description' => $description_field_storage_definition,
-        'homepage' => $homepage_field_storage_definition,
-        'string' => $string_field_storage_definition,
-        'user_id' => $user_id_field_storage_definition,
-        'revision_id' => $revision_id_field_storage_definition,
-      ]);
-  }
-
-  /**
-   * Tests fields on the base table.
-   */
-  public function testBaseTableFields() {
-    $base_field_definitions = $this->setupBaseFields(EntityTest::baseFieldDefinitions($this->baseEntityType));
-    $user_base_field_definitions = [
-      'uid' => BaseFieldDefinition::create('integer')
-        ->setLabel('ID')
-        ->setDescription('The ID of the user entity.')
-        ->setReadOnly(TRUE)
-        ->setSetting('unsigned', TRUE),
-    ];
-    $this->entityFieldManager->expects($this->any())
-      ->method('getBaseFieldDefinitions')
-      ->will($this->returnValueMap([
-        ['user', $user_base_field_definitions],
-        ['entity_test', $base_field_definitions],
-      ]));
-    $this->entityFieldManager->expects($this->any())
-      ->method('getBaseFieldDefinitions')
-      ->will($this->returnValueMap([
-        ['user', $user_base_field_definitions],
-        ['entity_test', $base_field_definitions],
-      ]));
-    // Setup the table mapping.
-    $table_mapping = $this->getMockBuilder(DefaultTableMapping::class)
-      ->disableOriginalConstructor()
-      ->getMock();
-    $table_mapping->expects($this->any())
-      ->method('getTableNames')
-      ->willReturn(['entity_test', 'entity_test__string']);
-    $table_mapping->expects($this->any())
-      ->method('getColumnNames')
-      ->willReturnMap([
-        ['id', ['value' => 'id']],
-        ['uuid', ['value' => 'uuid']],
-        ['type', ['value' => 'type']],
-        ['langcode', ['value' => 'langcode']],
-        ['name', ['value' => 'name']],
-        ['description', ['value' => 'description__value', 'format' => 'description__format']],
-        ['homepage', ['value' => 'homepage']],
-        ['user_id', ['target_id' => 'user_id']],
-        ['string', ['value' => 'string_value']],
-      ]);
-    $table_mapping->expects($this->any())
-      ->method('getFieldNames')
-      ->willReturnMap([
-        ['entity_test', ['id', 'uuid', 'type', 'langcode', 'name', 'description', 'homepage', 'user_id']],
-        ['entity_test__string', ['string']],
-      ]);
-    $table_mapping->expects($this->any())
-      ->method('requiresDedicatedTableStorage')
-      ->willReturnCallback(function (BaseFieldDefinition $base_field) {
-        return $base_field->getName() === 'string';
-      });
-    $table_mapping->expects($this->any())
-      ->method('getDedicatedDataTableName')
-      ->willReturnCallback(function (BaseFieldDefinition $base_field) {
-        if ($base_field->getName() === 'string') {
-          return 'entity_test__string';
-        }
-      });
-
-    $this->entityStorage->expects($this->once())
-      ->method('getTableMapping')
-      ->willReturn($table_mapping);
-
-    $this->setupFieldStorageDefinition();
-
-    $data = $this->viewsData->getViewsData();
-
-    $this->assertNumericField($data['entity_test']['id']);
-    $this->assertField($data['entity_test']['id'], 'id');
-    $this->assertUuidField($data['entity_test']['uuid']);
-    $this->assertField($data['entity_test']['uuid'], 'uuid');
-    $this->assertStringField($data['entity_test']['type']);
-    $this->assertEquals('type', $data['entity_test']['type']['entity field']);
-
-    $this->assertLanguageField($data['entity_test']['langcode']);
-    $this->assertField($data['entity_test']['langcode'], 'langcode');
-    $this->assertEquals('Original language', $data['entity_test']['langcode']['title']);
-
-    $this->assertStringField($data['entity_test']['name']);
-    $this->assertField($data['entity_test']['name'], 'name');
-
-    $this->assertLongTextField($data['entity_test'], 'description');
-    $this->assertField($data['entity_test']['description__value'], 'description');
-    $this->assertField($data['entity_test']['description__format'], 'description');
-
-    $this->assertUriField($data['entity_test']['homepage']);
-    $this->assertField($data['entity_test']['homepage'], 'homepage');
-
-    $this->assertEntityReferenceField($data['entity_test']['user_id']);
-    $this->assertField($data['entity_test']['user_id'], 'user_id');
-
-    $relationship = $data['entity_test']['user_id']['relationship'];
-    $this->assertEquals('users_field_data', $relationship['base']);
-    $this->assertEquals('uid', $relationship['base field']);
-
-    // The string field name should be used as the 'entity field' but the actual
-    // field should reflect what the column mapping is using for multi-value
-    // base fields NOT just the field name. The actual column name returned from
-    // mappings in the test mocks is 'value'.
-    $this->assertStringField($data['entity_test__string']['string_value']);
-    $this->assertField($data['entity_test__string']['string_value'], 'string');
-    $this->assertEquals([
-      'left_field' => 'id',
-      'field' => 'entity_id',
-      'extra' => [[
-          'field' => 'deleted',
-          'value' => 0,
-          'numeric' => TRUE,
-        ],
-      ],
-    ], $data['entity_test__string']['table']['join']['entity_test']);
-  }
-
-  /**
-   * Tests fields on the data table.
-   */
-  public function testDataTableFields() {
-    $entity_type = $this->baseEntityType
-      ->set('data_table', 'entity_test_mul_property_data')
-      ->set('base_table', 'entity_test_mul')
-      ->set('id', 'entity_test_mul')
-      ->setKey('bundle', 'type');
-    $base_field_definitions = $this->setupBaseFields(EntityTestMul::baseFieldDefinitions($this->baseEntityType));
-    $base_field_definitions['type'] = BaseFieldDefinition::create('entity_reference')
-      ->setLabel('entity test type')
-      ->setSetting('target_type', 'entity_test_bundle')
-      ->setTranslatable(TRUE);
-    $base_field_definitions = $this->setupBaseFields($base_field_definitions);
-    $user_base_field_definitions = [
-      'uid' => BaseFieldDefinition::create('integer')
-        ->setLabel('ID')
-        ->setDescription('The ID of the user entity.')
-        ->setReadOnly(TRUE)
-        ->setSetting('unsigned', TRUE),
-    ];
-    $entity_test_type = new ConfigEntityType(['id' => 'entity_test_bundle']);
-
-    $this->entityFieldManager->expects($this->any())
-      ->method('getBaseFieldDefinitions')
-      ->will($this->returnValueMap([
-        ['user', $user_base_field_definitions],
-        ['entity_test_mul', $base_field_definitions],
-      ]));
-    $this->entityFieldManager->expects($this->any())
-      ->method('getBaseFieldDefinitions')
-      ->will($this->returnValueMap([
-        ['user', $user_base_field_definitions],
-        ['entity_test_mul', $base_field_definitions],
-      ]));
-
-    $this->viewsData->setEntityType($entity_type);
-
-    // Setup the table mapping.
-    $table_mapping = $this->getMockBuilder(DefaultTableMapping::class)
-      ->disableOriginalConstructor()
-      ->getMock();
-    $table_mapping->expects($this->any())
-      ->method('getTableNames')
-      ->willReturn(['entity_test_mul', 'entity_test_mul_property_data', 'entity_test_mul__string']);
-    $table_mapping->expects($this->any())
-      ->method('getColumnNames')
-      ->willReturnMap([
-        ['id', ['value' => 'id']],
-        ['uuid', ['value' => 'uuid']],
-        ['type', ['value' => 'type']],
-        ['langcode', ['value' => 'langcode']],
-        ['name', ['value' => 'name']],
-        ['description', ['value' => 'description__value', 'format' => 'description__format']],
-        ['homepage', ['value' => 'homepage']],
-        ['user_id', ['target_id' => 'user_id']],
-        ['string', ['value' => 'string_value']],
-      ]);
-    $table_mapping->expects($this->any())
-      ->method('getFieldNames')
-      ->willReturnMap([
-        ['entity_test_mul', ['uuid']],
-        ['entity_test_mul_property_data', ['id', 'type', 'langcode', 'name', 'description', 'homepage', 'user_id']],
-        ['entity_test_mul__string', ['string']],
-      ]);
-
-    $table_mapping->expects($this->any())
-      ->method('getFieldTableName')
-      ->willReturnCallback(function ($field) {
-        if ($field == 'uuid') {
-          return 'entity_test_mul';
-        }
-        return 'entity_test_mul_property_data';
-      });
-    $table_mapping->expects($this->any())
-      ->method('requiresDedicatedTableStorage')
-      ->willReturnCallback(function (BaseFieldDefinition $base_field) {
-        return $base_field->getName() === 'string';
-      });
-    $table_mapping->expects($this->any())
-      ->method('getDedicatedDataTableName')
-      ->willReturnCallback(function (BaseFieldDefinition $base_field) {
-        if ($base_field->getName() === 'string') {
-          return 'entity_test_mul__string';
-        }
-      });
-
-    $this->entityStorage->expects($this->once())
-      ->method('getTableMapping')
-      ->willReturn($table_mapping);
-
-    $this->setupFieldStorageDefinition();
-
-    $user_entity_type = static::userEntityInfo();
-    $this->entityTypeManager->expects($this->any())
-      ->method('getDefinition')
-      ->will($this->returnValueMap([
-        ['user', TRUE, $user_entity_type],
-        ['entity_test_bundle', TRUE, $entity_test_type],
-      ]));
-
-    $data = $this->viewsData->getViewsData();
-
-    // Check the base fields.
-    $this->assertFalse(isset($data['entity_test_mul']['id']));
-    $this->assertFalse(isset($data['entity_test_mul']['type']));
-    $this->assertUuidField($data['entity_test_mul']['uuid']);
-    $this->assertField($data['entity_test_mul']['uuid'], 'uuid');
-
-    $this->assertFalse(isset($data['entity_test_mul']['type']['relationship']));
-
-    // Also ensure that field_data only fields don't appear on the base table.
-    $this->assertFalse(isset($data['entity_test_mul']['name']));
-    $this->assertFalse(isset($data['entity_test_mul']['description']));
-    $this->assertFalse(isset($data['entity_test_mul']['description__value']));
-    $this->assertFalse(isset($data['entity_test_mul']['description__format']));
-    $this->assertFalse(isset($data['entity_test_mul']['user_id']));
-    $this->assertFalse(isset($data['entity_test_mul']['homepage']));
-
-    // Check the data fields.
-    $this->assertNumericField($data['entity_test_mul_property_data']['id']);
-    $this->assertField($data['entity_test_mul_property_data']['id'], 'id');
-
-    $this->assertBundleField($data['entity_test_mul_property_data']['type']);
-    $this->assertField($data['entity_test_mul_property_data']['type'], 'type');
-
-    $this->assertLanguageField($data['entity_test_mul_property_data']['langcode']);
-    $this->assertField($data['entity_test_mul_property_data']['langcode'], 'langcode');
-    $this->assertEquals('Translation language', $data['entity_test_mul_property_data']['langcode']['title']);
-
-    $this->assertStringField($data['entity_test_mul_property_data']['name']);
-    $this->assertField($data['entity_test_mul_property_data']['name'], 'name');
-
-    $this->assertLongTextField($data['entity_test_mul_property_data'], 'description');
-    $this->assertField($data['entity_test_mul_property_data']['description__value'], 'description');
-    $this->assertField($data['entity_test_mul_property_data']['description__format'], 'description');
-
-    $this->assertUriField($data['entity_test_mul_property_data']['homepage']);
-    $this->assertField($data['entity_test_mul_property_data']['homepage'], 'homepage');
-
-    $this->assertEntityReferenceField($data['entity_test_mul_property_data']['user_id']);
-    $this->assertField($data['entity_test_mul_property_data']['user_id'], 'user_id');
-    $relationship = $data['entity_test_mul_property_data']['user_id']['relationship'];
-    $this->assertEquals('users_field_data', $relationship['base']);
-    $this->assertEquals('uid', $relationship['base field']);
-
-    $this->assertStringField($data['entity_test_mul__string']['string_value']);
-    $this->assertField($data['entity_test_mul__string']['string_value'], 'string');
-    $this->assertEquals([
-      'left_field' => 'id',
-      'field' => 'entity_id',
-      'extra' => [[
-          'field' => 'deleted',
-          'value' => 0,
-          'numeric' => TRUE,
-        ],
-      ],
-    ], $data['entity_test_mul__string']['table']['join']['entity_test_mul']);
-  }
-
-  /**
-   * Tests fields on the revision table.
-   */
-  public function testRevisionTableFields() {
-    $entity_type = $this->baseEntityType
-      ->set('base_table', 'entity_test_mulrev')
-      ->set('revision_table', 'entity_test_mulrev_revision')
-      ->set('data_table', 'entity_test_mulrev_property_data')
-      ->set('revision_data_table', 'entity_test_mulrev_property_revision')
-      ->set('id', 'entity_test_mulrev')
-      ->set('translatable', TRUE);
-    $base_field_definitions = $this->setupBaseFields(EntityTestMulRev::baseFieldDefinitions($this->baseEntityType));
-    $user_base_field_definitions = [
-      'uid' => BaseFieldDefinition::create('integer')
-        ->setLabel('ID')
-        ->setDescription('The ID of the user entity.')
-        ->setReadOnly(TRUE)
-        ->setSetting('unsigned', TRUE),
-    ];
-    $this->entityFieldManager->expects($this->any())
-      ->method('getBaseFieldDefinitions')
-      ->will($this->returnValueMap([
-        ['user', $user_base_field_definitions],
-        ['entity_test_mulrev', $base_field_definitions],
-      ]));
-    $this->entityFieldManager->expects($this->any())
-      ->method('getBaseFieldDefinitions')
-      ->will($this->returnValueMap([
-        ['user', $user_base_field_definitions],
-        ['entity_test_mulrev', $base_field_definitions],
-      ]));
-
-    $this->viewsData->setEntityType($entity_type);
-
-    // Setup the table mapping.
-    $table_mapping = $this->getMockBuilder(DefaultTableMapping::class)
-      ->disableOriginalConstructor()
-      ->getMock();
-    $table_mapping->expects($this->any())
-      ->method('getTableNames')
-      ->willReturn(['entity_test_mulrev', 'entity_test_mulrev_revision', 'entity_test_mulrev_property_data', 'entity_test_mulrev_property_revision', 'entity_test_mulrev__string', 'entity_test_mulrev_revision__string']);
-    $table_mapping->expects($this->any())
-      ->method('getColumnNames')
-      ->willReturnMap([
-        ['id', ['value' => 'id']],
-        ['uuid', ['value' => 'uuid']],
-        ['type', ['value' => 'type']],
-        ['langcode', ['value' => 'langcode']],
-        ['name', ['value' => 'name']],
-        ['description', ['value' => 'description__value', 'format' => 'description__format']],
-        ['homepage', ['value' => 'homepage']],
-        ['user_id', ['target_id' => 'user_id']],
-        ['revision_id', ['value' => 'revision_id']],
-        ['string', ['value' => 'string_value']],
-      ]);
-    $table_mapping->expects($this->any())
-      ->method('getFieldNames')
-      ->willReturnMap([
-        ['entity_test_mulrev', ['id', 'revision_id', 'uuid', 'type']],
-        ['entity_test_mulrev_revision', ['id', 'revision_id', 'langcode']],
-        ['entity_test_mulrev_property_data', ['id', 'revision_id', 'langcode', 'name', 'description', 'homepage', 'user_id']],
-        ['entity_test_mulrev_property_revision', ['id', 'revision_id', 'langcode', 'name', 'description', 'homepage', 'user_id']],
-        ['entity_test_mulrev__string', ['string']],
-        ['entity_test_mulrev_revision__string', ['string']],
-      ]);
-    $table_mapping->expects($this->any())
-      ->method('requiresDedicatedTableStorage')
-      ->willReturnCallback(function (BaseFieldDefinition $base_field) {
-        return $base_field->getName() === 'string';
-      });
-    $table_mapping->expects($this->any())
-      ->method('getDedicatedDataTableName')
-      ->willReturnCallback(function (BaseFieldDefinition $base_field) {
-        if ($base_field->getName() === 'string') {
-          return 'entity_test_mulrev__string';
-        }
-      });
-
-    $table_mapping->expects($this->any())
-      ->method('getDedicatedRevisionTableName')
-      ->willReturnCallback(function (BaseFieldDefinition $base_field) {
-        if ($base_field->getName() === 'string') {
-          return 'entity_test_mulrev_revision__string';
-        }
-      });
-
-    $table_mapping->expects($this->any())
-      ->method('getFieldTableName')
-      ->willReturnCallback(function ($field) {
-        if ($field == 'uuid') {
-          return 'entity_test_mulrev';
-        }
-        return 'entity_test_mulrev_property_data';
-      });
-
-    $this->entityStorage->expects($this->once())
-      ->method('getTableMapping')
-      ->willReturn($table_mapping);
-
-    $this->setupFieldStorageDefinition();
-
-    $data = $this->viewsData->getViewsData();
-
-    // Check the base fields.
-    $this->assertFalse(isset($data['entity_test_mulrev']['id']));
-    $this->assertFalse(isset($data['entity_test_mulrev']['type']));
-    $this->assertFalse(isset($data['entity_test_mulrev']['revision_id']));
-    $this->assertUuidField($data['entity_test_mulrev']['uuid']);
-    $this->assertField($data['entity_test_mulrev']['uuid'], 'uuid');
-
-    // Also ensure that field_data only fields don't appear on the base table.
-    $this->assertFalse(isset($data['entity_test_mulrev']['name']));
-    $this->assertFalse(isset($data['entity_test_mul']['description']));
-    $this->assertFalse(isset($data['entity_test_mul']['description__value']));
-    $this->assertFalse(isset($data['entity_test_mul']['description__format']));
-    $this->assertFalse(isset($data['entity_test_mul']['homepage']));
-    $this->assertFalse(isset($data['entity_test_mulrev']['langcode']));
-    $this->assertFalse(isset($data['entity_test_mulrev']['user_id']));
-
-    // Check the revision fields. The revision ID should only appear in the data
-    // table.
-    $this->assertFalse(isset($data['entity_test_mulrev_revision']['revision_id']));
-
-    // Also ensure that field_data only fields don't appear on the revision table.
-    $this->assertFalse(isset($data['entity_test_mulrev_revision']['id']));
-    $this->assertFalse(isset($data['entity_test_mulrev_revision']['name']));
-    $this->assertFalse(isset($data['entity_test_mulrev_revision']['description']));
-    $this->assertFalse(isset($data['entity_test_mulrev_revision']['description__value']));
-    $this->assertFalse(isset($data['entity_test_mulrev_revision']['description__format']));
-    $this->assertFalse(isset($data['entity_test_mulrev_revision']['homepage']));
-    $this->assertFalse(isset($data['entity_test_mulrev_revision']['user_id']));
-
-    // Check the data fields.
-    $this->assertNumericField($data['entity_test_mulrev_property_data']['id']);
-    $this->assertField($data['entity_test_mulrev_property_data']['id'], 'id');
-    $this->assertNumericField($data['entity_test_mulrev_property_data']['revision_id']);
-    $this->assertField($data['entity_test_mulrev_property_data']['revision_id'], 'revision_id');
-    $this->assertLanguageField($data['entity_test_mulrev_property_data']['langcode']);
-    $this->assertField($data['entity_test_mulrev_property_data']['langcode'], 'langcode');
-    $this->assertStringField($data['entity_test_mulrev_property_data']['name']);
-    $this->assertField($data['entity_test_mulrev_property_data']['name'], 'name');
-
-    $this->assertLongTextField($data['entity_test_mulrev_property_data'], 'description');
-    $this->assertField($data['entity_test_mulrev_property_data']['description__value'], 'description');
-    $this->assertField($data['entity_test_mulrev_property_data']['description__format'], 'description');
-    $this->assertUriField($data['entity_test_mulrev_property_data']['homepage']);
-    $this->assertField($data['entity_test_mulrev_property_data']['homepage'], 'homepage');
-
-    $this->assertEntityReferenceField($data['entity_test_mulrev_property_data']['user_id']);
-    $this->assertField($data['entity_test_mulrev_property_data']['user_id'], 'user_id');
-    $relationship = $data['entity_test_mulrev_property_data']['user_id']['relationship'];
-    $this->assertEquals('users_field_data', $relationship['base']);
-    $this->assertEquals('uid', $relationship['base field']);
-
-    // Check the property data fields.
-    $this->assertNumericField($data['entity_test_mulrev_property_revision']['id']);
-    $this->assertField($data['entity_test_mulrev_property_revision']['id'], 'id');
-
-    $this->assertLanguageField($data['entity_test_mulrev_property_revision']['langcode']);
-    $this->assertField($data['entity_test_mulrev_property_revision']['langcode'], 'langcode');
-    $this->assertEquals('Translation language', $data['entity_test_mulrev_property_revision']['langcode']['title']);
-
-    $this->assertStringField($data['entity_test_mulrev_property_revision']['name']);
-    $this->assertField($data['entity_test_mulrev_property_revision']['name'], 'name');
-
-    $this->assertLongTextField($data['entity_test_mulrev_property_revision'], 'description');
-    $this->assertField($data['entity_test_mulrev_property_revision']['description__value'], 'description');
-    $this->assertField($data['entity_test_mulrev_property_revision']['description__format'], 'description');
-
-    $this->assertUriField($data['entity_test_mulrev_property_revision']['homepage']);
-    $this->assertField($data['entity_test_mulrev_property_revision']['homepage'], 'homepage');
-
-    $this->assertEntityReferenceField($data['entity_test_mulrev_property_revision']['user_id']);
-    $this->assertField($data['entity_test_mulrev_property_revision']['user_id'], 'user_id');
-    $relationship = $data['entity_test_mulrev_property_revision']['user_id']['relationship'];
-    $this->assertEquals('users_field_data', $relationship['base']);
-    $this->assertEquals('uid', $relationship['base field']);
-
-    $this->assertStringField($data['entity_test_mulrev__string']['string_value']);
-    $this->assertField($data['entity_test_mulrev__string']['string_value'], 'string');
-    $this->assertEquals([
-      'left_field' => 'id',
-      'field' => 'entity_id',
-      'extra' => [[
-          'field' => 'deleted',
-          'value' => 0,
-          'numeric' => TRUE,
-        ],
-      ],
-    ], $data['entity_test_mulrev__string']['table']['join']['entity_test_mulrev_property_data']);
-
-    $this->assertStringField($data['entity_test_mulrev_revision__string']['string_value']);
-    $this->assertField($data['entity_test_mulrev_revision__string']['string_value'], 'string');
-    $this->assertEquals([
-      'left_field' => 'revision_id',
-      'field' => 'entity_id',
-      'extra' => [[
-          'field' => 'deleted',
-          'value' => 0,
-          'numeric' => TRUE,
-        ],
-      ],
-    ], $data['entity_test_mulrev_revision__string']['table']['join']['entity_test_mulrev_property_revision']);
-  }
-
-  /**
-   * Tests generic stuff per field.
-   *
-   * @param array $data
-   *   The views data to check.
-   * @param string $field_name
-   *   The entity field name.
-   */
-  protected function assertField($data, $field_name) {
-    $this->assertEquals($field_name, $data['entity field']);
-  }
-
-  /**
-   * Tests add link types.
-   */
-  public function testEntityLinks() {
-    $this->baseEntityType->setLinkTemplate('canonical', '/entity_test/{entity_test}');
-    $this->baseEntityType->setLinkTemplate('edit-form', '/entity_test/{entity_test}/edit');
-    $this->baseEntityType->setLinkTemplate('delete-form', '/entity_test/{entity_test}/delete');
-
-    $data = $this->viewsData->getViewsData();
-    foreach (['entity_test', 'entity_test_revision'] as $table_name) {
-      $this->assertEquals('entity_link', $data[$table_name]['view_entity_test']['field']['id']);
-      $this->assertEquals('entity_link_edit', $data[$table_name]['edit_entity_test']['field']['id']);
-      $this->assertEquals('entity_link_delete', $data[$table_name]['delete_entity_test']['field']['id']);
-    }
-  }
-
-  /**
-   * Tests additional edit links.
-   */
-  public function testEntityLinksJustEditForm() {
-    $this->baseEntityType->setLinkTemplate('edit-form', '/entity_test/{entity_test}/edit');
-
-    $data = $this->viewsData->getViewsData();
-
-    foreach (['entity_test', 'entity_test_revision'] as $table_name) {
-      $this->assertFalse(isset($data[$table_name]['view_entity_test']));
-      $this->assertFalse(isset($data[$table_name]['delete_entity_test']));
-
-      $this->assertEquals('entity_link_edit', $data[$table_name]['edit_entity_test']['field']['id']);
-    }
-  }
-
-  /**
-   * @covers ::getViewsData
-   */
-  public function testGetViewsDataWithoutEntityOperations() {
-    // Make sure there is no list builder. The API does not document is
-    // supports resetting entity handlers, so this might break in the future.
-    $this->baseEntityType->setListBuilderClass(NULL);
-    $data = $this->viewsData->getViewsData();
-    $this->assertArrayNotHasKey('operations', $data[$this->baseEntityType->getBaseTable()]);
-  }
-
-  /**
-   * @covers ::getViewsData
-   */
-  public function testGetViewsDataWithEntityOperations() {
-    $this->baseEntityType->setListBuilderClass('\Drupal\Core\Entity\EntityListBuilder');
-    $data = $this->viewsData->getViewsData();
-
-    $tables = ['entity_test', 'entity_test_revision'];
-    $this->assertSame($tables, array_keys($data));
-    foreach ($tables as $table_name) {
-      $this->assertSame('entity_operations', $data[$table_name]['operations']['field']['id']);
-    }
-  }
-
-  /**
-   * @covers ::getViewsData
-   */
-  public function testGetViewsDataWithNonRevisionableEntityOperations() {
-    $this->baseEntityType->setListBuilderClass('\Drupal\Core\Entity\EntityListBuilder');
-
-    $entity_type_without_revisions = $this->baseEntityType;
-    $views_data = $this->viewsData;
-
-    $entity_type_keys = $entity_type_without_revisions->getKeys();
-    unset($entity_type_keys['revision']);
-
-    $entity_type_without_revisions->set('entity_keys', $entity_type_keys);
-    $views_data->setEntityType($entity_type_without_revisions);
-
-    $data = $views_data->getViewsData();
-
-    $tables = ['entity_test'];
-    $this->assertSame($tables, array_keys($data));
-    foreach ($tables as $table_name) {
-      $this->assertSame('entity_operations', $data[$table_name]['operations']['field']['id']);
-    }
-  }
-
-  /**
-   * Tests views data for a string field.
-   *
-   * @param $data
-   *   The views data to check.
-   */
-  protected function assertStringField($data) {
-    $this->assertEquals('field', $data['field']['id']);
-    $this->assertEquals('string', $data['filter']['id']);
-    $this->assertEquals('string', $data['argument']['id']);
-    $this->assertEquals('standard', $data['sort']['id']);
-  }
-
-  /**
-   * Tests views data for a URI field.
-   *
-   * @param $data
-   *   The views data to check.
-   */
-  protected function assertUriField($data) {
-    $this->assertEquals('field', $data['field']['id']);
-    $this->assertEquals('string', $data['field']['default_formatter']);
-    $this->assertEquals('string', $data['filter']['id']);
-    $this->assertEquals('string', $data['argument']['id']);
-    $this->assertEquals('standard', $data['sort']['id']);
-  }
-
-  /**
-   * Tests views data for a long text field.
-   *
-   * @param $data
-   *   The views data for the table this field is in.
-   * @param $field_name
-   *   The name of the field being checked.
-   */
-  protected function assertLongTextField($data, $field_name) {
-    $value_field = $data[$field_name . '__value'];
-    $this->assertEquals('field', $value_field['field']['id']);
-    $this->assertEquals($field_name . '__format', $value_field['field']['format']);
-    $this->assertEquals('string', $value_field['filter']['id']);
-    $this->assertEquals('string', $value_field['argument']['id']);
-    $this->assertEquals('standard', $value_field['sort']['id']);
-
-    $this->assertStringField($data[$field_name . '__format']);
-  }
-
-  /**
-   * Tests views data for a UUID field.
-   *
-   * @param array $data
-   *   The views data to check.
-   */
-  protected function assertUuidField($data) {
-    // @todo Can we provide additional support for UUIDs in views?
-    $this->assertEquals('field', $data['field']['id']);
-    $this->assertFalse($data['field']['click sortable']);
-    $this->assertEquals('string', $data['filter']['id']);
-    $this->assertEquals('string', $data['argument']['id']);
-    $this->assertEquals('standard', $data['sort']['id']);
-  }
-
-  /**
-   * Tests views data for a numeric field.
-   *
-   * @param array $data
-   *   The views data to check.
-   */
-  protected function assertNumericField($data) {
-    $this->assertEquals('field', $data['field']['id']);
-    $this->assertEquals('numeric', $data['filter']['id']);
-    $this->assertEquals('numeric', $data['argument']['id']);
-    $this->assertEquals('standard', $data['sort']['id']);
-  }
-
-  /**
-   * Tests views data for a language field.
-   *
-   * @param array $data
-   *   The views data to check.
-   */
-  protected function assertLanguageField($data) {
-    $this->assertEquals('field', $data['field']['id']);
-    $this->assertEquals('language', $data['filter']['id']);
-    $this->assertEquals('language', $data['argument']['id']);
-    $this->assertEquals('standard', $data['sort']['id']);
-  }
-
-  /**
-   * Tests views data for an entity reference field.
-   */
-  protected function assertEntityReferenceField($data) {
-    $this->assertEquals('field', $data['field']['id']);
-    $this->assertEquals('numeric', $data['filter']['id']);
-    $this->assertEquals('numeric', $data['argument']['id']);
-    $this->assertEquals('standard', $data['sort']['id']);
-  }
-
-  /**
-   * Tests views data for a bundle field.
-   */
-  protected function assertBundleField($data) {
-    $this->assertEquals('field', $data['field']['id']);
-    $this->assertEquals('bundle', $data['filter']['id']);
-    $this->assertEquals('string', $data['argument']['id']);
-    $this->assertEquals('standard', $data['sort']['id']);
-  }
-
-  /**
-   * Returns entity info for the user entity.
-   *
-   * @return array
-   */
-  protected static function userEntityInfo() {
-    return new ContentEntityType([
-      'id' => 'user',
-      'class' => 'Drupal\user\Entity\User',
-      'label' => 'User',
-      'base_table' => 'users',
-      'data_table' => 'users_field_data',
-      'entity_keys' => [
-        'id' => 'uid',
-        'uuid' => 'uuid',
-      ],
-    ]);
-  }
-
-}
-
-class TestEntityViewsData extends EntityViewsData {
-
-  public function setEntityType(EntityTypeInterface $entity_type) {
-    $this->entityType = $entity_type;
-  }
-
-}
-
-class TestEntityType extends ContentEntityType {
-
-  /**
-   * Sets a specific entity key.
-   *
-   * @param string $key
-   *   The name of the entity key.
-   * @param string $value
-   *   The new value of the key.
-   *
-   * @return $this
-   */
-  public function setKey($key, $value) {
-    $this->entity_keys[$key] = $value;
-    return $this;
-  }
-
-}
diff --git a/web/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryAggregateTest.php b/web/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryAggregateTest.php
index b0cfc72804..796f6ca139 100644
--- a/web/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryAggregateTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryAggregateTest.php
@@ -139,7 +139,7 @@ public function testAggregation() {
       // We need to check that a character exists before and after the table,
       // column and alias identifiers. These would be the quote characters
       // specific for each database system.
-      $this->assertMatchesRegularExpression('/' . $aggregation_function . '\(.entity_test.\..id.\) AS .id_' . $aggregation_function . './', (string) $query, 'The argument to the aggregation function should be a quoted field.');
+      $this->assertMatchesRegularExpression('/' . $aggregation_function . '\(.*entity_test.\..id.\).* AS .id_' . $aggregation_function . './', (string) $query, 'The argument to the aggregation function should be a quoted field.');
       $this->assertEquals($expected, $this->queryResult);
     }
 
diff --git a/web/core/tests/Drupal/KernelTests/Core/Render/Element/MachineNameTest.php b/web/core/tests/Drupal/KernelTests/Core/Render/Element/MachineNameTest.php
new file mode 100644
index 0000000000..74c3e95a96
--- /dev/null
+++ b/web/core/tests/Drupal/KernelTests/Core/Render/Element/MachineNameTest.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Render\Element;
+
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Render\Element\MachineName
+ * @group Render
+ */
+class MachineNameTest extends KernelTestBase implements FormInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['system'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return __CLASS__;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $element = [
+      '#id' => 'test',
+      '#type' => 'machine_name',
+      '#machine_name' => [
+        'source' => [
+          'test_source',
+        ],
+      ],
+      '#name' => 'test_machine_name',
+      '#default_value' => NULL,
+    ];
+
+    $complete_form = [
+      'test_machine_name' => $element,
+      'test_source' => [
+        '#type' => 'textfield',
+      ],
+    ];
+    return $complete_form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * Tests the order of the machine name field and the source.
+   */
+  public function testMachineNameOrderException() {
+    $this->expectException(\LogicException::class);
+    $this->expectErrorMessage('The machine name element "test_machine_name" is defined before the source element "test_source", it must be defined after or the source element must specify an id.');
+    $form = \Drupal::formBuilder()->getForm($this);
+    $this->render($form);
+  }
+
+}
diff --git a/web/core/tests/Drupal/KernelTests/Core/Theme/ClaroVerticalTabsTest.php b/web/core/tests/Drupal/KernelTests/Core/Theme/ClaroVerticalTabsTest.php
index 6ae6391495..c880c1f377 100644
--- a/web/core/tests/Drupal/KernelTests/Core/Theme/ClaroVerticalTabsTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/Theme/ClaroVerticalTabsTest.php
@@ -26,9 +26,9 @@ public function testVerticalTabs() {
     $this->config('system.theme')->set('default', 'claro')->save();
 
     $form = [
-      '#parents' => [],
+      '#parents' => ['parent'],
       '#array_parents' => [],
-      '#tree' => FALSE,
+      '#tree' => TRUE,
     ];
 
     $form['vertical_tabs'] = [
@@ -38,7 +38,7 @@ public function testVerticalTabs() {
     $form['vertical_tabs_details'] = [
       '#type' => 'details',
       '#title' => 'Details',
-      '#group' => 'vertical_tabs',
+      '#group' => 'parent][vertical_tabs',
     ];
 
     // Needs to be rendered after the vertical tabs.
@@ -61,9 +61,9 @@ public function testVerticalTabs() {
     // Assert that the vertical tab details has the appropriate class.
     $this->assertCount(1, $this->cssSelect('.vertical-tabs__items details.vertical-tabs__item'));
     // Assert that there is a details element.
-    $this->assertCount(1, $this->cssSelect('#edit-container-details'));
+    $this->assertCount(1, $this->cssSelect('#edit-parent-container-details'));
     // Assert that details element doesn't have the vertical tab classes.
-    $this->assertCount(0, $this->cssSelect('#edit-container-details.vertical-tabs__item'));
+    $this->assertCount(0, $this->cssSelect('#edit-parent-container-details.vertical-tabs__item'));
   }
 
 }
diff --git a/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ManageGitIgnoreTest.php b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ManageGitIgnoreTest.php
index dee14dfcf4..e2fe7da222 100644
--- a/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ManageGitIgnoreTest.php
+++ b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ManageGitIgnoreTest.php
@@ -202,7 +202,14 @@ public function testUnmanagedGitIgnoreWhenGitNotAvailable() {
     // executable script named 'git' that always exits with 127, as if git were
     // not found. Note that we run our tests using process isolation, so we do
     // not need to restore the PATH when we are done.
-    $unavailableGitPath = $this->fixtures->binFixtureDir('disable-git-bin');
+    $unavailableGitPath = $sut . '/bin';
+    mkdir($unavailableGitPath);
+    $bash = <<<SH
+#!/bin/bash
+exit 127
+
+SH;
+    file_put_contents($unavailableGitPath . '/git', $bash);
     chmod($unavailableGitPath . '/git', 0755);
     $oldPath = getenv('PATH');
     putenv('PATH=' . $unavailableGitPath . ':' . getenv('PATH'));
diff --git a/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scripts/disable-git-bin/git b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scripts/disable-git-bin/git
deleted file mode 100755
index 36fa746230..0000000000
--- a/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scripts/disable-git-bin/git
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-exit 127
diff --git a/web/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerUnitTest.php b/web/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerUnitTest.php
new file mode 100644
index 0000000000..a27ce06197
--- /dev/null
+++ b/web/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerUnitTest.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\Tests\Core\Asset;
+
+use Drupal\Core\Asset\AssetCollectionGrouperInterface;
+use Drupal\Core\Asset\AssetDumperInterface;
+use Drupal\Core\Asset\AssetOptimizerInterface;
+use Drupal\Core\Asset\CssCollectionOptimizer;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the CSS asset optimizer.
+ *
+ * @group Asset
+ */
+class CssCollectionOptimizerUnitTest extends UnitTestCase {
+
+  /**
+   * The data from the dumper.
+   *
+   * @var string
+   */
+  protected $dumperData;
+
+  /**
+   * A CSS Collection optimizer.
+   *
+   * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
+   */
+  protected $optimizer;
+
+  protected function setUp(): void {
+    parent::setUp();
+    $mock_grouper = $this->createMock(AssetCollectionGrouperInterface::class);
+    $mock_grouper->method('group')
+      ->willReturnCallback(function ($assets) {
+        return [
+          [
+            'items' => $assets,
+            'type' => 'file',
+            'preprocess' => TRUE,
+          ],
+        ];
+      });
+    $mock_optimizer = $this->createMock(AssetOptimizerInterface::class);
+    $mock_optimizer->method('optimize')
+      ->willReturn(
+        file_get_contents(__DIR__ . '/css_test_files/css_input_with_import.css.optimized.css'),
+        file_get_contents(__DIR__ . '/css_test_files/css_subfolder/css_input_with_import.css.optimized.css')
+      );
+    $mock_dumper = $this->createMock(AssetDumperInterface::class);
+    $mock_dumper->method('dump')
+      ->willReturnCallback(function ($css) {
+        $this->dumperData = $css;
+      });
+    $mock_state = $this->createMock(StateInterface::class);
+    $mock_file_system = $this->createMock(FileSystemInterface::class);
+    $this->optimizer = new CssCollectionOptimizer($mock_grouper, $mock_optimizer, $mock_dumper, $mock_state, $mock_file_system);
+  }
+
+  /**
+   * Test that css imports with strange letters do not destroy the css output.
+   */
+  public function testCssImport() {
+    $this->optimizer->optimize([
+      'core/modules/system/tests/modules/common_test/common_test_css_import.css' => [
+        'type' => 'file',
+        'data' => 'core/modules/system/tests/modules/common_test/common_test_css_import.css',
+        'preprocess' => TRUE,
+      ],
+      'core/modules/system/tests/modules/common_test/common_test_css_import_not_preprocessed.css' => [
+        'type' => 'file',
+        'data' => 'core/modules/system/tests/modules/common_test/common_test_css_import.css',
+        'preprocess' => TRUE,
+      ],
+    ]);
+    self::assertEquals(file_get_contents(__DIR__ . '/css_test_files/css_input_with_import.css.optimized.aggregated.css'), $this->dumperData);
+  }
+
+}
diff --git a/web/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php b/web/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php
index dba9fc2ad2..669e221891 100644
--- a/web/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php
+++ b/web/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php
@@ -61,6 +61,8 @@ public function providerTestOptimize() {
       //   file_create_url(). (https://www.drupal.org/node/1961340)
       // - Imported files that are external (protocol-relative URL or not)
       //   should not be expanded. (https://www.drupal.org/node/2014851)
+      //   Potential forms of @import might also include media queries.
+      //   (https://developer.mozilla.org/en-US/docs/Web/CSS/@import)
       [
         [
           'group' => -100,
@@ -72,7 +74,7 @@ public function providerTestOptimize() {
           'browsers' => ['IE' => TRUE, '!IE' => TRUE],
           'basename' => 'css_input_with_import.css',
         ],
-        str_replace('url(images/icon.png)', 'url(' . file_url_transform_relative(file_create_url($path . 'images/icon.png')) . ')', file_get_contents($absolute_path . 'css_input_with_import.css.optimized.css')),
+        str_replace("url('import1.css')", 'url(' . file_url_transform_relative(file_create_url($path . 'import1.css')) . ')', str_replace('url(images/icon.png)', 'url(' . file_url_transform_relative(file_create_url($path . 'images/icon.png')) . ')', file_get_contents($absolute_path . 'css_input_with_import.css.optimized.css'))),
       ],
       // File. Tests:
       // - Retain comment hacks.
diff --git a/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css b/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css
index 34270ee696..d71b91b820 100644
--- a/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css
+++ b/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css
@@ -1,9 +1,25 @@
 
 
-@import "import1.css";
+@import 'import1.css';
 @import "import2.css";
+@import url('import1.css');
+@import url("https://fonts.fontprovider.com/css2?family=Roboto+Mono:wght@300;400&family=Roboto:ital,wght@0,300;0,400;1,300;1,400&display=swap") print;
+@import url(import1.css);
+@import url('import1.css') screen;
 @import url("http://example.com/style.css");
 @import url("//example.com/style.css");
+@import url("https://fonts.fontprovider.com/css2?family=Roboto+Mono:wght@300;400&family=Roboto:ital,wght@0,300;0,400;1,300;1,400&display=swap");
+@import url("http://example.com/style.css") screen and (orientation:landscape);
+@import "http://example.com/style.css" screen;
+@import "http://example.com/style.css" supports(display: table-cell);
+@import "http://example.com/style.css" supports(display: table-cell) screen;
+@import url("http://example.com/style.css") screen and (orientation:landscape);
+@import url("http://example.com/style.css") screen;
+@import url("http://user:pass@example.com/style.css") screen and (orientation:landscape);
+@import url(http://example.com/cus\(t;om.css);
+@import url('http://example.com/cu(st;o)m.css');
+@import url("http://user:pass@example.com/cu(s)t;om.css");
+@import url(http://user:pass@example.com/cu\(s\)t;om.css);
 
 body {
   margin: 0;
@@ -29,4 +45,3 @@ textarea, select {
   font: 1em/160% Verdana, sans-serif;
   color: #494949;
 }
-
diff --git a/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css.optimized.aggregated.css b/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css.optimized.aggregated.css
new file mode 100644
index 0000000000..5ca58da466
--- /dev/null
+++ b/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css.optimized.aggregated.css
@@ -0,0 +1,14 @@
+@import url("https://fonts.fontprovider.com/css2?family=Roboto+Mono:wght@300;400&family=Roboto:ital,wght@0,300;0,400;1,300;1,400&display=swap") print;@import url('import1.css') screen;@import url("http://example.com/style.css");@import url("//example.com/style.css");@import url("https://fonts.fontprovider.com/css2?family=Roboto+Mono:wght@300;400&family=Roboto:ital,wght@0,300;0,400;1,300;1,400&display=swap");@import url("http://example.com/style.css") screen and (orientation:landscape);@import "http://example.com/style.css" screen;@import "http://example.com/style.css" supports(display:table-cell);@import "http://example.com/style.css" supports(display:table-cell) screen;@import url("http://example.com/style.css") screen and (orientation:landscape);@import url("http://example.com/style.css") screen;@import url("http://user:pass@example.com/style.css") screen and (orientation:landscape);@import url(http://example.com/cus\(t;om.css);@import url('http://example.com/cu(st;o)m.css');@import url("http://user:pass@example.com/cu(s)t;om.css");@import url(http://user:pass@example.com/cu\(s\)t;om.css);ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
+p,select{font:1em/160% Verdana,sans-serif;color:#494949;}
+ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
+ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
+body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
+.is
+.a
+.test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;}
+ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(../images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
+p,select{font:1em/160% Verdana,sans-serif;color:#494949;}
+body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
+.is
+.a
+.test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;}
diff --git a/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css.optimized.css b/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css.optimized.css
index 16bd93be7e..a35ef29adb 100644
--- a/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css.optimized.css
+++ b/web/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css.optimized.css
@@ -1,6 +1,8 @@
 ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
 p,select{font:1em/160% Verdana,sans-serif;color:#494949;}
-@import url("http://example.com/style.css");@import url("//example.com/style.css");body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
+ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
+@import url("https://fonts.fontprovider.com/css2?family=Roboto+Mono:wght@300;400&family=Roboto:ital,wght@0,300;0,400;1,300;1,400&display=swap") print;ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
+@import url('import1.css') screen;@import url("http://example.com/style.css");@import url("//example.com/style.css");@import url("https://fonts.fontprovider.com/css2?family=Roboto+Mono:wght@300;400&family=Roboto:ital,wght@0,300;0,400;1,300;1,400&display=swap");@import url("http://example.com/style.css") screen and (orientation:landscape);@import "http://example.com/style.css" screen;@import "http://example.com/style.css" supports(display:table-cell);@import "http://example.com/style.css" supports(display:table-cell) screen;@import url("http://example.com/style.css") screen and (orientation:landscape);@import url("http://example.com/style.css") screen;@import url("http://user:pass@example.com/style.css") screen and (orientation:landscape);@import url(http://example.com/cus\(t;om.css);@import url('http://example.com/cu(st;o)m.css');@import url("http://user:pass@example.com/cu(s)t;om.css");@import url(http://user:pass@example.com/cu\(s\)t;om.css);body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
 .is
 .a
 .test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;}
diff --git a/web/core/tests/Drupal/Tests/Core/Logger/LogMessageParserTest.php b/web/core/tests/Drupal/Tests/Core/Logger/LogMessageParserTest.php
index 996ea54c2f..6e45055b51 100644
--- a/web/core/tests/Drupal/Tests/Core/Logger/LogMessageParserTest.php
+++ b/web/core/tests/Drupal/Tests/Core/Logger/LogMessageParserTest.php
@@ -60,8 +60,8 @@ public function providerTestParseMessagePlaceholders() {
       ],
       // Message with double PSR3 style messages.
       [
-        ['message' => 'Test {with} two {encapsuled} strings', 'context' => ['with' => 'together', 'encapsuled' => 'awesome']],
-        ['message' => 'Test @with two @encapsuled strings', 'context' => ['@with' => 'together', '@encapsuled' => 'awesome']],
+        ['message' => 'Test {with} two {{encapsuled}} strings', 'context' => ['with' => 'together', 'encapsuled' => 'awesome']],
+        ['message' => 'Test @with two {@encapsuled} strings', 'context' => ['@with' => 'together', '@encapsuled' => 'awesome']],
       ],
     ];
   }
diff --git a/web/core/themes/claro/claro.theme b/web/core/themes/claro/claro.theme
index 5916898c02..ba41e6d7c8 100644
--- a/web/core/themes/claro/claro.theme
+++ b/web/core/themes/claro/claro.theme
@@ -747,10 +747,12 @@ function claro_form_system_modules_alter(&$form, FormStateInterface $form_state)
       foreach (Element::children($form['modules'][$key]) as $module_key) {
         if (isset($form['modules'][$key][$module_key]['links'])) {
           foreach ($form['modules'][$key][$module_key]['links'] as $link_key => &$link) {
-            $action_link_type = $link_key_to_action_link_type[$link_key];
-            $link['#options']['attributes']['class'][] = 'action-link';
-            $link['#options']['attributes']['class'][] = 'action-link--small';
-            $link['#options']['attributes']['class'][] = "action-link--icon-$action_link_type";
+            if (array_key_exists($link_key, $link_key_to_action_link_type)) {
+              $action_link_type = $link_key_to_action_link_type[$link_key];
+              $link['#options']['attributes']['class'][] = 'action-link';
+              $link['#options']['attributes']['class'][] = 'action-link--small';
+              $link['#options']['attributes']['class'][] = "action-link--icon-$action_link_type";
+            }
           }
         }
       }
diff --git a/web/core/themes/claro/src/ClaroPreRender.php b/web/core/themes/claro/src/ClaroPreRender.php
index c31505dc2d..40885064e9 100644
--- a/web/core/themes/claro/src/ClaroPreRender.php
+++ b/web/core/themes/claro/src/ClaroPreRender.php
@@ -77,13 +77,10 @@ public static function verticalTabs($element) {
       $last_group_with_child_key = NULL;
       $last_group_with_child_key_last_child_key = NULL;
 
-      // Only iterate through the parents instead of all the group keys.
-      foreach ($element['#parents'] as $group_key) {
-        // Check parents against groups because we are only looking for group
-        // elements.
-        if (!in_array($group_key, $group_keys)) {
-          continue;
-        }
+      $group_key = implode('][', $element['#parents']);
+      // Only check siblings against groups because we are only looking for
+      // group elements.
+      if (in_array($group_key, $group_keys)) {
         $children_keys = Element::children($element['group']['#groups'][$group_key], TRUE);
 
         foreach ($children_keys as $child_key) {
diff --git a/web/core/themes/olivero/css/components/fieldset.css b/web/core/themes/olivero/css/components/fieldset.css
index ce2883e863..679eb821d5 100644
--- a/web/core/themes/olivero/css/components/fieldset.css
+++ b/web/core/themes/olivero/css/components/fieldset.css
@@ -49,7 +49,7 @@
   padding-top: 0;
   padding-bottom: 0;
   color: inherit;
-  border: solid 2px #7e96a7;
+  border: solid 2px #5d7585;
   border-radius: 0.1875rem;
   background-color: #fff;
 }
@@ -78,7 +78,7 @@ _:-ms-fullscreen,
   color: inherit;
   border-top-left-radius: 3px;
   border-top-right-radius: 3px;
-  background-color: #7e96a7;
+  background-color: #5d7585;
   font-size: 1.125rem;
   font-weight: 700;
   line-height: 1.6875rem
@@ -130,7 +130,7 @@ _:-ms-fullscreen,
 }
 
 .fieldset__label.is-disabled {
-  color: #7e96a7;
+  color: #5d7585;
 }
 
 .fieldset__description {
@@ -185,7 +185,7 @@ _:-ms-fullscreen,
 }
 
 .fieldset--group .fieldset__legend--visible ~ .fieldset__wrapper {
-  border: solid 2px #7e96a7;
+  border: solid 2px #5d7585;
   border-bottom-right-radius: 3px;
   border-bottom-left-radius: 3px;
 }
diff --git a/web/core/themes/olivero/css/components/fieldset.pcss.css b/web/core/themes/olivero/css/components/fieldset.pcss.css
index be0bcb62bc..35a70b2738 100644
--- a/web/core/themes/olivero/css/components/fieldset.pcss.css
+++ b/web/core/themes/olivero/css/components/fieldset.pcss.css
@@ -14,7 +14,7 @@
   padding-inline-start: 0;
   padding-inline-end: 0;
   color: inherit;
-  border: solid 2px var(--color--gray-30);
+  border: solid 2px var(--color--gray-25);
   border-radius: var(--border-radius);
   background-color: var(--color--white);
 }
@@ -42,7 +42,7 @@ _:-ms-fullscreen,
   color: inherit;
   border-top-left-radius: var(--border-radius);
   border-top-right-radius: var(--border-radius);
-  background-color: var(--color--gray-30);
+  background-color: var(--color--gray-25);
   font-size: var(--font-size-l);
   font-weight: 700;
   line-height: var(--line-height-base);
@@ -83,7 +83,7 @@ _:-ms-fullscreen,
 }
 
 .fieldset__label.is-disabled {
-  color: var(--color--gray-30);
+  color: var(--color--gray-25);
 }
 
 .fieldset__description {
@@ -126,7 +126,7 @@ _:-ms-fullscreen,
 }
 
 .fieldset--group .fieldset__legend--visible ~ .fieldset__wrapper {
-  border: solid 2px var(--color--gray-30);
+  border: solid 2px var(--color--gray-25);
   border-bottom-right-radius: var(--border-radius);
   border-bottom-left-radius: var(--border-radius);
 }
diff --git a/web/core/themes/olivero/css/components/form-select.css b/web/core/themes/olivero/css/components/form-select.css
index 0fa278ff3a..e51b026e5d 100644
--- a/web/core/themes/olivero/css/components/form-select.css
+++ b/web/core/themes/olivero/css/components/form-select.css
@@ -62,7 +62,7 @@ select:focus {
 
 select {
 
-  /* Disables border of select specifically for ms browser */
+  /* Hides default chevron within Internet Explorer. */
 }
 
 select::-ms-expand {
@@ -99,17 +99,58 @@ select.form-element--small {
 
 select {
 
-  /* Necessary for IE11 to show chevron. */
+  /* Necessary to show chevron in forced colors mode in modern browsers. */
 }
 
-@media screen and (-ms-high-contrast: active) {
+@media (forced-colors: active) {
+
+[dir="ltr"] select {
+    padding-right: 1.125rem
+  }
+
+[dir="rtl"] select {
+    padding-left: 1.125rem
+  }
 
 select {
-    background-image: url("data:image/svg+xml,%3csvg width='18' height='11' viewBox='0 0 18 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M18 1.49699C18 1.35271 17.9279 1.19038 17.8196 1.08216L16.9178 0.18036C16.8096 0.0721439 16.6473 0 16.503 0C16.3587 0 16.1964 0.0721439 16.0882 0.18036L9 7.26854L1.91182 0.18036C1.80361 0.0721439 1.64128 0 1.49699 0C1.33467 0 1.19038 0.0721439 1.08216 0.18036L0.180361 1.08216C0.0721442 1.19038 0 1.35271 0 1.49699C0 1.64128 0.0721442 1.80361 0.180361 1.91182L8.58517 10.3166C8.69339 10.4248 8.85571 10.497 9 10.497C9.14429 10.497 9.30661 10.4248 9.41483 10.3166L17.8196 1.91182C17.9279 1.80361 18 1.64128 18 1.49699Z' fill='%235D7585'/%3e%3c/svg%3e")
+    background-image: none;
+    -webkit-appearance: listbox;
+    -moz-appearance: listbox;
+    appearance: listbox /* Default <select> appearance value for modern browsers. */
+
+    /* Lets browser set <select> appearance to whatever the browser's default is. */
 }
+    @supports ((-webkit-appearance: revert) or (-moz-appearance: revert) or (appearance: revert)) {
 
-    select[multiple] {
-      background-image: none;
+select {
+      -webkit-appearance: revert;
+      -moz-appearance: revert;
+      appearance: revert
+}
+    }
+  }
+
+select {
+
+  /* Necessary for Internet Explorer to show chevron. */
+}
+
+@media screen and (-ms-high-contrast: active) {
+
+[dir="ltr"] select {
+    padding-right: 0
+  }
+
+[dir="rtl"] select {
+    padding-left: 0
+  }
+
+select {
+
+    /* Re-enable default chevron for Internet Explorer. */
+}
+    select::-ms-expand {
+      display: block;
     }
   }
 
diff --git a/web/core/themes/olivero/css/components/form-select.pcss.css b/web/core/themes/olivero/css/components/form-select.pcss.css
index 00a8c2833c..87eba9ab62 100644
--- a/web/core/themes/olivero/css/components/form-select.pcss.css
+++ b/web/core/themes/olivero/css/components/form-select.pcss.css
@@ -38,7 +38,7 @@ select {
     }
   }
 
-  /* Disables border of select specifically for ms browser */
+  /* Hides default chevron within Internet Explorer. */
   &::-ms-expand {
     display: none;
   }
@@ -71,12 +71,25 @@ select {
     height: var(--sp2-5);
   }
 
-  /* Necessary for IE11 to show chevron. */
+  /* Necessary to show chevron in forced colors mode in modern browsers. */
+  @media (forced-colors: active) {
+    padding-inline-end: var(--sp);
+    background-image: none;
+    appearance: listbox; /* Default <select> appearance value for modern browsers. */
+
+    /* Lets browser set <select> appearance to whatever the browser's default is. */
+    @supports (appearance: revert) {
+      appearance: revert;
+    }
+  }
+
+  /* Necessary for Internet Explorer to show chevron. */
   @media screen and (-ms-high-contrast: active) {
-    background-image: var(--form-element-select-icon);
+    padding-inline-end: 0;
 
-    &[multiple] {
-      background-image: none;
+    /* Re-enable default chevron for Internet Explorer. */
+    &::-ms-expand {
+      display: block;
     }
   }
 }
diff --git a/web/core/themes/olivero/css/components/header-search-wide.css b/web/core/themes/olivero/css/components/header-search-wide.css
index 6d86cf313d..53c9ac6352 100644
--- a/web/core/themes/olivero/css/components/header-search-wide.css
+++ b/web/core/themes/olivero/css/components/header-search-wide.css
@@ -402,6 +402,17 @@
     margin-left: auto;
 }
 
+@media (forced-colors: active) {
+
+.block-search-wide__button {
+    background: ButtonFace
+}
+
+    .block-search-wide__button path {
+      fill: ButtonText;
+    }
+  }
+
 /* Provide rudimentary access to site search if JS is disabled. */
 
 html:not(.js) .search-block-form:focus-within .block-search-wide__wrapper {
diff --git a/web/core/themes/olivero/css/components/header-search-wide.pcss.css b/web/core/themes/olivero/css/components/header-search-wide.pcss.css
index ab5adf95ba..bac07058c7 100644
--- a/web/core/themes/olivero/css/components/header-search-wide.pcss.css
+++ b/web/core/themes/olivero/css/components/header-search-wide.pcss.css
@@ -256,6 +256,14 @@
     margin-inline-start: auto;
     margin-inline-end: auto;
   }
+
+  @media (forced-colors: active) {
+    background: ButtonFace;
+
+    & path {
+      fill: ButtonText;
+    }
+  }
 }
 
 /* Provide rudimentary access to site search if JS is disabled. */
diff --git a/web/core/themes/olivero/css/components/site-header.css b/web/core/themes/olivero/css/components/site-header.css
index 879cdd564d..bc9a9fdd1e 100644
--- a/web/core/themes/olivero/css/components/site-header.css
+++ b/web/core/themes/olivero/css/components/site-header.css
@@ -68,6 +68,7 @@
 }
 
 .site-header__inner {
+  z-index: 1; /* Appear in front of Drupal's tabs. */
   flex-grow: 1;
   width: calc(100vw - 5.625rem);
   background: #fff;
diff --git a/web/core/themes/olivero/css/components/site-header.pcss.css b/web/core/themes/olivero/css/components/site-header.pcss.css
index 0a4d69cd2f..7bc651315c 100644
--- a/web/core/themes/olivero/css/components/site-header.pcss.css
+++ b/web/core/themes/olivero/css/components/site-header.pcss.css
@@ -62,6 +62,7 @@
 }
 
 .site-header__inner {
+  z-index: 1; /* Appear in front of Drupal's tabs. */
   flex-grow: 1;
   width: calc(100vw - var(--content-left));
   background: var(--color--white);
diff --git a/web/core/themes/olivero/css/components/vertical-tabs.css b/web/core/themes/olivero/css/components/vertical-tabs.css
index 7793996c04..588be625da 100644
--- a/web/core/themes/olivero/css/components/vertical-tabs.css
+++ b/web/core/themes/olivero/css/components/vertical-tabs.css
@@ -105,6 +105,7 @@
   padding-top: 0.5625rem;
   padding-bottom: 0.5625rem;
   text-decoration: none;
+  color: #0f6292;
   background-color: #e7edf1
 }
 
diff --git a/web/core/themes/olivero/css/components/vertical-tabs.pcss.css b/web/core/themes/olivero/css/components/vertical-tabs.pcss.css
index 52e0b73fa4..be8f2d9d5b 100644
--- a/web/core/themes/olivero/css/components/vertical-tabs.pcss.css
+++ b/web/core/themes/olivero/css/components/vertical-tabs.pcss.css
@@ -68,6 +68,7 @@
   padding-inline-start: var(--sp0-75);
   padding-inline-end: var(--sp0-75);
   text-decoration: none;
+  color: var(--color--blue-10);
   background-color: var(--color--gray-80);
 
   &:focus,
-- 
GitLab