diff --git a/composer.json b/composer.json index e2eaf2fac0d074f6973bb72cc15c2c627b74fd6f..5f09a500d69b994c328fa52c83192c89a478935e 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 2c6baecee22aad26565372b9e6d19de2910fd6fa..dafedf0334e1e7bd36731168b67c51e8d7bc06af 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 168810aa5308a29a5a7f0f95bd674b02eb780f84..3c69a301cbe281ebf73bd56712e8169fe8146bb6 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 4e54f5b87f9028d85d161885d619f0d3a9b7f5c5..c66606befba44a85574994380a51eb7004c10f3a 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 3e51f909a335238ffcf9d4054b511f486e206da5..a3e9914bb39445f433bebddcb5f88387f4364fa9 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 1f6be29a3507a703641162b35f1c72ba87bbea84..d4d09ff8b3cc3157ac32d2ef951059b5d5f66ce9 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 9b772874f35d9984ee81d65c2854d4c534f7703b..f9bfea503b9d7369510d0acc02b81537ab674a39 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 63fb2df74a1bc9faf82fc5a72d9a76e090d6f20c..8819d64317289890b300b0691bb9a0f7bd72d0bd 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 3b32b5aba1699ae93a35da0063c339b42c2bec4e..f84da04588491fbb945a29ac8e3364fcfc07db41 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 bc7547d4112b2f2361700df003802d98c10e71a0..f187114139a9b787db07464071e73de0b71dce58 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 d16d0d14a8c2e1940e25f4b1ba0a29de6952a95e..ac43522e2f697ef688b18a72a266e35b4bdfbfad 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 960c4869ff87f5fb7c011e8c53e0b3a7a484215f..fb0ce98134f3f5e05b8637f7b02ef0bbf0dcd4cd 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 6bc65584cf7111024ca33518b618dc68af2705f0..14ae12a8e8bf35dc19f2fde467c91914fc24c213 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 28b8e42b299ccde1e3512f1cd21e2dc11f0088b6..c713d1a62374d39562849bca04a95646575d8fb6 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 66f557108152c630e9a9e95995f87c65e2aa2eb6..4bd400a3662f7a9826d396890be5a020eb9d321f 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 e80882b0bec0409d0842df30818a112250674159..0d099a79f157ceb6bf8cb1a6082a6cf57936c56f 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 bbf70365e1c3c3011b896344f1f35771149d41f2..a38285e27abf861ec75d56d5706d789a356d2ff1 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"> </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 f2a730c4ba993bc81772c98d285aafd22f52bc79..65477c8dc3dc4666808c7900b4ad2a9a52a62f88 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 67f7f303d12944e5b4809458250347d5e115ff5c..38aa11f865758bf6086c4afad8443a8802adfce2 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 f7d921ff4af902d4657cb7e0e6758345eed47d48..51282d4b45501b368adda2d47b51f54f77e1db1b 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 271a01342e15113e5f57d7cf0aec08aa4668c66b..53013109324d79e4085849ab0beb227007138851 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 4e85773341460766e265339482a886f6ebbf0030..ea3b6c08c7ed79652457d659228010db232e0d50 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 173c9c775232fef66930f2457b5b8b0836b41841..6fbdff1d4dfd03398d64828a46fb2d193a757c22 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 b4b995501477e7f472c6a04987defcf5c1fdfccc..eba279690f0663d6dd9e7cab55b9e31532c4e364 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 185c8cde6eb3ea89223388f72f4cd425d9e0a562..d57ca6c17818ec5fab82d6ab0d6fe7c5a9ffb659 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 36570a4b6a117d7e9e02be15458e45100fb4be89..421347b8311fc69153af006f3aeb778e1ad3755e 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 a0002ba70c5fb25078296ec5057c386be9574a5c..d9b76b4fe050cd280055e3a1d9657c1d90c02aa8 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 9ae8c89f882086f03e282f7d7e660c1b55e66d0b..44f3d310df16797ef9c1cc299da370b4d318c21a 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 af57e3d7c9a4ed5389eb39c7e8e3bd09648a3845..9e056051247cb969b2fb4f2ca8210e9d6102f90d 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 19ca6f584a0103455c11f203914d18267c13704c..2ee17ddb73d46c5283166921539a9261240e63ef 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 9bc8d0e24397521b25fff635b3711b03c6938e33..35c7e526bd754278c9dafac8fb58199e8b8fb670 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 357437c334462a953a853cadaeb37ede692f11ef..cfc8cdb5038d7c59182d9766eb863c38eea0ecdc 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 269c13f447643bc729c8a4b3bd731da0674bca79..928bfd8a45e1aa8826a482971e11613297f288e0 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 de013135980d4874743ae16b7ff032c83509768e..959c616703b6a8b0064d727999d69692ae0ecb1e 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 4203045b5952b4b02e8a3c9aa79e935f3a791acb..0d8bd4b1f265a84c3e2e170787c2343e8aef8f1d 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 47b5681f1a9657cf086a859f4f90ef151026c9c7..427d51ac05de9cc4c12ea31770f4901af1dfe415 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 873568d1e13b3b18151dfd88f3222bfc7e940c9b..abb6160dffd71612230183c602d8968ca9ebdea7 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 b54f7732424e5c3df945166dacc4eec67b039b8f..71a04a87cb744476681e9d8de0582d738d6c1dc4 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 5025e43911dcf1a77960c9d9913645b0eec928ce..6ddaab36e616b094886aa167a6e58b04c1597403 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 5f49f298a83006d81b66ef7194286d8cff16ba85..5e4f7c250310ae24c484574366e74d254a4a96f1 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 5929fcb365eb55efa2075a2f4e17e453198ecdd8..7b6e8cb1e4fb8180f1c3e3fe04c232dd66406277 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 f0e71a659e628743ffae26f92acf6218fe7f2c40..36d5b97703d373d76736f1725c2fd63a8fb92439 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 00d73a73f0ca61cd809c669f70eecd4c2184c3a3..14d4dcc3c46af3f19a1ad8f8974171287a812692 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 55ef58fb5903fda8718171b59f958913bcb5edfa..314d175d74e87f86fa4e26b233b4304cb89e0c7c 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 d2c7fb7ee9947571214d39999fe0e1a60dff5ec7..12a3aef7124dbf63c4b279db6c5e2f1cccd080b4 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 e5c2b075916f49d8fec68d31df29a2611d1dbc8a..0b4c76958cc346c7e9d9bfefc8a0f40dcc574e24 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 484ccc75227e98e72d0fda04c68afd0e42eef008..a2d7fcbabb86cdf08b78591ae7f71d39eda157d7 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 7b795d72cfffbe4a70237cee5a789c536bb41203..74df9184ce3b078256d0fcbbed083bde297a0501 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 bce5391b19225deb8e4400c210a564eba7e397c4..e96c45538a87b587d4d5a1b1e47ae92d468804a0 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 aadfeaff47480ec9b5f49bc6246ab327d5500239..c4fbd04d314d32e69415b313d264e66395521d83 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 4206fa9abf8bbd5985ebb78dda3a6a9986b0439d..251ee45dc50ccc3de06cd66592b22889844094ba 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 df1b9969c3bcc6e0d255beea9108388a1ae4f743..e20debaf0a38c9491937382262c571d919ba1396 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 3d1c96f2df76c8f3b642c2aab54d3f9de1d09d23..79da8de4609c22785a004d43692a76c9c3bb65d3 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 19c2eec6f1050d0e79063ba74eca32f8a9e8739b..0e25e72ab6fd428ab87b21686544415d11d5deff 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 b48da4ed26a395501c7b0c693cec440f6f7c7dc6..4c595cef2f8b3ddf461eaf1a5094c15b830c96a1 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 535ed462aa1c6dfbfa8a9f628a74f0d5a69fc187..2f62d85eea3f5b7b1582a032134ba459324147f3 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 d76ef3f103588c6594ab3ff5fc930acc03e90817..e98b465cc3eaf732054e6a302ef765e1cda01097 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 7e49395d42a5e0053e1a9aea5bcf60f86ce1f090..18167fe7153d7b9f7a04d54ea676d7ea281dbb9b 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 1ca75fcf46a9c1f36da030527c1267ddba655bc8..ece513c13f65ae087f462a52178af27f7d2f6f3e 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 72edbe8d2eafd1b075b6bf62a935f9d125929da5..ff395da0fcfc8c3e1c6e589bad9f7f3aee489c62 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 1ba5d5e408acf5191e4c309a5cc643e69c6fc83a..01e7ce2a0f13f2ddc28140555db3c4c86da8ec9a 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 29d5280a15e7e876819737c7df96349c989295c2..561da0960794d4525e6c084eca6fe744e2649194 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 ec84fcfacfbf136d00a242fb421cfc198aa29a77..d634fa5291a67cf019656fa3b19bfc4ee2d57171 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 82a42a4e70b91cc09cec3826ff693785679da1f3..237afa64b435f324ad0db5c35c61cd02181a43cc 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 6e630d0433826ce238824481d120098c6ea2f767..4dce202cd15b786fead8892c5da24f7c6bc3faf7 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 e042045dcfb4f5a26976fa30c215c0490fe380c9..a314a8f1febdf35102d169750b97fa1778d4fc7d 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 45f635ca41e3264c9c2b9c4c022b947904ff8b0c..0022f24f68db15608fb2f56a1b0507daad6de09c 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 c97b8ae93f773d4a8a76407e6a43df36e82059ee..06c7d5ffe0bef6fc61278862ce8fb318207dc6b2 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 74cbc4eeff4a6e7ad07c44fe9749b19920b079c3..515aee921b4637ce650b44d485d2ae432bde0f89 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 b41787f452ad9e8fde1c75a98464486c2198168a..2875d43aff15103372d405a9285980cecdbc09e8 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 e49ff3b4c39ce3328e524a89367f9647d589d7fa..58a6d44a110f98d1aae8bc0a6285f266b909ffe6 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 34a7bdf0006be545f3ceeb4124cd66c2f2f03c31..1a38873a7144bb8c7cba67aba99c4ca55450b7d0 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 61cf2818080c67c549855c4462b3dc60d3a6cae9..eb3af5c239311e06d485ee0ce733fb8310433561 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 70dcd6606e2d41c9acdfa4b531d843c4bf61f17b..cb3e0ff35efaa1ee03d9960649605201d782b8bc 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 e9addd67e7db78042c273aeeba8f51192e3e9cb3..b77f0138f07d6aa01d7c966f4c7ca0ea7adde3ec 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 6a8a5ba90bba03280a2f3b5fa9cfe8c4719b2ccc..5ac8aba378a993c0f1552a6940716070d8c1b84c 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 f79915f18c6fb1296d83ae403fce8f2f476d8c14..0dbb6b2faf28c5951f1f843e38aae25f60a4bc08 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 24fcdfce2982d68a1e92aea5592014e359f4c3ee..a0baaa349b60a4a89f791841b652cbed8851f4f7 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 0000000000000000000000000000000000000000..afb5ddc95a452a3c192ebc4c573af8a7382cc696 --- /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 0000000000000000000000000000000000000000..63b785fcf1b3d9fd96dc29b02c0edec900da4ac3 --- /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 0000000000000000000000000000000000000000..18e5c3149ef411b31eac37f4323d19b9af251264 --- /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 9bab605732fcac8ae36045f0799c0390c4760e4e..f46f41db51372f099a844cdaf51f02cbc4b54753 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 4bd3e8bbab4f10079a30ccda9600e0d4dffe772a..1957beb81db65ab4edb7b483d1d04289dbbdaeec 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 46c8bd8b8eb21a4b438537c5a3777aaea15c78a2..0ba6a9ebad2ba2f300004651e6ca230a97b3e766 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 f33e425c16591d616cb5b4e4b2a8c3c0b581fecb..e9a4e3d62c185056f6575d45af742c9c58990f8b 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 e18d0906c9849f52fccf56a08d938b45b860d976..a47f4ac26e71e4ff4aab3eac5adb98b6810f9141 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 099aa43dd16471da97cb2de6cac50a53d58f30a1..bf7ba606c4b474aa2d3c30c0b1ea5e3b8c9f163a 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 b4228533a44392f4502daf6f8c2cd2873a862900..2566c45a7e3fb056c375c03cf81f87f68ca3bb6a 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 63fb2df74a1bc9faf82fc5a72d9a76e090d6f20c..8819d64317289890b300b0691bb9a0f7bd72d0bd 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 3b32b5aba1699ae93a35da0063c339b42c2bec4e..f84da04588491fbb945a29ac8e3364fcfc07db41 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 */