From e77feeef101c2da2e788d3018f6c070eab61af73 Mon Sep 17 00:00:00 2001
From: "lee.5151" <lee.5151@osu.edu>
Date: Thu, 2 May 2024 13:52:33 -0400
Subject: [PATCH] Upgrading drupal/core (10.2.5 => 10.2.6)

---
 composer.json                                 |   4 +-
 composer.lock                                 |  40 ++---
 vendor/composer/installed.json                |  44 ++---
 vendor/composer/installed.php                 |  70 ++++----
 .../drupal/core-composer-scaffold/Handler.php |   3 +-
 web/core/COPYRIGHT.txt                        |   2 +-
 web/core/USAGE.txt                            |   2 -
 .../scaffold/files/default.settings.php       |   4 +-
 .../assets/scaffold/files/example.sites.php   |   2 +-
 web/core/core.api.php                         |   4 +-
 web/core/lib/Drupal.php                       |   2 +-
 .../Annotation/Doctrine/DocParser.php         |  32 +++-
 .../Drupal/Component/Utility/UrlHelper.php    |  21 ++-
 .../lib/Drupal/Core/Database/database.api.php |   6 +-
 .../OptionsRequestSubscriber.php              |   5 +-
 .../Drupal/Core/Utility/PhpRequirements.php   |   6 +-
 web/core/misc/progress.js                     |   3 +-
 .../modules/ckeditor5/js/build/drupalMedia.js |   2 +-
 .../drupallinkmedia/drupallinkmediaediting.js |   2 +-
 .../ConfigTranslationListUiTest.php           |   5 +
 web/core/modules/contact/contact.module       |   8 +-
 .../src/Functional/ContactPersonalTest.php    |  49 +++++-
 .../tests/src/Unit/MailHandlerTest.php        | 161 +++++-------------
 .../content_translation.module                |   2 +-
 web/core/modules/contextual/contextual.module |   2 +-
 web/core/modules/field_ui/field_ui.module     |   2 +-
 .../tests/src/Kernel/EntityDisplayTest.php    |   5 +-
 .../rest/resource/FileUploadResource.php      |   2 +-
 .../Formatter/FileMediaFormatterTestBase.php  |   2 +-
 .../src/Kernel/FileManagedUnitTestBase.php    |   6 +-
 .../file/tests/src/Kernel/MoveTest.php        |   3 +-
 .../tests/src/Kernel/FilterKernelTest.php     |  33 ++--
 .../ForumListingBreadcrumbBuilderTest.php     |  53 +++---
 .../ForumNodeBreadcrumbBuilderTest.php        |  53 +++---
 web/core/modules/image/image.module           |  13 ++
 .../ImageStyleDownloadController.php          |  65 +++++--
 .../modules/image/src/Entity/ImageStyle.php   |  10 +-
 .../Functional/ImageStylesPathAndUrlTest.php  |  16 ++
 .../image/tests/src/Unit/ImageStyleTest.php   |  57 +++++++
 .../src/Form/DiscardLayoutChangesForm.php     |   4 +-
 .../LayoutBuilderUiTest.php                   |   9 +-
 .../src/Kernel/LinkItemSerializationTest.php  |   2 +-
 .../tests/src/Kernel/MenuLinksTest.php        |   4 +-
 web/core/modules/menu_ui/menu_ui.module       |   2 +-
 .../Plugin/MigrateDestinationInterface.php    |   4 +-
 .../src/Plugin/migrate/source/SqlBase.php     |   2 +-
 .../src/Kernel/mysql/TemporaryQueryTest.php   |   2 +-
 web/core/modules/node/node.module             |   2 +-
 .../tests/src/Kernel/NodeAccessTestBase.php   |  13 +-
 .../tests/src/Kernel/Views/PathPluginTest.php |   2 +-
 .../tests/src/Kernel/Views/RowPluginTest.php  |   4 +-
 web/core/modules/shortcut/shortcut.module     |   2 +-
 .../migrate/destination/NodeCounter.php       |   2 +-
 .../src/Controller/AssetControllerBase.php    |  23 +--
 .../src/Kernel/Common/SystemListingTest.php   |   4 +-
 .../src/Kernel/Form/ProgrammaticTest.php      |  11 +-
 web/core/modules/taxonomy/taxonomy.module     |  17 +-
 .../Views/TaxonomyIndexTidUiTest.php          |  63 ++++++-
 web/core/modules/toolbar/js/toolbar.js        |  30 ++--
 web/core/modules/toolbar/toolbar.module       |   2 +-
 .../release-history/drupal.broken.xml         |   3 +
 .../src/Functional/UpdateSemverCoreTest.php   |   5 +
 .../tests/src/Kernel/WhoIsOnlineBlockTest.php |   3 +-
 .../modules/views/src/ManyToOneHelper.php     |   5 +-
 .../src/Kernel/Handler/FieldKernelTest.php    |  31 ++--
 .../Drupal/KernelTests/ConfigFormTestBase.php |  10 +-
 .../Core/Database/InsertLobTest.php           |   4 +-
 .../Core/Database/UpdateLobTest.php           |   4 +-
 .../Core/Entity/ContentEntityCloneTest.php    |  33 ++--
 .../KernelTests/Core/File/FileTestBase.php    |   4 +-
 .../KernelTests/Core/File/HtaccessTest.php    |  11 +-
 .../Core/Path/PathValidatorTest.php           |   4 +-
 .../MarkupInterfaceComparatorTest.php         |   3 +-
 .../KernelTests/Core/Theme/ClaroTableTest.php |  32 ++++
 .../Annotation/Doctrine/DocParserTest.php     |   4 +
 .../Component/Serialization/YamlTestBase.php  |  10 +-
 .../Tests/Component/Utility/UrlHelperTest.php |   4 +-
 .../Scaffold/Functional/ComposerHookTest.php  |  11 ++
 .../composer.json                             |  16 ++
 ...ComposerPluginImplementsScaffoldEvents.php |  64 +++++++
 .../composer.json.tmpl                        |  44 +++++
 .../Config/Entity/Query/QueryFactoryTest.php  |  61 ++++---
 .../Tests/Core/Error/DrupalLogErrorTest.php   |   2 +-
 .../RecursiveContextualValidatorTest.php      |  14 +-
 web/core/themes/claro/claro.theme             |   2 +-
 web/core/themes/claro/src/ClaroPreRender.php  |   2 +-
 .../themes/olivero/css/components/form.css    |   4 +
 .../olivero/css/components/form.pcss.css      |   6 +
 web/sites/default/default.settings.php        |   4 +-
 web/sites/example.sites.php                   |   2 +-
 90 files changed, 894 insertions(+), 513 deletions(-)
 create mode 100644 web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/composer-plugin-implements-scaffold-events/composer.json
 create mode 100644 web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/composer-plugin-implements-scaffold-events/src/ComposerPluginImplementsScaffoldEvents.php
 create mode 100644 web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scaffold-events-fixture/composer.json.tmpl

diff --git a/composer.json b/composer.json
index e2eaf2fac0..5f09a500d6 100644
--- a/composer.json
+++ b/composer.json
@@ -104,8 +104,8 @@
         "drupal/cache_control_override": "^2.0",
         "drupal/ckeditor_indentblock": "^1.0",
         "drupal/config_ignore": "3.3",
-        "drupal/core-composer-scaffold": "10.2.5",
-        "drupal/core-recommended": "10.2.5",
+        "drupal/core-composer-scaffold": "10.2.6",
+        "drupal/core-recommended": "10.2.6",
         "drupal/crop": "2.3",
         "drupal/ctools": "^4.0",
         "drupal/decorative_image_widget": "^1.0",
diff --git a/composer.lock b/composer.lock
index 2c6baecee2..dafedf0334 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "8b5e1958cf88ad39dcc9ce56ecb73c5d",
+    "content-hash": "0f9e66ded600b67788591da093f17bd2",
     "packages": [
         {
             "name": "algolia/places",
@@ -2183,16 +2183,16 @@
         },
         {
             "name": "drupal/core",
-            "version": "10.2.5",
+            "version": "10.2.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core.git",
-                "reference": "dddd242b74f40df892a7f16a48245c3b76d9b003"
+                "reference": "cec9bc9e829e53e667da844edd5f4897be88d860"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core/zipball/dddd242b74f40df892a7f16a48245c3b76d9b003",
-                "reference": "dddd242b74f40df892a7f16a48245c3b76d9b003",
+                "url": "https://api.github.com/repos/drupal/core/zipball/cec9bc9e829e53e667da844edd5f4897be88d860",
+                "reference": "cec9bc9e829e53e667da844edd5f4897be88d860",
                 "shasum": ""
             },
             "require": {
@@ -2340,22 +2340,22 @@
             ],
             "description": "Drupal is an open source content management platform powering millions of websites and applications.",
             "support": {
-                "source": "https://github.com/drupal/core/tree/10.2.5"
+                "source": "https://github.com/drupal/core/tree/10.2.6"
             },
-            "time": "2024-04-03T07:19:20+00:00"
+            "time": "2024-05-01T21:00:24+00:00"
         },
         {
             "name": "drupal/core-composer-scaffold",
-            "version": "10.2.5",
+            "version": "10.2.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core-composer-scaffold.git",
-                "reference": "63effa1bc644e80a269e8b4415e627491d26fd3f"
+                "reference": "adc702b6ef38a0446abe90267acb96aa806995cf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core-composer-scaffold/zipball/63effa1bc644e80a269e8b4415e627491d26fd3f",
-                "reference": "63effa1bc644e80a269e8b4415e627491d26fd3f",
+                "url": "https://api.github.com/repos/drupal/core-composer-scaffold/zipball/adc702b6ef38a0446abe90267acb96aa806995cf",
+                "reference": "adc702b6ef38a0446abe90267acb96aa806995cf",
                 "shasum": ""
             },
             "require": {
@@ -2390,22 +2390,22 @@
                 "drupal"
             ],
             "support": {
-                "source": "https://github.com/drupal/core-composer-scaffold/tree/10.2.5"
+                "source": "https://github.com/drupal/core-composer-scaffold/tree/10.2.6"
             },
-            "time": "2024-01-26T14:59:30+00:00"
+            "time": "2024-04-09T07:27:23+00:00"
         },
         {
             "name": "drupal/core-recommended",
-            "version": "10.2.5",
+            "version": "10.2.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core-recommended.git",
-                "reference": "bd7fe9e734a82762814d9c31255cd362d9c044f1"
+                "reference": "6fbff9a26e06c047ec4a2313fc423a7a1c51c850"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core-recommended/zipball/bd7fe9e734a82762814d9c31255cd362d9c044f1",
-                "reference": "bd7fe9e734a82762814d9c31255cd362d9c044f1",
+                "url": "https://api.github.com/repos/drupal/core-recommended/zipball/6fbff9a26e06c047ec4a2313fc423a7a1c51c850",
+                "reference": "6fbff9a26e06c047ec4a2313fc423a7a1c51c850",
                 "shasum": ""
             },
             "require": {
@@ -2414,7 +2414,7 @@
                 "doctrine/annotations": "~1.14.3",
                 "doctrine/deprecations": "~1.1.2",
                 "doctrine/lexer": "~2.1.0",
-                "drupal/core": "10.2.5",
+                "drupal/core": "10.2.6",
                 "egulias/email-validator": "~4.0.2",
                 "guzzlehttp/guzzle": "~7.8.1",
                 "guzzlehttp/promises": "~2.0.2",
@@ -2475,9 +2475,9 @@
             ],
             "description": "Core and its dependencies with known-compatible minor versions. Require this project INSTEAD OF drupal/core.",
             "support": {
-                "source": "https://github.com/drupal/core-recommended/tree/10.2.5"
+                "source": "https://github.com/drupal/core-recommended/tree/10.2.6"
             },
-            "time": "2024-04-03T07:19:20+00:00"
+            "time": "2024-05-01T21:00:24+00:00"
         },
         {
             "name": "drupal/crop",
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 168810aa53..3c69a301cb 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -2296,17 +2296,17 @@
         },
         {
             "name": "drupal/core",
-            "version": "10.2.5",
-            "version_normalized": "10.2.5.0",
+            "version": "10.2.6",
+            "version_normalized": "10.2.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core.git",
-                "reference": "dddd242b74f40df892a7f16a48245c3b76d9b003"
+                "reference": "cec9bc9e829e53e667da844edd5f4897be88d860"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core/zipball/dddd242b74f40df892a7f16a48245c3b76d9b003",
-                "reference": "dddd242b74f40df892a7f16a48245c3b76d9b003",
+                "url": "https://api.github.com/repos/drupal/core/zipball/cec9bc9e829e53e667da844edd5f4897be88d860",
+                "reference": "cec9bc9e829e53e667da844edd5f4897be88d860",
                 "shasum": ""
             },
             "require": {
@@ -2386,7 +2386,7 @@
             "suggest": {
                 "ext-zip": "Needed to extend the plugin.manager.archiver service capability with the handling of files in the ZIP format."
             },
-            "time": "2024-04-03T07:19:20+00:00",
+            "time": "2024-05-01T21:00:24+00:00",
             "type": "drupal-core",
             "extra": {
                 "drupal-scaffold": {
@@ -2460,23 +2460,23 @@
             ],
             "description": "Drupal is an open source content management platform powering millions of websites and applications.",
             "support": {
-                "source": "https://github.com/drupal/core/tree/10.2.5"
+                "source": "https://github.com/drupal/core/tree/10.2.6"
             },
             "install-path": "../../web/core"
         },
         {
             "name": "drupal/core-composer-scaffold",
-            "version": "10.2.5",
-            "version_normalized": "10.2.5.0",
+            "version": "10.2.6",
+            "version_normalized": "10.2.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core-composer-scaffold.git",
-                "reference": "63effa1bc644e80a269e8b4415e627491d26fd3f"
+                "reference": "adc702b6ef38a0446abe90267acb96aa806995cf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core-composer-scaffold/zipball/63effa1bc644e80a269e8b4415e627491d26fd3f",
-                "reference": "63effa1bc644e80a269e8b4415e627491d26fd3f",
+                "url": "https://api.github.com/repos/drupal/core-composer-scaffold/zipball/adc702b6ef38a0446abe90267acb96aa806995cf",
+                "reference": "adc702b6ef38a0446abe90267acb96aa806995cf",
                 "shasum": ""
             },
             "require": {
@@ -2489,7 +2489,7 @@
             "require-dev": {
                 "composer/composer": "^1.8@stable"
             },
-            "time": "2024-01-26T14:59:30+00:00",
+            "time": "2024-04-09T07:27:23+00:00",
             "type": "composer-plugin",
             "extra": {
                 "class": "Drupal\\Composer\\Plugin\\Scaffold\\Plugin",
@@ -2513,23 +2513,23 @@
                 "drupal"
             ],
             "support": {
-                "source": "https://github.com/drupal/core-composer-scaffold/tree/10.2.5"
+                "source": "https://github.com/drupal/core-composer-scaffold/tree/10.2.6"
             },
             "install-path": "../drupal/core-composer-scaffold"
         },
         {
             "name": "drupal/core-recommended",
-            "version": "10.2.5",
-            "version_normalized": "10.2.5.0",
+            "version": "10.2.6",
+            "version_normalized": "10.2.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/drupal/core-recommended.git",
-                "reference": "bd7fe9e734a82762814d9c31255cd362d9c044f1"
+                "reference": "6fbff9a26e06c047ec4a2313fc423a7a1c51c850"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/drupal/core-recommended/zipball/bd7fe9e734a82762814d9c31255cd362d9c044f1",
-                "reference": "bd7fe9e734a82762814d9c31255cd362d9c044f1",
+                "url": "https://api.github.com/repos/drupal/core-recommended/zipball/6fbff9a26e06c047ec4a2313fc423a7a1c51c850",
+                "reference": "6fbff9a26e06c047ec4a2313fc423a7a1c51c850",
                 "shasum": ""
             },
             "require": {
@@ -2538,7 +2538,7 @@
                 "doctrine/annotations": "~1.14.3",
                 "doctrine/deprecations": "~1.1.2",
                 "doctrine/lexer": "~2.1.0",
-                "drupal/core": "10.2.5",
+                "drupal/core": "10.2.6",
                 "egulias/email-validator": "~4.0.2",
                 "guzzlehttp/guzzle": "~7.8.1",
                 "guzzlehttp/promises": "~2.0.2",
@@ -2592,7 +2592,7 @@
             "conflict": {
                 "webflo/drupal-core-strict": "*"
             },
-            "time": "2024-04-03T07:19:20+00:00",
+            "time": "2024-05-01T21:00:24+00:00",
             "type": "metapackage",
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -2600,7 +2600,7 @@
             ],
             "description": "Core and its dependencies with known-compatible minor versions. Require this project INSTEAD OF drupal/core.",
             "support": {
-                "source": "https://github.com/drupal/core-recommended/tree/10.2.5"
+                "source": "https://github.com/drupal/core-recommended/tree/10.2.6"
             },
             "install-path": null
         },
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 4e54f5b87f..c66606befb 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => 'osu-asc-webservices/d8-upstream',
         'pretty_version' => 'dev-main',
         'version' => 'dev-main',
