diff --git a/composer.lock b/composer.lock
index 06555cc03665309c910c79454b71aff187fe9f05..2e4118d494a5620e6f190fdca6e12d06fffbe3aa 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 f2c3cba795c6794dae580de3c484c271636a68e1..c3d3fbae53f5db3b3205afd9251efd7cf7131e8f 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 1e63755e084feeb5a42af11b20d70a011fb5cf3b..ddc65fc6a3bbd8b9aa0b61b9f468a5381a77d3f8 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 286a13668d9ba9179ed253c2404502cdf6778375..b4b1be906e0db56ac269cc5e4895f1010f05437b 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 62dff2c7f92aec3f2a1faab1a563175677547404..7d47dad86e6ce688188f5f9a24647c06f9733524 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 e939b3d0542f16fcb700dfc36b0b24fed1a3d45c..0aec443f987e0703d11f3678fb1d08b8466c8291 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 690847e7d5556ad66225059109d60f174085b188..dc0ba4fdc5ffeace1bd4c32b39d90e5162ab5c82 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 9e0532646861ca54931f8095ff5ef41760f10075..555a044c0e29f56cfd4db0d896141e179a44d954 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 d698604b15bf9bfc6716b3fabb7ee6259a5a7e39..2dc416f4bd7b19a8a9b55c0b35efc58c851ac539 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 85d84b75a7bd0f2ee72a94e5bd979bdf906c37a1..2315693eacf442ce5d7c5d44a8f13a73a688d7d3 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 80d6cb2fc6795b4bea1a56410f88a70899180164..9a23b6260e4df9ddd922147c7b5e5df354c0c800 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 20b128c3a06170653a9dded4862cdb3fab214c2f..16fd6cd9aa251e493c393231971dc4b71de7b07a 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 007c25d28946778fc0d06e9e0019920d320e3bc4..6cfa1d6ffcd6041fc67d9159f3dbd5587717344f 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 132599357b6d6ef90611713e62d9e2e38b6a6cf8..8eb01c8015ccb515dd5cbfe3dec9cab95ae8072e 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 1e8a88d471ee89d83f0b2914effb19a4467d57a0..f264f20892277b66172fd4f6ae204091e67680be 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 50685e3b3dad4dfb5211562f5f33d5abeaace9fb..234af073c01bd900fe3a3c86a2bb10fd499e80c3 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 4b9c5e89cca8345ffa856491c6c979b6d59d56fe..70a0711f4030c95d67660f15831019b175d17155 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 a297ff3cea8c662c090767cef1beac9a26d613c6..8298a0ee939efde1c3340667d1b3635e0cbf8a31 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 8b4dbe098013ad70fd6fb172b2dc92229016236d..135cdee2902f0b9dd4c1a90c2354f55f4f86b80a 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 2ccb43cb6106dbf6a8d9ad08f5006c9057c588fe..11215b4403eeed58fe0715a4347515e9ae1ece54 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 c40173d4fca9f38676cb7c60827e07bca50cd7f5..4515535d13242b6d80cffc1b72fff1c7af2287d2 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 a964a26f3117d4f82abb45fdaf0e61c8c3258aa5..10091a8e7607744fc7a3d6169afafd94974f212b 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 e3b7931519ae0760f2ece83724b6591dcb4470c8..1e57cde952b69f3ab99ca101ea1ade89d7e0f898 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 2dd2ed976028818b9cbad43bee47677eb059e8e8..f9dbec18049dbb404a983d87999952ed2be7b9e1 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 70fe59fa4151a984bc6f688e712cd3ddd4d79547..bdb7292d62a83ac1801728de8c8fc14121ba2a93 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 51aee88140011fbbb18408ab848cf6dce8c3b5ce..3d19d3e59961f2d92649e9fdd6b5c0ec5e024e17 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 0000000000000000000000000000000000000000..1a454ea349e432cdb9d07d27fca0ca6e0770a8e6
--- /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 d2c6392ae241a933d41fb01bf2c0433438b4a651..c3fba78ecccddf019bb894c6d64c96e3350518c4 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 ed302ea6dbbacc5de4fc62eb30af359a16b52905..66f24fd2237fd49ae523ba8251cf09d9b322d325 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 505aa1c9aafb73f2986063527ce6b4538ad06eca..c0f58b78e5007959ed226a7624540e794fac3f6f 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 438b2198e613c8d580c20026936c8da302dc7444..9112e9a63ee39dc64ebd040b964adcab71309ec9 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 b571991084bcf0d1a6b1f37ffd720eefafc0ce41..62b4e3d36854c55461c3e725c97a6e3ae6149df7 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 81edff92090ee78e52408797f5608122406df486..51382e6cc6fcd9cda5fad15bf4459b43df4880f2 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 94aaeb43682c5c0f9d35bb7758d9b82c7045dadb..325a27e8baa3e7c9671f5c03c7aa9f4d839fb27c 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 ae71d7dde0ff9a13fd1d5f91b714acb2d201bade..f978146afba9e8b87c7af34882995a9c7ce4d2b6 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 d23c1b52c4fc559345f94706decf4a09f79d60e3..079139b688dba3dfb6967ee93e2c9a015757b601 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 0ea30440a3545514a5a26e5d58c695368483cb09..e1f999718f8887a101cd988bbf63cbc172e76874 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 9e38bb247d7aa3aaf007e500fb6acda3b2b0d733..44a0b42ec41b298e4f032b0464466cda6767904a 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 1ea718af50a2803b698e2db52f75a79dfa9024e3..bec150fe78e3b323a4e41886be806224bc487533 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 5569541d2d43f5590500595a2bcf65526aa4a9ab..b7159b31d4fea107d3119905155415404324b95e 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 47bc9c056dedd941c3efba054de0750c39506c71..ca588d12f7a2e180d0a5d3bcdc0b651ebf15d759 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 0000000000000000000000000000000000000000..21b2a7b9a39b6a526050ff94731388a589086660
--- /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 bc6b453b4317533e4f57b300d48531b18a944030..2184c3bcb4b8e0a46e99e5d75b4cc10d20e1c2ff 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 9576cf7a79db939aff90e9fc5868cbd77d20e32b..870f9790e6591efcdd54bb1a434c558041130a20 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 9fbe85c831f3117e9f86cef7c8fe22d081b32770..d2f4c8b342b1cfc2b6064a68d57dd2ca2f4767af 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 246e635ddf2b16fcde459fb35f87def7567dce91..4eddde832ec42d497eef48f76360151a26b94024 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 6ce9283c5186980e4a53248cf341be7c36897e9b..ae0b11409d3e075a597162d522ac979993e7148f 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 9017a9e461d5b5d4ca253b67894ef7ad8359fa39..89622ab5b0100f7078a7e2a6db995ad6f75060b5 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 287a98e2fdfcb6fa4beae989a798dadd296a1973..29cb946832e8da9bce759dc4d6a85996999498c5 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 4032c9dd05ba95684d21c1c8f9570be7574ff905..db157140ced568544005c68e6b4c1d8ff10c4fb0 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 d9218b538776654f766fdc1bbba2c1e78012e5c6..8ed8137f253f5ad1dfb0d9f3e029ecf3dbb89995 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 0000000000000000000000000000000000000000..62a4a509dd3c334855e06abe701221af98a00c81
--- /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 0000000000000000000000000000000000000000..877fc3bfe7fa51a421893a05bc37a5246dad02ce
--- /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 ba6f5bc41c48e369a36c5ef39f5a6511b9a0eece..8f121c75b4738bc525de07e3267ea1c4d943739b 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 232ed903f9b582194b067e4c91979c7edaad8965..b118f449b1a80c51d4301b5add42241b4f1ab0ec 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 d6721e51f2e5f952493f9618244a72e62fa5517d..1142ec0c2005193b90ea6ecb852b2cbda6e7dff6 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 f9fddb2511c9d5754159dd7a34029156876f1c93..a2c5eed95d1e758b6b1b9a424f7820184664bf77 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 1252265e45b89261f7bff3f5e0dfc8a16b49576a..9ca270c6a17b9e2b593a1a4bb9d4666ed446fb65 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 0000000000000000000000000000000000000000..5d1caf11ae77667cdc728910d894ba5ec1597714
--- /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 e8ff550cbcb2ccde00f33b5e0b68477c955d0779..db12de615073f0cf1d1960c3822bfaf8b48a23cb 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 ca95070bc775dee456d166fb0a8aec5890534052..40fe5f7f537c4bdd40518e94a7b86dad982301d8 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 1b186a6e04a18749d3ccb7e09194364c5e485c44..ff413abf8a60d339feee76f32cc67b253b4d1cd7 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 54b20687342bdbec786c73dc7d9f4bdf685adc90..863e5bee494ad35d7d455a17d9df7caf05484a2e 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 b683186cea1b9cd5a4c8f25e3df13cce355f03eb..0b9e5ce30f1c27349a0a8858b3ed4089378a885e 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 b55a1a218f57ed6e7f08fa12527ee880be38c455..267e896b9bcad6637ea9f42186eb117b1233a2b9 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 c6efe07222775d9d52e283316f12cc23aa1d3ab5..59af3090bd47d0d914496301367e6215e48dc7e6 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 2ded666bff89cd3ab9e338c53e098107f2c2ce64..d565e532036783f43e131f4e799a6cf675f6e8d6 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 603cbbe70a5611dacdb2c5f3f60b899adbd99529..e66c8a170b0f896ed6904f088a86008ca073c584 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 54cce94105510b77a65e34627af9892a5cafadf6..428bfd2b42cdbb37ff76b0cf6e99df7dc0617ed5 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 1b7679ea0dbc00f919799f0fd791137a269c4133..aed5245d0a4a08dda5fc18592681d7f2c0a4788e 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 27d820fb9eb2482162ab1e2b8cee4544bd63b744..98f22edbda6787ac7a1f0f2d7bcb76950c3f75b0 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 eb897ee385802a0d111193ab6546cd08847c6bb3..c41bc5d2e225720656945c4a0cfee462a8c8072e 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 dbb5ed67d6c38418c3bbaf6c1aac5541dc45a079..176749480b5478fe9ab3f35947af739c1d0eedd2 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 eb71189a442a2f3c616981ce28b7cee9aefe29df..32bdfb2eb9b92f3ad1c86382cffd1387ea359c1f 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 5aba90f2acdf14f996f06d035f036b9e2f9a0a0b..16b545c0fc8bcb099c9257a9ec1e28f5341b3377 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 2412555aaf42fcc9a2014afa0390d763ce78c139..0662f876dc5be277476145638bf45eef6d62e204 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 94ef1d68f715b2252929090d62f1da4bb7a92a75..b67b3f335819ee5a37026167bcfab9a519364798 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 2652e6db235a4761b0a29532397689427ebb2e81..b29adb07a8548382529d3ede841d4057b85bd97d 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 4fe426d890d7776bbc5a0fd6e05a192034b273ab..e120ef9cc671aa9e950a38624f6244dc76d2eabd 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 ca61c6c563a05013346544e064c217629c391953..d9ea90c3739d07da0d15eb9f5e33e3031a4a6a9f 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 1d366d34144180b08ecdcfa9f3db93c9cbf36dc0..2c3acd8cf24c32d506e3fe2f75c02fea8c847b7b 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 9a78d61da3ff602041afaa898289197114886687..c6f72ee3eccc5eec6b7858c3e4e23cf3f676ed6f 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 1bbf8ebe65db07d8b133cdd3e0f268d702b60a86..955e3daa221de4d3d6c948c67325d115125ad094 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 f07424b455e31f0410a28c61bc95cd9452c5c9b4..9921fe04a9001f34fc563a55c56a0fad39514630 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 6fd6a1ce4e057a8dbfa4e802c7a86fee1b8caeb7..57397c62bca9ec69c85dc5cb70167acd0a9f2168 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 294da00e31874c1e4a27aae686b89109d26bdfcd..617daf7e4730dbf363e707a4bf76778a1ebda510 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 a06ec0d138c60446e4f25f1476121c33e86aa801..43d5cfaaf4a22a5d4e930bad9d548f1ccc1e62ec 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 50fb58161ab1b2f198881dbfe38e7e32105433ac..c58dba0731ee6ee51cd5d5e27e96de8927596911 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 0000000000000000000000000000000000000000..a218b3ec3ee783ea418ba742a91a846672e73b93
--- /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 2d2396ed87070ff921d494ff089a8e63d7cc0b54..21bcd74d8710eb7af5dcd8650bd39f753252a182 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 6923a2ec5e960f9d1e51ff6620139da42c6db319..71adb4e59d12b39a842b7a595445e3f54c1462b6 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 1f919a1e0b3f5d30493378ad097b9b572138fd01..cbc014054d3c5075601ec2ad65ae63d15fda7403 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 483a30b76ae22f3a9441f40741ac550bb0091be3..5451dcb751567d27c56b1cfda9f7937ce9f9d144 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 ce40c399afb1d80e4fc0b9d6d5c536a5f6b2a493..db87a26fc15e816ad529480b78cbff6b30bceb89 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 591cc441e5a30836203444a4c8cc0358b03d9331..cd4f0b00ac1c125e7c153bbb1b20a8a9424d8612 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 b74fb5e260e1fac637f703d6bb1f5b2288957cf3..41c384876521aa548f0649b43882f198d8455e52 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 f73aa059d44a0fa5e48a27a823166ba5e616ddf5..8445be5cc3f8ab898335790aefad3a857c345618 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 6be90ea8e75205e4422cefc2e60b9bcd2e65beaa..26cdc5408215a14c11c61bcba93be2690ed6ba98 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 99cdcdebdf425bac2b94e5c1b86bf4e446c4ba7c..6c2349f21081853332627062e4eea91dc8823a44 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 c32a77385da647b6148963945f644046afe55d8d..cd52c7bd20c87ce40c70ad5020f1b85203784c16 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 60e38e8e4dde1090d9690e0adf6a873f6935403d..02ba6163f6bcdd10811e1b85e840009f1e99276d 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 2939cd9549861464edc17999dd1450d6d6865f7d..d07842058a60e1040735053baf876ad3318b583c 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 e92ae14e5fac73ebc5926a8090455b8313f0384b..286857a6b6b8a78c77f5ef8fa11c31ecc77488d5 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 067f86b6950edc179b511e4b49fdf79fff94bd34..04c6cead822e36f6bdad3c20111b89c7b1395e81 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 c6200bc16a9671cf9e0e1e1bfff6d4694c858bb1..53a9ac037c68abaa1c53098583104c13be5a2b2c 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 0000000000000000000000000000000000000000..cb027c06e12c5f2e5bbb1aaaabc38be33b9bdb45
--- /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 0000000000000000000000000000000000000000..6bab4cfdbdec1756c16a56aa2bf30df6f42f098a
--- /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 6e0621e40fde5b6d0b9b87e9c4343ce4360b0576..0000000000000000000000000000000000000000
--- 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 b0cfc72804d42f84cf16adb2a1a24351eee0448c..796f6ca1391e61612cabfb54478ad60edd653f7c 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 0000000000000000000000000000000000000000..74c3e95a963b5b4794f16e6c651050deefa62d28
--- /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 6ae639149565575d0dd20058335568defda97233..c880c1f3770bdb39930c0cfc6908ba9b18308344 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 dee14dfcf4a304cfb5b1747b6f7e55a03dac3367..e2fe7da2229d6588b091faa723a737ec5104aece 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 36fa746230ae1c508ab92b249828c15ba2f23ef9..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..a27ce061978067d4a6ceb8541a321f4b7a6dc43a
--- /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 dba9fc2ad2b94615730fe12b5b5c13e08781c6ad..669e2218917b8fc16f0cf6d41f83634d6aacbcda 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 34270ee6966444719889593624a4176fea37f762..d71b91b82007e00b466bb83fc42a18ff4b29ca03 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 0000000000000000000000000000000000000000..5ca58da466734c3048ec3ed45e07637c60ebe275
--- /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 16bd93be7ede890e186df3573dc878a81f12f10f..a35ef29adb1dab70c32bbd9533b551a1e272fef1 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 996ea54c2fb71f43281cf8231340aa669676dcc2..6e45055b512bd4aeaa74adaf64336b29c2df911a 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 5916898c02c8bd99e8308fc6c3e4534625db3633..ba41e6d7c86c95354a5e8442404c7580f2a53074 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 c31505dc2d9c75251a7f37665f1875ca2d377264..40885064e95b2fb2b61276a6b9b442b5715bce4b 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 ce2883e86366368950429a44ccbc3bc65f464007..679eb821d574868ba6914191e6b7f51793aec1a0 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 be0bcb62bc17cc13d73903db6913956716249c14..35a70b2738f3343c4a6e9ffd03a2e2828df84e8f 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 0fa278ff3a6c9557fe17d76b064c62d87020c005..e51b026e5d7e2b3b2e60bedce250d6a6eb6e8912 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 00a8c2833cfa26ad794b04192801e173a1fcdc83..87eba9ab62790893fb012d3c8367070b63b10e30 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 6d86cf313d6530627063ba85f1c449654d5b75c9..53c9ac63524aa155ec9473ab68cd31e13c71e84b 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 ab5adf95ba90da547c0d0140359e804322e50416..bac07058c78a9e4aa445b047048aa261661de37b 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 879cdd564dfa05a95b62589aa340674ab3c0d637..bc9a9fdd1e6feecc2af42a0769fef8c7923e3ac4 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 0a4d69cd2f484888400a84cd49be2226c83a1dc2..7bc651315c4a18ad8822d1845b35e07c29878939 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 7793996c04645ad81bd70d339872da0aac50d6c3..588be625dab2166ed7cf9b3037a01c367eccd16c 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 52e0b73fa45a5ccbc2f0e8a4f722af3a1115607b..be8f2d9d5b1551dd936f5cff101e0b5fa7185043 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,