-        'reference' => 'ad7c08f3daec76a3d23c16cfe61eca6c36b44fff',
+        'reference' => 'a9dfcfd37f27e26840003568e79a6fe67d580020',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -377,9 +377,9 @@
             'dev_requirement' => false,
         ),
         'drupal/core' => array(
-            'pretty_version' => '10.2.5',
-            'version' => '10.2.5.0',
-            'reference' => 'dddd242b74f40df892a7f16a48245c3b76d9b003',
+            'pretty_version' => '10.2.6',
+            'version' => '10.2.6.0',
+            'reference' => 'cec9bc9e829e53e667da844edd5f4897be88d860',
             'type' => 'drupal-core',
             'install_path' => __DIR__ . '/../../web/core',
             'aliases' => array(),
@@ -388,25 +388,25 @@
         'drupal/core-annotation' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-assertion' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-class-finder' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-composer-scaffold' => array(
-            'pretty_version' => '10.2.5',
-            'version' => '10.2.5.0',
-            'reference' => '63effa1bc644e80a269e8b4415e627491d26fd3f',
+            'pretty_version' => '10.2.6',
+            'version' => '10.2.6.0',
+            'reference' => 'adc702b6ef38a0446abe90267acb96aa806995cf',
             'type' => 'composer-plugin',
             'install_path' => __DIR__ . '/../drupal/core-composer-scaffold',
             'aliases' => array(),
@@ -415,97 +415,97 @@
         'drupal/core-datetime' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-dependency-injection' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-diff' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-discovery' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-event-dispatcher' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-file-cache' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-file-security' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-filesystem' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-front-matter' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-gettext' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-graph' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-http-foundation' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-php-storage' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-plugin' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-proxy-builder' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-recommended' => array(
-            'pretty_version' => '10.2.5',
-            'version' => '10.2.5.0',
-            'reference' => 'bd7fe9e734a82762814d9c31255cd362d9c044f1',
+            'pretty_version' => '10.2.6',
+            'version' => '10.2.6.0',
+            'reference' => '6fbff9a26e06c047ec4a2313fc423a7a1c51c850',
             'type' => 'metapackage',
             'install_path' => NULL,
             'aliases' => array(),
@@ -514,37 +514,37 @@
         'drupal/core-render' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-serialization' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-transliteration' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-utility' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-uuid' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/core-version' => array(
             'dev_requirement' => false,
             'replaced' => array(
-                0 => '10.2.5',
+                0 => '10.2.6',
             ),
         ),
         'drupal/crop' => array(
@@ -1528,7 +1528,7 @@
         'osu-asc-webservices/d8-upstream' => array(
             'pretty_version' => 'dev-main',
             'version' => 'dev-main',
-            'reference' => 'ad7c08f3daec76a3d23c16cfe61eca6c36b44fff',
+            'reference' => 'a9dfcfd37f27e26840003568e79a6fe67d580020',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
diff --git a/vendor/drupal/core-composer-scaffold/Handler.php b/vendor/drupal/core-composer-scaffold/Handler.php
index 3e51f909a3..a3e9914bb3 100644
--- a/vendor/drupal/core-composer-scaffold/Handler.php
+++ b/vendor/drupal/core-composer-scaffold/Handler.php
@@ -3,7 +3,6 @@
 namespace Drupal\Composer\Plugin\Scaffold;
 
 use Composer\Composer;
-use Composer\EventDispatcher\EventDispatcher;
 use Composer\Installer\PackageEvent;
 use Composer\IO\IOInterface;
 use Composer\Package\PackageInterface;
@@ -143,7 +142,7 @@ public function scaffold() {
     }
 
     // Call any pre-scaffold scripts that may be defined.
-    $dispatcher = new EventDispatcher($this->composer, $this->io);
+    $dispatcher = $this->composer->getEventDispatcher();
     $dispatcher->dispatch(self::PRE_DRUPAL_SCAFFOLD_CMD);
 
     // Fetch the list of file mappings from each allowed package and normalize
diff --git a/web/core/COPYRIGHT.txt b/web/core/COPYRIGHT.txt
index 1f6be29a35..d4d09ff8b3 100644
--- a/web/core/COPYRIGHT.txt
+++ b/web/core/COPYRIGHT.txt
@@ -26,7 +26,7 @@ JavaScript
 
   CKEditor5 - Copyright (c) 2003 CKSource Holding sp. z o.o.
 
-  JavaScript Cookie - Copyright (c) 2006 Klaus Hartl & Fagner Brack
+  JavaScript Cookie - Copyright (c) 2018 Copyright 2018 Klaus Hartl, Fagner Brack, GitHub Contributors
 
   jQuery - Copyright (c) OpenJS Foundation and other contributors
 
diff --git a/web/core/USAGE.txt b/web/core/USAGE.txt
index 9b772874f3..f9bfea503b 100644
--- a/web/core/USAGE.txt
+++ b/web/core/USAGE.txt
@@ -15,8 +15,6 @@ More about configuration:
    See INSTALL.txt and UPDATE.txt in the "core" directory.
  * Learn about how to use Drupal to create your site:
    https://www.drupal.org/documentation
- * Follow best practices:
-   https://www.drupal.org/best-practices
  * Download contributed modules to /modules to extend Drupal's functionality:
    https://www.drupal.org/project/project_module
  * See also: "Developing for Drupal" for writing your own modules, below.
diff --git a/web/core/assets/scaffold/files/default.settings.php b/web/core/assets/scaffold/files/default.settings.php
index 63fb2df74a..8819d64317 100644
--- a/web/core/assets/scaffold/files/default.settings.php
+++ b/web/core/assets/scaffold/files/default.settings.php
@@ -181,8 +181,8 @@
  *
  * WARNING: The above defaults are designed for database portability. Changing
  * them may cause unexpected behavior, including potential data loss. See
- * https://www.drupal.org/developing/api/database/configuration for more
- * information on these defaults and the potential issues.
+ * https://www.drupal.org/docs/8/api/database-api/database-configuration for
+ * more information on these defaults and the potential issues.
  *
  * More details can be found in the constructor methods for each driver:
  * - \Drupal\mysql\Driver\Database\mysql\Connection::__construct()
diff --git a/web/core/assets/scaffold/files/example.sites.php b/web/core/assets/scaffold/files/example.sites.php
index 3b32b5aba1..f84da04588 100644
--- a/web/core/assets/scaffold/files/example.sites.php
+++ b/web/core/assets/scaffold/files/example.sites.php
@@ -53,5 +53,5 @@
  *
  * @see default.settings.php
  * @see \Drupal\Core\DrupalKernel::getSitePath()
- * @see https://www.drupal.org/documentation/install/multi-site
+ * @see https://www.drupal.org/docs/getting-started/multisite-drupal
  */
diff --git a/web/core/core.api.php b/web/core/core.api.php
index bc7547d411..f187114139 100644
--- a/web/core/core.api.php
+++ b/web/core/core.api.php
@@ -1555,9 +1555,7 @@
  * Ideally, all code that is included in Drupal Core and contributed modules,
  * themes, and distributions will be secure, internationalized, maintainable,
  * and efficient. In order to facilitate this, the Drupal community has
- * developed a set of guidelines and standards for developers to follow. Most of
- * these standards can be found under
- * @link https://www.drupal.org/developing/best-practices Best practices on Drupal.org @endlink
+ * developed a set of guidelines and standards for developers to follow.
  *
  * Standards and best practices that developers should be aware of include:
  * - Security: https://www.drupal.org/writing-secure-code and the
diff --git a/web/core/lib/Drupal.php b/web/core/lib/Drupal.php
index d16d0d14a8..ac43522e2f 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 = '10.2.5';
+  const VERSION = '10.2.6';
 
   /**
    * Core API compatibility.
diff --git a/web/core/lib/Drupal/Component/Annotation/Doctrine/DocParser.php b/web/core/lib/Drupal/Component/Annotation/Doctrine/DocParser.php
index 960c4869ff..fb0ce98134 100644
--- a/web/core/lib/Drupal/Component/Annotation/Doctrine/DocParser.php
+++ b/web/core/lib/Drupal/Component/Annotation/Doctrine/DocParser.php
@@ -937,10 +937,16 @@ private function Constant()
             }
         }
 
-        // checks if identifier ends with ::class, \strlen('::class') === 7
-        $classPos = stripos($identifier, '::class');
-        if ($classPos === strlen($identifier) - 7) {
-            return substr($identifier, 0, $classPos);
+        /**
+         * Checks if identifier ends with ::class and remove the leading backslash if it exists.
+         */
+        if ($this->identifierEndsWithClassConstant($identifier) && ! $this->identifierStartsWithBackslash($identifier))
+        {
+            return substr($identifier, 0, $this->getClassConstantPositionInIdentifier($identifier));
+        }
+        if ($this->identifierEndsWithClassConstant($identifier) && $this->identifierStartsWithBackslash($identifier))
+        {
+            return substr($identifier, 1, $this->getClassConstantPositionInIdentifier($identifier) - 1);
         }
 
         if (!defined($identifier)) {
@@ -950,6 +956,24 @@ private function Constant()
         return constant($identifier);
     }
 
+    private function identifierStartsWithBackslash(string $identifier) : bool
+    {
+        return '\\' === $identifier[0];
+    }
+
+    private function identifierEndsWithClassConstant(string $identifier) : bool
+    {
+        return $this->getClassConstantPositionInIdentifier($identifier) === strlen($identifier) - strlen('::class');
+    }
+
+    /**
+     * @return int|false
+     */
+    private function getClassConstantPositionInIdentifier(string $identifier)
+    {
+        return stripos($identifier, '::class');
+    }
+
     /**
      * Identifier ::= string
      *
diff --git a/web/core/lib/Drupal/Component/Utility/UrlHelper.php b/web/core/lib/Drupal/Component/Utility/UrlHelper.php
index 6bc65584cf..14ae12a8e8 100644
--- a/web/core/lib/Drupal/Component/Utility/UrlHelper.php
+++ b/web/core/lib/Drupal/Component/Utility/UrlHelper.php
@@ -80,6 +80,9 @@ public static function buildQuery(array $query, $parent = '') {
    *   The data compressed into a URL-safe string.
    */
   public static function compressQueryParameter(string $data): string {
+    if (!\extension_loaded('zlib')) {
+      return $data;
+    }
     // Use 'base64url' encoding. Note that the '=' sign is only used for padding
     // on the right of the string, and is otherwise not part of the data.
     // @see https://datatracker.ietf.org/doc/html/rfc4648#section-5
@@ -96,13 +99,23 @@ public static function compressQueryParameter(string $data): string {
    *   A string as compressed by
    *   \Drupal\Component\Utility\UrlHelper::compressQueryParameter().
    *
-   * @return string|bool
-   *   The uncompressed data or FALSE on failure.
+   * @return string
+   *   The uncompressed data, or the original string if it cannot be
+   *   uncompressed.
    */
-  public static function uncompressQueryParameter(string $compressed): string|bool {
+  public static function uncompressQueryParameter(string $compressed): string {
+    if (!\extension_loaded('zlib')) {
+      return $compressed;
+    }
     // Because this comes from user data, suppress the PHP warning that
     // gzcompress() throws if the base64-encoded string is invalid.
-    return @gzuncompress(base64_decode(str_replace(['-', '_'], ['+', '/'], $compressed)));
+    $return = @gzuncompress(base64_decode(str_replace(['-', '_'], ['+', '/'], $compressed)));
+
+    // If we failed to uncompress the query parameter, it may be a stale link
+    // from before compression was implemented with the URL parameter
+    // uncompressed already, or it may be an incorrectly formatted URL.
+    // In either case, pass back the original string to the caller.
+    return $return === FALSE ? $compressed : $return;
   }
 
   /**
diff --git a/web/core/lib/Drupal/Core/Database/database.api.php b/web/core/lib/Drupal/Core/Database/database.api.php
index 28b8e42b29..c713d1a623 100644
--- a/web/core/lib/Drupal/Core/Database/database.api.php
+++ b/web/core/lib/Drupal/Core/Database/database.api.php
@@ -29,7 +29,7 @@
  * mysqli or oci8.
  *
  * For more detailed information on the database abstraction layer, see
- * https://www.drupal.org/docs/8/api/database-api/database-api-overview.
+ * https://www.drupal.org/docs/drupal-apis/database-api/database-api-overview.
  *
  * @section sec_entity Querying entities
  * Any query on Drupal entities or fields should use the Entity Query API. See
@@ -119,7 +119,7 @@
  *
  * There are also methods to join to other tables, add fields with aliases,
  * isNull() to query for NULL values, etc. See
- * https://www.drupal.org/developing/api/database for many more details.
+ * https://www.drupal.org/docs/drupal-apis/database-api for many more details.
  *
  * One note on chaining: It is common in the dynamic database API to chain
  * method calls (as illustrated here), because most of the query methods modify
@@ -240,7 +240,7 @@
  * if you had a connection object variable $connection available to use. See
  * also the @link container Services and Dependency Injection topic. @endlink
  *
- * @see https://www.drupal.org/developing/api/database
+ * @see https://www.drupal.org/docs/drupal-apis/database-api
  * @see entity_api
  * @see schemaapi
  *
diff --git a/web/core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php b/web/core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php
index 66f5571081..4bd400a366 100644
--- a/web/core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php
+++ b/web/core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php
@@ -12,8 +12,9 @@
 /**
  * Handles options requests.
  *
- * Therefore it sends an options response using all methods on all possible
- * routes.
+ * Listens to KernelEvents::REQUEST and responds to OPTIONS requests by
+ * providing an Allow header listing all the HTTP methods allowed for the
+ * requested routes.
  */
 class OptionsRequestSubscriber implements EventSubscriberInterface {
 
diff --git a/web/core/lib/Drupal/Core/Utility/PhpRequirements.php b/web/core/lib/Drupal/Core/Utility/PhpRequirements.php
index e80882b0be..0d099a79f1 100644
--- a/web/core/lib/Drupal/Core/Utility/PhpRequirements.php
+++ b/web/core/lib/Drupal/Core/Utility/PhpRequirements.php
@@ -31,9 +31,9 @@ final class PhpRequirements {
    *   by the PHP version.
    */
   private static $phpEolDates = [
-    '8.1' => '2024-11-25',
-    '8.2' => '2025-12-08',
-    '8.3' => '2026-11-23',
+    '8.1' => '2025-12-31',
+    '8.2' => '2026-12-31',
+    '8.3' => '2027-12-31',
   ];
 
   /**
diff --git a/web/core/misc/progress.js b/web/core/misc/progress.js
index bbf70365e1..a38285e27a 100644
--- a/web/core/misc/progress.js
+++ b/web/core/misc/progress.js
@@ -14,8 +14,9 @@
    *   The HTML for the progress bar.
    */
   Drupal.theme.progressBar = function (id) {
+    const escapedId = Drupal.checkPlain(id);
     return (
-      `<div id="${id}" class="progress" aria-live="polite">` +
+      `<div id="${escapedId}" class="progress" aria-live="polite">` +
       '<div class="progress__label">&nbsp;</div>' +
       '<div class="progress__track"><div class="progress__bar"></div></div>' +
       '<div class="progress__percentage"></div>' +
diff --git a/web/core/modules/ckeditor5/js/build/drupalMedia.js b/web/core/modules/ckeditor5/js/build/drupalMedia.js
index f2a730c4ba..65477c8dc3 100644
--- a/web/core/modules/ckeditor5/js/build/drupalMedia.js
+++ b/web/core/modules/ckeditor5/js/build/drupalMedia.js
@@ -1 +1 @@
-!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.CKEditor5=t():(e.CKEditor5=e.CKEditor5||{},e.CKEditor5.drupalMedia=t())}(globalThis,(()=>(()=>{var e={"ckeditor5/src/core.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/core.js")},"ckeditor5/src/engine.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/engine.js")},"ckeditor5/src/ui.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/ui.js")},"ckeditor5/src/utils.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/utils.js")},"ckeditor5/src/widget.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/widget.js")},"dll-reference CKEditor5.dll":e=>{"use strict";e.exports=CKEditor5.dll}},t={};function i(n){var a=t[n];if(void 0!==a)return a.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,i),r.exports}i.d=(e,t)=>{for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var n={};return(()=>{"use strict";i.d(n,{default:()=>ue});var e=i("ckeditor5/src/core.js"),t=i("ckeditor5/src/widget.js");function a(e){return!!e&&e.is("element","drupalMedia")}function r(e){return(0,t.isWidget)(e)&&!!e.getCustomProperty("drupalMedia")}function o(e){const t=e.getSelectedElement();return a(t)?t:e.getFirstPosition().findAncestor("drupalMedia")}function s(e){const t=e.getSelectedElement();if(t&&r(t))return t;if(null===e.getFirstPosition())return null;let i=e.getFirstPosition().parent;for(;i;){if(i.is("element")&&r(i))return i;i=i.parent}return null}function l(e){const t=typeof e;return null!=e&&("object"===t||"function"===t)}function d(e){for(const t of e){if(t.hasAttribute("data-drupal-media-preview"))return t;if(t.childCount){const e=d(t.getChildren());if(e)return e}}return null}function u(e){return`drupalElementStyle${e[0].toUpperCase()+e.substring(1)}`}class c extends e.Command{execute(e){const t=this.editor.plugins.get("DrupalMediaEditing"),i=Object.entries(t.attrs).reduce(((e,[t,i])=>(e[i]=t,e)),{}),n=Object.keys(e).reduce(((t,n)=>(i[n]&&(t[i[n]]=e[n]),t)),{});if(this.editor.plugins.has("DrupalElementStyleEditing")){const t=this.editor.plugins.get("DrupalElementStyleEditing"),{normalizedStyles:i}=t;for(const a of Object.keys(i))for(const i of t.normalizedStyles[a])if(e[i.attributeName]&&i.attributeValue===e[i.attributeName]){const e=u(a);n[e]=i.name}}this.editor.model.change((e=>{this.editor.model.insertObject(function(e,t){return e.createElement("drupalMedia",t)}(e,n))}))}refresh(){const e=this.editor.model,t=e.document.selection,i=e.schema.findAllowedParent(t.getFirstPosition(),"drupalMedia");this.isEnabled=null!==i}}const m="METADATA_ERROR";class p extends e.Plugin{static get requires(){return[t.Widget]}constructor(e){super(e),this.attrs={drupalMediaAlt:"alt",drupalMediaEntityType:"data-entity-type",drupalMediaEntityUuid:"data-entity-uuid"},this.converterAttributes=["drupalMediaEntityUuid","drupalElementStyleViewMode","drupalMediaEntityType","drupalMediaAlt"]}init(){const e=this.editor.config.get("drupalMedia");if(!e)return;const{previewURL:t,themeError:i}=e;this.previewUrl=t,this.labelError=Drupal.t("Preview failed"),this.themeError=i||`\n      <p>${Drupal.t("An error occurred while trying to preview the media. Save your work and reload this page.")}<p>\n    `,this._defineSchema(),this._defineConverters(),this._defineListeners(),this.editor.commands.add("insertDrupalMedia",new c(this.editor))}upcastDrupalMediaIsImage(e){const{model:t,plugins:i}=this.editor;i.get("DrupalMediaMetadataRepository").getMetadata(e).then((i=>{e&&t.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaIsImage",!!i.imageSourceMetadata,e)}))})).catch((i=>{e&&(console.warn(i.toString()),t.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaIsImage",m,e)})))}))}upcastDrupalMediaType(e){this.editor.plugins.get("DrupalMediaMetadataRepository").getMetadata(e).then((t=>{e&&this.editor.model.enqueueChange({isUndoable:!1},(i=>{i.setAttribute("drupalMediaType",t.type,e)}))})).catch((t=>{e&&(console.warn(t.toString()),this.editor.model.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaType",m,e)})))}))}async _fetchPreview(e){const t={text:this._renderElement(e),uuid:e.getAttribute("drupalMediaEntityUuid")},i=await fetch(`${this.previewUrl}?${new URLSearchParams(t)}`,{headers:{"X-Drupal-MediaPreview-CSRF-Token":this.editor.config.get("drupalMedia").previewCsrfToken}});if(i.ok){return{label:i.headers.get("drupal-media-label"),preview:await i.text()}}return{label:this.labelError,preview:this.themeError}}_defineSchema(){this.editor.model.schema.register("drupalMedia",{inheritAllFrom:"$blockObject",allowAttributes:Object.keys(this.attrs)}),this.editor.editing.view.domConverter.blockElements.push("drupal-media")}_defineConverters(){const e=this.editor.conversion,i=this.editor.plugins.get("DrupalMediaMetadataRepository");e.for("upcast").elementToElement({view:{name:"drupal-media"},model:"drupalMedia"}).add((e=>{e.on("element:drupal-media",((e,t)=>{const[n]=t.modelRange.getItems();i.getMetadata(n).then((e=>{n&&(this.upcastDrupalMediaIsImage(n),this.editor.model.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaType",e.type,n)})))})).catch((e=>{console.warn(e.toString())}))}),{priority:"lowest"})})),e.for("dataDowncast").elementToElement({model:"drupalMedia",view:{name:"drupal-media"}}),e.for("editingDowncast").elementToElement({model:"drupalMedia",view:(e,{writer:i})=>{const n=i.createContainerElement("figure",{class:"drupal-media"});if(!this.previewUrl){const e=i.createRawElement("div",{"data-drupal-media-preview":"unavailable"});i.insert(i.createPositionAt(n,0),e)}return i.setCustomProperty("drupalMedia",!0,n),(0,t.toWidget)(n,i,{label:Drupal.t("Media widget")})}}).add((e=>{const t=(e,t,i)=>{const n=i.writer,a=t.item,r=i.mapper.toViewElement(t.item);let o=d(r.getChildren());if(o){if("ready"!==o.getAttribute("data-drupal-media-preview"))return;n.setAttribute("data-drupal-media-preview","loading",o)}else o=n.createRawElement("div",{"data-drupal-media-preview":"loading"}),n.insert(n.createPositionAt(r,0),o);this._fetchPreview(a).then((({label:e,preview:t})=>{o&&this.editor.editing.view.change((i=>{const n=i.createRawElement("div",{"data-drupal-media-preview":"ready","aria-label":e},(e=>{e.innerHTML=t}));i.insert(i.createPositionBefore(o),n),i.remove(o)}))}))};return this.converterAttributes.forEach((i=>{e.on(`attribute:${i}:drupalMedia`,t)})),e})),e.for("editingDowncast").add((e=>{e.on("attribute:drupalElementStyleAlign:drupalMedia",((e,t,i)=>{const n={left:"drupal-media-style-align-left",right:"drupal-media-style-align-right",center:"drupal-media-style-align-center"},a=i.mapper.toViewElement(t.item),r=i.writer;n[t.attributeOldValue]&&r.removeClass(n[t.attributeOldValue],a),n[t.attributeNewValue]&&i.consumable.consume(t.item,e.name)&&r.addClass(n[t.attributeNewValue],a)}))})),Object.keys(this.attrs).forEach((t=>{const i={model:{key:t,name:"drupalMedia"},view:{name:"drupal-media",key:this.attrs[t]}};e.for("dataDowncast").attributeToAttribute(i),e.for("upcast").attributeToAttribute(i)}))}_defineListeners(){this.editor.model.on("insertContent",((e,[t])=>{a(t)&&(this.upcastDrupalMediaIsImage(t),this.upcastDrupalMediaType(t))}))}_renderElement(e){const t=this.editor.model.change((t=>{const i=t.createDocumentFragment(),n=t.cloneElement(e,!1);return["linkHref"].forEach((e=>{t.removeAttribute(e,n)})),t.append(n,i),i}));return this.editor.data.stringify(t)}static get pluginName(){return"DrupalMediaEditing"}}var g=i("ckeditor5/src/ui.js");class h extends e.Plugin{init(){const e=this.editor,t=this.editor.config.get("drupalMedia");if(!t)return;const{libraryURL:i,openDialog:n,dialogSettings:a={}}=t;i&&"function"==typeof n&&e.ui.componentFactory.add("drupalMedia",(t=>{const r=e.commands.get("insertDrupalMedia"),o=new g.ButtonView(t);return o.set({label:Drupal.t("Insert Media"),icon:'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.1873 4.86414L10.2509 6.86414V7.02335H10.2499V15.5091C9.70972 15.1961 9.01793 15.1048 8.34069 15.3136C7.12086 15.6896 6.41013 16.8967 6.75322 18.0096C7.09631 19.1226 8.3633 19.72 9.58313 19.344C10.6666 19.01 11.3484 18.0203 11.2469 17.0234H11.2499V9.80173L18.1803 8.25067V14.3868C17.6401 14.0739 16.9483 13.9825 16.2711 14.1913C15.0513 14.5674 14.3406 15.7744 14.6836 16.8875C15.0267 18.0004 16.2937 18.5978 17.5136 18.2218C18.597 17.8877 19.2788 16.8982 19.1773 15.9011H19.1803V8.02687L19.1873 8.0253V4.86414Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M13.5039 0.743652H0.386932V12.1603H13.5039V0.743652ZM12.3379 1.75842H1.55289V11.1454H1.65715L4.00622 8.86353L6.06254 10.861L9.24985 5.91309L11.3812 9.22179L11.7761 8.6676L12.3379 9.45621V1.75842ZM6.22048 4.50869C6.22048 5.58193 5.35045 6.45196 4.27722 6.45196C3.20398 6.45196 2.33395 5.58193 2.33395 4.50869C2.33395 3.43546 3.20398 2.56543 4.27722 2.56543C5.35045 2.56543 6.22048 3.43546 6.22048 4.50869Z"/></svg>\n',tooltip:!0}),o.bind("isOn","isEnabled").to(r,"value","isEnabled"),this.listenTo(o,"execute",(()=>{n(i,(({attributes:t})=>{e.execute("insertDrupalMedia",t)}),a)})),o}))}}class f extends e.Plugin{static get requires(){return[t.WidgetToolbarRepository]}static get pluginName(){return"DrupalMediaToolbar"}afterInit(){const{editor:e}=this;var i;e.plugins.get(t.WidgetToolbarRepository).register("drupalMedia",{ariaLabel:Drupal.t("Drupal Media toolbar"),items:(i=e.config.get("drupalMedia.toolbar"),i.map((e=>l(e)?e.name:e))||[]),getRelatedElement:e=>s(e)})}}class b extends e.Command{refresh(){const e=o(this.editor.model.document.selection);this.isEnabled=!!e&&e.getAttribute("drupalMediaIsImage")&&e.getAttribute("drupalMediaIsImage")!==m,this.isEnabled?this.value=e.getAttribute("drupalMediaAlt"):this.value=!1}execute(e){const{model:t}=this.editor,i=o(t.document.selection);e.newValue=e.newValue.trim(),t.change((t=>{e.newValue.length>0?t.setAttribute("drupalMediaAlt",e.newValue,i):t.removeAttribute("drupalMediaAlt",i)}))}}class w extends e.Plugin{init(){this._data=new WeakMap}getMetadata(e){if(this._data.get(e))return new Promise((t=>{t(this._data.get(e))}));const t=this.editor.config.get("drupalMedia");if(!t)return new Promise(((e,t)=>{t(new Error("drupalMedia configuration is required for parsing metadata."))}));if(!e.hasAttribute("drupalMediaEntityUuid"))return new Promise(((e,t)=>{t(new Error("drupalMedia element must have drupalMediaEntityUuid attribute to retrieve metadata."))}));const{metadataUrl:i}=t;return(async e=>{const t=await fetch(e);if(t.ok)return JSON.parse(await t.text());throw new Error("Fetching media embed metadata from the server failed.")})(`${i}&${new URLSearchParams({uuid:e.getAttribute("drupalMediaEntityUuid")})}`).then((t=>(this._data.set(e,t),t)))}static get pluginName(){return"DrupalMediaMetadataRepository"}}class y extends e.Plugin{static get requires(){return[w]}static get pluginName(){return"MediaImageTextAlternativeEditing"}init(){const{editor:e,editor:{model:t,conversion:i}}=this;t.schema.extend("drupalMedia",{allowAttributes:["drupalMediaIsImage"]}),i.for("editingDowncast").add((e=>{e.on("attribute:drupalMediaIsImage",((e,t,i)=>{const{writer:n,mapper:a}=i,r=a.toViewElement(t.item);if(t.attributeNewValue!==m){const e=Array.from(r.getChildren()).find((e=>e.getCustomProperty("drupalMediaMetadataError")));return void(e&&(n.setCustomProperty("widgetLabel",e.getCustomProperty("drupalMediaOriginalWidgetLabel"),e),n.removeElement(e)))}const o=Drupal.t("Not all functionality may be available because some information could not be retrieved."),s=new g.Template({tag:"span",children:[{tag:"span",attributes:{class:"drupal-media__metadata-error-icon","data-cke-tooltip-text":o}}]}).render(),l=n.createRawElement("div",{class:"drupal-media__metadata-error"},((e,t)=>{t.setContentOf(e,s.outerHTML)}));n.setCustomProperty("drupalMediaMetadataError",!0,l);const d=r.getCustomProperty("widgetLabel");n.setCustomProperty("drupalMediaOriginalWidgetLabel",d,l),n.setCustomProperty("widgetLabel",`${d} (${o})`,r),n.insert(n.createPositionAt(r,0),l)}),{priority:"low"})})),e.commands.add("mediaImageTextAlternative",new b(this.editor))}}function v(e){const t=e.editing.view,i=g.BalloonPanelView.defaultPositions;return{target:t.domConverter.viewToDom(t.document.selection.getSelectedElement()),positions:[i.northArrowSouth,i.northArrowSouthWest,i.northArrowSouthEast,i.southArrowNorth,i.southArrowNorthWest,i.southArrowNorthEast]}}var E=i("ckeditor5/src/utils.js");class M extends g.View{constructor(t){super(t),this.focusTracker=new E.FocusTracker,this.keystrokes=new E.KeystrokeHandler,this.labeledInput=this._createLabeledInputView(),this.set("defaultAltText",void 0),this.defaultAltTextView=this._createDefaultAltTextView(),this.saveButtonView=this._createButton(Drupal.t("Save"),e.icons.check,"ck-button-save"),this.saveButtonView.type="submit",this.cancelButtonView=this._createButton(Drupal.t("Cancel"),e.icons.cancel,"ck-button-cancel","cancel"),this._focusables=new g.ViewCollection,this._focusCycler=new g.FocusCycler({focusables:this._focusables,focusTracker:this.focusTracker,keystrokeHandler:this.keystrokes,actions:{focusPrevious:"shift + tab",focusNext:"tab"}}),this.setTemplate({tag:"form",attributes:{class:["ck","ck-media-alternative-text-form","ck-vertical-form"],tabindex:"-1"},children:[this.defaultAltTextView,this.labeledInput,this.saveButtonView,this.cancelButtonView]}),(0,g.injectCssTransitionDisabler)(this)}render(){super.render(),this.keystrokes.listenTo(this.element),(0,g.submitHandler)({view:this}),[this.labeledInput,this.saveButtonView,this.cancelButtonView].forEach((e=>{this._focusables.add(e),this.focusTracker.add(e.element)}))}_createButton(e,t,i,n){const a=new g.ButtonView(this.locale);return a.set({label:e,icon:t,tooltip:!0}),a.extendTemplate({attributes:{class:i}}),n&&a.delegate("execute").to(this,n),a}_createLabeledInputView(){const e=new g.LabeledFieldView(this.locale,g.createLabeledInputText);return e.label=Drupal.t("Alternative text override"),e}_createDefaultAltTextView(){const e=g.Template.bind(this,this);return new g.Template({tag:"div",attributes:{class:["ck-media-alternative-text-form__default-alt-text",e.if("defaultAltText","ck-hidden",(e=>!e))]},children:[{tag:"strong",attributes:{class:"ck-media-alternative-text-form__default-alt-text-label"},children:[Drupal.t("Default alternative text:")]}," ",{tag:"span",attributes:{class:"ck-media-alternative-text-form__default-alt-text-value"},children:[{text:[e.to("defaultAltText")]}]}]})}}class k extends e.Plugin{static get requires(){return[g.ContextualBalloon]}static get pluginName(){return"MediaImageTextAlternativeUi"}init(){this._createButton(),this._createForm()}destroy(){super.destroy(),this._form.destroy()}_createButton(){const t=this.editor;t.ui.componentFactory.add("mediaImageTextAlternative",(i=>{const n=t.commands.get("mediaImageTextAlternative"),a=new g.ButtonView(i);return a.set({label:Drupal.t("Override media image alternative text"),icon:e.icons.lowVision,tooltip:!0}),a.bind("isVisible").to(n,"isEnabled"),this.listenTo(a,"execute",(()=>{this._showForm()})),a}))}_createForm(){const e=this.editor,t=e.editing.view.document;this._balloon=this.editor.plugins.get("ContextualBalloon"),this._form=new M(e.locale),this._form.render(),this.listenTo(this._form,"submit",(()=>{e.execute("mediaImageTextAlternative",{newValue:this._form.labeledInput.fieldView.element.value}),this._hideForm(!0)})),this.listenTo(this._form,"cancel",(()=>{this._hideForm(!0)})),this._form.keystrokes.set("Esc",((e,t)=>{this._hideForm(!0),t()})),this.listenTo(e.ui,"update",(()=>{s(t.selection)?this._isVisible&&function(e){const t=e.plugins.get("ContextualBalloon");if(s(e.editing.view.document.selection)){const i=v(e);t.updatePosition(i)}}(e):this._hideForm(!0)})),(0,g.clickOutsideHandler)({emitter:this._form,activator:()=>this._isVisible,contextElements:[this._balloon.view.element],callback:()=>this._hideForm()})}_showForm(){if(this._isVisible)return;const e=this.editor,t=e.commands.get("mediaImageTextAlternative"),i=e.plugins.get("DrupalMediaMetadataRepository"),n=this._form.labeledInput;this._form.disableCssTransitions(),this._isInBalloon||this._balloon.add({view:this._form,position:v(e)}),n.fieldView.element.value=t.value||"",n.fieldView.value=n.fieldView.element.value,this._form.defaultAltText="";const r=e.model.document.selection.getSelectedElement();a(r)&&i.getMetadata(r).then((e=>{this._form.defaultAltText=e.imageSourceMetadata?e.imageSourceMetadata.alt:""})).catch((e=>{console.warn(e.toString())})),this._form.labeledInput.fieldView.select(),this._form.enableCssTransitions()}_hideForm(e){this._isInBalloon&&(this._form.focusTracker.isFocused&&this._form.saveButtonView.focus(),this._balloon.remove(this._form),e&&this.editor.editing.view.focus())}get _isVisible(){return this._balloon.visibleView===this._form}get _isInBalloon(){return this._balloon.hasView(this._form)}}class A extends e.Plugin{static get requires(){return[y,k]}static get pluginName(){return"MediaImageTextAlternative"}}function D(e,t,i){if(t.attributes)for(const[n,a]of Object.entries(t.attributes))e.setAttribute(n,a,i);t.styles&&e.setStyle(t.styles,i),t.classes&&e.addClass(t.classes,i)}function C(e,t,i){if(!i.consumable.consume(t.item,e.name))return;const n=i.mapper.toViewElement(t.item);D(i.writer,t.attributeNewValue,n)}class _ extends e.Plugin{constructor(e){if(super(e),!e.plugins.has("GeneralHtmlSupport"))return;e.plugins.has("DataFilter")&&e.plugins.has("DataSchema")||console.error("DataFilter and DataSchema plugins are required for Drupal Media to integrate with General HTML Support plugin.");const{schema:t}=e.model,{conversion:i}=e,n=this.editor.plugins.get("DataFilter");this.editor.plugins.get("DataSchema").registerBlockElement({model:"drupalMedia",view:"drupal-media"}),n.on("register:drupal-media",((e,a)=>{"drupalMedia"===a.model&&(t.extend("drupalMedia",{allowAttributes:["htmlLinkAttributes","htmlAttributes"]}),i.for("upcast").add(function(e){return t=>{t.on("element:drupal-media",((t,i,n)=>{function a(t,a){const r=e.processViewAttributes(t,n);r&&n.writer.setAttribute(a,r,i.modelRange)}const r=i.viewItem,o=r.parent;a(r,"htmlAttributes"),o.is("element","a")&&a(o,"htmlLinkAttributes")}),{priority:"low"})}}(n)),i.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const n=i.mapper.toViewElement(t.item),a=function(e,t,i){const n=e.createRangeOn(t);for(const{item:e}of n.getWalker())if(e.is("element",i))return e}(i.writer,n,"a");D(i.writer,t.item.getAttribute("htmlLinkAttributes"),a)}),{priority:"low"})})),i.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const n=i.mapper.toViewElement(t.item).parent;D(i.writer,t.item.getAttribute("htmlLinkAttributes"),n)}),{priority:"low"}),e.on("attribute:htmlAttributes:drupalMedia",C,{priority:"low"})})),e.stop())}))}static get pluginName(){return"DrupalMediaGeneralHtmlSupport"}}class x extends e.Plugin{static get requires(){return[p,_,h,f,A]}static get pluginName(){return"DrupalMedia"}}var V=i("ckeditor5/src/engine.js");function S(e){return Array.from(e.getChildren()).find((e=>"drupal-media"===e.name))}function T(e){return t=>{t.on(`attribute:${e.id}:drupalMedia`,((t,i,n)=>{const a=n.mapper.toViewElement(i.item);let r=Array.from(a.getChildren()).find((e=>"a"===e.name));if(r=!r&&a.is("element","a")?a:Array.from(a.getAncestors()).find((e=>"a"===e.name)),r){for(const[t,i]of(0,E.toMap)(e.attributes))n.writer.setAttribute(t,i,r);e.classes&&n.writer.addClass(e.classes,r);for(const t in e.styles)Object.prototype.hasOwnProperty.call(e.styles,t)&&n.writer.setStyle(t,e.styles[t],r)}}))}}function I(e,t){return e=>{e.on("element:a",((e,i,n)=>{const a=i.viewItem;if(!S(a))return;const r=new V.Matcher(t._createPattern()).match(a);if(!r)return;if(!n.consumable.consume(a,r.match))return;const o=i.modelCursor.nodeBefore;n.writer.setAttribute(t.id,!0,o)}),{priority:"high"})}}class L extends e.Plugin{static get requires(){return["LinkEditing","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaEditing"}init(){const{editor:e}=this;e.model.schema.extend("drupalMedia",{allowAttributes:["linkHref"]}),e.conversion.for("upcast").add((e=>{e.on("element:a",((e,t,i)=>{const n=t.viewItem,a=S(n);if(!a)return;if(!i.consumable.consume(n,{attributes:["href"],name:!0}))return;const r=n.getAttribute("href");if(!r)return;const o=i.convertItem(a,t.modelCursor);t.modelRange=o.modelRange,t.modelCursor=o.modelCursor;const s=t.modelCursor.nodeBefore;s&&s.is("element","drupalMedia")&&i.writer.setAttribute("linkHref",r,s)}),{priority:"high"})})),e.conversion.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:n}=i;if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item),r=Array.from(a.getChildren()).find((e=>"a"===e.name));if(r)t.attributeNewValue?n.setAttribute("href",t.attributeNewValue,r):(n.move(n.createRangeIn(r),n.createPositionAt(a,0)),n.remove(r));else{const e=Array.from(a.getChildren()).find((e=>e.getAttribute("data-drupal-media-preview"))),i=n.createContainerElement("a",{href:t.attributeNewValue});n.insert(n.createPositionAt(a,0),i),n.move(n.createRangeOn(e),n.createPositionAt(i,0))}}),{priority:"high"})})),e.conversion.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:n}=i;if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item),r=n.createContainerElement("a",{href:t.attributeNewValue});n.insert(n.createPositionBefore(a),r),n.move(n.createRangeOn(a),n.createPositionAt(r,0))}),{priority:"high"})})),this._enableManualDecorators();if(e.commands.get("link").automaticDecorators.length>0)throw new Error("The Drupal Media plugin is not compatible with automatic link decorators. To use Drupal Media, disable any plugins providing automatic link decorators.")}_enableManualDecorators(){const e=this.editor,t=e.commands.get("link");for(const i of t.manualDecorators)e.model.schema.extend("drupalMedia",{allowAttributes:i.id}),e.conversion.for("downcast").add(T(i)),e.conversion.for("upcast").add(I(0,i))}}class P extends e.Plugin{static get requires(){return["LinkEditing","LinkUI","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaUi"}init(){const{editor:e}=this,t=e.editing.view.document;this.listenTo(t,"click",((t,i)=>{this._isSelectedLinkedMedia(e.model.document.selection)&&(i.preventDefault(),t.stop())}),{priority:"high"}),this._createToolbarLinkMediaButton()}_createToolbarLinkMediaButton(){const{editor:e}=this;e.ui.componentFactory.add("drupalLinkMedia",(t=>{const i=new g.ButtonView(t),n=e.plugins.get("LinkUI"),a=e.commands.get("link");return i.set({isEnabled:!0,label:Drupal.t("Link media"),icon:'<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="m11.077 15 .991-1.416a.75.75 0 1 1 1.229.86l-1.148 1.64a.748.748 0 0 1-.217.206 5.251 5.251 0 0 1-8.503-5.955.741.741 0 0 1 .12-.274l1.147-1.639a.75.75 0 1 1 1.228.86L4.933 10.7l.006.003a3.75 3.75 0 0 0 6.132 4.294l.006.004zm5.494-5.335a.748.748 0 0 1-.12.274l-1.147 1.639a.75.75 0 1 1-1.228-.86l.86-1.23a3.75 3.75 0 0 0-6.144-4.301l-.86 1.229a.75.75 0 0 1-1.229-.86l1.148-1.64a.748.748 0 0 1 .217-.206 5.251 5.251 0 0 1 8.503 5.955zm-4.563-2.532a.75.75 0 0 1 .184 1.045l-3.155 4.505a.75.75 0 1 1-1.229-.86l3.155-4.506a.75.75 0 0 1 1.045-.184z"/></svg>\n',keystroke:"Ctrl+K",tooltip:!0,isToggleable:!0}),i.bind("isEnabled").to(a,"isEnabled"),i.bind("isOn").to(a,"value",(e=>!!e)),this.listenTo(i,"execute",(()=>{this._isSelectedLinkedMedia(e.model.document.selection)?n._addActionsView():n._showUI(!0)})),i}))}_isSelectedLinkedMedia(e){const t=e.getSelectedElement();return!!t&&t.is("element","drupalMedia")&&t.hasAttribute("linkHref")}}class O extends e.Plugin{static get requires(){return[L,P]}static get pluginName(){return"DrupalLinkMedia"}}const{objectFullWidth:B,objectInline:N,objectLeft:R,objectRight:j,objectCenter:F,objectBlockLeft:U,objectBlockRight:H}=e.icons,$={get inline(){return{name:"inline",title:"In line",icon:N,modelElements:["imageInline"],isDefault:!0}},get alignLeft(){return{name:"alignLeft",title:"Left aligned image",icon:R,modelElements:["imageBlock","imageInline"],className:"image-style-align-left"}},get alignBlockLeft(){return{name:"alignBlockLeft",title:"Left aligned image",icon:U,modelElements:["imageBlock"],className:"image-style-block-align-left"}},get alignCenter(){return{name:"alignCenter",title:"Centered image",icon:F,modelElements:["imageBlock"],className:"image-style-align-center"}},get alignRight(){return{name:"alignRight",title:"Right aligned image",icon:j,modelElements:["imageBlock","imageInline"],className:"image-style-align-right"}},get alignBlockRight(){return{name:"alignBlockRight",title:"Right aligned image",icon:H,modelElements:["imageBlock"],className:"image-style-block-align-right"}},get block(){return{name:"block",title:"Centered image",icon:F,modelElements:["imageBlock"],isDefault:!0}},get side(){return{name:"side",title:"Side image",icon:j,modelElements:["imageBlock"],className:"image-style-side"}}},q={full:B,left:U,right:H,center:F,inlineLeft:R,inlineRight:j,inline:N},W=[{name:"imageStyle:wrapText",title:"Wrap text",defaultItem:"imageStyle:alignLeft",items:["imageStyle:alignLeft","imageStyle:alignRight"]},{name:"imageStyle:breakText",title:"Break text",defaultItem:"imageStyle:block",items:["imageStyle:alignBlockLeft","imageStyle:block","imageStyle:alignBlockRight"]}];function K(e){(0,E.logWarning)("image-style-configuration-definition-invalid",e)}const z={normalizeStyles:function(e){return(e.configuredStyles.options||[]).map((e=>function(e){e="string"==typeof e?$[e]?{...$[e]}:{name:e}:function(e,t){const i={...t};for(const n in e)Object.prototype.hasOwnProperty.call(t,n)||(i[n]=e[n]);return i}($[e.name],e);"string"==typeof e.icon&&(e.icon=q[e.icon]||e.icon);return e}(e))).filter((t=>function(e,{isBlockPluginLoaded:t,isInlinePluginLoaded:i}){const{modelElements:n,name:a}=e;if(!(n&&n.length&&a))return K({style:e}),!1;{const a=[t?"imageBlock":null,i?"imageInline":null];if(!n.some((e=>a.includes(e))))return(0,E.logWarning)("image-style-missing-dependency",{style:e,missingPlugins:n.map((e=>"imageBlock"===e?"ImageBlockEditing":"ImageInlineEditing"))}),!1}return!0}(t,e)))},getDefaultStylesConfiguration:function(e,t){return e&&t?{options:["inline","alignLeft","alignRight","alignCenter","alignBlockLeft","alignBlockRight","block","side"]}:e?{options:["block","side"]}:t?{options:["inline","alignLeft","alignRight"]}:{}},getDefaultDropdownDefinitions:function(e){return e.has("ImageBlockEditing")&&e.has("ImageInlineEditing")?[...W]:[]},warnInvalidStyle:K,DEFAULT_OPTIONS:$,DEFAULT_ICONS:q,DEFAULT_DROPDOWN_DEFINITIONS:W};function Z(e,t,i){for(const n of t)if(i.checkAttribute(e,n))return!0;return!1}function G(e,t,i){const n=e.getSelectedElement();if(n&&Z(n,i,t))return n;let{parent:a}=e.getFirstPosition();for(;a;){if(a.is("element")&&Z(a,i,t))return a;a=a.parent}return null}class J extends e.Command{constructor(e,t){super(e),this.styles={},Object.keys(t).forEach((e=>{this.styles[e]=new Map(t[e].map((e=>[e.name,e])))})),this.modelAttributes=[];for(const e of Object.keys(t)){const t=u(e);this.modelAttributes.push(t)}}refresh(){const{editor:e}=this,t=G(e.model.document.selection,e.model.schema,this.modelAttributes);this.isEnabled=!!t,this.isEnabled?this.value=this.getValue(t):this.value=!1}getValue(e){const t={};return Object.keys(this.styles).forEach((i=>{const n=u(i);if(e.hasAttribute(n))t[i]=e.getAttribute(n);else for(const[,e]of this.styles[i])e.isDefault&&(t[i]=e.name)})),t}execute(e={}){const{editor:{model:t}}=this,{value:i,group:n}=e,a=u(n);t.change((e=>{const r=G(t.document.selection,t.schema,this.modelAttributes);!i||this.styles[n].get(i).isDefault?e.removeAttribute(a,r):e.setAttribute(a,i,r)}))}}function X(e,t){for(const i of t)if(i.name===e)return i}class Q extends e.Plugin{init(){const{editor:t}=this,i=t.config.get("drupalElementStyles");this.normalizedStyles={},Object.keys(i).forEach((t=>{this.normalizedStyles[t]=i[t].map((t=>("string"==typeof t.icon&&e.icons[t.icon]&&(t.icon=e.icons[t.icon]),t.name&&(t.name=`${t.name}`),t))).filter((e=>e.isDefault||e.attributeName&&e.attributeValue?e.modelElements&&Array.isArray(e.modelElements)?!!e.name||(console.warn("drupalElementStyles options must include a name."),!1):(console.warn("drupalElementStyles options must include an array of supported modelElements."),!1):(console.warn(`${e.attributeValue} drupalElementStyles options must include attributeName and attributeValue.`),!1)))})),this._setupConversion(),t.commands.add("drupalElementStyle",new J(t,this.normalizedStyles))}_setupConversion(){const{editor:e}=this,{schema:t}=e.model;Object.keys(this.normalizedStyles).forEach((i=>{const n=u(i),a=(r=this.normalizedStyles[i],(e,t,i)=>{if(!i.consumable.consume(t.item,e.name))return;const n=X(t.attributeNewValue,r),a=X(t.attributeOldValue,r),o=i.mapper.toViewElement(t.item),s=i.writer;a&&("class"===a.attributeName?s.removeClass(a.attributeValue,o):s.removeAttribute(a.attributeName,o)),n&&("class"===n.attributeName?s.addClass(n.attributeValue,o):n.isDefault||s.setAttribute(n.attributeName,n.attributeValue,o))});var r;const o=function(e,t){const i=e.filter((e=>!e.isDefault));return(e,n,a)=>{if(!n.modelRange)return;const r=n.viewItem,o=(0,E.first)(n.modelRange.getItems());if(o&&a.schema.checkAttribute(o,t))for(const e of i)if("class"===e.attributeName)a.consumable.consume(r,{classes:e.attributeValue})&&a.writer.setAttribute(t,e.name,o);else if(a.consumable.consume(r,{attributes:[e.attributeName]}))for(const e of i)e.attributeValue===r.getAttribute(e.attributeName)&&a.writer.setAttribute(t,e.name,o)}}(this.normalizedStyles[i],n);e.editing.downcastDispatcher.on(`attribute:${n}`,a),e.data.downcastDispatcher.on(`attribute:${n}`,a);[...new Set(this.normalizedStyles[i].map((e=>e.modelElements)).flat())].forEach((e=>{t.extend(e,{allowAttributes:n})})),e.data.upcastDispatcher.on("element",o,{priority:"low"})}))}static get pluginName(){return"DrupalElementStyleEditing"}}const Y=e=>e,ee=(e,t)=>(e?`${e}: `:"")+t;function te(e,t){return`drupalElementStyle:${t}:${e}`}class ie extends e.Plugin{static get requires(){return[Q]}init(){const{plugins:e}=this.editor,t=this.editor.config.get("drupalMedia.toolbar")||[],i=e.get("DrupalElementStyleEditing").normalizedStyles;Object.keys(i).forEach((e=>{i[e].forEach((t=>{this._createButton(t,e,i[e])}))}));t.filter(l).filter((e=>{const t=[];if(!e.display)return console.warn("dropdown configuration must include a display key specifying either listDropdown or splitButton."),!1;e.items.includes(e.defaultItem)||console.warn("defaultItem must be part of items in the dropdown configuration.");for(const i of e.items){const e=i.split(":")[1];t.push(e)}return!!t.every((e=>e===t[0]))||(console.warn("dropdown configuration should only contain buttons from one group."),!1)})).forEach((e=>{if(e.items.length>=2){const t=e.name.split(":")[1];switch(e.display){case"splitButton":this._createDropdown(e,i[t]);break;case"listDropdown":this._createListDropdown(e,i[t])}}}))}updateOptionVisibility(e,t,i,n){const{selection:a}=this.editor.model.document,r={};r[n]=e;const o=a?a.getSelectedElement():G(a,this.editor.model.schema,r),s=e.filter((function(e){for(const[t,i]of(0,E.toMap)(e.modelAttributes))if(o&&o.hasAttribute(t))return i.includes(o.getAttribute(t));return!0}));i.hasOwnProperty("model")?s.includes(t)?i.model.set({class:""}):i.model.set({class:"ck-hidden"}):s.includes(t)?i.set({class:""}):i.set({class:"ck-hidden"})}_createDropdown(e,t){const i=this.editor.ui.componentFactory;i.add(e.name,(n=>{let a;const{defaultItem:r,items:o,title:s}=e,l=o.filter((e=>{const i=e.split(":")[1];return t.find((({name:t})=>te(t,i)===e))})).map((e=>{const t=i.create(e);return e===r&&(a=t),t}));o.length!==l.length&&z.warnInvalidStyle({dropdown:e});const d=(0,g.createDropdown)(n,g.SplitButtonView),u=d.buttonView;return(0,g.addToolbarToDropdown)(d,l),u.set({label:ee(s,a.label),class:null,tooltip:!0}),u.bind("icon").toMany(l,"isOn",((...e)=>{const t=e.findIndex(Y);return t<0?a.icon:l[t].icon})),u.bind("label").toMany(l,"isOn",((...e)=>{const t=e.findIndex(Y);return ee(s,t<0?a.label:l[t].label)})),u.bind("isOn").toMany(l,"isOn",((...e)=>e.some(Y))),u.bind("class").toMany(l,"isOn",((...e)=>e.some(Y)?"ck-splitbutton_flatten":null)),u.on("execute",(()=>{l.some((({isOn:e})=>e))?d.isOpen=!d.isOpen:a.fire("execute")})),d.bind("isEnabled").toMany(l,"isEnabled",((...e)=>e.some(Y))),d}))}_createButton(e,t,i){const n=e.name;this.editor.ui.componentFactory.add(te(n,t),(a=>{const r=this.editor.commands.get("drupalElementStyle"),o=new g.ButtonView(a);return o.set({label:e.title,icon:e.icon,tooltip:!0,isToggleable:!0}),o.bind("isEnabled").to(r,"isEnabled"),o.bind("isOn").to(r,"value",(e=>e&&e[t]===n)),o.on("execute",this._executeCommand.bind(this,n,t)),this.listenTo(this.editor.ui,"update",(()=>{this.updateOptionVisibility(i,e,o,t)})),o}))}getDropdownListItemDefinitions(e,t,i){const n=new E.Collection;return e.forEach((t=>{const a={type:"button",model:new g.Model({group:i,commandValue:t.name,label:t.title,withText:!0,class:""})};n.add(a),this.listenTo(this.editor.ui,"update",(()=>{this.updateOptionVisibility(e,t,a,i)}))})),n}_createListDropdown(e,t){const i=this.editor.ui.componentFactory;i.add(e.name,(n=>{let a;const{defaultItem:r,items:o,title:s,defaultText:l}=e,d=e.name.split(":")[1],u=o.filter((e=>t.find((({name:t})=>te(t,d)===e)))).map((e=>{const t=i.create(e);return e===r&&(a=t),t}));o.length!==u.length&&z.warnInvalidStyle({dropdown:e});const c=(0,g.createDropdown)(n,g.DropdownButtonView),m=c.buttonView;m.set({label:ee(s,a.label),class:null,tooltip:l,withText:!0});const p=this.editor.commands.get("drupalElementStyle");return m.bind("label").to(p,"value",(e=>{if(e&&e[d])for(const i of t)if(i.name===e[d])return i.title;return l})),c.bind("isOn").to(p),c.bind("isEnabled").to(this),(0,g.addListToDropdown)(c,this.getDropdownListItemDefinitions(t,p,d)),this.listenTo(c,"execute",(e=>{this._executeCommand(e.source.commandValue,e.source.group)})),c}))}_executeCommand(e,t){this.editor.execute("drupalElementStyle",{value:e,group:t}),this.editor.editing.view.focus()}static get pluginName(){return"DrupalElementStyleUi"}}class ne extends e.Plugin{static get requires(){return[Q,ie]}static get pluginName(){return"DrupalElementStyle"}}function ae(e){const t=e.getFirstPosition().findAncestor("caption");return t&&a(t.parent)?t:null}function re(e){for(const t of e.getChildren())if(t&&t.is("element","caption"))return t;return null}class oe extends e.Command{refresh(){const e=this.editor.model.document.selection,t=e.getSelectedElement();if(!t)return this.isEnabled=!!o(e),void(this.value=!!ae(e));this.isEnabled=a(t),this.isEnabled?this.value=!!re(t):this.value=!1}execute(e={}){const{focusCaptionOnShow:t}=e;this.editor.model.change((e=>{this.value?this._hideDrupalMediaCaption(e):this._showDrupalMediaCaption(e,t)}))}_showDrupalMediaCaption(e,t){const i=this.editor.model.document.selection,n=this.editor.plugins.get("DrupalMediaCaptionEditing"),a=o(i),r=n._getSavedCaption(a)||e.createElement("caption");e.append(r,a),t&&e.setSelection(r,"in")}_hideDrupalMediaCaption(e){const t=this.editor,i=t.model.document.selection,n=t.plugins.get("DrupalMediaCaptionEditing");let a,r=i.getSelectedElement();r?a=re(r):(a=ae(i),r=o(i)),n._saveCaption(r,a),e.setSelection(r,"on"),e.remove(a)}}class se extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionEditing"}constructor(e){super(e),this._savedCaptionsMap=new WeakMap}init(){const e=this.editor,t=e.model.schema;t.isRegistered("caption")?t.extend("caption",{allowIn:"drupalMedia"}):t.register("caption",{allowIn:"drupalMedia",allowContentOf:"$block",isLimit:!0}),e.commands.add("toggleMediaCaption",new oe(e)),this._setupConversion()}_setupConversion(){const e=this.editor,i=e.editing.view;var n;e.conversion.for("upcast").add(function(e){const t=(t,i,n)=>{const{viewItem:a}=i,{writer:r,consumable:o}=n;if(!i.modelRange||!o.consume(a,{attributes:["data-caption"]}))return;const s=r.createElement("caption"),l=i.modelRange.start.nodeAfter,d=e.data.processor.toView(a.getAttribute("data-caption"));n.consumable.constructor.createFrom(d,n.consumable),n.convertChildren(d,s),r.append(s,l)};return e=>{e.on("element:drupal-media",t,{priority:"low"})}}(e)),e.conversion.for("editingDowncast").elementToElement({model:"caption",view:(e,{writer:n})=>{if(!a(e.parent))return null;const r=n.createEditableElement("figcaption");return r.placeholder=Drupal.t("Enter media caption"),(0,V.enablePlaceholder)({view:i,element:r,keepOnFocus:!0}),(0,t.toWidgetEditable)(r,n)}}),e.editing.mapper.on("modelToViewPosition",(n=i,(e,t)=>{const i=t.modelPosition,r=i.parent;if(!a(r))return;const o=t.mapper.toViewElement(r);t.viewPosition=n.createPositionAt(o,i.offset+1)})),e.conversion.for("dataDowncast").add(function(e){return t=>{t.on("insert:caption",((t,i,n)=>{const{consumable:r,writer:o,mapper:s}=n;if(!a(i.item.parent)||!r.consume(i.item,"insert"))return;const l=e.model.createRangeIn(i.item),d=o.createDocumentFragment();s.bindElements(i.item,d);for(const{item:t}of Array.from(l)){const i={item:t,range:e.model.createRangeOn(t)},a=`insert:${t.name||"$text"}`;e.data.downcastDispatcher.fire(a,i,n);for(const a of t.getAttributeKeys())Object.assign(i,{attributeKey:a,attributeOldValue:null,attributeNewValue:i.item.getAttribute(a)}),e.data.downcastDispatcher.fire(`attribute:${a}`,i,n)}for(const e of o.createRangeIn(d).getItems())s.unbindViewElement(e);s.unbindViewElement(d);const u=e.data.processor.toData(d);if(u){const e=s.toViewElement(i.item.parent);o.setAttribute("data-caption",u,e)}}))}}(e))}_getSavedCaption(e){const t=this._savedCaptionsMap.get(e);return t?V.Element.fromJSON(t):null}_saveCaption(e,t){this._savedCaptionsMap.set(e,t.toJSON())}}class le extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionUI"}init(){const{editor:t}=this,i=t.editing.view;t.ui.componentFactory.add("toggleDrupalMediaCaption",(n=>{const a=new g.ButtonView(n),r=t.commands.get("toggleMediaCaption");return a.set({label:Drupal.t("Caption media"),icon:e.icons.caption,tooltip:!0,isToggleable:!0}),a.bind("isOn","isEnabled").to(r,"value","isEnabled"),a.bind("label").to(r,"value",(e=>e?Drupal.t("Toggle caption off"):Drupal.t("Toggle caption on"))),this.listenTo(a,"execute",(()=>{t.execute("toggleMediaCaption",{focusCaptionOnShow:!0});const e=ae(t.model.document.selection);if(e){const n=t.editing.mapper.toViewElement(e);i.scrollToTheSelection(),i.change((e=>{e.addClass("drupal-media__caption_highlighted",n)}))}t.editing.view.focus()})),a}))}}class de extends e.Plugin{static get requires(){return[se,le]}static get pluginName(){return"DrupalMediaCaption"}}const ue={DrupalMedia:x,MediaImageTextAlternative:A,MediaImageTextAlternativeEditing:y,MediaImageTextAlternativeUi:k,DrupalLinkMedia:O,DrupalMediaCaption:de,DrupalElementStyle:ne}})(),n=n.default})()));
\ No newline at end of file
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.CKEditor5=t():(e.CKEditor5=e.CKEditor5||{},e.CKEditor5.drupalMedia=t())}(globalThis,(()=>(()=>{var e={"ckeditor5/src/core.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/core.js")},"ckeditor5/src/engine.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/engine.js")},"ckeditor5/src/ui.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/ui.js")},"ckeditor5/src/utils.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/utils.js")},"ckeditor5/src/widget.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/widget.js")},"dll-reference CKEditor5.dll":e=>{"use strict";e.exports=CKEditor5.dll}},t={};function i(n){var a=t[n];if(void 0!==a)return a.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,i),r.exports}i.d=(e,t)=>{for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var n={};return(()=>{"use strict";i.d(n,{default:()=>ue});var e=i("ckeditor5/src/core.js"),t=i("ckeditor5/src/widget.js");function a(e){return!!e&&e.is("element","drupalMedia")}function r(e){return(0,t.isWidget)(e)&&!!e.getCustomProperty("drupalMedia")}function o(e){const t=e.getSelectedElement();return a(t)?t:e.getFirstPosition().findAncestor("drupalMedia")}function s(e){const t=e.getSelectedElement();if(t&&r(t))return t;if(null===e.getFirstPosition())return null;let i=e.getFirstPosition().parent;for(;i;){if(i.is("element")&&r(i))return i;i=i.parent}return null}function l(e){const t=typeof e;return null!=e&&("object"===t||"function"===t)}function d(e){for(const t of e){if(t.hasAttribute("data-drupal-media-preview"))return t;if(t.childCount){const e=d(t.getChildren());if(e)return e}}return null}function u(e){return`drupalElementStyle${e[0].toUpperCase()+e.substring(1)}`}class c extends e.Command{execute(e){const t=this.editor.plugins.get("DrupalMediaEditing"),i=Object.entries(t.attrs).reduce(((e,[t,i])=>(e[i]=t,e)),{}),n=Object.keys(e).reduce(((t,n)=>(i[n]&&(t[i[n]]=e[n]),t)),{});if(this.editor.plugins.has("DrupalElementStyleEditing")){const t=this.editor.plugins.get("DrupalElementStyleEditing"),{normalizedStyles:i}=t;for(const a of Object.keys(i))for(const i of t.normalizedStyles[a])if(e[i.attributeName]&&i.attributeValue===e[i.attributeName]){const e=u(a);n[e]=i.name}}this.editor.model.change((e=>{this.editor.model.insertObject(function(e,t){return e.createElement("drupalMedia",t)}(e,n))}))}refresh(){const e=this.editor.model,t=e.document.selection,i=e.schema.findAllowedParent(t.getFirstPosition(),"drupalMedia");this.isEnabled=null!==i}}const m="METADATA_ERROR";class p extends e.Plugin{static get requires(){return[t.Widget]}constructor(e){super(e),this.attrs={drupalMediaAlt:"alt",drupalMediaEntityType:"data-entity-type",drupalMediaEntityUuid:"data-entity-uuid"},this.converterAttributes=["drupalMediaEntityUuid","drupalElementStyleViewMode","drupalMediaEntityType","drupalMediaAlt"]}init(){const e=this.editor.config.get("drupalMedia");if(!e)return;const{previewURL:t,themeError:i}=e;this.previewUrl=t,this.labelError=Drupal.t("Preview failed"),this.themeError=i||`\n      <p>${Drupal.t("An error occurred while trying to preview the media. Save your work and reload this page.")}<p>\n    `,this._defineSchema(),this._defineConverters(),this._defineListeners(),this.editor.commands.add("insertDrupalMedia",new c(this.editor))}upcastDrupalMediaIsImage(e){const{model:t,plugins:i}=this.editor;i.get("DrupalMediaMetadataRepository").getMetadata(e).then((i=>{e&&t.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaIsImage",!!i.imageSourceMetadata,e)}))})).catch((i=>{e&&(console.warn(i.toString()),t.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaIsImage",m,e)})))}))}upcastDrupalMediaType(e){this.editor.plugins.get("DrupalMediaMetadataRepository").getMetadata(e).then((t=>{e&&this.editor.model.enqueueChange({isUndoable:!1},(i=>{i.setAttribute("drupalMediaType",t.type,e)}))})).catch((t=>{e&&(console.warn(t.toString()),this.editor.model.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaType",m,e)})))}))}async _fetchPreview(e){const t={text:this._renderElement(e),uuid:e.getAttribute("drupalMediaEntityUuid")},i=await fetch(`${this.previewUrl}?${new URLSearchParams(t)}`,{headers:{"X-Drupal-MediaPreview-CSRF-Token":this.editor.config.get("drupalMedia").previewCsrfToken}});if(i.ok){return{label:i.headers.get("drupal-media-label"),preview:await i.text()}}return{label:this.labelError,preview:this.themeError}}_defineSchema(){this.editor.model.schema.register("drupalMedia",{inheritAllFrom:"$blockObject",allowAttributes:Object.keys(this.attrs)}),this.editor.editing.view.domConverter.blockElements.push("drupal-media")}_defineConverters(){const e=this.editor.conversion,i=this.editor.plugins.get("DrupalMediaMetadataRepository");e.for("upcast").elementToElement({view:{name:"drupal-media"},model:"drupalMedia"}).add((e=>{e.on("element:drupal-media",((e,t)=>{const[n]=t.modelRange.getItems();i.getMetadata(n).then((e=>{n&&(this.upcastDrupalMediaIsImage(n),this.editor.model.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaType",e.type,n)})))})).catch((e=>{console.warn(e.toString())}))}),{priority:"lowest"})})),e.for("dataDowncast").elementToElement({model:"drupalMedia",view:{name:"drupal-media"}}),e.for("editingDowncast").elementToElement({model:"drupalMedia",view:(e,{writer:i})=>{const n=i.createContainerElement("figure",{class:"drupal-media"});if(!this.previewUrl){const e=i.createRawElement("div",{"data-drupal-media-preview":"unavailable"});i.insert(i.createPositionAt(n,0),e)}return i.setCustomProperty("drupalMedia",!0,n),(0,t.toWidget)(n,i,{label:Drupal.t("Media widget")})}}).add((e=>{const t=(e,t,i)=>{const n=i.writer,a=t.item,r=i.mapper.toViewElement(t.item);let o=d(r.getChildren());if(o){if("ready"!==o.getAttribute("data-drupal-media-preview"))return;n.setAttribute("data-drupal-media-preview","loading",o)}else o=n.createRawElement("div",{"data-drupal-media-preview":"loading"}),n.insert(n.createPositionAt(r,0),o);this._fetchPreview(a).then((({label:e,preview:t})=>{o&&this.editor.editing.view.change((i=>{const n=i.createRawElement("div",{"data-drupal-media-preview":"ready","aria-label":e},(e=>{e.innerHTML=t}));i.insert(i.createPositionBefore(o),n),i.remove(o)}))}))};return this.converterAttributes.forEach((i=>{e.on(`attribute:${i}:drupalMedia`,t)})),e})),e.for("editingDowncast").add((e=>{e.on("attribute:drupalElementStyleAlign:drupalMedia",((e,t,i)=>{const n={left:"drupal-media-style-align-left",right:"drupal-media-style-align-right",center:"drupal-media-style-align-center"},a=i.mapper.toViewElement(t.item),r=i.writer;n[t.attributeOldValue]&&r.removeClass(n[t.attributeOldValue],a),n[t.attributeNewValue]&&i.consumable.consume(t.item,e.name)&&r.addClass(n[t.attributeNewValue],a)}))})),Object.keys(this.attrs).forEach((t=>{const i={model:{key:t,name:"drupalMedia"},view:{name:"drupal-media",key:this.attrs[t]}};e.for("dataDowncast").attributeToAttribute(i),e.for("upcast").attributeToAttribute(i)}))}_defineListeners(){this.editor.model.on("insertContent",((e,[t])=>{a(t)&&(this.upcastDrupalMediaIsImage(t),this.upcastDrupalMediaType(t))}))}_renderElement(e){const t=this.editor.model.change((t=>{const i=t.createDocumentFragment(),n=t.cloneElement(e,!1);return["linkHref"].forEach((e=>{t.removeAttribute(e,n)})),t.append(n,i),i}));return this.editor.data.stringify(t)}static get pluginName(){return"DrupalMediaEditing"}}var g=i("ckeditor5/src/ui.js");class h extends e.Plugin{init(){const e=this.editor,t=this.editor.config.get("drupalMedia");if(!t)return;const{libraryURL:i,openDialog:n,dialogSettings:a={}}=t;i&&"function"==typeof n&&e.ui.componentFactory.add("drupalMedia",(t=>{const r=e.commands.get("insertDrupalMedia"),o=new g.ButtonView(t);return o.set({label:Drupal.t("Insert Media"),icon:'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.1873 4.86414L10.2509 6.86414V7.02335H10.2499V15.5091C9.70972 15.1961 9.01793 15.1048 8.34069 15.3136C7.12086 15.6896 6.41013 16.8967 6.75322 18.0096C7.09631 19.1226 8.3633 19.72 9.58313 19.344C10.6666 19.01 11.3484 18.0203 11.2469 17.0234H11.2499V9.80173L18.1803 8.25067V14.3868C17.6401 14.0739 16.9483 13.9825 16.2711 14.1913C15.0513 14.5674 14.3406 15.7744 14.6836 16.8875C15.0267 18.0004 16.2937 18.5978 17.5136 18.2218C18.597 17.8877 19.2788 16.8982 19.1773 15.9011H19.1803V8.02687L19.1873 8.0253V4.86414Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M13.5039 0.743652H0.386932V12.1603H13.5039V0.743652ZM12.3379 1.75842H1.55289V11.1454H1.65715L4.00622 8.86353L6.06254 10.861L9.24985 5.91309L11.3812 9.22179L11.7761 8.6676L12.3379 9.45621V1.75842ZM6.22048 4.50869C6.22048 5.58193 5.35045 6.45196 4.27722 6.45196C3.20398 6.45196 2.33395 5.58193 2.33395 4.50869C2.33395 3.43546 3.20398 2.56543 4.27722 2.56543C5.35045 2.56543 6.22048 3.43546 6.22048 4.50869Z"/></svg>\n',tooltip:!0}),o.bind("isOn","isEnabled").to(r,"value","isEnabled"),this.listenTo(o,"execute",(()=>{n(i,(({attributes:t})=>{e.execute("insertDrupalMedia",t)}),a)})),o}))}}class f extends e.Plugin{static get requires(){return[t.WidgetToolbarRepository]}static get pluginName(){return"DrupalMediaToolbar"}afterInit(){const{editor:e}=this;var i;e.plugins.get(t.WidgetToolbarRepository).register("drupalMedia",{ariaLabel:Drupal.t("Drupal Media toolbar"),items:(i=e.config.get("drupalMedia.toolbar"),i.map((e=>l(e)?e.name:e))||[]),getRelatedElement:e=>s(e)})}}class b extends e.Command{refresh(){const e=o(this.editor.model.document.selection);this.isEnabled=!!e&&e.getAttribute("drupalMediaIsImage")&&e.getAttribute("drupalMediaIsImage")!==m,this.isEnabled?this.value=e.getAttribute("drupalMediaAlt"):this.value=!1}execute(e){const{model:t}=this.editor,i=o(t.document.selection);e.newValue=e.newValue.trim(),t.change((t=>{e.newValue.length>0?t.setAttribute("drupalMediaAlt",e.newValue,i):t.removeAttribute("drupalMediaAlt",i)}))}}class w extends e.Plugin{init(){this._data=new WeakMap}getMetadata(e){if(this._data.get(e))return new Promise((t=>{t(this._data.get(e))}));const t=this.editor.config.get("drupalMedia");if(!t)return new Promise(((e,t)=>{t(new Error("drupalMedia configuration is required for parsing metadata."))}));if(!e.hasAttribute("drupalMediaEntityUuid"))return new Promise(((e,t)=>{t(new Error("drupalMedia element must have drupalMediaEntityUuid attribute to retrieve metadata."))}));const{metadataUrl:i}=t;return(async e=>{const t=await fetch(e);if(t.ok)return JSON.parse(await t.text());throw new Error("Fetching media embed metadata from the server failed.")})(`${i}&${new URLSearchParams({uuid:e.getAttribute("drupalMediaEntityUuid")})}`).then((t=>(this._data.set(e,t),t)))}static get pluginName(){return"DrupalMediaMetadataRepository"}}class y extends e.Plugin{static get requires(){return[w]}static get pluginName(){return"MediaImageTextAlternativeEditing"}init(){const{editor:e,editor:{model:t,conversion:i}}=this;t.schema.extend("drupalMedia",{allowAttributes:["drupalMediaIsImage"]}),i.for("editingDowncast").add((e=>{e.on("attribute:drupalMediaIsImage",((e,t,i)=>{const{writer:n,mapper:a}=i,r=a.toViewElement(t.item);if(t.attributeNewValue!==m){const e=Array.from(r.getChildren()).find((e=>e.getCustomProperty("drupalMediaMetadataError")));return void(e&&(n.setCustomProperty("widgetLabel",e.getCustomProperty("drupalMediaOriginalWidgetLabel"),e),n.removeElement(e)))}const o=Drupal.t("Not all functionality may be available because some information could not be retrieved."),s=new g.Template({tag:"span",children:[{tag:"span",attributes:{class:"drupal-media__metadata-error-icon","data-cke-tooltip-text":o}}]}).render(),l=n.createRawElement("div",{class:"drupal-media__metadata-error"},((e,t)=>{t.setContentOf(e,s.outerHTML)}));n.setCustomProperty("drupalMediaMetadataError",!0,l);const d=r.getCustomProperty("widgetLabel");n.setCustomProperty("drupalMediaOriginalWidgetLabel",d,l),n.setCustomProperty("widgetLabel",`${d} (${o})`,r),n.insert(n.createPositionAt(r,0),l)}),{priority:"low"})})),e.commands.add("mediaImageTextAlternative",new b(this.editor))}}function v(e){const t=e.editing.view,i=g.BalloonPanelView.defaultPositions;return{target:t.domConverter.viewToDom(t.document.selection.getSelectedElement()),positions:[i.northArrowSouth,i.northArrowSouthWest,i.northArrowSouthEast,i.southArrowNorth,i.southArrowNorthWest,i.southArrowNorthEast]}}var E=i("ckeditor5/src/utils.js");class M extends g.View{constructor(t){super(t),this.focusTracker=new E.FocusTracker,this.keystrokes=new E.KeystrokeHandler,this.labeledInput=this._createLabeledInputView(),this.set("defaultAltText",void 0),this.defaultAltTextView=this._createDefaultAltTextView(),this.saveButtonView=this._createButton(Drupal.t("Save"),e.icons.check,"ck-button-save"),this.saveButtonView.type="submit",this.cancelButtonView=this._createButton(Drupal.t("Cancel"),e.icons.cancel,"ck-button-cancel","cancel"),this._focusables=new g.ViewCollection,this._focusCycler=new g.FocusCycler({focusables:this._focusables,focusTracker:this.focusTracker,keystrokeHandler:this.keystrokes,actions:{focusPrevious:"shift + tab",focusNext:"tab"}}),this.setTemplate({tag:"form",attributes:{class:["ck","ck-media-alternative-text-form","ck-vertical-form"],tabindex:"-1"},children:[this.defaultAltTextView,this.labeledInput,this.saveButtonView,this.cancelButtonView]}),(0,g.injectCssTransitionDisabler)(this)}render(){super.render(),this.keystrokes.listenTo(this.element),(0,g.submitHandler)({view:this}),[this.labeledInput,this.saveButtonView,this.cancelButtonView].forEach((e=>{this._focusables.add(e),this.focusTracker.add(e.element)}))}_createButton(e,t,i,n){const a=new g.ButtonView(this.locale);return a.set({label:e,icon:t,tooltip:!0}),a.extendTemplate({attributes:{class:i}}),n&&a.delegate("execute").to(this,n),a}_createLabeledInputView(){const e=new g.LabeledFieldView(this.locale,g.createLabeledInputText);return e.label=Drupal.t("Alternative text override"),e}_createDefaultAltTextView(){const e=g.Template.bind(this,this);return new g.Template({tag:"div",attributes:{class:["ck-media-alternative-text-form__default-alt-text",e.if("defaultAltText","ck-hidden",(e=>!e))]},children:[{tag:"strong",attributes:{class:"ck-media-alternative-text-form__default-alt-text-label"},children:[Drupal.t("Default alternative text:")]}," ",{tag:"span",attributes:{class:"ck-media-alternative-text-form__default-alt-text-value"},children:[{text:[e.to("defaultAltText")]}]}]})}}class k extends e.Plugin{static get requires(){return[g.ContextualBalloon]}static get pluginName(){return"MediaImageTextAlternativeUi"}init(){this._createButton(),this._createForm()}destroy(){super.destroy(),this._form.destroy()}_createButton(){const t=this.editor;t.ui.componentFactory.add("mediaImageTextAlternative",(i=>{const n=t.commands.get("mediaImageTextAlternative"),a=new g.ButtonView(i);return a.set({label:Drupal.t("Override media image alternative text"),icon:e.icons.lowVision,tooltip:!0}),a.bind("isVisible").to(n,"isEnabled"),this.listenTo(a,"execute",(()=>{this._showForm()})),a}))}_createForm(){const e=this.editor,t=e.editing.view.document;this._balloon=this.editor.plugins.get("ContextualBalloon"),this._form=new M(e.locale),this._form.render(),this.listenTo(this._form,"submit",(()=>{e.execute("mediaImageTextAlternative",{newValue:this._form.labeledInput.fieldView.element.value}),this._hideForm(!0)})),this.listenTo(this._form,"cancel",(()=>{this._hideForm(!0)})),this._form.keystrokes.set("Esc",((e,t)=>{this._hideForm(!0),t()})),this.listenTo(e.ui,"update",(()=>{s(t.selection)?this._isVisible&&function(e){const t=e.plugins.get("ContextualBalloon");if(s(e.editing.view.document.selection)){const i=v(e);t.updatePosition(i)}}(e):this._hideForm(!0)})),(0,g.clickOutsideHandler)({emitter:this._form,activator:()=>this._isVisible,contextElements:[this._balloon.view.element],callback:()=>this._hideForm()})}_showForm(){if(this._isVisible)return;const e=this.editor,t=e.commands.get("mediaImageTextAlternative"),i=e.plugins.get("DrupalMediaMetadataRepository"),n=this._form.labeledInput;this._form.disableCssTransitions(),this._isInBalloon||this._balloon.add({view:this._form,position:v(e)}),n.fieldView.element.value=t.value||"",n.fieldView.value=n.fieldView.element.value,this._form.defaultAltText="";const r=e.model.document.selection.getSelectedElement();a(r)&&i.getMetadata(r).then((e=>{this._form.defaultAltText=e.imageSourceMetadata?e.imageSourceMetadata.alt:""})).catch((e=>{console.warn(e.toString())})),this._form.labeledInput.fieldView.select(),this._form.enableCssTransitions()}_hideForm(e){this._isInBalloon&&(this._form.focusTracker.isFocused&&this._form.saveButtonView.focus(),this._balloon.remove(this._form),e&&this.editor.editing.view.focus())}get _isVisible(){return this._balloon.visibleView===this._form}get _isInBalloon(){return this._balloon.hasView(this._form)}}class A extends e.Plugin{static get requires(){return[y,k]}static get pluginName(){return"MediaImageTextAlternative"}}function D(e,t,i){if(t.attributes)for(const[n,a]of Object.entries(t.attributes))e.setAttribute(n,a,i);t.styles&&e.setStyle(t.styles,i),t.classes&&e.addClass(t.classes,i)}function C(e,t,i){if(!i.consumable.consume(t.item,e.name))return;const n=i.mapper.toViewElement(t.item);D(i.writer,t.attributeNewValue,n)}class _ extends e.Plugin{constructor(e){if(super(e),!e.plugins.has("GeneralHtmlSupport"))return;e.plugins.has("DataFilter")&&e.plugins.has("DataSchema")||console.error("DataFilter and DataSchema plugins are required for Drupal Media to integrate with General HTML Support plugin.");const{schema:t}=e.model,{conversion:i}=e,n=this.editor.plugins.get("DataFilter");this.editor.plugins.get("DataSchema").registerBlockElement({model:"drupalMedia",view:"drupal-media"}),n.on("register:drupal-media",((e,a)=>{"drupalMedia"===a.model&&(t.extend("drupalMedia",{allowAttributes:["htmlLinkAttributes","htmlAttributes"]}),i.for("upcast").add(function(e){return t=>{t.on("element:drupal-media",((t,i,n)=>{function a(t,a){const r=e.processViewAttributes(t,n);r&&n.writer.setAttribute(a,r,i.modelRange)}const r=i.viewItem,o=r.parent;a(r,"htmlAttributes"),o.is("element","a")&&a(o,"htmlLinkAttributes")}),{priority:"low"})}}(n)),i.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const n=i.mapper.toViewElement(t.item),a=function(e,t,i){const n=e.createRangeOn(t);for(const{item:e}of n.getWalker())if(e.is("element",i))return e}(i.writer,n,"a");D(i.writer,t.item.getAttribute("htmlLinkAttributes"),a)}),{priority:"low"})})),i.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const n=i.mapper.toViewElement(t.item).parent;D(i.writer,t.item.getAttribute("htmlLinkAttributes"),n)}),{priority:"low"}),e.on("attribute:htmlAttributes:drupalMedia",C,{priority:"low"})})),e.stop())}))}static get pluginName(){return"DrupalMediaGeneralHtmlSupport"}}class x extends e.Plugin{static get requires(){return[p,_,h,f,A]}static get pluginName(){return"DrupalMedia"}}var V=i("ckeditor5/src/engine.js");function S(e){return Array.from(e.getChildren()).find((e=>"drupal-media"===e.name))}function T(e){return t=>{t.on(`attribute:${e.id}:drupalMedia`,((t,i,n)=>{const a=n.mapper.toViewElement(i.item);let r=Array.from(a.getChildren()).find((e=>"a"===e.name));if(r=!r&&a.is("element","a")?a:Array.from(a.getAncestors()).find((e=>"a"===e.name)),r){for(const[t,i]of(0,E.toMap)(e.attributes))n.writer.setAttribute(t,i,r);e.classes&&n.writer.addClass(e.classes,r);for(const t in e.styles)Object.prototype.hasOwnProperty.call(e.styles,t)&&n.writer.setStyle(t,e.styles[t],r)}}))}}function I(e,t){return e=>{e.on("element:a",((e,i,n)=>{const a=i.viewItem;if(!S(a))return;const r=new V.Matcher(t._createPattern()).match(a);if(!r)return;if(!n.consumable.consume(a,r.match))return;const o=i.modelCursor.nodeBefore;n.writer.setAttribute(t.id,!0,o)}),{priority:"high"})}}class L extends e.Plugin{static get requires(){return["LinkEditing","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaEditing"}init(){const{editor:e}=this;e.model.schema.extend("drupalMedia",{allowAttributes:["linkHref"]}),e.conversion.for("upcast").add((e=>{e.on("element:a",((e,t,i)=>{const n=t.viewItem,a=S(n);if(!a)return;if(!i.consumable.consume(n,{attributes:["href"],name:!0}))return;const r=n.getAttribute("href");if(null===r)return;const o=i.convertItem(a,t.modelCursor);t.modelRange=o.modelRange,t.modelCursor=o.modelCursor;const s=t.modelCursor.nodeBefore;s&&s.is("element","drupalMedia")&&i.writer.setAttribute("linkHref",r,s)}),{priority:"high"})})),e.conversion.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:n}=i;if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item),r=Array.from(a.getChildren()).find((e=>"a"===e.name));if(r)t.attributeNewValue?n.setAttribute("href",t.attributeNewValue,r):(n.move(n.createRangeIn(r),n.createPositionAt(a,0)),n.remove(r));else{const e=Array.from(a.getChildren()).find((e=>e.getAttribute("data-drupal-media-preview"))),i=n.createContainerElement("a",{href:t.attributeNewValue});n.insert(n.createPositionAt(a,0),i),n.move(n.createRangeOn(e),n.createPositionAt(i,0))}}),{priority:"high"})})),e.conversion.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:n}=i;if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item),r=n.createContainerElement("a",{href:t.attributeNewValue});n.insert(n.createPositionBefore(a),r),n.move(n.createRangeOn(a),n.createPositionAt(r,0))}),{priority:"high"})})),this._enableManualDecorators();if(e.commands.get("link").automaticDecorators.length>0)throw new Error("The Drupal Media plugin is not compatible with automatic link decorators. To use Drupal Media, disable any plugins providing automatic link decorators.")}_enableManualDecorators(){const e=this.editor,t=e.commands.get("link");for(const i of t.manualDecorators)e.model.schema.extend("drupalMedia",{allowAttributes:i.id}),e.conversion.for("downcast").add(T(i)),e.conversion.for("upcast").add(I(0,i))}}class P extends e.Plugin{static get requires(){return["LinkEditing","LinkUI","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaUi"}init(){const{editor:e}=this,t=e.editing.view.document;this.listenTo(t,"click",((t,i)=>{this._isSelectedLinkedMedia(e.model.document.selection)&&(i.preventDefault(),t.stop())}),{priority:"high"}),this._createToolbarLinkMediaButton()}_createToolbarLinkMediaButton(){const{editor:e}=this;e.ui.componentFactory.add("drupalLinkMedia",(t=>{const i=new g.ButtonView(t),n=e.plugins.get("LinkUI"),a=e.commands.get("link");return i.set({isEnabled:!0,label:Drupal.t("Link media"),icon:'<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="m11.077 15 .991-1.416a.75.75 0 1 1 1.229.86l-1.148 1.64a.748.748 0 0 1-.217.206 5.251 5.251 0 0 1-8.503-5.955.741.741 0 0 1 .12-.274l1.147-1.639a.75.75 0 1 1 1.228.86L4.933 10.7l.006.003a3.75 3.75 0 0 0 6.132 4.294l.006.004zm5.494-5.335a.748.748 0 0 1-.12.274l-1.147 1.639a.75.75 0 1 1-1.228-.86l.86-1.23a3.75 3.75 0 0 0-6.144-4.301l-.86 1.229a.75.75 0 0 1-1.229-.86l1.148-1.64a.748.748 0 0 1 .217-.206 5.251 5.251 0 0 1 8.503 5.955zm-4.563-2.532a.75.75 0 0 1 .184 1.045l-3.155 4.505a.75.75 0 1 1-1.229-.86l3.155-4.506a.75.75 0 0 1 1.045-.184z"/></svg>\n',keystroke:"Ctrl+K",tooltip:!0,isToggleable:!0}),i.bind("isEnabled").to(a,"isEnabled"),i.bind("isOn").to(a,"value",(e=>!!e)),this.listenTo(i,"execute",(()=>{this._isSelectedLinkedMedia(e.model.document.selection)?n._addActionsView():n._showUI(!0)})),i}))}_isSelectedLinkedMedia(e){const t=e.getSelectedElement();return!!t&&t.is("element","drupalMedia")&&t.hasAttribute("linkHref")}}class O extends e.Plugin{static get requires(){return[L,P]}static get pluginName(){return"DrupalLinkMedia"}}const{objectFullWidth:B,objectInline:N,objectLeft:R,objectRight:j,objectCenter:F,objectBlockLeft:U,objectBlockRight:H}=e.icons,$={get inline(){return{name:"inline",title:"In line",icon:N,modelElements:["imageInline"],isDefault:!0}},get alignLeft(){return{name:"alignLeft",title:"Left aligned image",icon:R,modelElements:["imageBlock","imageInline"],className:"image-style-align-left"}},get alignBlockLeft(){return{name:"alignBlockLeft",title:"Left aligned image",icon:U,modelElements:["imageBlock"],className:"image-style-block-align-left"}},get alignCenter(){return{name:"alignCenter",title:"Centered image",icon:F,modelElements:["imageBlock"],className:"image-style-align-center"}},get alignRight(){return{name:"alignRight",title:"Right aligned image",icon:j,modelElements:["imageBlock","imageInline"],className:"image-style-align-right"}},get alignBlockRight(){return{name:"alignBlockRight",title:"Right aligned image",icon:H,modelElements:["imageBlock"],className:"image-style-block-align-right"}},get block(){return{name:"block",title:"Centered image",icon:F,modelElements:["imageBlock"],isDefault:!0}},get side(){return{name:"side",title:"Side image",icon:j,modelElements:["imageBlock"],className:"image-style-side"}}},q={full:B,left:U,right:H,center:F,inlineLeft:R,inlineRight:j,inline:N},W=[{name:"imageStyle:wrapText",title:"Wrap text",defaultItem:"imageStyle:alignLeft",items:["imageStyle:alignLeft","imageStyle:alignRight"]},{name:"imageStyle:breakText",title:"Break text",defaultItem:"imageStyle:block",items:["imageStyle:alignBlockLeft","imageStyle:block","imageStyle:alignBlockRight"]}];function K(e){(0,E.logWarning)("image-style-configuration-definition-invalid",e)}const z={normalizeStyles:function(e){return(e.configuredStyles.options||[]).map((e=>function(e){e="string"==typeof e?$[e]?{...$[e]}:{name:e}:function(e,t){const i={...t};for(const n in e)Object.prototype.hasOwnProperty.call(t,n)||(i[n]=e[n]);return i}($[e.name],e);"string"==typeof e.icon&&(e.icon=q[e.icon]||e.icon);return e}(e))).filter((t=>function(e,{isBlockPluginLoaded:t,isInlinePluginLoaded:i}){const{modelElements:n,name:a}=e;if(!(n&&n.length&&a))return K({style:e}),!1;{const a=[t?"imageBlock":null,i?"imageInline":null];if(!n.some((e=>a.includes(e))))return(0,E.logWarning)("image-style-missing-dependency",{style:e,missingPlugins:n.map((e=>"imageBlock"===e?"ImageBlockEditing":"ImageInlineEditing"))}),!1}return!0}(t,e)))},getDefaultStylesConfiguration:function(e,t){return e&&t?{options:["inline","alignLeft","alignRight","alignCenter","alignBlockLeft","alignBlockRight","block","side"]}:e?{options:["block","side"]}:t?{options:["inline","alignLeft","alignRight"]}:{}},getDefaultDropdownDefinitions:function(e){return e.has("ImageBlockEditing")&&e.has("ImageInlineEditing")?[...W]:[]},warnInvalidStyle:K,DEFAULT_OPTIONS:$,DEFAULT_ICONS:q,DEFAULT_DROPDOWN_DEFINITIONS:W};function Z(e,t,i){for(const n of t)if(i.checkAttribute(e,n))return!0;return!1}function G(e,t,i){const n=e.getSelectedElement();if(n&&Z(n,i,t))return n;let{parent:a}=e.getFirstPosition();for(;a;){if(a.is("element")&&Z(a,i,t))return a;a=a.parent}return null}class J extends e.Command{constructor(e,t){super(e),this.styles={},Object.keys(t).forEach((e=>{this.styles[e]=new Map(t[e].map((e=>[e.name,e])))})),this.modelAttributes=[];for(const e of Object.keys(t)){const t=u(e);this.modelAttributes.push(t)}}refresh(){const{editor:e}=this,t=G(e.model.document.selection,e.model.schema,this.modelAttributes);this.isEnabled=!!t,this.isEnabled?this.value=this.getValue(t):this.value=!1}getValue(e){const t={};return Object.keys(this.styles).forEach((i=>{const n=u(i);if(e.hasAttribute(n))t[i]=e.getAttribute(n);else for(const[,e]of this.styles[i])e.isDefault&&(t[i]=e.name)})),t}execute(e={}){const{editor:{model:t}}=this,{value:i,group:n}=e,a=u(n);t.change((e=>{const r=G(t.document.selection,t.schema,this.modelAttributes);!i||this.styles[n].get(i).isDefault?e.removeAttribute(a,r):e.setAttribute(a,i,r)}))}}function X(e,t){for(const i of t)if(i.name===e)return i}class Q extends e.Plugin{init(){const{editor:t}=this,i=t.config.get("drupalElementStyles");this.normalizedStyles={},Object.keys(i).forEach((t=>{this.normalizedStyles[t]=i[t].map((t=>("string"==typeof t.icon&&e.icons[t.icon]&&(t.icon=e.icons[t.icon]),t.name&&(t.name=`${t.name}`),t))).filter((e=>e.isDefault||e.attributeName&&e.attributeValue?e.modelElements&&Array.isArray(e.modelElements)?!!e.name||(console.warn("drupalElementStyles options must include a name."),!1):(console.warn("drupalElementStyles options must include an array of supported modelElements."),!1):(console.warn(`${e.attributeValue} drupalElementStyles options must include attributeName and attributeValue.`),!1)))})),this._setupConversion(),t.commands.add("drupalElementStyle",new J(t,this.normalizedStyles))}_setupConversion(){const{editor:e}=this,{schema:t}=e.model;Object.keys(this.normalizedStyles).forEach((i=>{const n=u(i),a=(r=this.normalizedStyles[i],(e,t,i)=>{if(!i.consumable.consume(t.item,e.name))return;const n=X(t.attributeNewValue,r),a=X(t.attributeOldValue,r),o=i.mapper.toViewElement(t.item),s=i.writer;a&&("class"===a.attributeName?s.removeClass(a.attributeValue,o):s.removeAttribute(a.attributeName,o)),n&&("class"===n.attributeName?s.addClass(n.attributeValue,o):n.isDefault||s.setAttribute(n.attributeName,n.attributeValue,o))});var r;const o=function(e,t){const i=e.filter((e=>!e.isDefault));return(e,n,a)=>{if(!n.modelRange)return;const r=n.viewItem,o=(0,E.first)(n.modelRange.getItems());if(o&&a.schema.checkAttribute(o,t))for(const e of i)if("class"===e.attributeName)a.consumable.consume(r,{classes:e.attributeValue})&&a.writer.setAttribute(t,e.name,o);else if(a.consumable.consume(r,{attributes:[e.attributeName]}))for(const e of i)e.attributeValue===r.getAttribute(e.attributeName)&&a.writer.setAttribute(t,e.name,o)}}(this.normalizedStyles[i],n);e.editing.downcastDispatcher.on(`attribute:${n}`,a),e.data.downcastDispatcher.on(`attribute:${n}`,a);[...new Set(this.normalizedStyles[i].map((e=>e.modelElements)).flat())].forEach((e=>{t.extend(e,{allowAttributes:n})})),e.data.upcastDispatcher.on("element",o,{priority:"low"})}))}static get pluginName(){return"DrupalElementStyleEditing"}}const Y=e=>e,ee=(e,t)=>(e?`${e}: `:"")+t;function te(e,t){return`drupalElementStyle:${t}:${e}`}class ie extends e.Plugin{static get requires(){return[Q]}init(){const{plugins:e}=this.editor,t=this.editor.config.get("drupalMedia.toolbar")||[],i=e.get("DrupalElementStyleEditing").normalizedStyles;Object.keys(i).forEach((e=>{i[e].forEach((t=>{this._createButton(t,e,i[e])}))}));t.filter(l).filter((e=>{const t=[];if(!e.display)return console.warn("dropdown configuration must include a display key specifying either listDropdown or splitButton."),!1;e.items.includes(e.defaultItem)||console.warn("defaultItem must be part of items in the dropdown configuration.");for(const i of e.items){const e=i.split(":")[1];t.push(e)}return!!t.every((e=>e===t[0]))||(console.warn("dropdown configuration should only contain buttons from one group."),!1)})).forEach((e=>{if(e.items.length>=2){const t=e.name.split(":")[1];switch(e.display){case"splitButton":this._createDropdown(e,i[t]);break;case"listDropdown":this._createListDropdown(e,i[t])}}}))}updateOptionVisibility(e,t,i,n){const{selection:a}=this.editor.model.document,r={};r[n]=e;const o=a?a.getSelectedElement():G(a,this.editor.model.schema,r),s=e.filter((function(e){for(const[t,i]of(0,E.toMap)(e.modelAttributes))if(o&&o.hasAttribute(t))return i.includes(o.getAttribute(t));return!0}));i.hasOwnProperty("model")?s.includes(t)?i.model.set({class:""}):i.model.set({class:"ck-hidden"}):s.includes(t)?i.set({class:""}):i.set({class:"ck-hidden"})}_createDropdown(e,t){const i=this.editor.ui.componentFactory;i.add(e.name,(n=>{let a;const{defaultItem:r,items:o,title:s}=e,l=o.filter((e=>{const i=e.split(":")[1];return t.find((({name:t})=>te(t,i)===e))})).map((e=>{const t=i.create(e);return e===r&&(a=t),t}));o.length!==l.length&&z.warnInvalidStyle({dropdown:e});const d=(0,g.createDropdown)(n,g.SplitButtonView),u=d.buttonView;return(0,g.addToolbarToDropdown)(d,l),u.set({label:ee(s,a.label),class:null,tooltip:!0}),u.bind("icon").toMany(l,"isOn",((...e)=>{const t=e.findIndex(Y);return t<0?a.icon:l[t].icon})),u.bind("label").toMany(l,"isOn",((...e)=>{const t=e.findIndex(Y);return ee(s,t<0?a.label:l[t].label)})),u.bind("isOn").toMany(l,"isOn",((...e)=>e.some(Y))),u.bind("class").toMany(l,"isOn",((...e)=>e.some(Y)?"ck-splitbutton_flatten":null)),u.on("execute",(()=>{l.some((({isOn:e})=>e))?d.isOpen=!d.isOpen:a.fire("execute")})),d.bind("isEnabled").toMany(l,"isEnabled",((...e)=>e.some(Y))),d}))}_createButton(e,t,i){const n=e.name;this.editor.ui.componentFactory.add(te(n,t),(a=>{const r=this.editor.commands.get("drupalElementStyle"),o=new g.ButtonView(a);return o.set({label:e.title,icon:e.icon,tooltip:!0,isToggleable:!0}),o.bind("isEnabled").to(r,"isEnabled"),o.bind("isOn").to(r,"value",(e=>e&&e[t]===n)),o.on("execute",this._executeCommand.bind(this,n,t)),this.listenTo(this.editor.ui,"update",(()=>{this.updateOptionVisibility(i,e,o,t)})),o}))}getDropdownListItemDefinitions(e,t,i){const n=new E.Collection;return e.forEach((t=>{const a={type:"button",model:new g.Model({group:i,commandValue:t.name,label:t.title,withText:!0,class:""})};n.add(a),this.listenTo(this.editor.ui,"update",(()=>{this.updateOptionVisibility(e,t,a,i)}))})),n}_createListDropdown(e,t){const i=this.editor.ui.componentFactory;i.add(e.name,(n=>{let a;const{defaultItem:r,items:o,title:s,defaultText:l}=e,d=e.name.split(":")[1],u=o.filter((e=>t.find((({name:t})=>te(t,d)===e)))).map((e=>{const t=i.create(e);return e===r&&(a=t),t}));o.length!==u.length&&z.warnInvalidStyle({dropdown:e});const c=(0,g.createDropdown)(n,g.DropdownButtonView),m=c.buttonView;m.set({label:ee(s,a.label),class:null,tooltip:l,withText:!0});const p=this.editor.commands.get("drupalElementStyle");return m.bind("label").to(p,"value",(e=>{if(e&&e[d])for(const i of t)if(i.name===e[d])return i.title;return l})),c.bind("isOn").to(p),c.bind("isEnabled").to(this),(0,g.addListToDropdown)(c,this.getDropdownListItemDefinitions(t,p,d)),this.listenTo(c,"execute",(e=>{this._executeCommand(e.source.commandValue,e.source.group)})),c}))}_executeCommand(e,t){this.editor.execute("drupalElementStyle",{value:e,group:t}),this.editor.editing.view.focus()}static get pluginName(){return"DrupalElementStyleUi"}}class ne extends e.Plugin{static get requires(){return[Q,ie]}static get pluginName(){return"DrupalElementStyle"}}function ae(e){const t=e.getFirstPosition().findAncestor("caption");return t&&a(t.parent)?t:null}function re(e){for(const t of e.getChildren())if(t&&t.is("element","caption"))return t;return null}class oe extends e.Command{refresh(){const e=this.editor.model.document.selection,t=e.getSelectedElement();if(!t)return this.isEnabled=!!o(e),void(this.value=!!ae(e));this.isEnabled=a(t),this.isEnabled?this.value=!!re(t):this.value=!1}execute(e={}){const{focusCaptionOnShow:t}=e;this.editor.model.change((e=>{this.value?this._hideDrupalMediaCaption(e):this._showDrupalMediaCaption(e,t)}))}_showDrupalMediaCaption(e,t){const i=this.editor.model.document.selection,n=this.editor.plugins.get("DrupalMediaCaptionEditing"),a=o(i),r=n._getSavedCaption(a)||e.createElement("caption");e.append(r,a),t&&e.setSelection(r,"in")}_hideDrupalMediaCaption(e){const t=this.editor,i=t.model.document.selection,n=t.plugins.get("DrupalMediaCaptionEditing");let a,r=i.getSelectedElement();r?a=re(r):(a=ae(i),r=o(i)),n._saveCaption(r,a),e.setSelection(r,"on"),e.remove(a)}}class se extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionEditing"}constructor(e){super(e),this._savedCaptionsMap=new WeakMap}init(){const e=this.editor,t=e.model.schema;t.isRegistered("caption")?t.extend("caption",{allowIn:"drupalMedia"}):t.register("caption",{allowIn:"drupalMedia",allowContentOf:"$block",isLimit:!0}),e.commands.add("toggleMediaCaption",new oe(e)),this._setupConversion()}_setupConversion(){const e=this.editor,i=e.editing.view;var n;e.conversion.for("upcast").add(function(e){const t=(t,i,n)=>{const{viewItem:a}=i,{writer:r,consumable:o}=n;if(!i.modelRange||!o.consume(a,{attributes:["data-caption"]}))return;const s=r.createElement("caption"),l=i.modelRange.start.nodeAfter,d=e.data.processor.toView(a.getAttribute("data-caption"));n.consumable.constructor.createFrom(d,n.consumable),n.convertChildren(d,s),r.append(s,l)};return e=>{e.on("element:drupal-media",t,{priority:"low"})}}(e)),e.conversion.for("editingDowncast").elementToElement({model:"caption",view:(e,{writer:n})=>{if(!a(e.parent))return null;const r=n.createEditableElement("figcaption");return r.placeholder=Drupal.t("Enter media caption"),(0,V.enablePlaceholder)({view:i,element:r,keepOnFocus:!0}),(0,t.toWidgetEditable)(r,n)}}),e.editing.mapper.on("modelToViewPosition",(n=i,(e,t)=>{const i=t.modelPosition,r=i.parent;if(!a(r))return;const o=t.mapper.toViewElement(r);t.viewPosition=n.createPositionAt(o,i.offset+1)})),e.conversion.for("dataDowncast").add(function(e){return t=>{t.on("insert:caption",((t,i,n)=>{const{consumable:r,writer:o,mapper:s}=n;if(!a(i.item.parent)||!r.consume(i.item,"insert"))return;const l=e.model.createRangeIn(i.item),d=o.createDocumentFragment();s.bindElements(i.item,d);for(const{item:t}of Array.from(l)){const i={item:t,range:e.model.createRangeOn(t)},a=`insert:${t.name||"$text"}`;e.data.downcastDispatcher.fire(a,i,n);for(const a of t.getAttributeKeys())Object.assign(i,{attributeKey:a,attributeOldValue:null,attributeNewValue:i.item.getAttribute(a)}),e.data.downcastDispatcher.fire(`attribute:${a}`,i,n)}for(const e of o.createRangeIn(d).getItems())s.unbindViewElement(e);s.unbindViewElement(d);const u=e.data.processor.toData(d);if(u){const e=s.toViewElement(i.item.parent);o.setAttribute("data-caption",u,e)}}))}}(e))}_getSavedCaption(e){const t=this._savedCaptionsMap.get(e);return t?V.Element.fromJSON(t):null}_saveCaption(e,t){this._savedCaptionsMap.set(e,t.toJSON())}}class le extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionUI"}init(){const{editor:t}=this,i=t.editing.view;t.ui.componentFactory.add("toggleDrupalMediaCaption",(n=>{const a=new g.ButtonView(n),r=t.commands.get("toggleMediaCaption");return a.set({label:Drupal.t("Caption media"),icon:e.icons.caption,tooltip:!0,isToggleable:!0}),a.bind("isOn","isEnabled").to(r,"value","isEnabled"),a.bind("label").to(r,"value",(e=>e?Drupal.t("Toggle caption off"):Drupal.t("Toggle caption on"))),this.listenTo(a,"execute",(()=>{t.execute("toggleMediaCaption",{focusCaptionOnShow:!0});const e=ae(t.model.document.selection);if(e){const n=t.editing.mapper.toViewElement(e);i.scrollToTheSelection(),i.change((e=>{e.addClass("drupal-media__caption_highlighted",n)}))}t.editing.view.focus()})),a}))}}class de extends e.Plugin{static get requires(){return[se,le]}static get pluginName(){return"DrupalMediaCaption"}}const ue={DrupalMedia:x,MediaImageTextAlternative:A,MediaImageTextAlternativeEditing:y,MediaImageTextAlternativeUi:k,DrupalLinkMedia:O,DrupalMediaCaption:de,DrupalElementStyle:ne}})(),n=n.default})()));
\ No newline at end of file
diff --git a/web/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupallinkmedia/drupallinkmediaediting.js b/web/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupallinkmedia/drupallinkmediaediting.js
index 67f7f303d1..38aa11f865 100644
--- a/web/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupallinkmedia/drupallinkmediaediting.js
+++ b/web/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupallinkmedia/drupallinkmediaediting.js
@@ -53,7 +53,7 @@ function upcastMediaLink() {
         const linkHref = viewLink.getAttribute('href');
 
         // Missing the `href` attribute.
-        if (!linkHref) {
+        if (linkHref === null) {
           return;
         }
 
diff --git a/web/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php b/web/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php
index f7d921ff4a..51282d4b45 100644
--- a/web/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php
+++ b/web/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php
@@ -190,6 +190,11 @@ protected function doVocabularyListTest() {
     // Test if the link to translate actually goes to the translate page.
     $this->drupalGet($translate_link);
     $this->assertSession()->responseContains('<th>Language</th>');
+
+    // Test if the local task for translation is on this page.
+    $this->assertSession()->linkExists('Translate taxonomy vocabulary');
+    $local_task_url = parse_url($this->getSession()->getPage()->findLink('Translate taxonomy vocabulary')->getAttribute('href'));
+    $this->assertSame(base_path() . $translate_link, $local_task_url['path']);
   }
 
   /**
diff --git a/web/core/modules/contact/contact.module b/web/core/modules/contact/contact.module
index 271a01342e..5301310932 100644
--- a/web/core/modules/contact/contact.module
+++ b/web/core/modules/contact/contact.module
@@ -161,7 +161,13 @@ function contact_mail($key, &$message, $params) {
       $message['subject'] .= t('[@site-name] @subject', $variables, $options);
       $message['body'][] = t('Hello @recipient-name,', $variables, $options);
       $message['body'][] = t("@sender-name (@sender-url) has sent you a message via your contact form at @site-name.", $variables, $options);
-      $message['body'][] = t("If you don't want to receive such emails, you can change your settings at @recipient-edit-url.", $variables, $options);
+      // Only include the opt-out line in the original email and not in the
+      // copy to the sender. Also exclude this if the email was sent from a
+      // user administrator because they can always send emails even if the
+      // contacted user has disabled their contact form.
+      if ($key === 'user_mail' && !$params['sender']->hasPermission('administer users')) {
+        $message['body'][] = t("If you don't want to receive such messages, you can change your settings at @recipient-edit-url.", $variables, $options);
+      }
       $build = \Drupal::entityTypeManager()
         ->getViewBuilder('contact_message')
         ->view($contact_message, 'mail');
diff --git a/web/core/modules/contact/tests/src/Functional/ContactPersonalTest.php b/web/core/modules/contact/tests/src/Functional/ContactPersonalTest.php
index 4e85773341..ea3b6c08c7 100644
--- a/web/core/modules/contact/tests/src/Functional/ContactPersonalTest.php
+++ b/web/core/modules/contact/tests/src/Functional/ContactPersonalTest.php
@@ -336,18 +336,65 @@ protected function checkContactAccess($response, $contact_value = NULL) {
    * @param array $message
    *   (optional) An array with the form fields being used. Defaults to an empty
    *   array.
+   * @param bool $user_copy
+   *   (optional) A boolean to determine whether to send a user copy email.
+   *   Defaults to FALSE.
    *
    * @return array
    *   An array with the form fields being used.
    */
-  protected function submitPersonalContact(AccountInterface $account, array $message = []) {
+  protected function submitPersonalContact(AccountInterface $account, array $message = [], bool $user_copy = FALSE) {
     $message += [
       'subject[0][value]' => $this->randomMachineName(16) . '< " =+ >',
       'message[0][value]' => $this->randomMachineName(64) . '< " =+ >',
+      'copy' => $user_copy,
     ];
     $this->drupalGet('user/' . $account->id() . '/contact');
     $this->submitForm($message, 'Send message');
     return $message;
   }
 
+  /**
+   * Tests that the opt-out message is included correctly in contact emails.
+   */
+  public function testPersonalContactForm(): void {
+    $opt_out_message = "If you don't want to receive such messages, you can change your settings at";
+
+    // Send an email from an admin (should not contain the opt-out message).
+    $this->drupalLogin($this->adminUser);
+    $this->submitPersonalContact($this->contactUser);
+    $this->drupalLogout();
+
+    $this->assertStringNotContainsString($opt_out_message, $this->getMails()[0]['body'], 'Opt-out message excluded in email.');
+
+    // Send an email from a non-admin (should contain the opt-out message).
+    $this->drupalLogin($this->webUser);
+    $this->submitPersonalContact($this->contactUser);
+
+    $this->assertMailString('body', $opt_out_message, 1, 'Opt-out message included in email.');
+  }
+
+  /**
+   * Tests that the opt-out message is not included in user copy emails.
+   */
+  public function testPersonalContactFormUserCopy(): void {
+    $opt_out_message = "If you don't want to receive such messages, you can change your settings at";
+
+    // Send an email from an admin.
+    $this->drupalLogin($this->adminUser);
+    $this->submitPersonalContact($this->contactUser, [], TRUE);
+    $this->drupalLogout();
+
+    // Send an email from a non-admin.
+    $this->drupalLogin($this->webUser);
+    $this->submitPersonalContact($this->contactUser, [], TRUE);
+
+    $user_copy_emails = $this->getMails(['id' => 'contact_user_copy']);
+
+    // Tests that the opt-out message is not included in admin user copy emails.
+    $this->assertStringNotContainsString($opt_out_message, $user_copy_emails[0]['body'], 'Opt-out message not included in admin user copy email.');
+    // Tests that the opt-out message is not included in non-admin user copy emails.
+    $this->assertStringNotContainsString($opt_out_message, $user_copy_emails[1]['body'], 'Opt-out message not included in non-admin user copy email.');
+  }
+
 }
diff --git a/web/core/modules/contact/tests/src/Unit/MailHandlerTest.php b/web/core/modules/contact/tests/src/Unit/MailHandlerTest.php
index 173c9c7752..6fbdff1d4d 100644
--- a/web/core/modules/contact/tests/src/Unit/MailHandlerTest.php
+++ b/web/core/modules/contact/tests/src/Unit/MailHandlerTest.php
@@ -6,10 +6,8 @@
 
 use Drupal\contact\MailHandler;
 use Drupal\contact\MailHandlerException;
-use Drupal\contact\MessageInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Language\Language;
-use Drupal\Core\Session\AccountInterface;
 use Drupal\Tests\UnitTestCase;
 use Drupal\user\Entity\User;
 
@@ -135,19 +133,37 @@ public function testInvalidRecipient() {
    *
    * @covers ::sendMailMessages
    */
-  public function testSendMailMessages(MessageInterface $message, AccountInterface $sender, $results) {
+  public function testSendMailMessages(bool $anonymous, ?bool $auto_reply, bool $copy_sender, array $results) {
+    if ($anonymous) {
+      $message = $this->getAnonymousMockMessage(explode(', ', $results[0]['to']), $auto_reply, $copy_sender);
+      $sender = $this->getMockSender();
+    }
+    else {
+      $message = $this->getAuthenticatedMockMessage($copy_sender);
+      $sender = $this->getMockSender(FALSE, 'user@drupal.org');
+    }
+
+    $expected_params['contact_message'] = $message;
+    $expected_params['sender'] = $sender;
+    if ($anonymous) {
+      $expected_params['contact_form'] = $message->getContactForm();
+    }
+    else {
+      $expected_params['recipient'] = $message->getPersonalRecipient();
+    }
+
     $this->logger->expects($this->once())
       ->method('info');
     $this->mailManager->expects($this->any())
       ->method('mail')
       ->willReturnCallback(
-        function ($module, $key, $to, $langcode, $params, $from) use (&$results) {
+        function ($module, $key, $to, $langcode, $params, $from) use (&$results, $expected_params) {
           $result = array_shift($results);
           $this->assertEquals($module, $result['module']);
           $this->assertEquals($key, $result['key']);
           $this->assertEquals($to, $result['to']);
           $this->assertEquals($langcode, $result['langcode']);
-          $this->assertEquals($params, $result['params']);
+          $this->assertEquals($params, $expected_params);
           $this->assertEquals($from, $result['from']);
         });
     $this->userStorage->expects($this->any())
@@ -159,128 +175,48 @@ function ($module, $key, $to, $langcode, $params, $from) use (&$results) {
   /**
    * Data provider for ::testSendMailMessages.
    */
-  public function getSendMailMessages() {
-    $data = [];
-    $recipients = ['admin@drupal.org', 'user@drupal.org'];
+  public static function getSendMailMessages() {
     $default_result = [
       'module' => 'contact',
-      'key' => '',
-      'to' => implode(', ', $recipients),
+      'key' => 'page_mail',
+      'to' => 'admin@drupal.org, user@drupal.org',
       'langcode' => 'en',
       'params' => [],
       'from' => 'anonymous@drupal.org',
     ];
-    $results = [];
-    $message = $this->getAnonymousMockMessage($recipients, '');
-    $sender = $this->getMockSender();
-    $result = [
-      'key' => 'page_mail',
-      'params' => [
-        'contact_message' => $message,
-        'sender' => $sender,
-        'contact_form' => $message->getContactForm(),
-      ],
-    ];
-    $results[] = $result + $default_result;
-    $data[] = [$message, $sender, $results];
 
-    $results = [];
-    $message = $this->getAnonymousMockMessage($recipients, 'reply');
-    $sender = $this->getMockSender();
-    $result = [
-      'key' => 'page_mail',
-      'params' => [
-        'contact_message' => $message,
-        'sender' => $sender,
-        'contact_form' => $message->getContactForm(),
-      ],
-    ];
-    $results[] = $result + $default_result;
-    $result['key'] = 'page_autoreply';
-    $result['to'] = 'anonymous@drupal.org';
-    $result['from'] = NULL;
-    $results[] = $result + $default_result;
-    $data[] = [$message, $sender, $results];
-
-    $results = [];
-    $message = $this->getAnonymousMockMessage($recipients, '', TRUE);
-    $sender = $this->getMockSender();
-    $result = [
-      'key' => 'page_mail',
-      'params' => [
-        'contact_message' => $message,
-        'sender' => $sender,
-        'contact_form' => $message->getContactForm(),
-      ],
-    ];
-    $results[] = $result + $default_result;
-    $result['key'] = 'page_copy';
-    $result['to'] = 'anonymous@drupal.org';
-    $results[] = $result + $default_result;
-    $data[] = [$message, $sender, $results];
-
-    $results = [];
-    $message = $this->getAnonymousMockMessage($recipients, 'reply', TRUE);
-    $sender = $this->getMockSender();
-    $result = [
-      'key' => 'page_mail',
-      'params' => [
-        'contact_message' => $message,
-        'sender' => $sender,
-        'contact_form' => $message->getContactForm(),
-      ],
-    ];
-    $results[] = $result + $default_result;
-    $result['key'] = 'page_copy';
-    $result['to'] = 'anonymous@drupal.org';
-    $results[] = $result + $default_result;
-    $result['key'] = 'page_autoreply';
-    $result['from'] = NULL;
-    $results[] = $result + $default_result;
-    $data[] = [$message, $sender, $results];
+    $autoreply_result = [
+      'key' => 'page_autoreply',
+      'to' => 'anonymous@drupal.org',
+      'from' => NULL,
+    ] + $default_result;
 
-    // For authenticated user.
-    $results = [];
-    $message = $this->getAuthenticatedMockMessage();
-    $sender = $this->getMockSender(FALSE, 'user@drupal.org');
-    $result = [
-      'module' => 'contact',
-      'key' => 'user_mail',
-      'to' => 'user2@drupal.org',
-      'langcode' => 'en',
-      'params' => [
-        'contact_message' => $message,
-        'sender' => $sender,
-        'recipient' => $message->getPersonalRecipient(),
-      ],
-      'from' => 'user@drupal.org',
-    ];
-    $results[] = $result;
-    $data[] = [$message, $sender, $results];
+    $copy_result = [
+      'key' => 'page_copy',
+      'to' => 'anonymous@drupal.org',
+    ] + $default_result;
 
-    $results = [];
-    $message = $this->getAuthenticatedMockMessage(TRUE);
-    $sender = $this->getMockSender(FALSE, 'user@drupal.org');
-    $result = [
+    yield 'anonymous, no auto reply, no copy sender' => [TRUE, FALSE, FALSE, [$default_result]];
+    yield 'anonymous, auto reply, no copy sender' => [TRUE, TRUE, FALSE, [$default_result, $autoreply_result]];
+    yield 'anonymous, no auto reply, copy sender' => [TRUE, FALSE, TRUE, [$default_result, $copy_result]];
+    yield 'anonymous, auto reply, copy sender' => [TRUE, TRUE, TRUE, [$default_result, $copy_result, $autoreply_result]];
+
+    // For authenticated user.
+    $default_result = [
       'module' => 'contact',
       'key' => 'user_mail',
       'to' => 'user2@drupal.org',
       'langcode' => 'en',
-      'params' => [
-        'contact_message' => $message,
-        'sender' => $sender,
-        'recipient' => $message->getPersonalRecipient(),
-      ],
       'from' => 'user@drupal.org',
     ];
-    $results[] = $result;
 
-    $result['key'] = 'user_copy';
-    $result['to'] = $result['from'];
-    $results[] = $result;
-    $data[] = [$message, $sender, $results];
+    $copy_result = [
+      'key' => 'user_copy',
+      'to' => 'user@drupal.org',
+    ] + $default_result;
 
-    return $data;
+    yield 'authenticated, no copy sender' => [FALSE, NULL, FALSE, [$default_result]];
+    yield 'authenticated, copy sender' => [FALSE, NULL, TRUE, [$default_result, $copy_result]];
   }
 
   /**
@@ -347,7 +283,7 @@ protected function getAnonymousMockMessage($recipients, $auto_reply, $copy_sende
       ->willReturn($copy_sender);
     $message->expects($this->any())
       ->method('getContactForm')
-      ->willReturn($this->getMockContactForm($recipients, $auto_reply));
+      ->willReturn($this->getMockContactForm($recipients, $auto_reply ? 'reply' : ''));
     return $message;
   }
 
@@ -381,9 +317,6 @@ protected function getAuthenticatedMockMessage($copy_sender = FALSE) {
     $message->expects($this->any())
       ->method('getPersonalRecipient')
       ->willReturn($recipient);
-    $message->expects($this->any())
-      ->method('getContactForm')
-      ->willReturn($this->getMockContactForm('user2@drupal.org', FALSE));
     return $message;
   }
 
diff --git a/web/core/modules/content_translation/content_translation.module b/web/core/modules/content_translation/content_translation.module
index b4b9955014..eba279690f 100644
--- a/web/core/modules/content_translation/content_translation.module
+++ b/web/core/modules/content_translation/content_translation.module
@@ -28,7 +28,7 @@ function content_translation_help($route_name, RouteMatchInterface $route_match)
     case 'help.page.content_translation':
       $output = '';
       $output .= '<h2>' . t('About') . '</h2>';
-      $output .= '<p>' . t('The Content Translation module allows you to translate content, comments, content blocks, taxonomy terms, users and other <a href=":field_help" title="Field module help, with background on content entities">content entities</a>. Together with the modules <a href=":language">Language</a>, <a href=":config-trans">Configuration Translation</a>, and <a href=":locale">Interface Translation</a>, it allows you to build multilingual websites. For more information, see the <a href=":translation-entity">online documentation for the Content Translation module</a>.', [':locale' => (\Drupal::moduleHandler()->moduleExists('locale')) ? Url::fromRoute('help.page', ['name' => 'locale'])->toString() : '#', ':config-trans' => (\Drupal::moduleHandler()->moduleExists('config_translation')) ? Url::fromRoute('help.page', ['name' => 'config_translation'])->toString() : '#', ':language' => Url::fromRoute('help.page', ['name' => 'language'])->toString(), ':translation-entity' => 'https://www.drupal.org/documentation/modules/translation', ':field_help' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</p>';
+      $output .= '<p>' . t('The Content Translation module allows you to translate content, comments, content blocks, taxonomy terms, users and other <a href=":field_help" title="Field module help, with background on content entities">content entities</a>. Together with the modules <a href=":language">Language</a>, <a href=":config-trans">Configuration Translation</a>, and <a href=":locale">Interface Translation</a>, it allows you to build multilingual websites. For more information, see the <a href=":translation-entity">online documentation for the Content Translation module</a>.', [':locale' => (\Drupal::moduleHandler()->moduleExists('locale')) ? Url::fromRoute('help.page', ['name' => 'locale'])->toString() : '#', ':config-trans' => (\Drupal::moduleHandler()->moduleExists('config_translation')) ? Url::fromRoute('help.page', ['name' => 'config_translation'])->toString() : '#', ':language' => Url::fromRoute('help.page', ['name' => 'language'])->toString(), ':translation-entity' => 'https://www.drupal.org/docs/8/core/modules/content-translation', ':field_help' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</p>';
       $output .= '<h2>' . t('Uses') . '</h2>';
       $output .= '<dl>';
       $output .= '<dt>' . t('Enabling translation') . '</dt>';
diff --git a/web/core/modules/contextual/contextual.module b/web/core/modules/contextual/contextual.module
index 185c8cde6e..d57ca6c178 100644
--- a/web/core/modules/contextual/contextual.module
+++ b/web/core/modules/contextual/contextual.module
@@ -77,7 +77,7 @@ function contextual_help($route_name, RouteMatchInterface $route_match) {
     case 'help.page.contextual':
       $output = '';
       $output .= '<h2>' . t('About') . '</h2>';
-      $output .= '<p>' . t('The Contextual links module gives users with the <em>Use contextual links</em> permission quick access to tasks associated with certain areas of pages on your site. For example, a menu displayed as a block has links to edit the menu and configure the block. For more information, see the <a href=":contextual">online documentation for the Contextual Links module</a>.', [':contextual' => 'https://www.drupal.org/documentation/modules/contextual']) . '</p>';
+      $output .= '<p>' . t('The Contextual links module gives users with the <em>Use contextual links</em> permission quick access to tasks associated with certain areas of pages on your site. For example, a menu displayed as a block has links to edit the menu and configure the block. For more information, see the <a href=":contextual">online documentation for the Contextual Links module</a>.', [':contextual' => 'https://www.drupal.org/docs/8/core/modules/contextual']) . '</p>';
       $output .= '<h2>' . t('Uses') . '</h2>';
       $output .= '<dl>';
       $output .= '<dt>' . t('Displaying contextual links') . '</dt>';
diff --git a/web/core/modules/field_ui/field_ui.module b/web/core/modules/field_ui/field_ui.module
index 36570a4b6a..421347b831 100644
--- a/web/core/modules/field_ui/field_ui.module
+++ b/web/core/modules/field_ui/field_ui.module
@@ -26,7 +26,7 @@ function field_ui_help($route_name, RouteMatchInterface $route_match) {
     case 'help.page.field_ui':
       $output = '';
       $output .= '<h2>' . t('About') . '</h2>';
-      $output .= '<p>' . t('The Field UI module provides an administrative user interface (UI) for managing and displaying fields. Fields can be attached to most content entity sub-types. Different field types, widgets, and formatters are provided by the modules installed on your site, and managed by the Field module. For background information and terminology related to fields and entities, see the <a href=":field">Field module help page</a>. For more information about the Field UI, see the <a href=":field_ui_docs">online documentation for the Field UI module</a>.', [':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString(), ':field_ui_docs' => 'https://www.drupal.org/documentation/modules/field-ui']) . '</p>';
+      $output .= '<p>' . t('The Field UI module provides an administrative user interface (UI) for managing and displaying fields. Fields can be attached to most content entity sub-types. Different field types, widgets, and formatters are provided by the modules installed on your site, and managed by the Field module. For background information and terminology related to fields and entities, see the <a href=":field">Field module help page</a>. For more information about the Field UI, see the <a href=":field_ui_docs">online documentation for the Field UI module</a>.', [':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString(), ':field_ui_docs' => 'https://www.drupal.org/docs/8/core/modules/field-ui']) . '</p>';
       $output .= '<h2>' . t('Uses') . '</h2>';
       $output .= '<dl>';
       $output .= '<dt>' . t('Creating a field') . '</dt>';
diff --git a/web/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php b/web/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
index a0002ba70c..d9b76b4fe0 100644
--- a/web/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
+++ b/web/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\field_ui\Kernel;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Entity\Display\EntityDisplayInterface;
@@ -703,8 +702,8 @@ protected function assertDependencyHelper(bool $assertion, string $type, string
     $dependencies = !empty($all_dependencies[$type]) ? $all_dependencies[$type] : [];
     $context = $display instanceof EntityViewDisplayInterface ? 'View' : 'Form';
     $value = $assertion ? in_array($key, $dependencies) : !in_array($key, $dependencies);
-    $args = ['@context' => $context, '@id' => $display->id(), '@type' => $type, '@key' => $key];
-    $message = $assertion ? new FormattableMarkup("@context display '@id' depends on @type '@key'.", $args) : new FormattableMarkup("@context display '@id' do not depend on @type '@key'.", $args);
+    $display_id = $display->id();
+    $message = $assertion ? "$context display '$display_id' depends on $type '$key'." : "$context display '$display_id' do not depend on $type '$key'.";
     $this->assertTrue($value, $message);
   }
 
diff --git a/web/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php b/web/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
index 9ae8c89f88..44f3d310df 100644
--- a/web/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
+++ b/web/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
@@ -76,7 +76,7 @@ class FileUploadResource extends ResourceBase {
   /**
    * The file system service.
    *
-   * @var \Drupal\Core\File\FileSystem
+   * @var \Drupal\Core\File\FileSystemInterface
    */
   protected $fileSystem;
 
diff --git a/web/core/modules/file/tests/src/Functional/Formatter/FileMediaFormatterTestBase.php b/web/core/modules/file/tests/src/Functional/Formatter/FileMediaFormatterTestBase.php
index af57e3d7c9..9e05605124 100644
--- a/web/core/modules/file/tests/src/Functional/Formatter/FileMediaFormatterTestBase.php
+++ b/web/core/modules/file/tests/src/Functional/Formatter/FileMediaFormatterTestBase.php
@@ -84,7 +84,7 @@ protected function createMediaField($formatter, $file_extensions, array $formatt
    *     - The number of expected HTML tags.
    *     - An array of settings for the field formatter.
    */
-  public function dataProvider() {
+  public static function dataProvider(): array {
     return [
       [2, []],
       [1, ['multiple_file_display_type' => 'sources']],
diff --git a/web/core/modules/file/tests/src/Kernel/FileManagedUnitTestBase.php b/web/core/modules/file/tests/src/Kernel/FileManagedUnitTestBase.php
index 19ca6f584a..2ee17ddb73 100644
--- a/web/core/modules/file/tests/src/Kernel/FileManagedUnitTestBase.php
+++ b/web/core/modules/file/tests/src/Kernel/FileManagedUnitTestBase.php
@@ -57,16 +57,16 @@ public function assertFileHooksCalled($expected) {
     // Determine if there were any expected that were not called.
     $uncalled = array_diff($expected, $actual);
     if (count($uncalled)) {
-      $this->assertTrue(FALSE, new FormattableMarkup('Expected hooks %expected to be called but %uncalled was not called.', ['%expected' => implode(', ', $expected), '%uncalled' => implode(', ', $uncalled)]));
+      $this->assertTrue(FALSE, sprintf('Expected hooks %s to be called but %s was not called.', implode(', ', $expected), implode(', ', $uncalled)));
     }
     else {
-      $this->assertTrue(TRUE, new FormattableMarkup('All the expected hooks were called: %expected', ['%expected' => empty($expected) ? '(none)' : implode(', ', $expected)]));
+      $this->assertTrue(TRUE, sprintf('All the expected hooks were called: %s', empty($expected) ? '(none)' : implode(', ', $expected)));
     }
 
     // Determine if there were any unexpected calls.
     $unexpected = array_diff($actual, $expected);
     if (count($unexpected)) {
-      $this->assertTrue(FALSE, new FormattableMarkup('Unexpected hooks were called: %unexpected.', ['%unexpected' => empty($unexpected) ? '(none)' : implode(', ', $unexpected)]));
+      $this->assertTrue(FALSE, sprintf('Unexpected hooks were called: %s.', empty($unexpected) ? '(none)' : implode(', ', $unexpected)));
     }
     else {
       $this->assertTrue(TRUE, 'No unexpected hooks were called.');
diff --git a/web/core/modules/file/tests/src/Kernel/MoveTest.php b/web/core/modules/file/tests/src/Kernel/MoveTest.php
index 9bc8d0e243..35c7e526bd 100644
--- a/web/core/modules/file/tests/src/Kernel/MoveTest.php
+++ b/web/core/modules/file/tests/src/Kernel/MoveTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\file\Kernel;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\EntityTypeManager;
 use Drupal\Core\File\Exception\FileExistsException;
@@ -57,7 +56,7 @@ public function testNormal() {
     $this->assertFileHooksCalled(['move', 'load', 'update']);
 
     // Make sure we got the same file back.
-    $this->assertEquals($source->id(), $result->id(), new FormattableMarkup("Source file id's' %fid is unchanged after move.", ['%fid' => $source->id()]));
+    $this->assertEquals($source->id(), $result->id(), "Source file ID {$source->id()} should be unchanged after move.");
 
     // Reload the file from the database and check that the changes were
     // actually saved.
diff --git a/web/core/modules/filter/tests/src/Kernel/FilterKernelTest.php b/web/core/modules/filter/tests/src/Kernel/FilterKernelTest.php
index 357437c334..cfc8cdb503 100644
--- a/web/core/modules/filter/tests/src/Kernel/FilterKernelTest.php
+++ b/web/core/modules/filter/tests/src/Kernel/FilterKernelTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\filter\Kernel;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Render\RenderContext;
@@ -940,18 +939,18 @@ public function assertFilteredString(FilterInterface $filter, array $tests): voi
       $result = $filter->process($source, $filter)->getProcessedText();
       foreach ($tasks as $value => $is_expected) {
         if ($is_expected) {
-          $this->assertStringContainsString($value, $result, new FormattableMarkup('@source: @value found. Filtered result: @result.', [
-            '@source' => var_export($source, TRUE),
-            '@value' => var_export($value, TRUE),
-            '@result' => var_export($result, TRUE),
-          ]));
+          $this->assertStringContainsString($value, $result, sprintf('%s: %s found. Filtered result: %s.',
+            var_export($source, TRUE),
+            var_export($value, TRUE),
+            var_export($result, TRUE),
+          ));
         }
         else {
-          $this->assertStringNotContainsString($value, $result, new FormattableMarkup('@source: @value not found. Filtered result: @result.', [
-            '@source' => var_export($source, TRUE),
-            '@value' => var_export($value, TRUE),
-            '@result' => var_export($result, TRUE),
-          ]));
+          $this->assertStringNotContainsString($value, $result, sprintf('%s: %s not found. Filtered result: %s.',
+            var_export($source, TRUE),
+            var_export($value, TRUE),
+            var_export($result, TRUE),
+          ));
         }
       }
     }
@@ -1124,7 +1123,7 @@ public function testHtmlCorrectorFilter() {
 body {color:red}
 /*]]>*/
 </style></p>';
-    $this->assertEquals($html, Html::normalize($html), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '/*<![CDATA[*/']));
+    $this->assertEquals($html, Html::normalize($html), 'HTML corrector -- Existing cdata section /*<![CDATA[*/ properly escaped');
 
     $html = '<p><style>
 /*<![CDATA[*/
@@ -1132,28 +1131,28 @@ public function testHtmlCorrectorFilter() {
   body {color:red}
 /*]]>*/
 </style></p>';
-    $this->assertEquals($html, Html::normalize($html), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '<!--/*--><![CDATA[/* ><!--*/']));
+    $this->assertEquals($html, Html::normalize($html), 'HTML corrector -- Existing cdata section <!--/*--><![CDATA[/* ><!--*/ properly escaped');
 
     $html = '<p><script>
 //<![CDATA[
   alert("test");
 //]]>
 </script></p>';
-    $this->assertEquals($html, Html::normalize($html), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '<!--//--><![CDATA[// ><!--']));
+    $this->assertEquals($html, Html::normalize($html), 'HTML corrector -- Existing cdata section <!--//--><![CDATA[// ><!-- properly escaped');
 
     $html = '<p><script>
 // <![CDATA[
   alert("test");
 //]]>
 </script></p>';
-    $this->assertEquals($html, Html::normalize($html), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '// <![CDATA[']));
+    $this->assertEquals($html, Html::normalize($html), 'HTML corrector -- Existing cdata section // <![CDATA[ properly escaped');
 
     $html = '<p><script>
 // <![CDATA[![CDATA[![CDATA[
   alert("test");
 //]]]]]]>
 </script></p>';
-    $this->assertEquals($html, Html::normalize($html), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '// <![CDATA[![CDATA[![CDATA[']));
+    $this->assertEquals($html, Html::normalize($html), 'HTML corrector -- Existing cdata section // <![CDATA[![CDATA[![CDATA[ properly escaped');
 
     // Test calling Html::normalize() twice.
     $html = '<p><script>
@@ -1161,7 +1160,7 @@ public function testHtmlCorrectorFilter() {
   alert("test");
 //]]]]]]>
 </script></p>';
-    $this->assertEquals($html, Html::normalize(Html::normalize($html)), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '// <![CDATA[![CDATA[![CDATA[']));
+    $this->assertEquals($html, Html::normalize(Html::normalize($html)), 'HTML corrector -- Existing cdata section // <![CDATA[![CDATA[![CDATA[ properly escaped');
   }
 
   /**
diff --git a/web/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php b/web/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php
index 269c13f447..928bfd8a45 100644
--- a/web/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php
+++ b/web/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php
@@ -8,6 +8,7 @@
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Link;
 use Drupal\forum\Breadcrumb\ForumListingBreadcrumbBuilder;
+use Drupal\taxonomy\Entity\Term;
 use Drupal\taxonomy\TermStorageInterface;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\DependencyInjection\Container;
@@ -46,12 +47,21 @@ protected function setUp(): void {
    * @dataProvider providerTestApplies
    * @covers ::applies
    */
-  public function testApplies($expected, $route_name = NULL, $parameter_map = []) {
+  public function testApplies(bool $expected, ?string $route_name = NULL, array $parameter_map = []): void {
     // Make some test doubles.
     $entity_type_manager = $this->createMock(EntityTypeManagerInterface::class);
     $config_factory = $this->getConfigFactoryStub([]);
     $forum_manager = $this->createMock('Drupal\forum\ForumManagerInterface');
     $translation_manager = $this->createMock('Drupal\Core\StringTranslation\TranslationInterface');
+    $map = [];
+    if ($parameter_map) {
+      foreach ($parameter_map as $parameter) {
+        $map[] = [
+          $parameter[0],
+          $parameter[1] === TRUE ? $this->getMockBuilder(Term::class)->disableOriginalConstructor()->getMock() : $parameter[1],
+        ];
+      }
+    }
 
     // Make an object to test.
     $builder = new ForumListingBreadcrumbBuilder($entity_type_manager, $config_factory, $forum_manager, $translation_manager);
@@ -62,7 +72,7 @@ public function testApplies($expected, $route_name = NULL, $parameter_map = [])
       ->willReturn($route_name);
     $route_match->expects($this->any())
       ->method('getParameter')
-      ->willReturnMap($parameter_map);
+      ->willReturnMap($map);
 
     $this->assertEquals($expected, $builder->applies($route_match));
   }
@@ -70,40 +80,17 @@ public function testApplies($expected, $route_name = NULL, $parameter_map = [])
   /**
    * Provides test data for testApplies().
    *
-   * @return array
-   *   Array of datasets for testApplies(). Structured as such:
+   * @return \Generator
+   *   Datasets for testApplies(). Structured as such:
    *   - ForumListBreadcrumbBuilder::applies() expected result.
    *   - ForumListBreadcrumbBuilder::applies() $attributes input array.
    */
-  public function providerTestApplies() {
-    // Send a Node mock, because NodeInterface cannot be mocked.
-    $mock_term = $this->getMockBuilder('Drupal\taxonomy\Entity\Term')
-      ->disableOriginalConstructor()
-      ->getMock();
-
-    return [
-      [
-        FALSE,
-      ],
-      [
-        FALSE,
-        'NOT.forum.page',
-      ],
-      [
-        FALSE,
-        'forum.page',
-      ],
-      [
-        TRUE,
-        'forum.page',
-        [['taxonomy_term', 'anything']],
-      ],
-      [
-        TRUE,
-        'forum.page',
-        [['taxonomy_term', $mock_term]],
-      ],
-    ];
+  public static function providerTestApplies(): \Generator {
+    yield [FALSE];
+    yield [FALSE, 'NOT.forum.page'];
+    yield [FALSE, 'forum.page'];
+    yield [TRUE, 'forum.page', [['taxonomy_term', 'anything']]];
+    yield [TRUE, 'forum.page', [['taxonomy_term', TRUE]]];
   }
 
   /**
diff --git a/web/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php b/web/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php
index de01313598..959c616703 100644
--- a/web/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php
+++ b/web/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php
@@ -8,6 +8,7 @@
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Link;
 use Drupal\forum\Breadcrumb\ForumNodeBreadcrumbBuilder;
+use Drupal\node\Entity\Node;
 use Drupal\taxonomy\TermStorageInterface;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\DependencyInjection\Container;
@@ -46,10 +47,19 @@ protected function setUp(): void {
    * @dataProvider providerTestApplies
    * @covers ::applies
    */
-  public function testApplies($expected, $route_name = NULL, $parameter_map = []) {
+  public function testApplies(bool $expected, ?string $route_name = NULL, array $parameter_map = []): void {
     // Make some test doubles.
     $entity_type_manager = $this->createMock(EntityTypeManagerInterface::class);
     $config_factory = $this->getConfigFactoryStub([]);
+    $map = [];
+    if ($parameter_map) {
+      foreach ($parameter_map as $parameter) {
+        $map[] = [
+          $parameter[0],
+          $parameter[1] === TRUE ? $this->getMockBuilder(Node::class)->disableOriginalConstructor()->getMock() : $parameter[1],
+        ];
+      }
+    }
 
     $forum_manager = $this->createMock('Drupal\forum\ForumManagerInterface');
     $forum_manager->expects($this->any())
@@ -67,7 +77,7 @@ public function testApplies($expected, $route_name = NULL, $parameter_map = [])
       ->willReturn($route_name);
     $route_match->expects($this->any())
       ->method('getParameter')
-      ->willReturnMap($parameter_map);
+      ->willReturnMap($map);
 
     $this->assertEquals($expected, $builder->applies($route_match));
   }
@@ -77,40 +87,17 @@ public function testApplies($expected, $route_name = NULL, $parameter_map = [])
    *
    * Note that this test is incomplete, because we can't mock NodeInterface.
    *
-   * @return array
-   *   Array of datasets for testApplies(). Structured as such:
+   * @return \Generator
+   *   Datasets for testApplies(). Structured as such:
    *   - ForumNodeBreadcrumbBuilder::applies() expected result.
    *   - ForumNodeBreadcrumbBuilder::applies() $attributes input array.
    */
-  public function providerTestApplies() {
-    // Send a Node mock, because NodeInterface cannot be mocked.
-    $mock_node = $this->getMockBuilder('Drupal\node\Entity\Node')
-      ->disableOriginalConstructor()
-      ->getMock();
-
-    return [
-      [
-        FALSE,
-      ],
-      [
-        FALSE,
-        'NOT.entity.node.canonical',
-      ],
-      [
-        FALSE,
-        'entity.node.canonical',
-      ],
-      [
-        FALSE,
-        'entity.node.canonical',
-        [['node', NULL]],
-      ],
-      [
-        TRUE,
-        'entity.node.canonical',
-        [['node', $mock_node]],
-      ],
-    ];
+  public static function providerTestApplies(): \Generator {
+    yield [FALSE];
+    yield [FALSE, 'NOT.entity.node.canonical'];
+    yield [FALSE, 'entity.node.canonical'];
+    yield [FALSE, 'entity.node.canonical', [['node', NULL]]];
+    yield [TRUE, 'entity.node.canonical', [['node', TRUE]]];
   }
 
   /**
diff --git a/web/core/modules/image/image.module b/web/core/modules/image/image.module
index 4203045b59..0d8bd4b1f2 100644
--- a/web/core/modules/image/image.module
+++ b/web/core/modules/image/image.module
@@ -13,6 +13,7 @@
 use Drupal\field\FieldConfigInterface;
 use Drupal\field\FieldStorageConfigInterface;
 use Drupal\file\FileInterface;
+use Drupal\image\Controller\ImageStyleDownloadController;
 use Drupal\image\Entity\ImageStyle;
 
 /**
@@ -151,6 +152,18 @@ function image_file_download($uri) {
     // Check that the file exists and is an image.
     $image = \Drupal::service('image.factory')->get($uri);
     if ($image->isValid()) {
+      // If the image style converted the extension, it has been added to the
+      // original file, resulting in filenames like image.png.jpeg. So to find
+      // the actual source image, we remove the extension and check if that
+      // image exists.
+      if (!file_exists($original_uri)) {
+        $converted_original_uri = ImageStyleDownloadController::getUriWithoutConvertedExtension($original_uri);
+        if ($converted_original_uri !== $original_uri && file_exists($converted_original_uri)) {
+          // The converted file does exist, use it as the source.
+          $original_uri = $converted_original_uri;
+        }
+      }
+
       // Check the permissions of the original to grant access to this image.
       $headers = \Drupal::moduleHandler()->invokeAll('file_download', [$original_uri]);
       // Confirm there's at least one module granting access and none denying access.
diff --git a/web/core/modules/image/src/Controller/ImageStyleDownloadController.php b/web/core/modules/image/src/Controller/ImageStyleDownloadController.php
index 47b5681f1a..427d51ac05 100644
--- a/web/core/modules/image/src/Controller/ImageStyleDownloadController.php
+++ b/web/core/modules/image/src/Controller/ImageStyleDownloadController.php
@@ -167,6 +167,24 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st
 
     $headers = [];
 
+    // Don't try to generate file if source is missing.
+    if ($image_uri !== $sample_image_uri && !$this->sourceImageExists($image_uri, $token_is_valid)) {
+      // If the image style converted the extension, it has been added to the
+      // original file, resulting in filenames like image.png.jpeg. So to find
+      // the actual source image, we remove the extension and check if that
+      // image exists.
+      $converted_image_uri = static::getUriWithoutConvertedExtension($image_uri);
+      if ($converted_image_uri !== $image_uri &&
+          $this->sourceImageExists($converted_image_uri, $token_is_valid)) {
+        // The converted file does exist, use it as the source.
+        $image_uri = $converted_image_uri;
+      }
+      else {
+        $this->logger->notice('Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', ['%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri]);
+        return new Response($this->t('Error generating image, missing source file.'), 404);
+      }
+    }
+
     // If not using a public scheme, let other modules provide headers and
     // control access to the file.
     if (!$is_public) {
@@ -177,28 +195,12 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st
     }
 
     // If it is default sample.png, ignore scheme.
+    // This value swap must be done after hook_file_download is called since
+    // the hooks are expecting a URI, not a file path.
     if ($image_uri === $sample_image_uri) {
       $image_uri = $target;
     }
 
-    // Don't try to generate file if source is missing.
-    if (!$this->sourceImageExists($image_uri, $token_is_valid)) {
-      // If the image style converted the extension, it has been added to the
-      // original file, resulting in filenames like image.png.jpeg. So to find
-      // the actual source image, we remove the extension and check if that
-      // image exists.
-      $path_info = pathinfo(StreamWrapperManager::getTarget($image_uri));
-      $converted_image_uri = sprintf('%s://%s%s%s', $this->streamWrapperManager->getScheme($derivative_uri), $path_info['dirname'], DIRECTORY_SEPARATOR, $path_info['filename']);
-      if (!$this->sourceImageExists($converted_image_uri, $token_is_valid)) {
-        $this->logger->notice('Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', ['%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri]);
-        return new Response($this->t('Error generating image, missing source file.'), 404);
-      }
-      else {
-        // The converted file does exist, use it as the source.
-        $image_uri = $converted_image_uri;
-      }
-    }
-
     // Don't start generating the image if the derivative already exists or if
     // generation is in progress in another thread.
     if (!file_exists($derivative_uri)) {
@@ -277,4 +279,31 @@ private function sourceImageExists(string $image_uri, bool $token_is_valid): boo
     return TRUE;
   }
 
+  /**
+   * Get the file URI without the extension from any conversion image style.
+   *
+   * If the image style converted the image, then an extension has been added
+   * to the original file, resulting in filenames like image.png.jpeg.
+   *
+   * @param string $uri
+   *   The file URI.
+   *
+   * @return string
+   *   The file URI without the extension from any conversion image style.
+   */
+  public static function getUriWithoutConvertedExtension(string $uri): string {
+    $original_uri = $uri;
+    $path_info = pathinfo(StreamWrapperManager::getTarget($uri));
+    // Only convert the URI when the filename still has an extension.
+    if (!empty($path_info['filename']) && pathinfo($path_info['filename'], PATHINFO_EXTENSION)) {
+      $original_uri = StreamWrapperManager::getScheme($uri) . '://';
+      if (!empty($path_info['dirname']) && $path_info['dirname'] !== '.') {
+        $original_uri .= $path_info['dirname'] . DIRECTORY_SEPARATOR;
+      }
+      $original_uri .= $path_info['filename'];
+    }
+
+    return $original_uri;
+  }
+
 }
diff --git a/web/core/modules/image/src/Entity/ImageStyle.php b/web/core/modules/image/src/Entity/ImageStyle.php
index 873568d1e1..abb6160dff 100644
--- a/web/core/modules/image/src/Entity/ImageStyle.php
+++ b/web/core/modules/image/src/Entity/ImageStyle.php
@@ -298,10 +298,12 @@ public function flush($path = NULL) {
     $module_handler = \Drupal::moduleHandler();
     $module_handler->invokeAll('image_style_flush', [$this, $path]);
 
-    // Clear caches so that formatters may be added for this style.
-    \Drupal::service('theme.registry')->reset();
-
-    Cache::invalidateTags($this->getCacheTagsToInvalidate());
+    // Clear caches when the complete image style is flushed,
+    // so that field formatters may be added for this style.
+    if (!isset($path)) {
+      \Drupal::service('theme.registry')->reset();
+      Cache::invalidateTags($this->getCacheTagsToInvalidate());
+    }
 
     return $this;
   }
diff --git a/web/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php b/web/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php
index b54f773242..71a04a87cb 100644
--- a/web/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php
+++ b/web/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php
@@ -119,6 +119,22 @@ public function testImageStyleUrlExtraSlash() {
     $this->doImageStyleUrlAndPathTests('public', TRUE, TRUE);
   }
 
+  /**
+   * Test an image style URL with a private file that also gets converted.
+   */
+  public function testImageStylePrivateWithConversion(): void {
+    // Add the "convert" image style effect to our style.
+    $this->style->addImageEffect([
+      'uuid' => '',
+      'id' => 'image_convert',
+      'weight' => 1,
+      'data' => [
+        'extension' => 'jpeg',
+      ],
+    ]);
+    $this->doImageStyleUrlAndPathTests('private');
+  }
+
   /**
    * Tests that an invalid source image returns a 404.
    */
diff --git a/web/core/modules/image/tests/src/Unit/ImageStyleTest.php b/web/core/modules/image/tests/src/Unit/ImageStyleTest.php
index 5025e43911..6ddaab36e6 100644
--- a/web/core/modules/image/tests/src/Unit/ImageStyleTest.php
+++ b/web/core/modules/image/tests/src/Unit/ImageStyleTest.php
@@ -5,6 +5,7 @@
 namespace Drupal\Tests\image\Unit;
 
 use Drupal\Component\Utility\Crypt;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -198,6 +199,62 @@ public function testGetPathToken() {
     $this->assertEquals(substr(Crypt::hmacBase64($image_style->id() . ':' . 'public://test.jpeg', $private_key . $hash_salt), 0, 8), $image_style->getPathToken('public://test.jpeg'));
   }
 
+  /**
+   * @covers ::flush
+   */
+  public function testFlush() {
+    $cache_tag_invalidator = $this->createMock('\Drupal\Core\Cache\CacheTagsInvalidator');
+    $file_system = $this->createMock('\Drupal\Core\File\FileSystemInterface');
+    $module_handler = $this->createMock('\Drupal\Core\Extension\ModuleHandlerInterface');
+    $stream_wrapper_manager = $this->createMock('\Drupal\Core\StreamWrapper\StreamWrapperManagerInterface');
+    $stream_wrapper_manager->expects($this->any())
+      ->method('getWrappers')
+      ->will($this->returnValue([]));
+    $theme_registry = $this->createMock('\Drupal\Core\Theme\Registry');
+
+    $container = new ContainerBuilder();
+    $container->set('cache_tags.invalidator', $cache_tag_invalidator);
+    $container->set('file_system', $file_system);
+    $container->set('module_handler', $module_handler);
+    $container->set('stream_wrapper_manager', $stream_wrapper_manager);
+    $container->set('theme.registry', $theme_registry);
+    \Drupal::setContainer($container);
+
+    $image_effect_id = $this->randomMachineName();
+    $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase');
+
+    $image_style = $this->getImageStyleMock($image_effect_id, $image_effect, ['buildUri', 'getCacheTagsToInvalidate']);
+    $image_style->expects($this->any())
+      ->method('buildUri')
+      ->willReturn('test.jpg');
+    $image_style->expects($this->any())
+      ->method('getCacheTagsToInvalidate')
+      ->willReturn([]);
+
+    // Assert the theme registry is reset.
+    $theme_registry
+      ->expects($this->once())
+      ->method('reset');
+    // Assert the cache tags are invalidated.
+    $cache_tag_invalidator
+      ->expects($this->once())
+      ->method('invalidateTags');
+
+    $image_style->flush();
+
+    // Assert the theme registry is not reset a path is flushed.
+    $theme_registry
+      ->expects($this->never())
+      ->method('reset');
+    // Assert the cache tags are not reset a path is flushed.
+    $cache_tag_invalidator
+      ->expects($this->never())
+      ->method('invalidateTags');
+
+    $image_style->flush('test.jpg');
+
+  }
+
   /**
    * Mock function for ImageStyle::fileDefaultScheme().
    */
diff --git a/web/core/modules/layout_builder/src/Form/DiscardLayoutChangesForm.php b/web/core/modules/layout_builder/src/Form/DiscardLayoutChangesForm.php
index 5f49f298a8..5e4f7c2503 100644
--- a/web/core/modules/layout_builder/src/Form/DiscardLayoutChangesForm.php
+++ b/web/core/modules/layout_builder/src/Form/DiscardLayoutChangesForm.php
@@ -79,7 +79,7 @@ public function getQuestion() {
    * {@inheritdoc}
    */
   public function getCancelUrl() {
-    return $this->sectionStorage->getRedirectUrl();
+    return $this->sectionStorage->getLayoutBuilderUrl();
   }
 
   /**
@@ -100,7 +100,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     $this->messenger->addMessage($this->t('The changes to the layout have been discarded.'));
 
-    $form_state->setRedirectUrl($this->getCancelUrl());
+    $form_state->setRedirectUrl($this->sectionStorage->getRedirectUrl());
   }
 
 }
diff --git a/web/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php b/web/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php
index 5929fcb365..7b6e8cb1e4 100644
--- a/web/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php
+++ b/web/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php
@@ -94,10 +94,17 @@ public function testUnsavedChangesMessage() {
     $assert_session = $this->assertSession();
     $page = $this->getSession()->getPage();
 
-    // Make and then discard changes.
     $this->assertModifiedLayout(static::FIELD_UI_PREFIX . '/display/default/layout');
+    // Discard then cancel.
+    $page->pressButton('Discard changes');
+    $page->clickLink('Cancel');
+    $assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display/default/layout');
+    $assert_session->pageTextContainsOnce('You have unsaved changes.');
+
+    // Discard then confirm.
     $page->pressButton('Discard changes');
     $page->pressButton('Confirm');
+    $assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display/default');
     $assert_session->pageTextNotContains('You have unsaved changes.');
 
     // Make and then save changes.
diff --git a/web/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php b/web/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php
index f0e71a659e..36d5b97703 100644
--- a/web/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php
+++ b/web/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php
@@ -96,7 +96,7 @@ public function testLinkDeserialization() {
       ->set('query', $parsed_url['query']);
     $json = json_decode($this->serializer->serialize($entity, 'json'), TRUE);
     $json['field_test'][0]['options'] = 'string data';
-    $serialized = json_encode($json, TRUE);
+    $serialized = json_encode($json);
     $this->expectException(\LogicException::class);
     $this->expectExceptionMessage('The generic FieldItemNormalizer cannot denormalize string values for "options" properties of the "field_test" field (field item class: Drupal\link\Plugin\Field\FieldType\LinkItem).');
     $this->serializer->deserialize($serialized, EntityTest::class, 'json');
diff --git a/web/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php b/web/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php
index 00d73a73f0..14d4dcc3c4 100644
--- a/web/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php
+++ b/web/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\menu_link_content\Kernel;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\entity_test\Entity\EntityTestExternal;
 use Drupal\KernelTests\KernelTestBase;
@@ -129,7 +128,8 @@ public function assertMenuLinkParents(array $links, array $expected_hierarchy):
       $menu_link_plugin = $this->menuLinkManager->createInstance($links[$id]);
       $expected_parent = $links[$parent] ?? '';
 
-      $this->assertEquals($expected_parent, $menu_link_plugin->getParent(), new FormattableMarkup('Menu link %id has parent of %parent, expected %expected_parent.', ['%id' => $id, '%parent' => $menu_link_plugin->getParent(), '%expected_parent' => $expected_parent]));
+      $link_parent = $menu_link_plugin->getParent();
+      $this->assertEquals($expected_parent, $link_parent, "Menu link $id has parent of $link_parent, expected $expected_parent.");
     }
   }
 
diff --git a/web/core/modules/menu_ui/menu_ui.module b/web/core/modules/menu_ui/menu_ui.module
index 55ef58fb59..314d175d74 100644
--- a/web/core/modules/menu_ui/menu_ui.module
+++ b/web/core/modules/menu_ui/menu_ui.module
@@ -30,7 +30,7 @@ function menu_ui_help($route_name, RouteMatchInterface $route_match) {
     case 'help.page.menu_ui':
       $output = '';
       $output .= '<h2>' . t('About') . '</h2>';
-      $output .= '<p>' . t('The Menu UI module provides an interface for managing menus. A menu is a hierarchical collection of links, which can be within or external to the site, generally used for navigation. For more information, see the <a href=":menu">online documentation for the Menu UI module</a>.', [':menu' => 'https://www.drupal.org/documentation/modules/menu/']) . '</p>';
+      $output .= '<p>' . t('The Menu UI module provides an interface for managing menus. A menu is a hierarchical collection of links, which can be within or external to the site, generally used for navigation. For more information, see the <a href=":menu">online documentation for the Menu UI module</a>.', [':menu' => 'https://www.drupal.org/docs/core-modules-and-themes/core-modules/menu-ui-module']) . '</p>';
       $output .= '<h2>' . t('Uses') . '</h2>';
       $output .= '<dl>';
       $output .= '<dt>' . t('Managing menus') . '</dt>';
diff --git a/web/core/modules/migrate/src/Plugin/MigrateDestinationInterface.php b/web/core/modules/migrate/src/Plugin/MigrateDestinationInterface.php
index d2c7fb7ee9..12a3aef712 100644
--- a/web/core/modules/migrate/src/Plugin/MigrateDestinationInterface.php
+++ b/web/core/modules/migrate/src/Plugin/MigrateDestinationInterface.php
@@ -98,7 +98,9 @@ public function fields();
    * @param \Drupal\migrate\Row $row
    *   The row object.
    * @param array $old_destination_id_values
-   *   (optional) The old destination IDs. Defaults to an empty array.
+   *   (optional) The destination IDs from the previous import of this source
+   *   row. This is empty the first time a source row is migrated. Defaults to
+   *   an empty array.
    *
    * @return array|bool
    *   An indexed array of destination IDs in the same order as defined in the
diff --git a/web/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php b/web/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
index e5c2b07591..0b4c76958c 100644
--- a/web/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
+++ b/web/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
@@ -63,7 +63,7 @@
  * For a complete example on migrating data from an SQL source, refer to
  * https://www.drupal.org/docs/8/api/migrate-api/migrating-data-from-sql-source
  *
- * @see https://www.drupal.org/docs/8/api/database-api
+ * @see https://www.drupal.org/docs/drupal-apis/database-api
  * @see \Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase
  */
 abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPluginInterface, RequirementsInterface {
diff --git a/web/core/modules/mysql/tests/src/Kernel/mysql/TemporaryQueryTest.php b/web/core/modules/mysql/tests/src/Kernel/mysql/TemporaryQueryTest.php
index 484ccc7522..a2d7fcbabb 100644
--- a/web/core/modules/mysql/tests/src/Kernel/mysql/TemporaryQueryTest.php
+++ b/web/core/modules/mysql/tests/src/Kernel/mysql/TemporaryQueryTest.php
@@ -23,7 +23,7 @@ public function testTemporaryQuery() {
 
     // Assert that the table is indeed a temporary one.
     $temporary_table_info = $connection->query("SHOW CREATE TABLE {" . $table_name_test . "}")->fetchAssoc();
-    $this->stringContains($temporary_table_info["Create Table"], "CREATE TEMPORARY TABLE");
+    $this->assertStringContainsString('CREATE TEMPORARY TABLE', $temporary_table_info['Create Table']);
 
     // Assert that both have the same field names.
     $normal_table_fields = $connection->query("SELECT * FROM {test}")->fetch();
diff --git a/web/core/modules/node/node.module b/web/core/modules/node/node.module
index 7b795d72cf..74df9184ce 100644
--- a/web/core/modules/node/node.module
+++ b/web/core/modules/node/node.module
@@ -56,7 +56,7 @@ function node_help($route_name, RouteMatchInterface $route_match) {
     case 'help.page.node':
       $output = '';
       $output .= '<h2>' . t('About') . '</h2>';
-      $output .= '<p>' . t('The Node module manages the creation, editing, deletion, settings, and display of the main site content. Content items managed by the Node module are typically displayed as pages on your site, and include a title, some meta-data (author, creation time, content type, etc.), and optional fields containing text or other data (fields are managed by the <a href=":field">Field module</a>). For more information, see the <a href=":node">online documentation for the Node module</a>.', [':node' => 'https://www.drupal.org/documentation/modules/node', ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</p>';
+      $output .= '<p>' . t('The Node module manages the creation, editing, deletion, settings, and display of the main site content. Content items managed by the Node module are typically displayed as pages on your site, and include a title, some meta-data (author, creation time, content type, etc.), and optional fields containing text or other data (fields are managed by the <a href=":field">Field module</a>). For more information, see the <a href=":node">online documentation for the Node module</a>.', [':node' => 'https://www.drupal.org/docs/core-modules-and-themes/core-modules/node-module', ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</p>';
       $output .= '<h2>' . t('Uses') . '</h2>';
       $output .= '<dl>';
       $output .= '<dt>' . t('Creating content') . '</dt>';
diff --git a/web/core/modules/node/tests/src/Kernel/NodeAccessTestBase.php b/web/core/modules/node/tests/src/Kernel/NodeAccessTestBase.php
index bce5391b19..e96c45538a 100644
--- a/web/core/modules/node/tests/src/Kernel/NodeAccessTestBase.php
+++ b/web/core/modules/node/tests/src/Kernel/NodeAccessTestBase.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\node\Kernel;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\node\NodeInterface;
@@ -137,13 +136,11 @@ public function assertNodeCreateAccess(string $bundle, bool $result, AccountInte
    *   about the node access permission test that was performed.
    */
   public function nodeAccessAssertMessage($operation, $result, $langcode = NULL) {
-    return new FormattableMarkup(
-      'Node access returns @result with operation %op, language code %langcode.',
-      [
-        '@result' => $result ? 'true' : 'false',
-        '%op' => $operation,
-        '%langcode' => !empty($langcode) ? $langcode : 'empty',
-      ]
+    return sprintf(
+     'Node access returns %s with operation %s, language code %s.',
+     $result ? 'true' : 'false',
+     $operation,
+     !empty($langcode) ? $langcode : 'empty',
     );
   }
 
diff --git a/web/core/modules/node/tests/src/Kernel/Views/PathPluginTest.php b/web/core/modules/node/tests/src/Kernel/Views/PathPluginTest.php
index aadfeaff47..c4fbd04d31 100644
--- a/web/core/modules/node/tests/src/Kernel/Views/PathPluginTest.php
+++ b/web/core/modules/node/tests/src/Kernel/Views/PathPluginTest.php
@@ -86,7 +86,7 @@ public function testPathPlugin(): void {
 
     // Test with view_mode full.
     $output = $view->preview();
-    $output = $renderer->renderRoot($output);
+    $output = (string) $renderer->renderRoot($output);
     foreach ($this->nodes as $node) {
       $this->assertStringContainsString('This is <strong>not escaped</strong> and this is ' . $node->toLink('the link')->toString(), $output, 'Make sure path field rewriting is not escaped.');
     }
diff --git a/web/core/modules/node/tests/src/Kernel/Views/RowPluginTest.php b/web/core/modules/node/tests/src/Kernel/Views/RowPluginTest.php
index 4206fa9abf..251ee45dc5 100644
--- a/web/core/modules/node/tests/src/Kernel/Views/RowPluginTest.php
+++ b/web/core/modules/node/tests/src/Kernel/Views/RowPluginTest.php
@@ -98,7 +98,7 @@ public function testRowPlugin(): void {
 
     // Test with view_mode full.
     $output = $view->preview();
-    $output = $renderer->renderRoot($output);
+    $output = (string) $renderer->renderRoot($output);
     foreach ($this->nodes as $node) {
       $this->assertStringNotContainsString($node->body->summary, $output, 'Make sure the teaser appears in the output of the view.');
       $this->assertStringContainsString($node->body->value, $output, 'Make sure the full text appears in the output of the view.');
@@ -107,7 +107,7 @@ public function testRowPlugin(): void {
     // Test with teasers.
     $view->rowPlugin->options['view_mode'] = 'teaser';
     $output = $view->preview();
-    $output = $renderer->renderRoot($output);
+    $output = (string) $renderer->renderRoot($output);
     foreach ($this->nodes as $node) {
       $this->assertStringContainsString($node->body->summary, $output, 'Make sure the teaser appears in the output of the view.');
       $this->assertStringNotContainsString($node->body->value, $output);
diff --git a/web/core/modules/shortcut/shortcut.module b/web/core/modules/shortcut/shortcut.module
index df1b9969c3..e20debaf0a 100644
--- a/web/core/modules/shortcut/shortcut.module
+++ b/web/core/modules/shortcut/shortcut.module
@@ -22,7 +22,7 @@ function shortcut_help($route_name, RouteMatchInterface $route_match) {
   switch ($route_name) {
     case 'help.page.shortcut':
       $output = '<h2>' . t('About') . '</h2>';
-      $output .= '<p>' . t('The Shortcut module allows users to create sets of <em>shortcut</em> links to commonly-visited pages of the site. Shortcuts are contained within <em>sets</em>. Each user with <em>Select any shortcut set</em> permission can select a shortcut set created by anyone at the site. For more information, see the <a href=":shortcut">online documentation for the Shortcut module</a>.', [':shortcut' => 'https://www.drupal.org/documentation/modules/shortcut']) . '</p>';
+      $output .= '<p>' . t('The Shortcut module allows users to create sets of <em>shortcut</em> links to commonly-visited pages of the site. Shortcuts are contained within <em>sets</em>. Each user with <em>Select any shortcut set</em> permission can select a shortcut set created by anyone at the site. For more information, see the <a href=":shortcut">online documentation for the Shortcut module</a>.', [':shortcut' => 'https://www.drupal.org/docs/8/core/modules/shortcut']) . '</p>';
       $output .= '<h2>' . t('Uses') . '</h2>';
       $output .= '<dl><dt>' . t('Administering shortcuts') . '</dt>';
       $output .= '<dd>' . t('Users with the <em>Administer shortcuts</em> permission can manage shortcut sets and edit the shortcuts within sets from the <a href=":shortcuts">Shortcuts administration page</a>.', [':shortcuts' => Url::fromRoute('entity.shortcut_set.collection')->toString()]) . '</dd>';
diff --git a/web/core/modules/statistics/src/Plugin/migrate/destination/NodeCounter.php b/web/core/modules/statistics/src/Plugin/migrate/destination/NodeCounter.php
index 3d1c96f2df..79da8de460 100644
--- a/web/core/modules/statistics/src/Plugin/migrate/destination/NodeCounter.php
+++ b/web/core/modules/statistics/src/Plugin/migrate/destination/NodeCounter.php
@@ -98,7 +98,7 @@ public function import(Row $row, array $old_destination_id_values = []) {
       ->expression('totalcount', '[totalcount] + :totalcount', [':totalcount' => $totalcount])
       // Per Drupal policy: "A query may have any number of placeholders, but
       // all must have unique names even if they have the same value."
-      // https://www.drupal.org/docs/8/api/database-api/static-queries#placeholders
+      // https://www.drupal.org/docs/drupal-apis/database-api/static-queries#placeholders
       ->expression('timestamp', 'CASE WHEN [timestamp] > :timestamp1 THEN [timestamp] ELSE :timestamp2 END', [':timestamp1' => $timestamp, ':timestamp2' => $timestamp])
       ->execute();
 
diff --git a/web/core/modules/system/src/Controller/AssetControllerBase.php b/web/core/modules/system/src/Controller/AssetControllerBase.php
index 19c2eec6f1..0e25e72ab6 100644
--- a/web/core/modules/system/src/Controller/AssetControllerBase.php
+++ b/web/core/modules/system/src/Controller/AssetControllerBase.php
@@ -160,19 +160,22 @@ public function deliver(Request $request, string $file_name) {
     $this->themeManager->setActiveTheme($active_theme);
 
     $attached_assets = new AttachedAssets();
-    $include_string = UrlHelper::uncompressQueryParameter($request->query->get('include'));
+    $include_libraries = explode(',', UrlHelper::uncompressQueryParameter($request->query->get('include')));
 
-    if (!$include_string) {
-      throw new BadRequestHttpException('The libraries to include are encoded incorrectly.');
-    }
-    $attached_assets->setLibraries(explode(',', $include_string));
+    $validate = function ($libraries_to_check) {
+      foreach ($libraries_to_check as $library) {
+        if (substr_count($library, '/') !== 1) {
+          throw new BadRequestHttpException('The libraries to include are encoded incorrectly.');
+        }
+      }
+    };
+    $validate($include_libraries);
+    $attached_assets->setLibraries($include_libraries);
 
     if ($request->query->has('exclude')) {
-      $exclude_string = UrlHelper::uncompressQueryParameter($request->query->get('exclude'));
-      if (!$exclude_string) {
-        throw new BadRequestHttpException('The libraries to exclude are encoded incorrectly.');
-      }
-      $attached_assets->setAlreadyLoadedLibraries(explode(',', $exclude_string));
+      $exclude_libraries = explode(',', UrlHelper::uncompressQueryParameter($request->query->get('exclude')));
+      $validate($exclude_libraries);
+      $attached_assets->setAlreadyLoadedLibraries($exclude_libraries);
     }
     $groups = $this->getGroups($attached_assets, $request);
 
diff --git a/web/core/modules/system/tests/src/Kernel/Common/SystemListingTest.php b/web/core/modules/system/tests/src/Kernel/Common/SystemListingTest.php
index b48da4ed26..4c595cef2f 100644
--- a/web/core/modules/system/tests/src/Kernel/Common/SystemListingTest.php
+++ b/web/core/modules/system/tests/src/Kernel/Common/SystemListingTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\system\Kernel\Common;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Extension\ExtensionDiscovery;
 use Drupal\KernelTests\KernelTestBase;
 
@@ -46,7 +45,8 @@ public function testDirectoryPrecedence() {
     foreach ($expected_directories as $module => $directories) {
       $expected_directory = array_shift($directories);
       $expected_uri = "$expected_directory/$module/$module.info.yml";
-      $this->assertEquals($expected_uri, $files[$module]->getPathname(), new FormattableMarkup('Module @actual was found at @expected.', ['@actual' => $files[$module]->getPathname(), '@expected' => $expected_uri]));
+      $module_path = $files[$module]->getPathname();
+      $this->assertEquals($expected_uri, $module_path, "Module $module_path was found at $expected_uri.");
     }
   }
 
diff --git a/web/core/modules/system/tests/src/Kernel/Form/ProgrammaticTest.php b/web/core/modules/system/tests/src/Kernel/Form/ProgrammaticTest.php
index 535ed462aa..2f62d85eea 100644
--- a/web/core/modules/system/tests/src/Kernel/Form/ProgrammaticTest.php
+++ b/web/core/modules/system/tests/src/Kernel/Form/ProgrammaticTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\system\Kernel\Form;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Form\FormState;
 use Drupal\KernelTests\KernelTestBase;
 
@@ -73,18 +72,16 @@ protected function doSubmitForm($values, $valid_input) {
     // Check that the form returns an error when expected, and vice versa.
     $errors = $form_state->getErrors();
     $valid_form = empty($errors);
-    $args = [
-      '%values' => print_r($values, TRUE),
-      '%errors' => $valid_form ? 'None' : implode(' ', $errors),
-    ];
-    $this->assertSame($valid_form, $valid_input, new FormattableMarkup('Input values: %values<br />Validation handler errors: %errors', $args));
+    $input_values = print_r($values, TRUE);
+    $validation_errors = $valid_form ? t('None') : implode(' ', $errors);
+    $this->assertSame($valid_form, $valid_input, sprintf('Input values: %s<br />Validation handler errors: %s', $input_values, $validation_errors));
 
     // We check submitted values only if we have a valid input.
     if ($valid_input) {
       // Fetching the values that were set in the submission handler.
       $stored_values = $form_state->get('programmatic_form_submit');
       foreach ($values as $key => $value) {
-        $this->assertEquals($value, $stored_values[$key], new FormattableMarkup('Submission handler correctly executed: %stored_key is %stored_value', ['%stored_key' => $key, '%stored_value' => print_r($value, TRUE)]));
+        $this->assertEquals($value, $stored_values[$key], sprintf('Submission handler correctly executed: %s is %s', $key, print_r($value, TRUE)));
       }
     }
   }
diff --git a/web/core/modules/taxonomy/taxonomy.module b/web/core/modules/taxonomy/taxonomy.module
index d76ef3f103..e98b465cc3 100644
--- a/web/core/modules/taxonomy/taxonomy.module
+++ b/web/core/modules/taxonomy/taxonomy.module
@@ -22,7 +22,7 @@ function taxonomy_help($route_name, RouteMatchInterface $route_match) {
       $output = '';
       $output .= '<h2>' . t('About') . '</h2>';
       $output .= '<p>' . t('The Taxonomy module allows users who have permission to create and edit content to categorize (tag) content of that type. Users who have the <em>Administer vocabularies and terms</em> <a href=":permissions" title="Taxonomy module permissions">permission</a> can add <em>vocabularies</em> that contain a set of related <em>terms</em>. The terms in a vocabulary can either be pre-set by an administrator or built gradually as content is added and edited. Terms may be organized hierarchically if desired.', [':permissions' => Url::fromRoute('user.admin_permissions.module', ['modules' => 'taxonomy'])->toString()]) . '</p>';
-      $output .= '<p>' . t('For more information, see the <a href=":taxonomy">online documentation for the Taxonomy module</a>.', [':taxonomy' => 'https://www.drupal.org/documentation/modules/taxonomy/']) . '</p>';
+      $output .= '<p>' . t('For more information, see the <a href=":taxonomy">online documentation for the Taxonomy module</a>.', [':taxonomy' => 'https://www.drupal.org/docs/8/core/modules/taxonomy']) . '</p>';
       $output .= '<h2>' . t('Uses') . '</h2>';
       $output .= '<dl>';
       $output .= '<dt>' . t('Managing vocabularies') . '</dt>';
@@ -72,6 +72,21 @@ function taxonomy_theme_suggestions_taxonomy_term(array $variables) {
   return $suggestions;
 }
 
+/**
+ * Implements hook_local_tasks_alter().
+ *
+ * @todo Evaluate removing as part of https://www.drupal.org/node/2358923.
+ */
+function taxonomy_local_tasks_alter(&$local_tasks) {
+  $local_task_key = 'config_translation.local_tasks:entity.taxonomy_vocabulary.config_translation_overview';
+  if (isset($local_tasks[$local_task_key])) {
+    // The config_translation module expects the base route to be
+    // entity.taxonomy_vocabulary.edit_form like it is for other configuration
+    // entities. Taxonomy uses the overview_form as the base route.
+    $local_tasks[$local_task_key]['base_route'] = 'entity.taxonomy_vocabulary.overview_form';
+  }
+}
+
 /**
  * Prepares variables for taxonomy term templates.
  *
diff --git a/web/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyIndexTidUiTest.php b/web/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyIndexTidUiTest.php
index 7e49395d42..18167fe715 100644
--- a/web/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyIndexTidUiTest.php
+++ b/web/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyIndexTidUiTest.php
@@ -92,6 +92,18 @@ protected function setUp($import_test_views = TRUE, $modules = []): void {
     }
     ViewTestData::createTestViews(static::class, ['taxonomy_test_views']);
 
+    // Extra taxonomy and terms.
+    Vocabulary::create([
+      'vid' => 'other_tags',
+      'name' => 'Other tags',
+    ])->save();
+
+    $this->terms[3][0] = $term = Term::create([
+      'vid' => 'tags',
+      'name' => "Term 3.0",
+    ]);
+    $term->save();
+
     Vocabulary::create([
       'vid' => 'empty_vocabulary',
       'name' => 'Empty Vocabulary',
@@ -369,8 +381,12 @@ public function testFilterGrouping() {
     $field_name = 'taxonomy_tags';
     $this->createEntityReferenceField('node', $node_type->id(), $field_name, NULL, 'taxonomy_term');
 
-    // Create 4 nodes: 1 node without any tagging, 2 nodes tagged with 1 term,
-    // and 1 node with 2 tagged terms.
+    // Create the other tag field itself.
+    $field_name2 = 'taxonomy_other_tags';
+    $this->createEntityReferenceField('node', $node_type->id(), $field_name2, NULL, 'taxonomy_term');
+
+    // Create 5 nodes: 1 node without any tagging, 2 nodes tagged with 1 term,
+    // 1 node with 2 tagged terms and 1 with other tags term.
     $node_no_term = $this->drupalCreateNode();
     $node_with_term_1_0 = $this->drupalCreateNode([
       $field_name => [['target_id' => $this->terms[1][0]->id()]],
@@ -385,6 +401,10 @@ public function testFilterGrouping() {
       $field_name => [['target_id' => $this->terms[2][0]->id()]],
     ]);
 
+    $node_with_term_3_0 = $this->drupalCreateNode([
+      $field_name2 => [['target_id' => $this->terms[3][0]->id()]],
+    ]);
+
     // Create two groups. The first group contains the published filter and set
     // up the second group as an 'OR' group. The first subgroup of the second
     // filter group will vary as follows:
@@ -490,6 +510,45 @@ public function testFilterGrouping() {
     $this->assertSession()->pageTextContainsOnce($node_with_terms_1_0_and_1_1->label());
     $this->assertSession()->pageTextContainsOnce($node_with_term_2_0->label());
     $this->assertSession()->pageTextNotContains($node_no_term->label());
+
+    // Different fields/taxonomies filters/values.
+    // Case 5: OR
+    // - filter "tid" with terms from tags as "is one of"
+    // - filter "taxonomy_other_tags_target_id" with term from other tags
+    // as "is one of".
+    $view = View::load('test_filter_taxonomy_index_tid');
+    $display = &$view->getDisplay('default');
+    $display['display_options']['filters']['tid']['value'][0] = $this->terms[1][0]->id();
+    $display['display_options']['filters']['tid']['value'][1] = $this->terms[1][1]->id();
+    $display['display_options']['filters']['tid']['operator'] = 'or';
+    $display['display_options']['filters']['tid']['group'] = 2;
+    $display['display_options']['filters']['taxonomy_other_tags_target_id'] = $display['display_options']['filters']['tid'];
+    $display['display_options']['filters']['taxonomy_other_tags_target_id']['id'] = 'taxonomy_other_tags_target_id';
+    $display['display_options']['filters']['taxonomy_other_tags_target_id']['value'][0] = $this->terms[3][0]->id();
+    $display['display_options']['filters']['taxonomy_other_tags_target_id']['operator'] = 'or';
+    $display['display_options']['filters']['taxonomy_other_tags_target_id']['group'] = 2;
+    $display['display_options']['filters']['taxonomy_other_tags_target_id']['table'] = 'node__taxonomy_other_tags';
+    $display['display_options']['filters']['taxonomy_other_tags_target_id']['field'] = 'taxonomy_other_tags_target_id';
+    unset($display['display_options']['filters']['tid_2']);
+    $display['display_options']['filter_groups'] = [
+      'operator' => 'AND',
+      'groups' => [
+        1 => 'AND',
+        2 => 'OR',
+      ],
+    ];
+    $view->save();
+
+    $this->drupalGet('test-filter-taxonomy-index-tid');
+    // We expect no nodes tagged with term 1.0 or 1.1. The node tagged with
+    // term 3.0 and the untagged node will be shown.
+    $this->assertSession()->pageTextContainsOnce($node_with_term_1_0->label());
+    // The view does not have DISTINCT query enabled, the node tagged with
+    // both 1.0 and 1.1 will appear twice.
+    $this->assertSession()->pageTextMatchesCount(2, "/{$node_with_terms_1_0_and_1_1->label()}/");
+    $this->assertSession()->pageTextContainsOnce($node_with_term_3_0->label());
+    $this->assertSession()->pageTextNotContains($node_with_term_2_0->label());
+    $this->assertSession()->pageTextNotContains($node_no_term->label());
   }
 
 }
diff --git a/web/core/modules/toolbar/js/toolbar.js b/web/core/modules/toolbar/js/toolbar.js
index 1ca75fcf46..ece513c13f 100644
--- a/web/core/modules/toolbar/js/toolbar.js
+++ b/web/core/modules/toolbar/js/toolbar.js
@@ -208,26 +208,30 @@
         $(window).on({
           'dialog:aftercreate': (event, dialog, $element, settings) => {
             const toolbarBar = document.getElementById('toolbar-bar');
-            toolbarBar.style.marginTop = '0';
+            if (toolbarBar) {
+              toolbarBar.style.marginTop = '0';
 
-            // When off-canvas is positioned in top, toolbar has to be moved down.
-            if (settings.drupalOffCanvasPosition === 'top') {
-              const height = Drupal.offCanvas
-                .getContainer($element)
-                .outerHeight();
-              toolbarBar.style.marginTop = `${height}px`;
-
-              $element.on('dialogContentResize.off-canvas', () => {
-                const newHeight = Drupal.offCanvas
+              // When off-canvas is positioned in top, toolbar has to be moved down.
+              if (settings.drupalOffCanvasPosition === 'top') {
+                const height = Drupal.offCanvas
                   .getContainer($element)
                   .outerHeight();
-                toolbarBar.style.marginTop = `${newHeight}px`;
-              });
+                toolbarBar.style.marginTop = `${height}px`;
+
+                $element.on('dialogContentResize.off-canvas', () => {
+                  const newHeight = Drupal.offCanvas
+                    .getContainer($element)
+                    .outerHeight();
+                  toolbarBar.style.marginTop = `${newHeight}px`;
+                });
+              }
             }
           },
           'dialog:beforeclose': () => {
             const toolbarBar = document.getElementById('toolbar-bar');
-            toolbarBar.style.marginTop = '0';
+            if (toolbarBar) {
+              toolbarBar.style.marginTop = '0';
+            }
           },
         });
       });
diff --git a/web/core/modules/toolbar/toolbar.module b/web/core/modules/toolbar/toolbar.module
index 72edbe8d2e..ff395da0fc 100644
--- a/web/core/modules/toolbar/toolbar.module
+++ b/web/core/modules/toolbar/toolbar.module
@@ -21,7 +21,7 @@ function toolbar_help($route_name, RouteMatchInterface $route_match) {
   switch ($route_name) {
     case 'help.page.toolbar':
       $output = '<h2>' . t('About') . '</h2>';
-      $output .= '<p>' . t('The Toolbar module provides a toolbar for site administrators, which displays tabs and trays provided by the Toolbar module itself and other modules. For more information, see the <a href=":toolbar_docs">online documentation for the Toolbar module</a>.', [':toolbar_docs' => 'https://www.drupal.org/documentation/modules/toolbar']) . '</p>';
+      $output .= '<p>' . t('The Toolbar module provides a toolbar for site administrators, which displays tabs and trays provided by the Toolbar module itself and other modules. For more information, see the <a href=":toolbar_docs">online documentation for the Toolbar module</a>.', [':toolbar_docs' => 'https://www.drupal.org/docs/8/core/modules/toolbar']) . '</p>';
       $output .= '<h4>' . t('Terminology') . '</h4>';
       $output .= '<dl>';
       $output .= '<dt>' . t('Tabs') . '</dt>';
diff --git a/web/core/modules/update/tests/fixtures/release-history/drupal.broken.xml b/web/core/modules/update/tests/fixtures/release-history/drupal.broken.xml
index 1ba5d5e408..01e7ce2a0f 100644
--- a/web/core/modules/update/tests/fixtures/release-history/drupal.broken.xml
+++ b/web/core/modules/update/tests/fixtures/release-history/drupal.broken.xml
@@ -1,3 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+This XML file is empty to test no information scenarios.
+-->
 <project xmlns:dc="http://purl.org/dc/elements/1.1/">
 </project>
diff --git a/web/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php b/web/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php
index 29d5280a15..561da09607 100644
--- a/web/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php
+++ b/web/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php
@@ -243,6 +243,11 @@ public function testLocalActions() {
 
   /**
    * Checks that Drupal recovers after problems connecting to update server.
+   *
+   * This test uses the following XML fixtures.
+   *  - drupal.broken.xml
+   *  - drupal.sec.8.0.2.xml
+   *     'supported_branches' is '8.0.,8.1.'.
    */
   public function testBrokenThenFixedUpdates() {
     $this->drupalLogin($this->drupalCreateUser([
diff --git a/web/core/modules/user/tests/src/Kernel/WhoIsOnlineBlockTest.php b/web/core/modules/user/tests/src/Kernel/WhoIsOnlineBlockTest.php
index ec84fcfacf..d634fa5291 100644
--- a/web/core/modules/user/tests/src/Kernel/WhoIsOnlineBlockTest.php
+++ b/web/core/modules/user/tests/src/Kernel/WhoIsOnlineBlockTest.php
@@ -115,7 +115,8 @@ public function testWhoIsOnlineBlock() {
     $this->assertText($user2->getAccountName(), 'Active user 2 found in online list.');
     $this->assertNoText($user3->getAccountName(), 'Inactive user not found in online list.');
     // Verify that online users are ordered correctly.
-    $this->assertGreaterThan(strpos($this->getRawContent(), $user2->getAccountName()), strpos($this->getRawContent(), $user1->getAccountName()));
+    $raw_content = (string) $this->getRawContent();
+    $this->assertGreaterThan(strpos($raw_content, $user2->getAccountName()), strpos($raw_content, $user1->getAccountName()));
   }
 
 }
diff --git a/web/core/modules/views/src/ManyToOneHelper.php b/web/core/modules/views/src/ManyToOneHelper.php
index 82a42a4e70..237afa64b4 100644
--- a/web/core/modules/views/src/ManyToOneHelper.php
+++ b/web/core/modules/views/src/ManyToOneHelper.php
@@ -181,9 +181,12 @@ public function ensureMyTable() {
           // query optimization, INNER joins are slightly faster, so use them
           // when we know we can.
           $join = $this->getJoin();
-          if (isset($join)) {
+          $group = $this->handler->options['group'] ?? FALSE;
+          // Only if there is no group with OR operator.
+          if (isset($join) && !($group && $this->handler->query->where[$group]['type'] === 'OR')) {
             $join->type = 'INNER';
           }
+
           $this->handler->tableAlias = $this->handler->query->ensureTable($this->handler->table, $this->handler->relationship, $join);
           $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
         }
diff --git a/web/core/modules/views/tests/src/Kernel/Handler/FieldKernelTest.php b/web/core/modules/views/tests/src/Kernel/Handler/FieldKernelTest.php
index 6e630d0433..4dce202cd1 100644
--- a/web/core/modules/views/tests/src/Kernel/Handler/FieldKernelTest.php
+++ b/web/core/modules/views/tests/src/Kernel/Handler/FieldKernelTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\views\Kernel\Handler;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Render\RenderContext;
 use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
 use Drupal\views\Plugin\views\field\FieldPluginBase;
@@ -102,9 +101,8 @@ public function testQuery() {
    *   The value to search for.
    * @param string $message
    *   (optional) A message to display with the assertion. Do not translate
-   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
-   *   variables in the message text, not t(). If left blank, a default message
-   *   will be displayed.
+   *   messages: use string interpolation to embed variables in the message
+   *   text, not t(). If left blank, a default message will be displayed.
    *
    * @internal
    */
@@ -121,9 +119,8 @@ protected function assertSubString(string $haystack, string $needle, string $mes
    *   The value to search for.
    * @param string $message
    *   (optional) A message to display with the assertion. Do not translate
-   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
-   *   variables in the message text, not t(). If left blank, a default message
-   *   will be displayed.
+   *   messages: use string interpolation to embed variables in the message
+   *   text, not t(). If left blank, a default message will be displayed.
    *
    * @internal
    */
@@ -288,17 +285,17 @@ public function testFieldTokens() {
       $output = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_0, $row) {
         return $name_field_0->advancedRender($row);
       });
-      $this->assertEquals($expected_output_0, $output, new FormattableMarkup('Test token replacement: "@token" gave "@output"', ['@token' => $name_field_0->options['alter']['text'], '@output' => $output]));
+      $this->assertEquals($expected_output_0, $output, sprintf('Test token replacement: "%s" gave "%s"', $name_field_0->options['alter']['text'], $output));
 
       $output = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_1, $row) {
         return $name_field_1->advancedRender($row);
       });
-      $this->assertEquals($expected_output_1, $output, new FormattableMarkup('Test token replacement: "@token" gave "@output"', ['@token' => $name_field_1->options['alter']['text'], '@output' => $output]));
+      $this->assertEquals($expected_output_1, $output, sprintf('Test token replacement: "%s" gave "%s"', $name_field_1->options['alter']['text'], $output));
 
       $output = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_2, $row) {
         return $name_field_2->advancedRender($row);
       });
-      $this->assertEquals($expected_output_2, $output, new FormattableMarkup('Test token replacement: "@token" gave "@output"', ['@token' => $name_field_2->options['alter']['text'], '@output' => $output]));
+      $this->assertEquals($expected_output_2, $output, sprintf('Test token replacement: "%s" gave %s"', $name_field_2->options['alter']['text'], $output));
     }
 
     $job_field = $view->field['job'];
@@ -310,11 +307,11 @@ public function testFieldTokens() {
     $output = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
       return $job_field->advancedRender($row);
     });
-    $this->assertSubString($output, $random_text, new FormattableMarkup('Make sure the self token (@token => @value) appears in the output (@output)', [
-      '@value' => $random_text,
-      '@output' => $output,
-      '@token' => $job_field->options['alter']['text'],
-    ]));
+    $this->assertSubString($output, $random_text, sprintf('Make sure the self token (%s => %s) appears in the output (%s)',
+      $job_field->options['alter']['text'],
+      $random_text,
+      $output,
+    ));
 
     // Verify the token format used in D7 and earlier does not get substituted.
     $old_token = '[job]';
@@ -324,7 +321,7 @@ public function testFieldTokens() {
     $output = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
       return $job_field->advancedRender($row);
     });
-    $this->assertEquals($old_token, $output, new FormattableMarkup('Make sure the old token style (@token => @value) is not changed in the output (@output)', ['@value' => $random_text, '@output' => $output, '@token' => $job_field->options['alter']['text']]));
+    $this->assertEquals($old_token, $output, sprintf('Make sure the old token style (%s => %s) is not changed in the output (%s)', $job_field->options['alter']['text'], $random_text, $output));
 
     // Verify HTML tags are allowed in rewrite templates while token
     // replacements are escaped.
@@ -353,7 +350,7 @@ public function testFieldTokens() {
     $output = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
       return $job_field->advancedRender($row);
     });
-    $this->assertEquals($random_text, $output, new FormattableMarkup('Make sure a script tag in the template (@template) is removed, leaving only the replaced token in the output (@output)', ['@output' => $output, '@template' => $rewrite_template]));
+    $this->assertEquals($random_text, $output, "Make sure a script tag in the template ($rewrite_template) is removed, leaving only the replaced token in the output ($output)");
   }
 
   /**
diff --git a/web/core/tests/Drupal/KernelTests/ConfigFormTestBase.php b/web/core/tests/Drupal/KernelTests/ConfigFormTestBase.php
index e042045dcf..a314a8f1fe 100644
--- a/web/core/tests/Drupal/KernelTests/ConfigFormTestBase.php
+++ b/web/core/tests/Drupal/KernelTests/ConfigFormTestBase.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\KernelTests;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Form\FormState;
 
 /**
@@ -53,12 +52,9 @@ public function testConfigForm() {
     // Check that the form returns an error when expected, and vice versa.
     $errors = $form_state->getErrors();
     $valid_form = empty($errors);
-    $args = [
-      '%values' => print_r($values, TRUE),
-      '%errors' => $valid_form ? t('None') : implode(' ', $errors),
-    ];
-    $this->assertTrue($valid_form, new FormattableMarkup('Input values: %values<br/>Validation handler errors: %errors', $args));
-
+    $values = print_r($values, TRUE);
+    $errors = $valid_form ? t('None') : implode(' ', $errors);
+    $this->assertTrue($valid_form, sprintf('Input values: %s<br/>Validation handler errors: %s', $values, $errors));
     foreach ($this->values as $data) {
       $this->assertEquals($this->config($data['#config_name'])->get($data['#config_key']), $data['#value']);
     }
diff --git a/web/core/tests/Drupal/KernelTests/Core/Database/InsertLobTest.php b/web/core/tests/Drupal/KernelTests/Core/Database/InsertLobTest.php
index 45f635ca41..0022f24f68 100644
--- a/web/core/tests/Drupal/KernelTests/Core/Database/InsertLobTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/Database/InsertLobTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\KernelTests\Core\Database;
 
-use Drupal\Component\Render\FormattableMarkup;
-
 /**
  * Tests the Insert query builder with LOB fields.
  *
@@ -21,7 +19,7 @@ public function testInsertOneBlob() {
       ->fields(['blob1' => $data])
       ->execute();
     $r = $this->connection->query('SELECT * FROM {test_one_blob} WHERE [id] = :id', [':id' => $id])->fetchAssoc();
-    $this->assertSame($data, $r['blob1'], new FormattableMarkup('Can insert a blob: id @id, @data.', ['@id' => $id, '@data' => serialize($r)]));
+    $this->assertSame($data, $r['blob1'], "Can insert a blob: id $id, " . serialize($r));
   }
 
   /**
diff --git a/web/core/tests/Drupal/KernelTests/Core/Database/UpdateLobTest.php b/web/core/tests/Drupal/KernelTests/Core/Database/UpdateLobTest.php
index c97b8ae93f..06c7d5ffe0 100644
--- a/web/core/tests/Drupal/KernelTests/Core/Database/UpdateLobTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/Database/UpdateLobTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\KernelTests\Core\Database;
 
-use Drupal\Component\Render\FormattableMarkup;
-
 /**
  * Tests the Update query builder with LOB fields.
  *
@@ -28,7 +26,7 @@ public function testUpdateOneBlob() {
       ->execute();
 
     $r = $this->connection->query('SELECT * FROM {test_one_blob} WHERE [id] = :id', [':id' => $id])->fetchAssoc();
-    $this->assertSame($data, $r['blob1'], new FormattableMarkup('Can update a blob: id @id, @data.', ['@id' => $id, '@data' => serialize($r)]));
+    $this->assertSame($data, $r['blob1'], "Can update a blob: id $id, " . serialize($r));
   }
 
   /**
diff --git a/web/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityCloneTest.php b/web/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityCloneTest.php
index 74cbc4eeff..515aee921b 100644
--- a/web/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityCloneTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityCloneTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\KernelTests\Core\Entity;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\entity_test\Entity\EntityTestMul;
 use Drupal\entity_test\Entity\EntityTestMulRev;
 use Drupal\language\Entity\ConfigurableLanguage;
@@ -61,14 +60,12 @@ public function testFieldEntityReferenceAfterClone() {
       $translation = $clone->getTranslation($langcode);
       foreach ($translation->getFields() as $field_name => $field) {
         if ($field->getFieldDefinition()->isTranslatable()) {
-          $args = ['%field_name' => $field_name, '%langcode' => $langcode];
-          $this->assertEquals($langcode, $field->getEntity()->language()->getId(), new FormattableMarkup('Translatable field %field_name on translation %langcode has correct entity reference in translation %langcode after cloning.', $args));
-          $this->assertSame($translation, $field->getEntity(), new FormattableMarkup('Translatable field %field_name on translation %langcode has correct reference to the cloned entity object.', $args));
+          $this->assertEquals($langcode, $field->getEntity()->language()->getId(), "Translatable field $field_name on translation $langcode has correct entity reference in translation $langcode after cloning.");
+          $this->assertSame($translation, $field->getEntity(), "Translatable field $field_name on translation $langcode has correct reference to the cloned entity object.");
         }
         else {
-          $args = ['%field_name' => $field_name, '%langcode' => $langcode, '%default_langcode' => $default_langcode];
-          $this->assertEquals($default_langcode, $field->getEntity()->language()->getId(), new FormattableMarkup('Non translatable field %field_name on translation %langcode has correct entity reference in the default translation %default_langcode after cloning.', $args));
-          $this->assertSame($translation->getUntranslated(), $field->getEntity(), new FormattableMarkup('Non translatable field %field_name on translation %langcode has correct reference to the cloned entity object in the default translation %default_langcode.', $args));
+          $this->assertEquals($default_langcode, $field->getEntity()->language()->getId(), "Non translatable field $field_name on translation $langcode has correct entity reference in the default translation $default_langcode after cloning.");
+          $this->assertSame($translation->getUntranslated(), $field->getEntity(), "Non translatable field $field_name on translation $langcode has correct reference to the cloned entity object in the default translation $default_langcode.");
         }
       }
     }
@@ -300,6 +297,8 @@ public function testEntityPropertiesModifications() {
     $translation_unique_properties = ['activeLangcode', 'translationInitialize', 'fieldDefinitions', 'languages', 'langcodeKey', 'defaultLangcode', 'defaultLangcodeKey', 'revisionTranslationAffectedKey', 'validated', 'validationRequired', 'entityTypeId', 'typedData', 'cacheContexts', 'cacheTags', 'cacheMaxAge', '_serviceIds', '_entityStorages', 'enforceDefaultTranslation'];
 
     foreach ($properties as $property) {
+      $property_name = $property->getName();
+
       // Modify each entity property on the clone and assert that the change is
       // not propagated to the original entity.
       $property->setValue($entity, 'default-value');
@@ -307,14 +306,14 @@ public function testEntityPropertiesModifications() {
       $property->setValue($clone, 'test-entity-cloning');
       // Static properties remain the same across all instances of the class.
       if ($property->isStatic()) {
-        $this->assertEquals('test-entity-cloning', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
-        $this->assertEquals('test-entity-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
-        $this->assertEquals('test-entity-cloning', $property->getValue($clone), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
+        $this->assertEquals('test-entity-cloning', $property->getValue($entity), "Entity property $property_name is not cloned properly.");
+        $this->assertEquals('test-entity-cloning', $property->getValue($translation), "Entity property $property_name is not cloned properly.");
+        $this->assertEquals('test-entity-cloning', $property->getValue($clone), "Entity property $property_name is not cloned properly.");
       }
       else {
-        $this->assertEquals('default-value', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
-        $this->assertEquals('default-value', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
-        $this->assertEquals('test-entity-cloning', $property->getValue($clone), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
+        $this->assertEquals('default-value', $property->getValue($entity), "Entity property $property_name is not cloned properly.");
+        $this->assertEquals('default-value', $property->getValue($translation), "Entity property $property_name is not cloned properly.");
+        $this->assertEquals('test-entity-cloning', $property->getValue($clone), "Entity property $property_name is not cloned properly.");
       }
 
       // Modify each entity property on the translation entity object and assert
@@ -330,12 +329,12 @@ public function testEntityPropertiesModifications() {
       // not be able to properly access all properties and this will cause
       // exceptions without a proper backtrace.
       if (in_array($property->getName(), $translation_unique_properties)) {
-        $this->assertEquals('default-value', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
-        $this->assertEquals('test-translation-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
+        $this->assertEquals('default-value', $property->getValue($entity), "Entity property $property_name is not cloned properly.");
+        $this->assertEquals('test-translation-cloning', $property->getValue($translation), "Entity property $property_name is not cloned properly.");
       }
       else {
-        $this->assertEquals('test-translation-cloning', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
-        $this->assertEquals('test-translation-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
+        $this->assertEquals('test-translation-cloning', $property->getValue($entity), "Entity property $property_name is not cloned properly.");
+        $this->assertEquals('test-translation-cloning', $property->getValue($translation), "Entity property $property_name is not cloned properly.");
       }
     }
   }
diff --git a/web/core/tests/Drupal/KernelTests/Core/File/FileTestBase.php b/web/core/tests/Drupal/KernelTests/Core/File/FileTestBase.php
index b41787f452..2875d43aff 100644
--- a/web/core/tests/Drupal/KernelTests/Core/File/FileTestBase.php
+++ b/web/core/tests/Drupal/KernelTests/Core/File/FileTestBase.php
@@ -106,7 +106,7 @@ public function assertFilePermissions($filepath, $expected_mode, $message = NULL
     }
 
     if (!isset($message)) {
-      $message = t('Expected file permission to be %expected, actually were %actual.', ['%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)]);
+      $message = sprintf('Expected file permission to be %s, actually were %s.', decoct($actual_mode), decoct($expected_mode));
     }
     $this->assertEquals($expected_mode, $actual_mode, $message);
   }
@@ -142,7 +142,7 @@ public function assertDirectoryPermissions($directory, $expected_mode, $message
     }
 
     if (!isset($message)) {
-      $message = t('Expected directory permission to be %expected, actually were %actual.', ['%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)]);
+      $message = sprintf('Expected directory permission to be %s, actually were %s.', decoct($expected_mode), decoct($actual_mode));
     }
     $this->assertEquals($expected_mode, $actual_mode, $message);
   }
diff --git a/web/core/tests/Drupal/KernelTests/Core/File/HtaccessTest.php b/web/core/tests/Drupal/KernelTests/Core/File/HtaccessTest.php
index e49ff3b4c3..58a6d44a11 100644
--- a/web/core/tests/Drupal/KernelTests/Core/File/HtaccessTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/File/HtaccessTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\KernelTests\Core\File;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Site\Settings;
 use Drupal\KernelTests\KernelTestBase;
 
@@ -102,11 +101,11 @@ public function testHtaccessSave() {
    */
   protected function assertFilePermissions(string $uri, int $expected): void {
     $actual = fileperms($uri) & 0777;
-    $this->assertSame($actual, $expected, new FormattableMarkup('@uri file permissions @actual are identical to @expected.', [
-      '@uri' => $uri,
-      '@actual' => 0 . decoct($actual),
-      '@expected' => 0 . decoct($expected),
-    ]));
+    $this->assertSame($actual, $expected, sprintf('%s file permissions %s are identical to %s.',
+      $uri,
+      0 . decoct($actual),
+      0 . decoct($expected),
+    ));
   }
 
 }
diff --git a/web/core/tests/Drupal/KernelTests/Core/Path/PathValidatorTest.php b/web/core/tests/Drupal/KernelTests/Core/Path/PathValidatorTest.php
index 34a7bdf000..1a38873a71 100644
--- a/web/core/tests/Drupal/KernelTests/Core/Path/PathValidatorTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/Path/PathValidatorTest.php
@@ -62,8 +62,10 @@ public function testGetUrlIfValidWithoutAccessCheck() {
         }
         $this->container->set('router.request_context', new RequestContext());
       }
+      else {
+        $requestContext->setMethod($method);
+      }
 
-      $requestContext->setMethod($method);
       /** @var \Drupal\Core\Url $url */
       $url = $pathValidator->getUrlIfValidWithoutAccessCheck($entity->toUrl()->toString(TRUE)->getGeneratedUrl());
       $this->assertEquals($method, $requestContext->getMethod());
diff --git a/web/core/tests/Drupal/KernelTests/Core/Test/Comparator/MarkupInterfaceComparatorTest.php b/web/core/tests/Drupal/KernelTests/Core/Test/Comparator/MarkupInterfaceComparatorTest.php
index 61cf281808..eb3af5c239 100644
--- a/web/core/tests/Drupal/KernelTests/Core/Test/Comparator/MarkupInterfaceComparatorTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/Test/Comparator/MarkupInterfaceComparatorTest.php
@@ -6,7 +6,6 @@
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\TestTools\Comparator\MarkupInterfaceComparator;
-use PHPUnit\Framework\Error\Warning;
 use SebastianBergmann\Comparator\Factory;
 use SebastianBergmann\Comparator\ComparisonFailure;
 
@@ -108,7 +107,7 @@ public function dataSetProvider() {
         new FormattableMarkup('GoldFinger', []),
         ['GoldFinger'],
         FALSE,
-        Warning::class,
+        FALSE,
       ],
       'stdClass vs TranslatableMarkup' => [
         (object) ['GoldFinger'],
diff --git a/web/core/tests/Drupal/KernelTests/Core/Theme/ClaroTableTest.php b/web/core/tests/Drupal/KernelTests/Core/Theme/ClaroTableTest.php
index 70dcd6606e..cb3e0ff35e 100644
--- a/web/core/tests/Drupal/KernelTests/Core/Theme/ClaroTableTest.php
+++ b/web/core/tests/Drupal/KernelTests/Core/Theme/ClaroTableTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\KernelTests\Core\Theme;
 
+use Drupal\claro\ClaroPreRender;
 use Drupal\KernelTests\KernelTestBase;
 
 /**
@@ -38,4 +39,35 @@ public function testThemeTableStickyHeaders() {
     $this->assertRaw('position-sticky');
   }
 
+  /**
+   * Confirm Claro prerender callback is not executed for non-array class.
+   */
+  public function testThemeTablePositionStickyPreRender(): void {
+    // Enable the Claro theme.
+    \Drupal::service('theme_installer')->install(['claro']);
+    $this->config('system.theme')->set('default', 'claro')->save();
+    $header = ['one', 'two', 'three'];
+    $rows = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
+    $table = [
+      '#type' => 'table',
+      '#header' => $header,
+      '#rows' => $rows,
+      '#sticky' => TRUE,
+      '#attributes' => [
+        'class' => 'class',
+      ],
+      '#pre_render' => [
+        [
+          ClaroPreRender::class,
+          'tablePositionSticky',
+        ],
+      ],
+    ];
+
+    $renderedTable = (string) \Drupal::service('renderer')->renderRoot($table);
+
+    // Confirm that table is rendered.
+    $this->assertStringContainsString('class="class"', $renderedTable);
+  }
+
 }
diff --git a/web/core/tests/Drupal/Tests/Component/Annotation/Doctrine/DocParserTest.php b/web/core/tests/Drupal/Tests/Component/Annotation/Doctrine/DocParserTest.php
index e9addd67e7..b77f0138f0 100644
--- a/web/core/tests/Drupal/Tests/Component/Annotation/Doctrine/DocParserTest.php
+++ b/web/core/tests/Drupal/Tests/Component/Annotation/Doctrine/DocParserTest.php
@@ -764,6 +764,10 @@ public function getConstantsProvider()
             '@AnnotationWithConstants(PHP_EOL)',
             PHP_EOL
         );
+        $provider[] = array(
+            '@AnnotationWithConstants(\SimpleXMLElement::class)',
+            \SimpleXMLElement::class
+        );
         $provider[] = array(
             '@AnnotationWithConstants(AnnotationWithConstants::INTEGER)',
             AnnotationWithConstants::INTEGER
diff --git a/web/core/tests/Drupal/Tests/Component/Serialization/YamlTestBase.php b/web/core/tests/Drupal/Tests/Component/Serialization/YamlTestBase.php
index 6a8a5ba90b..5ac8aba378 100644
--- a/web/core/tests/Drupal/Tests/Component/Serialization/YamlTestBase.php
+++ b/web/core/tests/Drupal/Tests/Component/Serialization/YamlTestBase.php
@@ -14,7 +14,7 @@ abstract class YamlTestBase extends TestCase {
   /**
    * Some data that should be able to be serialized.
    */
-  public function providerEncodeDecodeTests() {
+  public static function providerEncodeDecodeTests() {
     return [
       [
         'foo' => 'bar',
@@ -46,7 +46,7 @@ public function providerEncodeDecodeTests() {
   /**
    * Some data that should be able to be deserialized.
    */
-  public function providerDecodeTests() {
+  public static function providerDecodeTests() {
     $data = [
       // NULL files.
       ['', NULL],
@@ -74,10 +74,10 @@ public function providerDecodeTests() {
     ];
 
     // 1.2 Bool values.
-    foreach ($this->providerBoolTest() as $test) {
+    foreach (static::providerBoolTest() as $test) {
       $data[] = ['bool: ' . $test[0], ['bool' => $test[1]]];
     }
-    $data = array_merge($data, $this->providerBoolTest());
+    $data = array_merge($data, static::providerBoolTest());
 
     return $data;
   }
@@ -85,7 +85,7 @@ public function providerDecodeTests() {
   /**
    * Tests different boolean serialization and deserialization.
    */
-  public function providerBoolTest() {
+  public static function providerBoolTest() {
     return [
       ['true', TRUE],
       ['TRUE', TRUE],
diff --git a/web/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php b/web/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
index f79915f18c..0dbb6b2faf 100644
--- a/web/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
+++ b/web/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
@@ -129,8 +129,8 @@ public function testCompressUncompress() {
    */
   public function testUncompressInvalidString() {
     // Pass an invalid string to ::uncompressQueryParameter() and ensure it
-    // doesn't result in a PHP warning.
-    $this->assertFalse(UrlHelper::uncompressQueryParameter('llama'));
+    // returns the passed string without resulting in a PHP warning.
+    $this->assertSame('llama', UrlHelper::uncompressQueryParameter('llama'));
   }
 
   /**
diff --git a/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ComposerHookTest.php b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ComposerHookTest.php
index 24fcdfce29..a0baaa349b 100644
--- a/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ComposerHookTest.php
+++ b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ComposerHookTest.php
@@ -148,4 +148,15 @@ public function testScaffoldMessagesDoNotPrintTwice() {
     $this->assertStringNotContainsString('- Copy [web-root]/update.php from assets/update.php', $stdout);
   }
 
+  /**
+   * Tests to see if scaffold events are dispatched and picked up by the plugin.
+   */
+  public function testScaffoldEvents(): void {
+    $topLevelProjectDir = 'scaffold-events-fixture';
+    $sut = $this->fixturesDir . '/' . $topLevelProjectDir;
+    $output = $this->mustExec("composer install --no-ansi", $sut);
+    $this->assertStringContainsString('Hello preDrupalScaffoldCmd', $output);
+    $this->assertStringContainsString('Hello postDrupalScaffoldCmd', $output);
+  }
+
 }
diff --git a/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/composer-plugin-implements-scaffold-events/composer.json b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/composer-plugin-implements-scaffold-events/composer.json
new file mode 100644
index 0000000000..afb5ddc95a
--- /dev/null
+++ b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/composer-plugin-implements-scaffold-events/composer.json
@@ -0,0 +1,16 @@
+{
+  "name": "fixtures/composer-plugin-implements-scaffold-events",
+  "type": "composer-plugin",
+  "require": {
+    "composer-plugin-api": "^2",
+    "drupal/core-composer-scaffold": "*"
+  },
+  "autoload": {
+    "psr-4": {
+      "Drupal\\Tests\\fixture\\Composer\\Plugin\\": "src"
+    }
+  },
+  "extra": {
+    "class": "Drupal\\Tests\\fixture\\Composer\\Plugin\\ComposerPluginImplementsScaffoldEvents"
+  }
+}
diff --git a/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/composer-plugin-implements-scaffold-events/src/ComposerPluginImplementsScaffoldEvents.php b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/composer-plugin-implements-scaffold-events/src/ComposerPluginImplementsScaffoldEvents.php
new file mode 100644
index 0000000000..63b785fcf1
--- /dev/null
+++ b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/composer-plugin-implements-scaffold-events/src/ComposerPluginImplementsScaffoldEvents.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Tests\fixture\Composer\Plugin;
+
+use Composer\EventDispatcher\Event;
+use Composer\EventDispatcher\EventSubscriberInterface;
+use Composer\Plugin\PluginInterface;
+use Composer\Composer;
+use Composer\IO\IOInterface;
+use Drupal\Composer\Plugin\Scaffold\Handler;
+
+/**
+ * A fixture composer plugin implement Drupal scaffold events.
+ */
+class ComposerPluginImplementsScaffoldEvents implements PluginInterface, EventSubscriberInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents(): array {
+    return [
+      Handler::PRE_DRUPAL_SCAFFOLD_CMD => 'preDrupalScaffoldCmd',
+      Handler::POST_DRUPAL_SCAFFOLD_CMD => 'postDrupalScaffoldCmd',
+    ];
+  }
+
+  /**
+   * Implements pre Drupal scaffold cmd.
+   */
+  public static function preDrupalScaffoldCmd(Event $event): void {
+    print 'Hello preDrupalScaffoldCmd' . PHP_EOL;
+  }
+
+  /**
+   * Implements post Drupal scaffold cmd.
+   */
+  public static function postDrupalScaffoldCmd(Event $event): void {
+    print 'Hello postDrupalScaffoldCmd' . PHP_EOL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function activate(Composer $composer, IOInterface $io) {
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deactivate(Composer $composer, IOInterface $io) {
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function uninstall(Composer $composer, IOInterface $io) {
+
+  }
+
+}
diff --git a/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scaffold-events-fixture/composer.json.tmpl b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scaffold-events-fixture/composer.json.tmpl
new file mode 100644
index 0000000000..18e5c3149e
--- /dev/null
+++ b/web/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scaffold-events-fixture/composer.json.tmpl
@@ -0,0 +1,44 @@
+{
+  "name": "fixtures/drupal-drupal",
+  "type": "project",
+  "minimum-stability": "dev",
+  "prefer-stable": true,
+  "repositories": {
+    "packagist.org": false,
+    "composer-scaffold": {
+      "type": "path",
+      "url": "__PROJECT_ROOT__",
+      "options": {
+        "symlink": true
+      }
+    },
+    "fixtures/composer-plugin-implements-scaffold-events": {
+      "type": "path",
+      "url": "../composer-plugin-implements-scaffold-events",
+      "options": {
+        "symlink": true
+      }
+    }
+  },
+  "require": {
+    "drupal/core-composer-scaffold": "*",
+    "fixtures/composer-plugin-implements-scaffold-events": "*"
+  },
+  "extra": {
+    "drupal-scaffold": {
+      "allowed-packages": [
+        "fixtures/composer-plugin-implements-scaffold-events"
+      ],
+      "locations": {
+        "web-root": "./"
+      },
+      "symlink": __SYMLINK__
+    }
+  },
+  "config": {
+     "allow-plugins": {
+       "drupal/core-composer-scaffold": true,
+       "fixtures/composer-plugin-implements-scaffold-events": true
+     }
+  }
+}
diff --git a/web/core/tests/Drupal/Tests/Core/Config/Entity/Query/QueryFactoryTest.php b/web/core/tests/Drupal/Tests/Core/Config/Entity/Query/QueryFactoryTest.php
index 9bab605732..f46f41db51 100644
--- a/web/core/tests/Drupal/Tests/Core/Config/Entity/Query/QueryFactoryTest.php
+++ b/web/core/tests/Drupal/Tests/Core/Config/Entity/Query/QueryFactoryTest.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\Entity\Query\QueryFactory;
 use Drupal\Tests\UnitTestCase;
+use PHPUnit\Framework\MockObject\MockObject;
 
 /**
  * @coversDefaultClass \Drupal\Core\Config\Entity\Query\QueryFactory
@@ -20,7 +21,11 @@ class QueryFactoryTest extends UnitTestCase {
    *
    * @dataProvider providerTestGetKeys
    */
-  public function testGetKeys(array $expected, $key, Config $config) {
+  public function testGetKeys(array $expected, string $key, array $sets): void {
+    $config = $this->getConfigObject('test');
+    foreach ($sets as $set) {
+      $config->set(...$set);
+    }
     $config_factory = $this->createMock('Drupal\Core\Config\ConfigFactoryInterface');
     $key_value_factory = $this->createMock('Drupal\Core\KeyValueStore\KeyValueFactoryInterface');
     $config_manager = $this->createMock('Drupal\Core\Config\ConfigManagerInterface');
@@ -32,68 +37,68 @@ public function testGetKeys(array $expected, $key, Config $config) {
     $this->assertEquals($expected, $actual);
   }
 
-  public function providerTestGetKeys() {
-    $tests = [];
-
-    $tests[] = [
+  public static function providerTestGetKeys(): \Generator {
+    yield [
       ['uuid:abc'],
       'uuid',
-      $this->getConfigObject('test')->set('uuid', 'abc'),
+      [['uuid', 'abc']],
     ];
 
     // Tests a lookup being set to a top level key when sub-keys exist.
-    $tests[] = [
+    yield [
       [],
       'uuid',
-      $this->getConfigObject('test')->set('uuid.blah', 'abc'),
+      [['uuid.blah', 'abc']],
     ];
 
     // Tests a non existent key.
-    $tests[] = [
+    yield [
       [],
       'uuid',
-      $this->getConfigObject('test'),
+      [],
     ];
 
     // Tests a non existent sub key.
-    $tests[] = [
+    yield [
       [],
       'uuid.blah',
-      $this->getConfigObject('test')->set('uuid', 'abc'),
+      [['uuid', 'abc']],
     ];
 
     // Tests an existent sub key.
-    $tests[] = [
+    yield [
       ['uuid.blah:abc'],
       'uuid.blah',
-      $this->getConfigObject('test')->set('uuid.blah', 'abc'),
+      [['uuid.blah', 'abc']],
     ];
 
     // One wildcard.
-    $tests[] = [
+    yield [
       ['test.*.value:a', 'test.*.value:b'],
       'test.*.value',
-      $this->getConfigObject('test')->set('test.a.value', 'a')->set('test.b.value', 'b'),
+      [['test.a.value', 'a'], ['test.b.value', 'b']],
     ];
 
     // Three wildcards.
-    $tests[] = [
+    yield [
       ['test.*.sub2.*.sub4.*.value:aaa', 'test.*.sub2.*.sub4.*.value:aab', 'test.*.sub2.*.sub4.*.value:bab'],
       'test.*.sub2.*.sub4.*.value',
-      $this->getConfigObject('test')
-        ->set('test.a.sub2.a.sub4.a.value', 'aaa')
-        ->set('test.a.sub2.a.sub4.b.value', 'aab')
-        ->set('test.b.sub2.a.sub4.b.value', 'bab'),
+      [
+        ['test.a.sub2.a.sub4.a.value', 'aaa'],
+        ['test.a.sub2.a.sub4.b.value', 'aab'],
+        ['test.b.sub2.a.sub4.b.value', 'bab'],
+      ],
     ];
 
     // Three wildcards in a row.
-    $tests[] = [
+    yield [
       ['test.*.*.*.value:abc', 'test.*.*.*.value:abd'],
       'test.*.*.*.value',
-      $this->getConfigObject('test')->set('test.a.b.c.value', 'abc')->set('test.a.b.d.value', 'abd'),
+      [
+        ['test.a.b.c.value', 'abc'],
+        ['test.a.b.d.value', 'abd'],
+      ],
     ];
-
-    return $tests;
   }
 
   /**
@@ -122,11 +127,11 @@ public function testGetKeysWildCardEnd() {
    * @param string $name
    *   The config name.
    *
-   * @return \Drupal\Core\Config\Config|\PHPUnit\Framework\MockObject\MockObject
+   * @return \Drupal\Core\Config\Config&\PHPUnit\Framework\MockObject\MockObject
    *   The test configuration object.
    */
-  protected function getConfigObject($name) {
-    $config = $this->getMockBuilder('Drupal\Core\Config\Config')
+  protected function getConfigObject(string $name): Config&MockObject {
+    $config = $this->getMockBuilder(Config::class)
       ->disableOriginalConstructor()
       ->onlyMethods(['save', 'delete'])
       ->getMock();
diff --git a/web/core/tests/Drupal/Tests/Core/Error/DrupalLogErrorTest.php b/web/core/tests/Drupal/Tests/Core/Error/DrupalLogErrorTest.php
index 4bd3e8bbab..1957beb81d 100644
--- a/web/core/tests/Drupal/Tests/Core/Error/DrupalLogErrorTest.php
+++ b/web/core/tests/Drupal/Tests/Core/Error/DrupalLogErrorTest.php
@@ -32,7 +32,7 @@ public function testFatalExitCode(string $script, string $output, string $errorO
     $this->assertSame($processIsSuccessful, $process->isSuccessful());
   }
 
-  public function provideFatalExitCodeData(): array {
+  public static function provideFatalExitCodeData(): array {
     $verbose = "\$GLOBALS['config']['system.logging']['error_level'] = 'verbose';";
     $scriptBody = self::getScriptBody();
     $data['normal'] = [
diff --git a/web/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php b/web/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php
index 46c8bd8b8e..0ba6a9ebad 100644
--- a/web/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php
+++ b/web/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php
@@ -8,6 +8,7 @@
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\TypedData\DataDefinition;
 use Drupal\Core\TypedData\MapDataDefinition;
+use Drupal\Core\TypedData\TypedData as TypedDataBase;
 use Drupal\Core\TypedData\TypedDataManager;
 use Drupal\Core\TypedData\Validation\ExecutionContextFactory;
 use Drupal\Core\TypedData\Validation\RecursiveValidator;
@@ -250,16 +251,13 @@ public function testValidatePropertyWithInvalidObjects($object) {
 
   /**
    * Provides data for testValidatePropertyWithInvalidObjects.
-   * @return array
    */
-  public function providerTestValidatePropertyWithInvalidObjects() {
-    $data = [];
-    $data[] = [new \stdClass()];
-    $data[] = [new class() {}];
+  public static function providerTestValidatePropertyWithInvalidObjects(): \Generator {
+    $dataDefinition = new DataDefinition();
 
-    $data[] = [$this->createMock('Drupal\Core\TypedData\TypedDataInterface')];
-
-    return $data;
+    yield [new \stdClass()];
+    yield [new class() {}];
+    yield [new class($dataDefinition) extends TypedDataBase {}];
   }
 
   /**
diff --git a/web/core/themes/claro/claro.theme b/web/core/themes/claro/claro.theme
index f33e425c16..e9a4e3d62c 100644
--- a/web/core/themes/claro/claro.theme
+++ b/web/core/themes/claro/claro.theme
@@ -1647,7 +1647,7 @@ function claro_form_views_ui_config_item_form_alter(array &$form, FormStateInter
       foreach (['views-left-30', 'views-left-40'] as $left_class) {
         if (str_contains($form['options']['operator']['#prefix'], $left_class)) {
           $form['options']['operator']['#prefix'] = '<div class="views-config-group-region">' . str_replace($left_class, 'views-group-box--operator', $form['options']['operator']['#prefix']);
-          $form['options']['value']['#suffix'] = $form['options']['value']['#suffix'] . '</div>';
+          $form['options']['value']['#suffix'] = ($form['options']['value']['#suffix'] ?? '') . '</div>';
         }
       }
     }
diff --git a/web/core/themes/claro/src/ClaroPreRender.php b/web/core/themes/claro/src/ClaroPreRender.php
index e18d0906c9..a47f4ac26e 100644
--- a/web/core/themes/claro/src/ClaroPreRender.php
+++ b/web/core/themes/claro/src/ClaroPreRender.php
@@ -193,7 +193,7 @@ public static function messagePlaceholder(array $element) {
    * Prerender callback for table elements.
    */
   public static function tablePositionSticky(array $element) {
-    if (isset($element['#attributes']['class']) && in_array('sticky-enabled', $element['#attributes']['class'])) {
+    if (isset($element['#attributes']['class']) && is_array($element['#attributes']['class']) && in_array('sticky-enabled', $element['#attributes']['class'], TRUE)) {
       unset($element['#attributes']['class'][array_search('sticky-enabled', $element['#attributes']['class'])]);
       $element['#attributes']['class'][] = 'position-sticky';
     }
diff --git a/web/core/themes/olivero/css/components/form.css b/web/core/themes/olivero/css/components/form.css
index 099aa43dd1..bf7ba606c4 100644
--- a/web/core/themes/olivero/css/components/form.css
+++ b/web/core/themes/olivero/css/components/form.css
@@ -210,3 +210,7 @@ tr .form-item,
 .form--inline .form-actions {
   margin-top: var(--sp1-5);
 }
+
+.layout-builder-form .form-actions {
+  align-items: center;
+}
diff --git a/web/core/themes/olivero/css/components/form.pcss.css b/web/core/themes/olivero/css/components/form.pcss.css
index b4228533a4..2566c45a7e 100644
--- a/web/core/themes/olivero/css/components/form.pcss.css
+++ b/web/core/themes/olivero/css/components/form.pcss.css
@@ -191,3 +191,9 @@ tr .form-item,
     margin-top: var(--sp1-5);
   }
 }
+
+.layout-builder-form {
+  & .form-actions {
+    align-items: center;
+  }
+}
diff --git a/web/sites/default/default.settings.php b/web/sites/default/default.settings.php
index 63fb2df74a..8819d64317 100644
--- a/web/sites/default/default.settings.php
+++ b/web/sites/default/default.settings.php
@@ -181,8 +181,8 @@
  *
  * WARNING: The above defaults are designed for database portability. Changing
  * them may cause unexpected behavior, including potential data loss. See
- * https://www.drupal.org/developing/api/database/configuration for more
- * information on these defaults and the potential issues.
+ * https://www.drupal.org/docs/8/api/database-api/database-configuration for
+ * more information on these defaults and the potential issues.
  *
  * More details can be found in the constructor methods for each driver:
  * - \Drupal\mysql\Driver\Database\mysql\Connection::__construct()
diff --git a/web/sites/example.sites.php b/web/sites/example.sites.php
index 3b32b5aba1..f84da04588 100644
--- a/web/sites/example.sites.php
+++ b/web/sites/example.sites.php
@@ -53,5 +53,5 @@
  *
  * @see default.settings.php
  * @see \Drupal\Core\DrupalKernel::getSitePath()
- * @see https://www.drupal.org/documentation/install/multi-site
+ * @see https://www.drupal.org/docs/getting-started/multisite-drupal
  */
-- 
GitLab