diff options
486 files changed, 8106 insertions, 6104 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 58f39c03ac17..936401851968 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -234,6 +234,7 @@ default: _TARGET_DB_DRIVER: "mysql" _TARGET_DB_DRIVER_MODULE: "mysql" PERFORMANCE_TEST: $PERFORMANCE_TEST + OPENTELEMETRY_COLLECTOR: $OPENTELEMETRY_COLLECTOR # Run on MR, schedule, push, parent pipeline and performance test. rules: - if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ROOT_NAMESPACE == "project" diff --git a/.gitlab-ci/pipeline.yml b/.gitlab-ci/pipeline.yml index 88a60ff66594..4efc20087814 100644 --- a/.gitlab-ci/pipeline.yml +++ b/.gitlab-ci/pipeline.yml @@ -273,7 +273,7 @@ variables: [[ $_TARGET_DB == mariadb* ]] && export SIMPLETEST_DB=$_TARGET_DB_DRIVER://$MYSQL_USER:$MYSQL_PASSWORD@database/$MYSQL_DATABASE?module=$_TARGET_DB_DRIVER_MODULE [[ $_TARGET_DB == pgsql* ]] && export SIMPLETEST_DB=pgsql://$POSTGRES_USER:$POSTGRES_PASSWORD@database/$POSTGRES_DB?module=pgsql - composer install --optimize-autoloader - - export OTEL_COLLECTOR="$OTEL_COLLECTOR" + - export OTEL_COLLECTOR="$OPENTELEMETRY_COLLECTOR" - mkdir -p ./sites/simpletest ./sites/default/files ./build/logs/junit /var/www/.composer - chown -R www-data:www-data ./sites ./build/logs/junit ./vendor /var/www/ - sudo -u www-data git config --global --add safe.directory $CI_PROJECT_DIR diff --git a/composer.json b/composer.json index 735482c6ba62..6dbdb9c3ed9a 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,9 @@ "drupal/core-recipe-unpack": "self.version", "drupal/core-vendor-hardening": "self.version" }, + "conflict": { + "dealerdirect/phpcodesniffer-composer-installer": "1.1.0" + }, "require-dev": { "behat/mink": "^1.11", "behat/mink-browserkit-driver": "^2.2", @@ -24,7 +27,7 @@ "justinrainbow/json-schema": "^5.2 || ^6.3", "lullabot/mink-selenium2-driver": "^1.7.3", "lullabot/php-webdriver": "^2.0.5", - "mglaman/phpstan-drupal": "^2.0.7", + "mglaman/phpstan-drupal": "^1.3.9 || ^2.0.7", "micheh/phpcs-gitlab": "^1.1 || ^2.0", "mikey179/vfsstream": "^1.6.11", "open-telemetry/exporter-otlp": "^1", @@ -32,8 +35,8 @@ "php-http/guzzle7-adapter": "^1.0", "phpspec/prophecy-phpunit": "^2", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.17", - "phpstan/phpstan-phpunit": "^2.0.6", + "phpstan/phpstan": "^1.12.27 || ^2.1.17", + "phpstan/phpstan-phpunit": "^1.4.2 || ^2.0.6", "phpunit/phpunit": "^10.5.19 || ^11.5.3", "symfony/browser-kit": "^7.3", "symfony/css-selector": "^7.3", diff --git a/composer.lock b/composer.lock index 24834277046b..23f098b5b9a5 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": "cb5ffa459396dd81c6c044041488251c", + "content-hash": "7e45f46f86addf53ba6170d650e30fc2", "packages": [ { "name": "asm89/stack-cors", @@ -497,7 +497,7 @@ "dist": { "type": "path", "url": "core", - "reference": "6f52d7dd7a51d6925d01eb25052131d20ba3ee73" + "reference": "cc47b8e83dc7fe1fb24f2baa25102b85a24212de" }, "require": { "asm89/stack-cors": "^2.3", @@ -541,6 +541,7 @@ "symfony/mime": "^7.3", "symfony/polyfill-iconv": "^1.32", "symfony/polyfill-php84": "^1.32", + "symfony/polyfill-php85": "^1.32", "symfony/process": "^7.3", "symfony/psr-http-message-bridge": "^7.3", "symfony/routing": "^7.3", @@ -550,6 +551,7 @@ "twig/twig": "^3.21.0" }, "conflict": { + "dealerdirect/phpcodesniffer-composer-installer": "1.1.0", "drupal/automatic_updates": "<4", "drupal/project_browser": "<2.1", "drush/drush": "<12.4.3" @@ -3636,6 +3638,82 @@ "time": "2025-02-20T12:04:08+00:00" }, { + "name": "symfony/polyfill-php85", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd", + "reference": "6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-02T08:40:52+00:00" + }, + { "name": "symfony/process", "version": "v7.3.0", "source": { @@ -10127,8 +10205,8 @@ }, "prefer-stable": true, "prefer-lowest": false, - "platform": {}, - "platform-dev": {}, + "platform": [], + "platform-dev": [], "platform-overrides": { "php": "8.3.0" }, diff --git a/composer/Metapackage/CoreRecommended/composer.json b/composer/Metapackage/CoreRecommended/composer.json index 069bed420fc6..0986f1cb8a9f 100644 --- a/composer/Metapackage/CoreRecommended/composer.json +++ b/composer/Metapackage/CoreRecommended/composer.json @@ -52,6 +52,7 @@ "symfony/polyfill-intl-normalizer": "~v1.32.0", "symfony/polyfill-mbstring": "~v1.32.0", "symfony/polyfill-php84": "~v1.32.0", + "symfony/polyfill-php85": "~v1.32.0", "symfony/process": "~v7.3.0", "symfony/psr-http-message-bridge": "~v7.3.0", "symfony/routing": "~v7.3.0", diff --git a/composer/Metapackage/DevDependencies/composer.json b/composer/Metapackage/DevDependencies/composer.json index 6e2436f30377..56eb4f756244 100644 --- a/composer/Metapackage/DevDependencies/composer.json +++ b/composer/Metapackage/DevDependencies/composer.json @@ -15,7 +15,7 @@ "justinrainbow/json-schema": "^5.2 || ^6.3", "lullabot/mink-selenium2-driver": "^1.7.3", "lullabot/php-webdriver": "^2.0.5", - "mglaman/phpstan-drupal": "^2.0.7", + "mglaman/phpstan-drupal": "^1.3.9 || ^2.0.7", "micheh/phpcs-gitlab": "^1.1 || ^2.0", "mikey179/vfsstream": "^1.6.11", "open-telemetry/exporter-otlp": "^1", @@ -23,8 +23,8 @@ "php-http/guzzle7-adapter": "^1.0", "phpspec/prophecy-phpunit": "^2", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.17", - "phpstan/phpstan-phpunit": "^2.0.6", + "phpstan/phpstan": "^1.12.27 || ^2.1.17", + "phpstan/phpstan-phpunit": "^1.4.2 || ^2.0.6", "phpunit/phpunit": "^10.5.19 || ^11.5.3", "symfony/browser-kit": "^7.3", "symfony/css-selector": "^7.3", diff --git a/composer/Plugin/RecipeUnpack/Plugin.php b/composer/Plugin/RecipeUnpack/Plugin.php index e3738aeae2c4..fe294ecb8b78 100644 --- a/composer/Plugin/RecipeUnpack/Plugin.php +++ b/composer/Plugin/RecipeUnpack/Plugin.php @@ -7,7 +7,7 @@ use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\Installer; use Composer\IO\IOInterface; -use Composer\Package\Package; +use Composer\Package\PackageInterface; use Composer\Plugin\Capability\CommandProvider; use Composer\Plugin\Capable; use Composer\Plugin\PluginInterface; @@ -107,7 +107,7 @@ final class Plugin implements PluginInterface, EventSubscriberInterface, Capable $packages = $composer->getRepositoryManager()->getLocalRepository()->findPackages($package_name); $package = reset($packages); - if (!$package instanceof Package) { + if (!$package instanceof PackageInterface) { if (!$isInstalling) { $event->getIO()->write('Recipes are not unpacked when the --no-install option is used.', verbosity: IOInterface::VERBOSE); return; diff --git a/composer/Plugin/RecipeUnpack/UnpackCommand.php b/composer/Plugin/RecipeUnpack/UnpackCommand.php index be87544677c7..60bd86f08c17 100644 --- a/composer/Plugin/RecipeUnpack/UnpackCommand.php +++ b/composer/Plugin/RecipeUnpack/UnpackCommand.php @@ -3,7 +3,7 @@ namespace Drupal\Composer\Plugin\RecipeUnpack; use Composer\Command\BaseCommand; -use Composer\Package\Package; +use Composer\Package\PackageInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -75,7 +75,7 @@ EOT $packages = $local_repo->findPackages($package_name); $package = reset($packages); - if (!$package instanceof Package) { + if (!$package instanceof PackageInterface) { $io->error(sprintf('<info>%s</info> does not resolve to a package.', $package_name)); return 1; } diff --git a/core/.deprecation-ignore.txt b/core/.deprecation-ignore.txt index 12772fbb55bc..f96b75628d14 100644 --- a/core/.deprecation-ignore.txt +++ b/core/.deprecation-ignore.txt @@ -39,6 +39,7 @@ %The "Drupal\\Core\\Database\\Query\\SelectExtender::hasAnyTag\(\)" method will require a new "string \.\.\. \$tags" argument in the next major version of its interface% %The "Drupal\\Core\\Entity\\Query\\QueryBase::hasAllTags\(\)" method will require a new "string \.\.\. \$tags" argument in the next major version of its interface% %The "Drupal\\Core\\Entity\\Query\\QueryBase::hasAnyTag\(\)" method will require a new "string \.\.\. \$tags" argument in the next major version of its interface% +%The "Drupal\\workspaces\\WorkspaceManager::setActiveWorkspace\(\)" method will require a new "bool \$persist" argument in the next major version of its interface% # Symfony 7.3. %Since symfony/validator 7.3: Passing an array of options to configure the "[^"]+" constraint is deprecated, use named arguments instead.% diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php index 07367cd8a2d0..21b7ee78a040 100644 --- a/core/.phpstan-baseline.php +++ b/core/.phpstan-baseline.php @@ -212,12 +212,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/includes/theme.inc', ]; $ignoreErrors[] = [ - 'message' => '#^Variable \\$items might not be defined\\.$#', - 'identifier' => 'variable.undefined', - 'count' => 1, - 'path' => __DIR__ . '/includes/theme.inc', -]; -$ignoreErrors[] = [ 'message' => '#^Variable \\$custom_theme might not be defined\\.$#', 'identifier' => 'variable.undefined', 'count' => 1, @@ -9716,12 +9710,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/lib/Drupal/Core/Render/Element/Weight.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Core\\\\Render\\\\ElementInfoManager\\:\\:clearCachedDefinitions\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/lib/Drupal/Core/Render/ElementInfoManager.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Core\\\\Render\\\\HtmlResponseAttachmentsProcessor\\:\\:renderHtmlResponseAttachmentPlaceholders\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -11540,24 +11528,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/block/tests/src/Functional/Rest/BlockJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block\\\\Functional\\\\Rest\\\\BlockJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block/tests/src/Functional/Rest/BlockJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block\\\\Functional\\\\Rest\\\\BlockJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block/tests/src/Functional/Rest/BlockJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block\\\\Functional\\\\Rest\\\\BlockJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block/tests/src/Functional/Rest/BlockJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\block\\\\Functional\\\\Rest\\\\BlockResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -11588,24 +11558,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/block/tests/src/Functional/Rest/BlockXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block\\\\Functional\\\\Rest\\\\BlockXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block/tests/src/Functional/Rest/BlockXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block\\\\Functional\\\\Rest\\\\BlockXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block/tests/src/Functional/Rest/BlockXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block\\\\Functional\\\\Rest\\\\BlockXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block/tests/src/Functional/Rest/BlockXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\block\\\\Functional\\\\Views\\\\DisplayBlockTest\\:\\:assertBlockAppears\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -11864,24 +11816,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -11912,24 +11846,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentTypeJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentTypeJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentTypeJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentTypeJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentTypeResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -11960,24 +11876,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentTypeXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentTypeXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentTypeXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentTypeXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentXmlAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -12002,24 +11900,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Functional\\\\Rest\\\\BlockContentXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/block_content/tests/src/Functional/Rest/BlockContentXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\block_content\\\\Kernel\\\\BlockContentEntityReferenceSelectionTest\\:\\:fieldConditionProvider\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -13028,24 +12908,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentResourceTestBase\\:\\:addDefaultCommentField\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -13082,24 +12944,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentTypeJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentTypeJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentTypeJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentTypeJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentTypeResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -13130,24 +12974,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentTypeXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentTypeXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentTypeXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentTypeXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentXmlAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -13172,24 +12998,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Rest\\\\CommentXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/Rest/CommentXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\Views\\\\CommentTestBase\\:\\:addDefaultCommentField\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -13598,24 +13406,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/config/tests/config_test/tests/src/Functional/Rest/ConfigTestJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\config_test\\\\Functional\\\\Rest\\\\ConfigTestJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/config/tests/config_test/tests/src/Functional/Rest/ConfigTestJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\config_test\\\\Functional\\\\Rest\\\\ConfigTestJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/config/tests/config_test/tests/src/Functional/Rest/ConfigTestJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\config_test\\\\Functional\\\\Rest\\\\ConfigTestJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/config/tests/config_test/tests/src/Functional/Rest/ConfigTestJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\config_test\\\\Functional\\\\Rest\\\\ConfigTestResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -13646,24 +13436,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/config/tests/config_test/tests/src/Functional/Rest/ConfigTestXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\config_test\\\\Functional\\\\Rest\\\\ConfigTestXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/config/tests/config_test/tests/src/Functional/Rest/ConfigTestXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\config_test\\\\Functional\\\\Rest\\\\ConfigTestXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/config/tests/config_test/tests/src/Functional/Rest/ConfigTestXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\config_test\\\\Functional\\\\Rest\\\\ConfigTestXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/config/tests/config_test/tests/src/Functional/Rest/ConfigTestXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\config_transformer_test\\\\EventSubscriber\\:\\:onExportTransform\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -14138,24 +13910,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/ContactFormJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\ContactFormJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/ContactFormJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\ContactFormJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/ContactFormJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\ContactFormJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/ContactFormJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\ContactFormResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -14186,24 +13940,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/ContactFormXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\ContactFormXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/ContactFormXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\ContactFormXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/ContactFormXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\ContactFormXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/ContactFormXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\MessageJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -14228,24 +13964,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/MessageJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\MessageJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/MessageJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\MessageJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/MessageJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\MessageJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/MessageJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\MessageResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -14276,24 +13994,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/MessageXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\MessageXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/MessageXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\MessageXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/MessageXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\Rest\\\\MessageXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/Rest/MessageXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Unit\\\\MailHandlerTest\\:\\:getSendMailMessages\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -15692,30 +15392,12 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/dblog/src/Plugin/views/filter/DblogTypes.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\dblog\\\\Functional\\\\DbLogResourceTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/dblog/tests/src/Functional/DbLogResourceTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\dblog\\\\Functional\\\\DbLogResourceTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/dblog/tests/src/Functional/DbLogResourceTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\dblog\\\\Functional\\\\DbLogResourceTest\\:\\:getExpectedUnauthorizedEntityAccessCacheability\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, 'path' => __DIR__ . '/modules/dblog/tests/src/Functional/DbLogResourceTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\dblog\\\\Functional\\\\DbLogResourceTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/dblog/tests/src/Functional/DbLogResourceTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\dblog\\\\Functional\\\\DbLogTest\\:\\:assertBreadcrumb\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -15842,24 +15524,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/editor/tests/src/Functional/Rest/EditorJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\editor\\\\Functional\\\\Rest\\\\EditorJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/editor/tests/src/Functional/Rest/EditorJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\editor\\\\Functional\\\\Rest\\\\EditorJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/editor/tests/src/Functional/Rest/EditorJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\editor\\\\Functional\\\\Rest\\\\EditorJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/editor/tests/src/Functional/Rest/EditorJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\editor\\\\Functional\\\\Rest\\\\EditorResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -15890,24 +15554,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/editor/tests/src/Functional/Rest/EditorXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\editor\\\\Functional\\\\Rest\\\\EditorXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/editor/tests/src/Functional/Rest/EditorXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\editor\\\\Functional\\\\Rest\\\\EditorXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/editor/tests/src/Functional/Rest/EditorXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\editor\\\\Functional\\\\Rest\\\\EditorXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/editor/tests/src/Functional/Rest/EditorXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\editor\\\\Unit\\\\EditorXssFilter\\\\StandardTest\\:\\:providerTestFilterXss\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -16190,24 +15836,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldConfigJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldConfigJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldConfigJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldConfigJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldConfigJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldConfigJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldConfigJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldConfigResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -16238,24 +15866,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldConfigXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldConfigXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldConfigXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldConfigXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldConfigXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldConfigXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldConfigXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldStorageConfigJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -16280,24 +15890,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldStorageConfigJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldStorageConfigJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldStorageConfigJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldStorageConfigJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldStorageConfigJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldStorageConfigJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldStorageConfigJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldStorageConfigResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -16328,24 +15920,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldStorageConfigXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldStorageConfigXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldStorageConfigXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldStorageConfigXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldStorageConfigXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Rest\\\\FieldStorageConfigXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/Rest/FieldStorageConfigXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\Views\\\\FieldTestBase\\:\\:setUpFieldStorages\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -17468,18 +17042,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/file/tests/file_module_test/src/Form/FileModuleTestForm.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\file_test\\\\Form\\\\FileTestForm\\:\\:submitForm\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/file_test/src/Form/FileTestForm.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\file_test\\\\Form\\\\FileTestForm\\:\\:validateForm\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/file_test/src/Form/FileTestForm.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\file_test\\\\Form\\\\FileTestSaveUploadFromForm\\:\\:create\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -17696,24 +17258,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/file/tests/src/Functional/FileUploadJsonCookieTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\FileUploadJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/FileUploadJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\FileUploadJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/FileUploadJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\FileUploadJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/FileUploadJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\Rest\\\\FileJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -17738,24 +17282,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/file/tests/src/Functional/Rest/FileJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\Rest\\\\FileJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/Rest/FileJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\Rest\\\\FileJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/Rest/FileJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\Rest\\\\FileJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/Rest/FileJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\Rest\\\\FileResourceTestBase\\:\\:makeCurrentUserFileOwner\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -17792,24 +17318,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/file/tests/src/Functional/Rest/FileXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\Rest\\\\FileXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/Rest/FileXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\Rest\\\\FileXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/Rest/FileXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\Rest\\\\FileXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/Rest/FileXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\FunctionalJavascript\\\\FileFieldValidateTest\\:\\:attachFileField\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -18242,24 +17750,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/filter/tests/src/Functional/Rest/FilterFormatJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\filter\\\\Functional\\\\Rest\\\\FilterFormatJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/filter/tests/src/Functional/Rest/FilterFormatJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\filter\\\\Functional\\\\Rest\\\\FilterFormatJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/filter/tests/src/Functional/Rest/FilterFormatJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\filter\\\\Functional\\\\Rest\\\\FilterFormatJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/filter/tests/src/Functional/Rest/FilterFormatJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\filter\\\\Functional\\\\Rest\\\\FilterFormatResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -18290,24 +17780,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/filter/tests/src/Functional/Rest/FilterFormatXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\filter\\\\Functional\\\\Rest\\\\FilterFormatXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/filter/tests/src/Functional/Rest/FilterFormatXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\filter\\\\Functional\\\\Rest\\\\FilterFormatXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/filter/tests/src/Functional/Rest/FilterFormatXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\filter\\\\Functional\\\\Rest\\\\FilterFormatXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/filter/tests/src/Functional/Rest/FilterFormatXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\filter\\\\Kernel\\\\TextFormatElementFormTest\\:\\:submitForm\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -18986,24 +18458,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/image/tests/src/Functional/Rest/ImageStyleJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\image\\\\Functional\\\\Rest\\\\ImageStyleJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/image/tests/src/Functional/Rest/ImageStyleJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\image\\\\Functional\\\\Rest\\\\ImageStyleJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/image/tests/src/Functional/Rest/ImageStyleJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\image\\\\Functional\\\\Rest\\\\ImageStyleJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/image/tests/src/Functional/Rest/ImageStyleJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\image\\\\Functional\\\\Rest\\\\ImageStyleResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -19034,24 +18488,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/image/tests/src/Functional/Rest/ImageStyleXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\image\\\\Functional\\\\Rest\\\\ImageStyleXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/image/tests/src/Functional/Rest/ImageStyleXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\image\\\\Functional\\\\Rest\\\\ImageStyleXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/image/tests/src/Functional/Rest/ImageStyleXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\image\\\\Functional\\\\Rest\\\\ImageStyleXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/image/tests/src/Functional/Rest/ImageStyleXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\image\\\\FunctionalJavascript\\\\ImageFieldTestBase\\:\\:createImageField\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -20612,24 +20048,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ConfigurableLanguageJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ConfigurableLanguageJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ConfigurableLanguageJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ConfigurableLanguageResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -20660,24 +20078,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ConfigurableLanguageXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ConfigurableLanguageXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ConfigurableLanguageXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ContentLanguageSettingsJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -20702,24 +20102,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ContentLanguageSettingsJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ContentLanguageSettingsJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ContentLanguageSettingsJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ContentLanguageSettingsResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -20750,24 +20132,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ContentLanguageSettingsXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ContentLanguageSettingsXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Functional\\\\Rest\\\\ContentLanguageSettingsXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\language\\\\Kernel\\\\Views\\\\LanguageTestBase\\:\\:dataSet\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -21500,24 +20864,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\layout_builder\\\\Functional\\\\Rest\\\\LayoutBuilderEntityViewDisplayJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\layout_builder\\\\Functional\\\\Rest\\\\LayoutBuilderEntityViewDisplayJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\layout_builder\\\\Functional\\\\Rest\\\\LayoutBuilderEntityViewDisplayJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\layout_builder\\\\Functional\\\\Rest\\\\LayoutBuilderEntityViewDisplayXmlAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -21542,24 +20888,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\layout_builder\\\\Functional\\\\Rest\\\\LayoutBuilderEntityViewDisplayXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\layout_builder\\\\Functional\\\\Rest\\\\LayoutBuilderEntityViewDisplayXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\layout_builder\\\\Functional\\\\Rest\\\\LayoutBuilderEntityViewDisplayXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\layout_builder\\\\Functional\\\\Rest\\\\LayoutRestTestBase\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -23210,24 +22538,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -23264,24 +22574,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaTypeJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaTypeJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaTypeJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaTypeJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaTypeResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -23312,24 +22604,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaTypeXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaTypeXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaTypeXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaTypeXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaXmlAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -23354,24 +22628,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\Rest\\\\MediaXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/Rest/MediaXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\UrlResolverTest\\:\\:hijackProviderEndpoints\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -23960,24 +23216,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\menu_link_content\\\\Functional\\\\Rest\\\\MenuLinkContentJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\menu_link_content\\\\Functional\\\\Rest\\\\MenuLinkContentJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\menu_link_content\\\\Functional\\\\Rest\\\\MenuLinkContentJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\menu_link_content\\\\Functional\\\\Rest\\\\MenuLinkContentResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -24008,24 +23246,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\menu_link_content\\\\Functional\\\\Rest\\\\MenuLinkContentXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\menu_link_content\\\\Functional\\\\Rest\\\\MenuLinkContentXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\menu_link_content\\\\Functional\\\\Rest\\\\MenuLinkContentXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\menu_link_content\\\\Kernel\\\\MenuLinksTest\\:\\:createLinkHierarchy\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -26405,7 +25625,7 @@ $ignoreErrors[] = [ $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\node\\\\Form\\\\NodeForm\\:\\:save\\(\\) should return int but return statement is missing\\.$#', 'identifier' => 'return.missing', - 'count' => 2, + 'count' => 1, 'path' => __DIR__ . '/modules/node/src/Form/NodeForm.php', ]; $ignoreErrors[] = [ @@ -26505,12 +25725,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/node/src/NodeAccessControlHandlerInterface.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\node\\\\NodeGrantDatabaseStorage\\:\\:alterQuery\\(\\) should return int but return statement is missing\\.$#', - 'identifier' => 'return.missing', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/src/NodeGrantDatabaseStorage.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\node\\\\NodeGrantDatabaseStorage\\:\\:delete\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -27039,24 +26253,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -27087,24 +26283,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeTypeJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeTypeJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeTypeJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeTypeJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeTypeJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeTypeResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -27135,24 +26313,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeTypeXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeTypeXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeTypeXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeTypeXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeTypeXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeXmlAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -27177,24 +26337,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Rest\\\\NodeXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/node/tests/src/Functional/Rest/NodeXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\node\\\\Functional\\\\Views\\\\FrontPageTest\\:\\:assertCacheContext\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -27837,24 +26979,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/path_alias/tests/src/Functional/Rest/PathAliasJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\path_alias\\\\Functional\\\\Rest\\\\PathAliasJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/path_alias/tests/src/Functional/Rest/PathAliasJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\path_alias\\\\Functional\\\\Rest\\\\PathAliasJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/path_alias/tests/src/Functional/Rest/PathAliasJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\path_alias\\\\Functional\\\\Rest\\\\PathAliasJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/path_alias/tests/src/Functional/Rest/PathAliasJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\path_alias\\\\Functional\\\\Rest\\\\PathAliasResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -27885,24 +27009,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/path_alias/tests/src/Functional/Rest/PathAliasXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\path_alias\\\\Functional\\\\Rest\\\\PathAliasXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/path_alias/tests/src/Functional/Rest/PathAliasXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\path_alias\\\\Functional\\\\Rest\\\\PathAliasXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/path_alias/tests/src/Functional/Rest/PathAliasXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\path_alias\\\\Functional\\\\Rest\\\\PathAliasXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/path_alias/tests/src/Functional/Rest/PathAliasXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\path_alias\\\\Functional\\\\UrlAlterFunctionalTest\\:\\:assertPathAliasExists\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -28191,24 +27297,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\Functional\\\\Rest\\\\ResponsiveImageStyleJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\Functional\\\\Rest\\\\ResponsiveImageStyleJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\Functional\\\\Rest\\\\ResponsiveImageStyleJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\Functional\\\\Rest\\\\ResponsiveImageStyleResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -28239,24 +27327,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\Functional\\\\Rest\\\\ResponsiveImageStyleXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\Functional\\\\Rest\\\\ResponsiveImageStyleXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\Functional\\\\Rest\\\\ResponsiveImageStyleXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\FunctionalJavascript\\\\ResponsiveImageFieldUiTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -28551,24 +27621,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\EntityResource\\\\ModeratedNode\\\\ModeratedNodeJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\EntityResource\\\\ModeratedNode\\\\ModeratedNodeJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\EntityResource\\\\ModeratedNode\\\\ModeratedNodeJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\EntityResource\\\\ModeratedNode\\\\ModeratedNodeResourceTestBase\\:\\:addEntityTypeAndBundleToWorkflow\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -28605,24 +27657,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\EntityResource\\\\ModeratedNode\\\\ModeratedNodeXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\EntityResource\\\\ModeratedNode\\\\ModeratedNodeXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\EntityResource\\\\ModeratedNode\\\\ModeratedNodeXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\FileUploadResourceTestBase\\:\\:assertNormalizationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -28743,24 +27777,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\Rest\\\\RestResourceConfigJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\Rest\\\\RestResourceConfigJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\Rest\\\\RestResourceConfigJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\Rest\\\\RestResourceConfigResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -28791,24 +27807,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\Rest\\\\RestResourceConfigXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\Rest\\\\RestResourceConfigXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\Rest\\\\RestResourceConfigXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\rest\\\\Functional\\\\Views\\\\StyleSerializerEntityTest\\:\\:assertCacheContext\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -29295,24 +28293,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/search/tests/src/Functional/Rest/SearchPageJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\search\\\\Functional\\\\Rest\\\\SearchPageJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/search/tests/src/Functional/Rest/SearchPageJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\search\\\\Functional\\\\Rest\\\\SearchPageJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/search/tests/src/Functional/Rest/SearchPageJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\search\\\\Functional\\\\Rest\\\\SearchPageJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/search/tests/src/Functional/Rest/SearchPageJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\search\\\\Functional\\\\Rest\\\\SearchPageResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -29343,24 +28323,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/search/tests/src/Functional/Rest/SearchPageXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\search\\\\Functional\\\\Rest\\\\SearchPageXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/search/tests/src/Functional/Rest/SearchPageXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\search\\\\Functional\\\\Rest\\\\SearchPageXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/search/tests/src/Functional/Rest/SearchPageXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\search\\\\Functional\\\\Rest\\\\SearchPageXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/search/tests/src/Functional/Rest/SearchPageXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\search\\\\Functional\\\\SearchCommentCountToggleTest\\:\\:addDefaultCommentField\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -29985,24 +28947,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -30033,24 +28977,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutSetJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutSetJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutSetJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutSetResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -30081,24 +29007,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutSetXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutSetXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutSetXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutXmlAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -30123,24 +29031,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\Rest\\\\ShortcutXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\shortcut\\\\Functional\\\\ShortcutCacheTagsTest\\:\\:assertCacheContext\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -30867,6 +29757,24 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/src/Form/ThemeSettingsForm.php', ]; $ignoreErrors[] = [ + 'message' => '#^Variable \\$directories might not be defined\\.$#', + 'identifier' => 'variable.undefined', + 'count' => 1, + 'path' => __DIR__ . '/modules/system/src/Install/Requirements/SystemRequirements.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Variable \\$pdo_message might not be defined\\.$#', + 'identifier' => 'variable.undefined', + 'count' => 1, + 'path' => __DIR__ . '/modules/system/src/Install/Requirements/SystemRequirements.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Variable \\$site_path might not be defined\\.$#', + 'identifier' => 'variable.undefined', + 'count' => 1, + 'path' => __DIR__ . '/modules/system/src/Install/Requirements/SystemRequirements.php', +]; +$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\system\\\\PhpStorage\\\\MockPhpStorage\\:\\:getConfiguration\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -31035,24 +29943,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/src/TimeZoneResolver.php', ]; $ignoreErrors[] = [ - 'message' => '#^Variable \\$directories might not be defined\\.$#', - 'identifier' => 'variable.undefined', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/system.install', -]; -$ignoreErrors[] = [ - 'message' => '#^Variable \\$pdo_message might not be defined\\.$#', - 'identifier' => 'variable.undefined', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/system.install', -]; -$ignoreErrors[] = [ - 'message' => '#^Variable \\$site_path might not be defined\\.$#', - 'identifier' => 'variable.undefined', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/system.install', -]; -$ignoreErrors[] = [ 'message' => '#^Function system_authorized_batch_process\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -31811,24 +30701,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestBundleJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestBundleJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestBundleJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestBundleResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -31859,24 +30731,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestBundleXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestBundleXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestBundleXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestComputedFieldNormalizerTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -31919,24 +30773,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestJsonInternalPropertyNormalizerTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -31973,24 +30809,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestLabelJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestLabelJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestLabelJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestLabelResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -32021,24 +30839,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestLabelXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestLabelXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestLabelXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestMapFieldJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -32111,24 +30911,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\entity_test\\\\Functional\\\\Rest\\\\EntityTestXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\entity_test_bundle_class\\\\Entity\\\\EntityTestBundleClass\\:\\:postCreate\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -33671,12 +32453,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\theme_test\\\\EventSubscriber\\\\ThemeTestSubscriber\\:\\:onView\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\theme_test\\\\ThemeTestController\\:\\:generalSuggestionAlter\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -34231,24 +33007,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/ActionJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\ActionJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/ActionJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\ActionJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/ActionJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\ActionJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/ActionJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\ActionResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -34279,24 +33037,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/ActionXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\ActionXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/ActionXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\ActionXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/ActionXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\ActionXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/ActionXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\MenuJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -34321,24 +33061,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/MenuJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\MenuJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/MenuJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\MenuJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/MenuJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\MenuJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/MenuJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\MenuResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -34369,24 +33091,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/MenuXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\MenuXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/MenuXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\MenuXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/MenuXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Rest\\\\MenuXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Rest/MenuXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\SecurityAdvisories\\\\SecurityAdvisoryTest\\:\\:cronRun\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -35167,24 +33871,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/TermJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\TermJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/TermJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\TermJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/TermJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\TermJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/TermJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\TermResourceTestBase\\:\\:providerTestGetTermWithParent\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -35221,24 +33907,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/TermXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\TermXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/TermXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\TermXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/TermXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\TermXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/TermXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\VocabularyJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -35263,24 +33931,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/VocabularyJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\VocabularyJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/VocabularyJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\VocabularyJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/VocabularyJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\VocabularyJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/VocabularyJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\VocabularyResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -35311,24 +33961,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/VocabularyXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\VocabularyXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/VocabularyXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\VocabularyXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/VocabularyXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\Rest\\\\VocabularyXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/taxonomy/tests/src/Functional/Rest/VocabularyXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\taxonomy\\\\Functional\\\\TaxonomyTermContentModerationTest\\:\\:addEntityTypeAndBundleToWorkflow\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -36627,24 +35259,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/RoleJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\RoleJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/RoleJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\RoleJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/RoleJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\RoleJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/RoleJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\RoleResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -36675,24 +35289,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/RoleXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\RoleXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/RoleXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\RoleXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/RoleXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\RoleXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/RoleXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\UserJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -36717,24 +35313,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/UserJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\UserJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/UserJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\UserJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/UserJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\UserJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/UserJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\UserResourceTestBase\\:\\:assertRpcLogin\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -36771,24 +35349,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/UserXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\UserXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/UserXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\UserXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/UserXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\Rest\\\\UserXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/Rest/UserXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\UserAdminTest\\:\\:assertMailPattern\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -36897,12 +35457,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/user/tests/src/Functional/UserRegistrationRestTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\UserRegistrationRestTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/UserRegistrationRestTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\UserRegistrationRestTest\\:\\:assertMailPattern\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -36915,18 +35469,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/user/tests/src/Functional/UserRegistrationRestTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\UserRegistrationRestTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/UserRegistrationRestTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\UserRegistrationRestTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/user/tests/src/Functional/UserRegistrationRestTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\user\\\\Functional\\\\UserTranslationUITest\\:\\:getNewEntityValues\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -44133,24 +42675,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/views/tests/src/Functional/Rest/ViewJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\views\\\\Functional\\\\Rest\\\\ViewJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/views/tests/src/Functional/Rest/ViewJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\views\\\\Functional\\\\Rest\\\\ViewJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/views/tests/src/Functional/Rest/ViewJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\views\\\\Functional\\\\Rest\\\\ViewJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/views/tests/src/Functional/Rest/ViewJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\views\\\\Functional\\\\Rest\\\\ViewResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -44181,24 +42705,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/views/tests/src/Functional/Rest/ViewXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\views\\\\Functional\\\\Rest\\\\ViewXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/views/tests/src/Functional/Rest/ViewXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\views\\\\Functional\\\\Rest\\\\ViewXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/views/tests/src/Functional/Rest/ViewXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\views\\\\Functional\\\\Rest\\\\ViewXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/views/tests/src/Functional/Rest/ViewXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\views\\\\Functional\\\\SearchIntegrationTest\\:\\:cronRun\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -45633,24 +44139,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/workflows/tests/src/Functional/Rest/WorkflowJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workflows\\\\Functional\\\\Rest\\\\WorkflowJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workflows/tests/src/Functional/Rest/WorkflowJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workflows\\\\Functional\\\\Rest\\\\WorkflowJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workflows/tests/src/Functional/Rest/WorkflowJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workflows\\\\Functional\\\\Rest\\\\WorkflowJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workflows/tests/src/Functional/Rest/WorkflowJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\workflows\\\\Functional\\\\Rest\\\\WorkflowResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -45681,24 +44169,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/workflows/tests/src/Functional/Rest/WorkflowXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workflows\\\\Functional\\\\Rest\\\\WorkflowXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workflows/tests/src/Functional/Rest/WorkflowXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workflows\\\\Functional\\\\Rest\\\\WorkflowXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workflows/tests/src/Functional/Rest/WorkflowXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workflows\\\\Functional\\\\Rest\\\\WorkflowXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workflows/tests/src/Functional/Rest/WorkflowXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\workflows\\\\Unit\\\\WorkflowStateTransitionOperationsAccessCheckTest\\:\\:accessTestCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -45885,6 +44355,18 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/workspaces/src/Form/WorkspaceSwitcherForm.php', ]; $ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\workspaces\\\\Negotiator\\\\QueryParameterWorkspaceNegotiator\\:\\:setActiveWorkspace\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/workspaces/src/Negotiator/QueryParameterWorkspaceNegotiator.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\workspaces\\\\Negotiator\\\\QueryParameterWorkspaceNegotiator\\:\\:unsetActiveWorkspace\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/workspaces/src/Negotiator/QueryParameterWorkspaceNegotiator.php', +]; +$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\workspaces\\\\Negotiator\\\\SessionWorkspaceNegotiator\\:\\:getActiveWorkspace\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -46149,24 +44631,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/workspaces/tests/src/Functional/Rest/WorkspaceJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Functional\\\\Rest\\\\WorkspaceJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Functional/Rest/WorkspaceJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Functional\\\\Rest\\\\WorkspaceJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Functional/Rest/WorkspaceJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Functional\\\\Rest\\\\WorkspaceJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Functional/Rest/WorkspaceJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Functional\\\\Rest\\\\WorkspaceResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -46197,24 +44661,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/workspaces/tests/src/Functional/Rest/WorkspaceXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Functional\\\\Rest\\\\WorkspaceXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Functional/Rest/WorkspaceXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Functional\\\\Rest\\\\WorkspaceXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Functional/Rest/WorkspaceXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Functional\\\\Rest\\\\WorkspaceXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Functional/Rest/WorkspaceXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Functional\\\\WorkspaceBypassTest\\:\\:isLabelInContentOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -46749,6 +45195,30 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceEntityRepositoryTest.php', ]; $ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:assertWorkspaceAssociation\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:createWorkspaceHierarchy\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:initializeWorkspacesModule\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceFormPersistenceTest\\:\\:switchToWorkspace\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php', +]; +$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Kernel\\\\WorkspaceInformationTest\\:\\:assertWorkspaceAssociation\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47433,24 +45903,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/BaseFieldOverrideJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\BaseFieldOverrideJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/BaseFieldOverrideJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\BaseFieldOverrideJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/BaseFieldOverrideJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\BaseFieldOverrideJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/BaseFieldOverrideJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\BaseFieldOverrideResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47481,24 +45933,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/BaseFieldOverrideXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\BaseFieldOverrideXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/BaseFieldOverrideXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\BaseFieldOverrideXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/BaseFieldOverrideXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\BaseFieldOverrideXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/BaseFieldOverrideXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\DateFormatJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47523,24 +45957,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/DateFormatJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\DateFormatJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/DateFormatJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\DateFormatJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/DateFormatJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\DateFormatJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/DateFormatJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\DateFormatResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47571,24 +45987,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/DateFormatXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\DateFormatXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/DateFormatXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\DateFormatXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/DateFormatXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\DateFormatXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/DateFormatXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormDisplayJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47613,24 +46011,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormDisplayJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormDisplayJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormDisplayJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormDisplayJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormDisplayJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormDisplayJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormDisplayJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormDisplayResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47661,24 +46041,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormDisplayXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormDisplayXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormDisplayXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormDisplayXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormDisplayXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormDisplayXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormDisplayXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormModeJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47703,24 +46065,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormModeJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormModeJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormModeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormModeJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormModeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormModeJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormModeJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormModeResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47751,24 +46095,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormModeXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormModeXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormModeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormModeXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormModeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityFormModeXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityFormModeXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewDisplayJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47793,24 +46119,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewDisplayJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewDisplayJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewDisplayJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewDisplayJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewDisplayJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewDisplayJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewDisplayJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewDisplayResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47841,24 +46149,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewDisplayXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewDisplayXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewDisplayXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewDisplayXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewDisplayXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewDisplayXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewDisplayXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewModeJsonAnonTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47883,24 +46173,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewModeJsonBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewModeJsonCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewModeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewModeJsonCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewModeJsonCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewModeJsonCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewModeJsonCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewModeResourceTestBase\\:\\:setUpAuthorization\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47931,24 +46203,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewModeXmlBasicAuthTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewModeXmlCookieTest\\:\\:assertAuthenticationEdgeCases\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewModeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewModeXmlCookieTest\\:\\:assertResponseWhenMissingAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewModeXmlCookieTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Rest\\\\EntityViewModeXmlCookieTest\\:\\:initAuthentication\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalTests/Rest/EntityViewModeXmlCookieTest.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalTests\\\\Routing\\\\PathEncodedTest\\:\\:assertPathAliasExists\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index 075ea1b1e4ae..8fe671891f7b 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -120,14 +120,12 @@ Cache - Kristiaan Van den Eynde 'kristiaanvandeneynde' https://www.drupal.org/u/kristiaanvandeneynde CKEditor 5 -- Lauri Timmanee 'lauriii' https://www.drupal.org/u/lauriii - Wim Leers 'Wim Leers' https://www.drupal.org/u/wim-leers - Ben Mullins 'bnjmnm' https://www.drupal.org/u/bnjmnm Claro - Cristina Chumillas 'ckrina' https://www.drupal.org/u/ckrina - Sascha Eggenberger 'saschaeggi' https://www.drupal.org/u/saschaeggi -- Lauri Timmanee 'lauriii' https://www.drupal.org/u/lauriii - Ben Mullins 'bnjmnm' https://www.drupal.org/u/bnjmnm Comment @@ -154,7 +152,7 @@ Contact - Andrey Postnikov 'andypost' https://www.drupal.org/u/andypost Content Moderation -- Sam Becker 'Sam152' https://www.drupal.org/u/sam152 +- ? Content Translation - ? @@ -224,6 +222,7 @@ Field UI - Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu File +- Mohit Aghera 'mohit_aghera' https://www.drupal.org/u/mohit_aghera - Kim Pepper 'kim.pepper' https://www.drupal.org/u/kimpepper Filter @@ -260,7 +259,6 @@ JavaScript JSON:API - Mateu Aguiló Bosch 'e0ipso' https://www.drupal.org/u/e0ipso - Björn Brala 'bbrala' https://www.drupal.org/u/bbrala -- Gabe Sullice 'gabesullice' https://www.drupal.org/u/gabesullice - Wim Leers 'Wim Leers' https://www.drupal.org/u/wim-leers Language @@ -412,7 +410,6 @@ Text Field Theme API - Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia - Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx -- Joël Pittet 'joelpittet' https://www.drupal.org/u/joelpittet - Lauri Timmanee 'lauriii' https://www.drupal.org/u/lauriii Single-Directory Components @@ -454,7 +451,7 @@ Views - Len Swaneveld 'Lendude' https://www.drupal.org/u/lendude Workflows -- Sam Becker 'Sam152' https://www.drupal.org/u/sam152 +- ? Workspaces - Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu @@ -486,7 +483,6 @@ Testing Usability - Cristina Chumillas 'ckrina' https://www.drupal.org/u/ckrina -- Roy Scholten 'yoroy' https://www.drupal.org/u/yoroy - Bojhan Somers 'Bojhan' https://www.drupal.org/u/bojhan diff --git a/core/assets/scaffold/files/default.settings.php b/core/assets/scaffold/files/default.settings.php index d4ba8a91829a..a1b3eba99ca5 100644 --- a/core/assets/scaffold/files/default.settings.php +++ b/core/assets/scaffold/files/default.settings.php @@ -602,6 +602,18 @@ $settings['update_free_access'] = FALSE; # $settings['file_temp_path'] = '/tmp'; /** + * Automatically create an Apache HTTP .htaccess file in writable directories. + * + * This setting can be disabled if you are not using Apache HTTP server, or if + * you have a web server configuration that protects the various writable file + * directories. + * + * @see \Drupal\Component\FileSecurity\FileSecurity::writeHtaccess() + * @see https://www.drupal.org/docs/administering-a-drupal-site/security-in-drupal/securing-file-permissions-and-ownership + */ +# $settings['auto_create_htaccess'] = FALSE; + +/** * Session write interval: * * Set the minimum interval between each session write to database. diff --git a/core/composer.json b/core/composer.json index ab8f93e49ba9..63ebe5803f1f 100644 --- a/core/composer.json +++ b/core/composer.json @@ -34,6 +34,7 @@ "symfony/process": "^7.3", "symfony/polyfill-iconv": "^1.32", "symfony/polyfill-php84": "^1.32", + "symfony/polyfill-php85": "^1.32", "symfony/yaml": "^7.3", "revolt/event-loop": "^1.0", "twig/twig": "^3.21.0", @@ -56,7 +57,8 @@ "conflict": { "drupal/automatic_updates": "<4", "drupal/project_browser": "<2.1", - "drush/drush": "<12.4.3" + "drush/drush": "<12.4.3", + "dealerdirect/phpcodesniffer-composer-installer": "1.1.0" }, "replace": { "drupal/core-annotation": "self.version", diff --git a/core/core.api.php b/core/core.api.php index 23c0ef741c43..33c04722da42 100644 --- a/core/core.api.php +++ b/core/core.api.php @@ -2707,9 +2707,10 @@ function hook_validation_constraint_alter(array &$definitions) { * the proxy service, and not on all the dependencies of the lazy service. * * To define a service as lazy, add "lazy: true" to the service definition, and - * use the "core/scripts/generate-proxy.sh" script to generate the proxy class. + * use the "core/scripts/generate-proxy-class.php" script to generate the proxy + * class. * - * @see core/scripts/generate-proxy.sh + * @see core/scripts/generate-proxy-class.php */ /** diff --git a/core/core.services.yml b/core/core.services.yml index f1df7c0200ea..9f7c1a2d11cd 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -854,6 +854,9 @@ services: class: Drupal\Core\Menu\ContextualLinkManager arguments: ['@controller_resolver', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user', '@request_stack'] Drupal\Core\Menu\ContextualLinkManagerInterface: '@plugin.manager.menu.contextual_link' + Drupal\Core\Menu\MenuPreprocess: + class: Drupal\Core\Menu\MenuPreprocess + autowire: true plugin.manager.display_variant: class: Drupal\Core\Display\VariantManager parent: default_plugin_manager @@ -1535,6 +1538,9 @@ services: tags: - { name: service_collector, tag: breadcrumb_builder, call: addBuilder } Drupal\Core\Breadcrumb\ChainBreadcrumbBuilderInterface: '@breadcrumb' + Drupal\Core\Breadcrumb\BreadcrumbPreprocess: + class: Drupal\Core\Breadcrumb\BreadcrumbPreprocess + autowire: true token: class: Drupal\Core\Utility\Token arguments: ['@module_handler', '@cache.default', '@language_manager', '@cache_tags.invalidator', '@renderer'] @@ -1575,6 +1581,9 @@ services: Drupal\Core\Theme\ThemePreprocess: class: Drupal\Core\Theme\ThemePreprocess autowire: true + Drupal\Core\Theme\ImagePreprocess: + class: Drupal\Core\Theme\ImagePreprocess + autowire: true Drupal\Core\Datetime\DatePreprocess: class: Drupal\Core\Datetime\DatePreprocess autowire: true @@ -1797,7 +1806,7 @@ services: alias: plugin.manager.element_info file.htaccess_writer: class: Drupal\Core\File\HtaccessWriter - arguments: ['@logger.channel.security', '@stream_wrapper_manager'] + arguments: ['@logger.channel.security', '@stream_wrapper_manager', '@settings'] Drupal\Core\File\HtaccessWriterInterface: '@file.htaccess_writer' file.mime_type.guesser: class: Drupal\Core\File\MimeType\MimeTypeGuesser @@ -1902,6 +1911,9 @@ services: class: Drupal\Core\Pager\PagerParameters arguments: ['@request_stack'] Drupal\Core\Pager\PagerParametersInterface: '@pager.parameters' + Drupal\Core\Pager\PagerPreprocess: + class: Drupal\Core\Pager\PagerPreprocess + autowire: true Drupal\Core\Theme\Component\SchemaCompatibilityChecker: {} Drupal\Core\Theme\Component\ComponentValidator: calls: diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index cde18fc831e5..35fa223c31de 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -389,7 +389,7 @@ function install_begin_request($class_loader, &$install_state): void { } $install_state['database_verified'] = install_verify_database_settings($site_path); // A valid settings.php has database settings and a hash_salt value. Other - // settings will be checked by system_requirements(). + // settings will be checked by \Drupal\system\Install\SystemRequirements. $install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified'] && (bool) Settings::get('hash_salt', FALSE); if ($install_state['settings_verified']) { diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 5a53d94962b5..587f9e70c200 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -10,18 +10,16 @@ use Drupal\Core\Datetime\DatePreprocess; use Drupal\Core\Field\FieldPreprocess; +use Drupal\Core\Pager\PagerPreprocess; +use Drupal\Core\Breadcrumb\BreadcrumbPreprocess; +use Drupal\Core\Menu\MenuPreprocess; +use Drupal\Core\Theme\ImagePreprocess; use Drupal\Core\Theme\ThemePreprocess; -use Drupal\Core\Url; -use Drupal\Component\Utility\Html; use Drupal\Core\Config\Config; use Drupal\Core\Config\StorageException; -use Drupal\Core\Template\Attribute; use Drupal\Core\Template\AttributeHelper; use Drupal\Core\Theme\ThemeCommonElements; use Drupal\Core\Theme\ThemeSettings; -use Drupal\Core\Render\Element; -use Drupal\Core\Utility\TableSort; -use Drupal\Core\Installer\InstallerKernel; /** * @defgroup content_flags Content markers @@ -484,49 +482,15 @@ function template_preprocess_links(&$variables): void { * - sizes: The sizes attribute for viewport-based selection of images. * phpcs:ignore * - http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:viewport-based-selection-2 + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_image(&$variables): void { - /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ - $file_url_generator = \Drupal::service('file_url_generator'); - - if (!empty($variables['uri'])) { - $variables['attributes']['src'] = $file_url_generator->generateString($variables['uri']); - } - // Generate a srcset attribute conforming to the spec at - // https://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-srcset - if (!empty($variables['srcset'])) { - $srcset = []; - foreach ($variables['srcset'] as $src) { - // URI is mandatory. - $source = $file_url_generator->generateString($src['uri']); - if (isset($src['width']) && !empty($src['width'])) { - $source .= ' ' . $src['width']; - } - elseif (isset($src['multiplier']) && !empty($src['multiplier'])) { - $source .= ' ' . $src['multiplier']; - } - $srcset[] = $source; - } - $variables['attributes']['srcset'] = implode(', ', $srcset); - } - - foreach (['width', 'height', 'alt', 'title', 'sizes'] as $key) { - if (isset($variables[$key])) { - // If the property has already been defined in the attributes, - // do not override, including NULL. - if (AttributeHelper::attributeExists($key, $variables['attributes'])) { - continue; - } - $variables['attributes'][$key] = $variables[$key]; - } - } - - // Without dimensions specified, layout shifts can occur, - // which are more noticeable on pages that take some time to load. - // As a result, only mark images as lazy load that have dimensions. - if (isset($variables['width'], $variables['height']) && !isset($variables['attributes']['loading'])) { - $variables['attributes']['loading'] = 'lazy'; - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ImagePreprocess::class)->preprocessImage($variables); } /** @@ -620,167 +584,15 @@ function template_preprocess_image(&$variables): void { * - sticky: Use a "sticky" table header. * - empty: The message to display in an extra row if table does not have any * rows. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_table(&$variables): void { - // Format the table columns: - if (!empty($variables['colgroups'])) { - foreach ($variables['colgroups'] as &$colgroup) { - // Check if we're dealing with a simple or complex column - if (isset($colgroup['data'])) { - $cols = $colgroup['data']; - unset($colgroup['data']); - $colgroup_attributes = $colgroup; - } - else { - $cols = $colgroup; - $colgroup_attributes = []; - } - $colgroup = []; - $colgroup['attributes'] = new Attribute($colgroup_attributes); - $colgroup['cols'] = []; - - // Build columns. - if (is_array($cols) && !empty($cols)) { - foreach ($cols as $col_key => $col) { - $colgroup['cols'][$col_key]['attributes'] = new Attribute($col); - } - } - } - } - - // Build an associative array of responsive classes keyed by column. - $responsive_classes = []; - - // Format the table header: - $ts = []; - $header_columns = 0; - if (!empty($variables['header'])) { - $ts = TableSort::getContextFromRequest($variables['header'], \Drupal::request()); - - // Use a separate index with responsive classes as headers - // may be associative. - $responsive_index = -1; - foreach ($variables['header'] as $col_key => $cell) { - // Increase the responsive index. - $responsive_index++; - - if (!is_array($cell)) { - $header_columns++; - $cell_content = $cell; - $cell_attributes = new Attribute(); - $is_header = TRUE; - } - else { - if (isset($cell['colspan'])) { - $header_columns += $cell['colspan']; - } - else { - $header_columns++; - } - $cell_content = ''; - if (isset($cell['data'])) { - $cell_content = $cell['data']; - unset($cell['data']); - } - // Flag the cell as a header or not and remove the flag. - $is_header = $cell['header'] ?? TRUE; - unset($cell['header']); - - // Track responsive classes for each column as needed. Only the header - // cells for a column are marked up with the responsive classes by a - // module developer or themer. The responsive classes on the header - // cells must be transferred to the content cells. - if (!empty($cell['class']) && is_array($cell['class'])) { - if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) { - $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM; - } - elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) { - $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW; - } - } - - TableSort::header($cell_content, $cell, $variables['header'], $ts); - - // TableSort::header() removes the 'sort', 'initial_click_sort' and - // 'field' keys. - $cell_attributes = new Attribute($cell); - } - $variables['header'][$col_key] = []; - $variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td'; - $variables['header'][$col_key]['attributes'] = $cell_attributes; - $variables['header'][$col_key]['content'] = $cell_content; - } - } - $variables['header_columns'] = $header_columns; - - // Rows and footer have the same structure. - $sections = ['rows' , 'footer']; - foreach ($sections as $section) { - if (!empty($variables[$section])) { - foreach ($variables[$section] as $row_key => $row) { - $cells = $row; - $row_attributes = []; - - // Check if we're dealing with a simple or complex row - if (isset($row['data'])) { - $cells = $row['data']; - $variables['no_striping'] = $row['no_striping'] ?? FALSE; - - // Set the attributes array and exclude 'data' and 'no_striping'. - $row_attributes = $row; - unset($row_attributes['data']); - unset($row_attributes['no_striping']); - } - - // Build row. - $variables[$section][$row_key] = []; - $variables[$section][$row_key]['attributes'] = new Attribute($row_attributes); - $variables[$section][$row_key]['cells'] = []; - if (!empty($cells)) { - // Reset the responsive index. - $responsive_index = -1; - foreach ($cells as $col_key => $cell) { - // Increase the responsive index. - $responsive_index++; - - if (!is_array($cell)) { - $cell_content = $cell; - $cell_attributes = []; - $is_header = FALSE; - } - else { - $cell_content = ''; - if (isset($cell['data'])) { - $cell_content = $cell['data']; - unset($cell['data']); - } - - // Flag the cell as a header or not and remove the flag. - $is_header = !empty($cell['header']); - unset($cell['header']); - - $cell_attributes = $cell; - } - // Active table sort information. - if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) { - $variables[$section][$row_key]['cells'][$col_key]['active_table_sort'] = TRUE; - } - // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM - // class from header to cell as needed. - if (isset($responsive_classes[$responsive_index])) { - $cell_attributes['class'][] = $responsive_classes[$responsive_index]; - } - $variables[$section][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td'; - $variables[$section][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes); - $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content; - } - } - } - } - } - if (empty($variables['no_striping'])) { - $variables['attributes']['data-striping'] = 1; - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessTable($variables); } /** @@ -800,56 +612,14 @@ function template_preprocess_table(&$variables): void { * - list_type: The type of list to return (e.g. "ul", "ol"). * - wrapper_attributes: HTML attributes to be applied to the list wrapper. * - * @see https://www.drupal.org/node/1842756 + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_item_list(&$variables): void { - $variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']); - $variables['#attached']['library'][] = 'core/drupal.item-list'; - foreach ($variables['items'] as &$item) { - $attributes = []; - // If the item value is an array, then it is a render array. - if (is_array($item)) { - // List items support attributes via the '#wrapper_attributes' property. - if (isset($item['#wrapper_attributes'])) { - $attributes = $item['#wrapper_attributes']; - } - // Determine whether there are any child elements in the item that are not - // fully-specified render arrays. If there are any, then the child - // elements present nested lists and we automatically inherit the render - // array properties of the current list to them. - foreach (Element::children($item) as $key) { - $child = &$item[$key]; - // If this child element does not specify how it can be rendered, then - // we need to inherit the render properties of the current list. - if (!isset($child['#type']) && !isset($child['#theme']) && !isset($child['#markup'])) { - // Since item-list.html.twig supports both strings and render arrays - // as items, the items of the nested list may have been specified as - // the child elements of the nested list, instead of #items. For - // convenience, we automatically move them into #items. - if (!isset($child['#items'])) { - // This is the same condition as in - // \Drupal\Core\Render\Element::children(), which cannot be used - // here, since it triggers an error on string values. - foreach ($child as $child_key => $child_value) { - if (is_int($child_key) || $child_key === '' || $child_key[0] !== '#') { - $child['#items'][$child_key] = $child_value; - unset($child[$child_key]); - } - } - } - // Lastly, inherit the original theme variables of the current list. - $child['#theme'] = $variables['theme_hook_original']; - $child['#list_type'] = $variables['list_type']; - } - } - } - - // Set the item's value and attributes for the template. - $item = [ - 'value' => $item, - 'attributes' => new Attribute($attributes), - ]; - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessItemList($variables); } /** @@ -883,27 +653,15 @@ function template_preprocess_container(&$variables): void { * It's the caller's responsibility to ensure this array's items contain no * dangerous HTML such as <script> tags. * - active: The key for the currently active maintenance task. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_maintenance_task_list(&$variables): void { - $items = $variables['items']; - $active = $variables['active']; - - $done = isset($items[$active]) || $active == NULL; - foreach ($items as $k => $item) { - $variables['tasks'][$k]['item'] = $item; - $variables['tasks'][$k]['attributes'] = new Attribute(); - if ($active == $k) { - $variables['tasks'][$k]['attributes']->addClass('is-active'); - $variables['tasks'][$k]['status'] = t('active'); - $done = FALSE; - } - else { - if ($done) { - $variables['tasks'][$k]['attributes']->addClass('done'); - $variables['tasks'][$k]['status'] = t('done'); - } - } - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessMaintenanceTaskList($variables); } /** @@ -1053,9 +811,15 @@ function theme_get_suggestions($args, $base, $delimiter = '__'): array { * Prepares variables for tablesort indicators. * * Default template: tablesort-indicator.html.twig. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_tablesort_indicator(&$variables): void { - $variables['#attached']['library'][] = 'core/drupal.tablesort'; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessTablesortIndicator($variables); } /** @@ -1067,25 +831,14 @@ function template_preprocess_tablesort_indicator(&$variables): void { * An associative array containing: * - content - An array of page content. * - * @see system_page_attachments() + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_maintenance_page(&$variables): void { - // @todo Rename the templates to page--maintenance + page--install. - \Drupal::service(ThemePreprocess::class)->preprocessPage($variables); - - // @see system_page_attachments() - $variables['#attached']['library'][] = 'system/maintenance'; - - // Maintenance page and install page need branding info in variables because - // there is no blocks. - $site_config = \Drupal::config('system.site'); - $variables['logo'] = theme_get_setting('logo.url'); - $variables['site_name'] = $site_config->get('name'); - $variables['site_slogan'] = $site_config->get('slogan'); - - // Maintenance page and install page need page title in variable because there - // are no blocks. - $variables['title'] = $variables['page']['#title']; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessMaintenancePage($variables); } /** @@ -1097,21 +850,14 @@ function template_preprocess_maintenance_page(&$variables): void { * An associative array containing: * - content - An array of page content. * - * @see template_preprocess_maintenance_page() + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_install_page(&$variables): void { - $installer_active_task = NULL; - if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE === 'install' && InstallerKernel::installationAttempted()) { - $installer_active_task = $GLOBALS['install_state']['active_task']; - } - - template_preprocess_maintenance_page($variables); - - // Override the site name that is displayed on the page, since Drupal is - // still in the process of being installed. - $distribution_name = drupal_install_profile_distribution_name(); - $variables['site_name'] = $distribution_name; - $variables['site_version'] = $installer_active_task ? drupal_install_profile_distribution_version() : ''; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessInstallPage($variables); } /** @@ -1126,11 +872,15 @@ function template_preprocess_install_page(&$variables): void { * @param array $variables * An associative array containing: * - elements: An associative array containing properties of the region. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_region(&$variables): void { - // Create the $content variable that templates expect. - $variables['content'] = $variables['elements']['#children']; - $variables['region'] = $variables['elements']['#region']; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(ThemePreprocess::class)->preprocessRegion($variables); } /** @@ -1183,13 +933,15 @@ function template_preprocess_field_multiple_value_form(&$variables): void { * @param array $variables * An associative array containing: * - links: A list of \Drupal\Core\Link objects which should be rendered. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_breadcrumb(&$variables): void { - $variables['breadcrumb'] = []; - /** @var \Drupal\Core\Link $link */ - foreach ($variables['links'] as $key => $link) { - $variables['breadcrumb'][$key] = ['text' => $link->getText(), 'url' => $link->getUrl()->toString()]; - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(BreadcrumbPreprocess::class)->preprocessBreadcrumb($variables); } /** @@ -1212,137 +964,15 @@ function template_preprocess_breadcrumb(&$variables): void { * to the pager links. * - #route_parameters: An associative array of the route parameters. * - #quantity: The number of pages in the list. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_pager(&$variables): void { - $element = $variables['pager']['#element']; - $parameters = $variables['pager']['#parameters']; - $quantity = empty($variables['pager']['#quantity']) ? 0 : $variables['pager']['#quantity']; - $route_name = $variables['pager']['#route_name']; - $route_parameters = $variables['pager']['#route_parameters'] ?? []; - - /** @var \Drupal\Core\Pager\PagerManagerInterface $pager_manager */ - $pager_manager = \Drupal::service('pager.manager'); - - $pager = $pager_manager->getPager($element); - - // Nothing to do if there is no pager. - if (!isset($pager)) { - return; - } - - $pager_max = $pager->getTotalPages(); - - // Nothing to do if there is only one page. - if ($pager_max <= 1) { - return; - } - - $tags = $variables['pager']['#tags']; - - // Calculate various markers within this pager piece: - // Middle is used to "center" pages around the current page. - $pager_middle = ceil($quantity / 2); - $current_page = $pager->getCurrentPage(); - // The current pager is the page we are currently paged to. - $pager_current = $current_page + 1; - // The first pager is the first page listed by this pager piece (re quantity). - $pager_first = $pager_current - $pager_middle + 1; - // The last is the last page listed by this pager piece (re quantity). - $pager_last = $pager_current + $quantity - $pager_middle; - // End of marker calculations. - - // Prepare for generation loop. - $i = $pager_first; - if ($pager_last > $pager_max) { - // Adjust "center" if at end of query. - $i = $i + ($pager_max - $pager_last); - $pager_last = $pager_max; - } - if ($i <= 0) { - // Adjust "center" if at start of query. - $pager_last = $pager_last + (1 - $i); - $i = 1; - } - // End of generation loop preparation. - - // Create the "first" and "previous" links if we are not on the first page. - if ($current_page > 0) { - $items['first'] = []; - $items['first']['attributes'] = new Attribute(); - $options = [ - 'query' => $pager_manager->getUpdatedParameters($parameters, $element, 0), - ]; - $items['first']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); - if (isset($tags[0])) { - $items['first']['text'] = $tags[0]; - } - - $items['previous'] = []; - $items['previous']['attributes'] = new Attribute(); - $options = [ - 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current_page - 1), - ]; - $items['previous']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); - if (isset($tags[1])) { - $items['previous']['text'] = $tags[1]; - } - } - - // Add an ellipsis if there are further previous pages. - if ($i > 1) { - $variables['ellipses']['previous'] = TRUE; - } - // Now generate the actual pager piece. - for (; $i <= $pager_last && $i <= $pager_max; $i++) { - $options = [ - 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $i - 1), - ]; - $items['pages'][$i]['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); - $items['pages'][$i]['attributes'] = new Attribute(); - if ($i == $pager_current) { - $variables['current'] = $i; - $items['pages'][$i]['attributes']->setAttribute('aria-current', 'page'); - } - } - // Add an ellipsis if there are further next pages. - if ($i < $pager_max + 1) { - $variables['ellipses']['next'] = TRUE; - } - - // Create the "next" and "last" links if we are not on the last page. - if ($current_page < ($pager_max - 1)) { - $items['next'] = []; - $items['next']['attributes'] = new Attribute(); - $options = [ - 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current_page + 1), - ]; - $items['next']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); - if (isset($tags[3])) { - $items['next']['text'] = $tags[3]; - } - - $items['last'] = []; - $items['last']['attributes'] = new Attribute(); - $options = [ - 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $pager_max - 1), - ]; - $items['last']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); - if (isset($tags[4])) { - $items['last']['text'] = $tags[4]; - } - } - - $variables['items'] = $items; - $variables['heading_id'] = Html::getUniqueId('pagination-heading'); - $variables['pagination_heading_level'] = $variables['pager']['#pagination_heading_level'] ?? 'h4'; - if (!preg_match('/^h[1-6]$/', $variables['pagination_heading_level'])) { - $variables['pagination_heading_level'] = 'h4'; - } - - // The rendered link needs to play well with any other query parameter used - // on the page, like exposed filters, so for the cacheability all query - // parameters matter. - $variables['#cache']['contexts'][] = 'url.query_args'; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(PagerPreprocess::class)->preprocessPager($variables); } /** @@ -1356,26 +986,15 @@ function template_preprocess_pager(&$variables): void { * - #link: A menu link array with 'title', 'url', and (optionally) * 'localized_options' keys. * - #active: A boolean indicating whether the local task is active. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_menu_local_task(&$variables): void { - $link = $variables['element']['#link']; - $link += [ - 'localized_options' => [], - ]; - $link_text = $link['title']; - - if (!empty($variables['element']['#active'])) { - $variables['is_active'] = TRUE; - } - - $link['localized_options']['set_active_class'] = TRUE; - - $variables['link'] = [ - '#type' => 'link', - '#title' => $link_text, - '#url' => $link['url'], - '#options' => $link['localized_options'], - ]; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(MenuPreprocess::class)->preprocessMenuLocalTask($variables); } /** @@ -1388,22 +1007,15 @@ function template_preprocess_menu_local_task(&$variables): void { * - element: A render element containing: * - #link: A menu link array with 'title', 'url', and (optionally) * 'localized_options' keys. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial + * template_preprocess functions are registered directly in hook_theme(). + * + * @see https://www.drupal.org/node/3504125 */ function template_preprocess_menu_local_action(&$variables): void { - $link = $variables['element']['#link']; - $link += [ - 'localized_options' => [], - ]; - $link['localized_options']['attributes']['class'][] = 'button'; - $link['localized_options']['attributes']['class'][] = 'button-action'; - $link['localized_options']['set_active_class'] = TRUE; - - $variables['link'] = [ - '#type' => 'link', - '#title' => $link['title'], - '#options' => $link['localized_options'], - '#url' => $link['url'], - ]; + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Initial template_preprocess functions are registered directly in hook_theme(). See https://www.drupal.org/node/3504125', E_USER_DEPRECATED); + \Drupal::service(MenuPreprocess::class)->preprocessMenuLocalAction($variables); } /** diff --git a/core/lib/Drupal/Component/Gettext/PoItem.php b/core/lib/Drupal/Component/Gettext/PoItem.php index 7cc32568ad23..89dee6cc0858 100644 --- a/core/lib/Drupal/Component/Gettext/PoItem.php +++ b/core/lib/Drupal/Component/Gettext/PoItem.php @@ -271,7 +271,7 @@ class PoItem { private function formatSingular() { $output = ''; $output .= 'msgid ' . $this->formatString($this->source); - $output .= 'msgstr ' . (isset($this->translation) ? $this->formatString($this->translation) : '""'); + $output .= 'msgstr ' . (isset($this->translation) ? $this->formatString($this->translation) : '""' . "\n"); return $output; } diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbPreprocess.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbPreprocess.php new file mode 100644 index 000000000000..4de3fde48a45 --- /dev/null +++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbPreprocess.php @@ -0,0 +1,32 @@ +<?php + +namespace Drupal\Core\Breadcrumb; + +/** + * Breadcrumb theme preprocess. + * + * @internal + */ +class BreadcrumbPreprocess { + + /** + * Prepares variables for breadcrumb templates. + * + * Default template: breadcrumb.html.twig. + * + * @param array $variables + * An associative array containing: + * - links: A list of \Drupal\Core\Link objects which should be rendered. + */ + public function preprocessBreadcrumb(array &$variables): void { + $variables['breadcrumb'] = []; + /** @var \Drupal\Core\Link $link */ + foreach ($variables['links'] as $key => $link) { + $variables['breadcrumb'][$key] = [ + 'text' => $link->getText(), + 'url' => $link->getUrl()->toString(), + ]; + } + } + +} diff --git a/core/lib/Drupal/Core/Command/GenerateProxyClassApplication.php b/core/lib/Drupal/Core/Command/GenerateProxyClassApplication.php index 2a36dde2845c..88dca58edcf3 100644 --- a/core/lib/Drupal/Core/Command/GenerateProxyClassApplication.php +++ b/core/lib/Drupal/Core/Command/GenerateProxyClassApplication.php @@ -11,7 +11,7 @@ use Symfony\Component\Console\Input\InputInterface; * Provides a console command to generate proxy classes. * * @see lazy_services - * @see core/scripts/generate-proxy.sh + * @see core/scripts/generate-proxy-class.php */ class GenerateProxyClassApplication extends Application { diff --git a/core/lib/Drupal/Core/Command/GenerateProxyClassCommand.php b/core/lib/Drupal/Core/Command/GenerateProxyClassCommand.php index f39932237075..e6b9cd02c863 100644 --- a/core/lib/Drupal/Core/Command/GenerateProxyClassCommand.php +++ b/core/lib/Drupal/Core/Command/GenerateProxyClassCommand.php @@ -12,7 +12,7 @@ use Symfony\Component\Console\Output\OutputInterface; * Provides a console command to generate proxy classes. * * @see lazy_services - * @see core/scripts/generate-proxy.sh + * @see core/scripts/generate-proxy-class.php */ class GenerateProxyClassCommand extends Command { diff --git a/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php b/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php index 617699bda15f..a0824c65f17f 100644 --- a/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php +++ b/core/lib/Drupal/Core/DependencyInjection/AutowireTrait.php @@ -34,6 +34,10 @@ trait AutowireTrait { } if (!$container->has($service)) { + if ($parameter->allowsNull()) { + $args[] = NULL; + continue; + } throw new AutowiringFailedException($service, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s::_construct()", you should configure its value explicitly.', $service, $parameter->getName(), static::class)); } diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php index 514cd9e9f550..13aa3dcf3f78 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php @@ -3,8 +3,10 @@ namespace Drupal\Core\DependencyInjection\Compiler; use Drupal\Component\Utility\Reflection; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Reference; @@ -178,7 +180,8 @@ class TaggedHandlersPass implements CompilerPassInterface { foreach ($this->tagCache[$tag] ?? [] as $id => $attributes) { // Validate the interface. $handler = $container->getDefinition($id); - if (!is_a($handler->getClass(), $interface, TRUE)) { + $class = $this->resolveDefinitionClass($handler, $container); + if (!is_a($class, $interface, TRUE)) { throw new LogicException("Service '$id' for consumer '$consumer_id' does not implement $interface."); } $handlers[$id] = $attributes[0]['priority'] ?? 0; @@ -249,4 +252,33 @@ class TaggedHandlersPass implements CompilerPassInterface { $consumer->addArgument(array_keys($handlers)); } + /** + * Resolves the definition class. + * + * @param \Symfony\Component\DependencyInjection\Definition $definition + * The service definition. + * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container + * The service container. + * + * @return class-string|null + * The resolved class-string or null if the class cannot be resolved. + */ + protected function resolveDefinitionClass(Definition $definition, ContainerBuilder $container): ?string { + $class = $definition->getClass(); + if ($class) { + return $class; + } + + if (!$definition instanceof ChildDefinition) { + return NULL; + } + + if (!$container->hasDefinition($definition->getParent())) { + return NULL; + } + + $parent = $container->getDefinition($definition->getParent()); + return $this->resolveDefinitionClass($parent, $container); + } + } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index ef5669e8184e..53b4ac0cdf33 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -578,6 +578,17 @@ abstract class ContentEntityBase extends EntityBase implements \IteratorAggregat /** * {@inheritdoc} */ + public function getBundleEntity(): ?EntityInterface { + $entityType = $this->getEntityType(); + if (!$entityType->hasKey('bundle') || !$entityType->getBundleEntityType()) { + return NULL; + } + return $this->get($entityType->getKey('bundle'))->entity; + } + + /** + * {@inheritdoc} + */ public function uuid() { return $this->getEntityKey('uuid'); } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityForm.php b/core/lib/Drupal/Core/Entity/ContentEntityForm.php index 0855f1e3f775..4efdbd8e7c3b 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityForm.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php @@ -422,7 +422,6 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface '#open' => $new_revision_default, '#group' => 'advanced', '#weight' => 20, - '#access' => $new_revision_default || $this->entity->get($entity_type->getKey('revision'))->access('update'), '#optional' => TRUE, '#attributes' => [ 'class' => ['entity-content-form-revision-information'], @@ -436,7 +435,7 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface '#type' => 'checkbox', '#title' => $this->t('Create new revision'), '#default_value' => $new_revision_default, - '#access' => !$this->entity->isNew() && $this->entity->get($entity_type->getKey('revision'))->access('update'), + '#access' => !$this->entity->isNew(), '#group' => 'revision_information', ]; // Get log message field's key from definition. diff --git a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php index d01f7ed59ffd..bd2f8f381fb5 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php @@ -23,4 +23,13 @@ namespace Drupal\Core\Entity; */ interface ContentEntityInterface extends \Traversable, FieldableEntityInterface, TranslatableRevisionableInterface, SynchronizableInterface { + /** + * Gets the bundle entity of this entity. + * + * @return \Drupal\Core\Entity\EntityInterface|null + * The entity which is the bundle of this entity, or NULL if this entity's + * entity type does not represent bundles with an entity. + */ + public function getBundleEntity(): ?EntityInterface; + } diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityController.php b/core/lib/Drupal/Core/Entity/Controller/EntityController.php index 9a873730466d..ca700c3152db 100644 --- a/core/lib/Drupal/Core/Entity/Controller/EntityController.php +++ b/core/lib/Drupal/Core/Entity/Controller/EntityController.php @@ -335,13 +335,16 @@ class EntityController implements ContainerInjectionInterface { /** * Expands the bundle information with descriptions, if known. * + * Also sorts the bundles before adding the bundle descriptions. Sorting is + * being done here to avoid having to load bundle entities multiple times. + * * @param array $bundles * An array of bundle information. * @param \Drupal\Core\Entity\EntityTypeInterface $bundle_entity_type * The bundle entity type definition. * * @return array - * The expanded array of bundle information. + * An array of sorted bundle information including bundle descriptions. */ protected function loadBundleDescriptions(array $bundles, EntityTypeInterface $bundle_entity_type) { if (!$bundle_entity_type->entityClassImplements(EntityDescriptionInterface::class)) { @@ -351,6 +354,10 @@ class EntityController implements ContainerInjectionInterface { $storage = $this->entityTypeManager->getStorage($bundle_entity_type->id()); /** @var \Drupal\Core\Entity\EntityDescriptionInterface[] $bundle_entities */ $bundle_entities = $storage->loadMultiple($bundle_names); + + uasort($bundle_entities, [$bundle_entity_type->getClass(), 'sort']); + $bundles = array_replace($bundle_entities, $bundles); + foreach ($bundles as $bundle_name => &$bundle_info) { if (isset($bundle_entities[$bundle_name])) { $bundle_info['description'] = $bundle_entities[$bundle_name]->getDescription(); diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php index 132aaa439974..11422c6790c9 100644 --- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php +++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php @@ -19,29 +19,38 @@ use Drupal\Core\Site\Settings; * entities, which can come from all or specific bundles of an entity type. * * Properties: - * - #target_type: (required) The ID of the target entity type. - * - #tags: (optional) TRUE if the element allows multiple selection. Defaults + * + * @property $target_type + * (required) The ID of the target entity type. + * @property $tags + * (optional) TRUE if the element allows multiple selection. Defaults * to FALSE. - * - #default_value: (optional) The default entity or an array of default + * @property $default_value + * (optional) The default entity or an array of default * entities, depending on the value of #tags. - * - #selection_handler: (optional) The plugin ID of the entity reference + * @property $selection_handler + * (optional) The plugin ID of the entity reference * selection handler (a plugin of type EntityReferenceSelection). The default * value is the lowest-weighted plugin that is compatible with #target_type. - * - #selection_settings: (optional) An array of settings for the selection + * @property $selection_settings + * (optional) An array of settings for the selection * handler. Settings for the default selection handler * \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection are: * - target_bundles: Array of bundles to allow (omit to allow all bundles). * - sort: Array with 'field' and 'direction' keys, determining how results * will be sorted. Defaults to unsorted. - * - #autocreate: (optional) Array of settings used to auto-create entities + * @property $autocreate + * (optional) Array of settings used to auto-create entities * that do not exist (omit to not auto-create entities). Elements: * - bundle: (required) Bundle to use for auto-created entities. * - uid: User ID to use as the author of auto-created entities. Defaults to * the current user. - * - #process_default_value: (optional) Set to FALSE if the #default_value + * @property $process_default_value + * (optional) Set to FALSE if the #default_value * property is processed and access checked elsewhere (such as by a Field API * widget). Defaults to TRUE. - * - #validate_reference: (optional) Set to FALSE if validation of the selected + * @property $validate_reference + * (optional) Set to FALSE if validation of the selected * entities is performed elsewhere. Defaults to TRUE. * * Usage example: diff --git a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php index 30a2220777ee..ff3384063ef5 100644 --- a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php +++ b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php @@ -204,9 +204,9 @@ interface QueryInterface extends AlterableInterface { * * @param array $headers * An array of headers of the same structure as described in - * template_preprocess_table(). Use a 'specifier' in place of a 'field' to - * specify what to sort on. This can be an entity or a field as described - * in condition(). + * \Drupal\Core\Theme\ThemePreprocess::preprocessTable(). Use a 'specifier' + * in place of a 'field' to specify what to sort on. This can be an entity + * or a field as described in condition(). * * @return $this */ diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index ad361d3fe669..599e61f1df8a 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -19,16 +19,16 @@ class ModuleHandler implements ModuleHandlerInterface { /** * List of loaded files. * - * @var array + * @var array<string, true> * An associative array whose keys are file paths of loaded files, relative * to the application's root directory. */ protected $loadedFiles; /** - * List of installed modules. + * Installed modules, as extension objects keyed by module name. * - * @var \Drupal\Core\Extension\Extension[] + * @var array<string, \Drupal\Core\Extension\Extension> */ protected $moduleList; @@ -40,9 +40,9 @@ class ModuleHandler implements ModuleHandlerInterface { protected $loaded = FALSE; /** - * List of events which implement an alter hook keyed by hook name(s). + * Lists of callbacks which implement an alter hook, keyed by hook name(s). * - * @var array + * @var array<string, list<callable>> */ protected array $alterEventListeners = []; @@ -56,7 +56,11 @@ class ModuleHandler implements ModuleHandlerInterface { /** * A list of module include file keys. * - * @var array + * The array keys are generated from the arguments to ->loadInclude(). + * Each value is either the path of a file that was successfully included, or + * FALSE if the given file did not exist. + * + * @var array<string, string|false> */ protected $includeFileKeys = []; @@ -230,7 +234,9 @@ class ModuleHandler implements ModuleHandlerInterface { protected function add($type, $name, $path) { $pathname = "$path/$name.info.yml"; $php_file_path = $this->root . "/$path/$name.$type"; - $filename = file_exists($php_file_path) ? "$name.$type" : NULL; + if ($filename = file_exists($php_file_path) ? "$name.$type" : NULL) { + include_once $php_file_path; + } $this->moduleList[$name] = new Extension($this->root, $type, $pathname, $filename); $this->resetImplementations(); $hook_collector = HookCollectorPass::collectAllHookImplementations([$name => ['pathname' => $pathname]]); @@ -622,12 +628,12 @@ class ModuleHandler implements ModuleHandlerInterface { /** * Reorder modules for alters. * - * @param array $modules - * A list of modules. + * @param list<string> $modules + * A list of module names. * @param string $hook * The hook being worked on, for example form_alter. * - * @return array + * @return list<string> * The list, potentially reordered and changed by * hook_module_implements_alter(). */ diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php index 529fd7275a81..98d5c8938673 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php @@ -46,7 +46,7 @@ interface ModuleHandlerInterface { /** * Returns the list of currently active modules. * - * @return \Drupal\Core\Extension\Extension[] + * @return array<string, \Drupal\Core\Extension\Extension> * An associative array whose keys are the names of the modules and whose * values are Extension objects. */ @@ -69,7 +69,7 @@ interface ModuleHandlerInterface { /** * Sets an explicit list of currently active modules. * - * @param \Drupal\Core\Extension\Extension[] $module_list + * @param array<string, \Drupal\Core\Extension\Extension> $module_list * An associative array whose keys are the names of the modules and whose * values are Extension objects. */ @@ -106,12 +106,12 @@ interface ModuleHandlerInterface { /** * Determines which modules require and are required by each module. * - * @param array $modules + * @param array<string, \Drupal\Core\Extension\Extension> $modules * An array of module objects keyed by module name. Each object contains * information discovered during a Drupal\Core\Extension\ExtensionDiscovery * scan. * - * @return array + * @return array<string, \Drupal\Core\Extension\Extension> * The same array with the new keys for each module: * - requires: An array with the keys being the modules that this module * requires. @@ -171,7 +171,7 @@ interface ModuleHandlerInterface { /** * Retrieves a list of hooks that are declared through hook_hook_info(). * - * @return array + * @return array<string, array{group: string}> * An associative array whose keys are hook names and whose values are an * associative array containing a group name. The structure of the array * is the same as the return value of hook_hook_info(). @@ -411,7 +411,7 @@ interface ModuleHandlerInterface { * This is useful for tasks such as finding a file that exists in all module * directories. * - * @return array + * @return array<string, string> * An associative array of the directories for all enabled modules, keyed by * the extension machine name. */ diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php index 4d8d0a863a34..41d84b333466 100644 --- a/core/lib/Drupal/Core/Extension/module.api.php +++ b/core/lib/Drupal/Core/Extension/module.api.php @@ -75,7 +75,7 @@ use Drupal\Core\Utility\UpdateException; * Once a module requires 12.0.0 as a minimum version of Drupal the module can * safely remove hook_hook_info() implementations. * - * @return array + * @return array<string, array{group: string}> * An associative array whose keys are hook names and whose values are an * associative array containing: * - group: A string defining the group to which the hook belongs. The module @@ -114,7 +114,7 @@ function hook_hook_info(): array { * you will have to change the order of hook_form_alter() implementation in * hook_module_implements_alter(). * - * @param array $implementations + * @param array<string, string|false> $implementations * An array keyed by the module's name. The value of each item corresponds * to a $group, which is usually FALSE, unless the implementation is in a * file named $module.$group.inc. diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php index f00aebbc046f..dbc79b29c54a 100644 --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php @@ -249,6 +249,10 @@ interface FieldItemInterface extends ComplexDataInterface { /** * Defines the storage-level settings for this plugin. * + * Setting names defined by this method must not duplicate the setting names + * returned by this plugin's implementation of defaultFieldSettings(), as + * both lists of settings are merged. + * * @return array * A list of default settings, keyed by the setting name. */ @@ -257,6 +261,10 @@ interface FieldItemInterface extends ComplexDataInterface { /** * Defines the field-level settings for this plugin. * + * Setting names defined by this method must not duplicate the setting names + * returned by this plugin's implementation of defaultStorageSettings(), as + * both lists of settings are merged. + * * @return array * A list of default settings, keyed by the setting name. */ diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php index 60920fad60f5..2a3e3657354e 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php @@ -9,6 +9,7 @@ use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\OptGroup; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; /** @@ -56,8 +57,8 @@ abstract class OptionsWidgetBase extends WidgetBase { /** * {@inheritdoc} */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings) { - parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ?ElementInfoManagerInterface $elementInfoManager = NULL) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager); $property_names = $this->fieldDefinition->getFieldStorageDefinition()->getPropertyNames(); $this->column = $property_names[0]; } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/StringTextfieldWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/StringTextfieldWidget.php index 1523e4f618f7..2f8893243111 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/StringTextfieldWidget.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/StringTextfieldWidget.php @@ -6,6 +6,9 @@ use Drupal\Core\Field\Attribute\FieldWidget; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element\ElementInterface; +use Drupal\Core\Render\Element\Textfield; +use Drupal\Core\Render\Element\Widget; use Drupal\Core\StringTranslation\TranslatableMarkup; /** @@ -66,17 +69,14 @@ class StringTextfieldWidget extends WidgetBase { /** * {@inheritdoc} */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - $element['value'] = $element + [ - '#type' => 'textfield', - '#default_value' => $items[$delta]->value ?? NULL, - '#size' => $this->getSetting('size'), - '#placeholder' => $this->getSetting('placeholder'), - '#maxlength' => $this->getFieldSetting('max_length'), - '#attributes' => ['class' => ['js-text-full', 'text-full']], - ]; - - return $element; + public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface { + $value = $widget->createChild('value', Textfield::class, copyProperties: TRUE); + $value->default_value = $items[$delta]->value ?? NULL; + $value->size = $this->getSetting('size'); + $value->placeholder = $this->getSetting('placeholder'); + $value->maxlength = $this->getFieldSetting('max_length'); + $value->attributes = ['class' => ['js-text-full', 'text-full']]; + return $widget; } } diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php index 5c3da0a1bf59..b6d7a283830e 100644 --- a/core/lib/Drupal/Core/Field/WidgetBase.php +++ b/core/lib/Drupal/Core/Field/WidgetBase.php @@ -11,6 +11,9 @@ use Drupal\Core\Ajax\InsertCommand; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Render\Element; +use Drupal\Core\Render\Element\ElementInterface; +use Drupal\Core\Render\Element\Widget; +use Drupal\Core\Render\ElementInfoManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\Validator\ConstraintViolationListInterface; @@ -49,19 +52,32 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface, * The widget settings. * @param array $third_party_settings * Any third party settings. + * @param \Drupal\Core\Render\ElementInfoManagerInterface $elementInfoManager + * The element info manager. */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings) { + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, protected ?ElementInfoManagerInterface $elementInfoManager = NULL) { parent::__construct([], $plugin_id, $plugin_definition); $this->fieldDefinition = $field_definition; $this->settings = $settings; $this->thirdPartySettings = $third_party_settings; + if (!$this->elementInfoManager) { + @trigger_error('Calling ' . __METHOD__ . '() without the $elementInfoManager argument is deprecated in drupal:11.3.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/node/3526683', E_USER_DEPRECATED); + $this->elementInfoManager = \Drupal::service('plugin.manager.element_info'); + } } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings']); + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['third_party_settings'], + $container->get('plugin.manager.element_info'), + ); } /** @@ -461,7 +477,9 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface, '#weight' => $delta, ]; - $element = $this->formElement($items, $delta, $element, $form, $form_state); + $formObject = $this->elementInfoManager->fromRenderable($form); + $widget = $this->elementInfoManager->fromRenderable($element, Widget::class); + $element = $this->singleElementObject($items, $delta, $widget, $formObject, $form_state)->toRenderable(); if ($element) { // Allow modules to alter the field widget form element. @@ -484,6 +502,21 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface, /** * {@inheritdoc} */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + return $element; + } + + /** + * {@inheritdoc} + */ + public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface { + $element = $this->formElement($items, $delta, $widget->toRenderable(), $form->toRenderable(), $form_state); + return $this->elementInfoManager->fromRenderable($element); + } + + /** + * {@inheritdoc} + */ public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) { $field_name = $this->fieldDefinition->getName(); diff --git a/core/lib/Drupal/Core/Field/WidgetInterface.php b/core/lib/Drupal/Core/Field/WidgetInterface.php index ab78308291bd..9107bd437f74 100644 --- a/core/lib/Drupal/Core/Field/WidgetInterface.php +++ b/core/lib/Drupal/Core/Field/WidgetInterface.php @@ -3,15 +3,17 @@ namespace Drupal\Core\Field; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element\ElementInterface; +use Drupal\Core\Render\Element\Widget; use Symfony\Component\Validator\ConstraintViolationInterface; /** * Interface definition for field widget plugins. * * This interface details the methods that most plugin implementations will want - * to override. See Drupal\Core\Field\WidgetBaseInterface for base + * to override. See \Drupal\Core\Field\WidgetBaseInterface for base * wrapping methods that should most likely be inherited directly from - * Drupal\Core\Field\WidgetBase.. + * \Drupal\Core\Field\WidgetBase. * * @ingroup field_widget */ @@ -104,6 +106,51 @@ interface WidgetInterface extends WidgetBaseInterface { public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state); /** + * Returns the form for a single field widget. + * + * Field widget form elements should be based on the passed-in $element, which + * contains the base form element properties derived from the field + * configuration. + * + * The BaseWidget methods will set the weight, field name and delta values for + * each form element. If there are multiple values for this field, the + * formElement() method will be called as many times as needed. + * + * Other modules may alter the form element provided by this function using + * hook_field_widget_single_element_form_alter() or + * hook_field_widget_single_element_WIDGET_TYPE_form_alter(). + * + * The FAPI element callbacks (such as #process, #element_validate, + * #value_callback, etc.) used by the widget do not have access to the + * original $field_definition passed to the widget's constructor. Therefore, + * if any information is needed from that definition by those callbacks, the + * widget implementing this method, or a + * hook_field_widget[_WIDGET_TYPE]_form_alter() implementation, must extract + * the needed properties from the field definition and set them as ad-hoc + * $element['#custom'] properties, for later use by its element callbacks. + * + * @param \Drupal\Core\Field\FieldItemListInterface $items + * Array of default values for this field. + * @param int $delta + * The order of this item in the array of sub-elements (0, 1, 2, etc.). + * @param \Drupal\Core\Render\Element\Widget $widget + * A widget element. + * @param \Drupal\Core\Render\Element\ElementInterface $form + * The form structure where widgets are being attached to. This might be a + * full form structure, or a sub-element of a larger form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Render\Element\ElementInterface + * The wrapper object. Some widgets need to change the type of it so the + * returned object might not be a Wrapper object. + * + * @see hook_field_widget_single_element_form_alter() + * @see hook_field_widget_single_element_WIDGET_TYPE_form_alter() + */ + public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface; + + /** * Assigns a field-level validation error to the right widget sub-element. * * Depending on the widget's internal structure, a field-level validation diff --git a/core/lib/Drupal/Core/File/HtaccessWriter.php b/core/lib/Drupal/Core/File/HtaccessWriter.php index c91510e63dc1..52b698b38bf4 100644 --- a/core/lib/Drupal/Core/File/HtaccessWriter.php +++ b/core/lib/Drupal/Core/File/HtaccessWriter.php @@ -15,36 +15,33 @@ use Psr\Log\LoggerInterface; class HtaccessWriter implements HtaccessWriterInterface { /** - * The stream wrapper manager. - * - * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface - */ - protected $streamWrapperManager; - - /** - * The logger. - * - * @var \Psr\Log\LoggerInterface - */ - protected $logger; - - /** * Htaccess constructor. * * @param \Psr\Log\LoggerInterface $logger * The logger. - * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager + * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager * The stream wrapper manager. + * @param \Drupal\Core\Site\Settings|null $settings + * The settings. */ - public function __construct(LoggerInterface $logger, StreamWrapperManagerInterface $stream_wrapper_manager) { - $this->logger = $logger; - $this->streamWrapperManager = $stream_wrapper_manager; + public function __construct( + protected LoggerInterface $logger, + protected StreamWrapperManagerInterface $streamWrapperManager, + protected ?Settings $settings = NULL, + ) { + if (!$settings) { + @trigger_error('Calling ' . __METHOD__ . '() without the $settings argument is deprecated in drupal:11.2.0 and will be required in drupal:12.0.0. See https://www.drupal.org/node/3249817', E_USER_DEPRECATED); + $this->settings = \Drupal::service('settings'); + } } /** * {@inheritdoc} */ public function ensure() { + if (!$this->settings->get('auto_create_htaccess', TRUE)) { + return; + } try { foreach ($this->defaultProtectedDirs() as $protected_dir) { $this->write($protected_dir->getPath(), $protected_dir->isPrivate()); @@ -78,11 +75,15 @@ class HtaccessWriter implements HtaccessWriterInterface { * @internal * * @return bool - * TRUE if the .htaccess file was saved or already exists, FALSE otherwise. + * TRUE if the .htaccess file was saved, already exists or auto-creation is + * disabled, FALSE otherwise. * * @see \Drupal\Component\FileSecurity\FileSecurity::writeHtaccess() */ public function write($directory, $deny_public_access = TRUE, $force_overwrite = FALSE) { + if (!$this->settings->get('auto_create_htaccess', TRUE)) { + return TRUE; + } if (StreamWrapperManager::getScheme($directory)) { $directory = $this->streamWrapperManager->normalizeUri($directory); } diff --git a/core/lib/Drupal/Core/Form/FormBase.php b/core/lib/Drupal/Core/Form/FormBase.php index d88810943acd..44ca953ab077 100644 --- a/core/lib/Drupal/Core/Form/FormBase.php +++ b/core/lib/Drupal/Core/Form/FormBase.php @@ -6,6 +6,7 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Logger\LoggerChannelTrait; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Routing\RedirectDestinationTrait; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; @@ -76,6 +77,13 @@ abstract class FormBase implements FormInterface, ContainerInjectionInterface { protected $routeMatch; /** + * The element info manager. + * + * @var \Drupal\Core\Render\ElementInfoManagerInterface + */ + protected ElementInfoManagerInterface $elementInfoManager; + + /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { @@ -126,6 +134,29 @@ abstract class FormBase implements FormInterface, ContainerInjectionInterface { } /** + * The element info manager. + * + * @return \Drupal\Core\Render\ElementInfoManagerInterface + * The element info manager. + */ + protected function elementInfoManager(): ElementInfoManagerInterface { + if (!isset($this->elementInfoManager)) { + $this->elementInfoManager = $this->container()->get('plugin.manager.element_info'); + } + return $this->elementInfoManager; + } + + /** + * Sets the element info manager for this form. + * + * @return $this + */ + public function setElementInfoManager(ElementInfoManagerInterface $elementInfoManager): static { + $this->elementInfoManager = $elementInfoManager; + return $this; + } + + /** * Sets the config factory for this form. * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index ece07555fac2..c40b341a7ec6 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -1317,7 +1317,11 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS $buttons[] = $element; $form_state->setButtons($buttons); if ($this->buttonWasClicked($element, $form_state)) { - $form_state->setTriggeringElement($element); + // A correctly formatted request should only have one triggering + // element. + if (empty($form_state->getTriggeringElement())) { + $form_state->setTriggeringElement($element); + } } } } diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php index 7c0f913ca219..75bbb039414e 100644 --- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php +++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php @@ -403,12 +403,6 @@ class HookCollectorPass implements CompilerPassInterface { $extension = $fileinfo->getExtension(); $filename = $fileinfo->getPathname(); - if (($extension === 'module' || $extension === 'profile') && !$iterator->getDepth() && !$skip_procedural) { - // There is an expectation for all modules and profiles to be loaded. - // .module and .profile files are not supposed to be in subdirectories. - // These need to be loaded even if the module has no procedural hooks. - include_once $filename; - } if ($extension === 'php') { $cached = $hook_file_cache->get($filename); if ($cached) { @@ -512,11 +506,13 @@ class HookCollectorPass implements CompilerPassInterface { $function = $module . '_' . $hook; if ($hook === 'hook_info') { $this->hookInfo[] = $function; + include_once $fileinfo->getPathname(); } elseif ($hook === 'module_implements_alter') { $message = "$function without a #[LegacyModuleImplementsAlter] attribute is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. See https://www.drupal.org/node/3496788"; @trigger_error($message, E_USER_DEPRECATED); $this->moduleImplementsAlters[] = $function; + include_once $fileinfo->getPathname(); } $this->proceduralImplementations[$hook][] = $module; if ($fileinfo->getExtension() !== 'module') { diff --git a/core/lib/Drupal/Core/Menu/MenuPreprocess.php b/core/lib/Drupal/Core/Menu/MenuPreprocess.php new file mode 100644 index 000000000000..a0834704c531 --- /dev/null +++ b/core/lib/Drupal/Core/Menu/MenuPreprocess.php @@ -0,0 +1,73 @@ +<?php + +namespace Drupal\Core\Menu; + +/** + * Menu theme preprocess. + * + * @internal + */ +class MenuPreprocess { + + /** + * Prepares variables for single local task link templates. + * + * Default template: menu-local-task.html.twig. + * + * @param array $variables + * An associative array containing: + * - element: A render element containing: + * - #link: A menu link array with 'title', 'url', and (optionally) + * 'localized_options' keys. + * - #active: A boolean indicating whether the local task is active. + */ + public function preprocessMenuLocalTask(array &$variables): void { + $link = $variables['element']['#link']; + $link += [ + 'localized_options' => [], + ]; + $link_text = $link['title']; + + if (!empty($variables['element']['#active'])) { + $variables['is_active'] = TRUE; + } + + $link['localized_options']['set_active_class'] = TRUE; + + $variables['link'] = [ + '#type' => 'link', + '#title' => $link_text, + '#url' => $link['url'], + '#options' => $link['localized_options'], + ]; + } + + /** + * Prepares variables for single local action link templates. + * + * Default template: menu-local-action.html.twig. + * + * @param array $variables + * An associative array containing: + * - element: A render element containing: + * - #link: A menu link array with 'title', 'url', and (optionally) + * 'localized_options' keys. + */ + public function preprocessMenuLocalAction(array &$variables): void { + $link = $variables['element']['#link']; + $link += [ + 'localized_options' => [], + ]; + $link['localized_options']['attributes']['class'][] = 'button'; + $link['localized_options']['attributes']['class'][] = 'button-action'; + $link['localized_options']['set_active_class'] = TRUE; + + $variables['link'] = [ + '#type' => 'link', + '#title' => $link['title'], + '#options' => $link['localized_options'], + '#url' => $link['url'], + ]; + } + +} diff --git a/core/lib/Drupal/Core/Pager/PagerPreprocess.php b/core/lib/Drupal/Core/Pager/PagerPreprocess.php new file mode 100644 index 000000000000..e8921818440e --- /dev/null +++ b/core/lib/Drupal/Core/Pager/PagerPreprocess.php @@ -0,0 +1,171 @@ +<?php + +namespace Drupal\Core\Pager; + +use Drupal\Component\Utility\Html; +use Drupal\Core\Template\Attribute; +use Drupal\Core\Url; + +/** + * Pager theme preprocess. + * + * @internal + */ +class PagerPreprocess { + + public function __construct(protected PagerManagerInterface $pagerManager) { + } + + /** + * Prepares variables for pager templates. + * + * Default template: pager.html.twig. + * + * Menu callbacks that display paged query results should use #type => pager + * to retrieve a pager control so that users can view other results. Format a + * list of nearby pages with additional query results. + * + * @param array $variables + * An associative array containing: + * - pager: A render element containing: + * - #tags: An array of labels for the controls in the pager. + * - #element: An optional integer to distinguish between multiple pagers + * on one page. + * - #pagination_heading_level: An optional heading level for the pager. + * - #parameters: An associative array of query string parameters to + * append to the pager links. + * - #route_parameters: An associative array of the route parameters. + * - #quantity: The number of pages in the list. + */ + public function preprocessPager(array &$variables): void { + $element = $variables['pager']['#element']; + $parameters = $variables['pager']['#parameters']; + $quantity = empty($variables['pager']['#quantity']) ? 0 : $variables['pager']['#quantity']; + $route_name = $variables['pager']['#route_name']; + $route_parameters = $variables['pager']['#route_parameters'] ?? []; + + $pager = $this->pagerManager->getPager($element); + + // Nothing to do if there is no pager. + if (!isset($pager)) { + return; + } + + $pager_max = $pager->getTotalPages(); + + // Nothing to do if there is only one page. + if ($pager_max <= 1) { + return; + } + + $tags = $variables['pager']['#tags']; + + // Calculate various markers within this pager piece: + // Middle is used to "center" pages around the current page. + $pager_middle = ceil($quantity / 2); + $current_page = $pager->getCurrentPage(); + // The current pager is the page we are currently paged to. + $pager_current = $current_page + 1; + // The first pager is the first page listed by this pager piece (re + // quantity). + $pager_first = $pager_current - $pager_middle + 1; + // The last is the last page listed by this pager piece (re quantity). + $pager_last = $pager_current + $quantity - $pager_middle; + // End of marker calculations. + + // Prepare for generation loop. + $i = $pager_first; + if ($pager_last > $pager_max) { + // Adjust "center" if at end of query. + $i = $i + ($pager_max - $pager_last); + $pager_last = $pager_max; + } + if ($i <= 0) { + // Adjust "center" if at start of query. + $pager_last = $pager_last + (1 - $i); + $i = 1; + } + // End of generation loop preparation. + + // Create the "first" and "previous" links if we are not on the first page. + $items = []; + if ($current_page > 0) { + $items['first'] = []; + $items['first']['attributes'] = new Attribute(); + $options = [ + 'query' => $this->pagerManager->getUpdatedParameters($parameters, $element, 0), + ]; + $items['first']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); + if (isset($tags[0])) { + $items['first']['text'] = $tags[0]; + } + + $items['previous'] = []; + $items['previous']['attributes'] = new Attribute(); + $options = [ + 'query' => $this->pagerManager->getUpdatedParameters($parameters, $element, $current_page - 1), + ]; + $items['previous']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); + if (isset($tags[1])) { + $items['previous']['text'] = $tags[1]; + } + } + + // Add an ellipsis if there are further previous pages. + if ($i > 1) { + $variables['ellipses']['previous'] = TRUE; + } + // Now generate the actual pager piece. + for (; $i <= $pager_last && $i <= $pager_max; $i++) { + $options = [ + 'query' => $this->pagerManager->getUpdatedParameters($parameters, $element, $i - 1), + ]; + $items['pages'][$i]['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); + $items['pages'][$i]['attributes'] = new Attribute(); + if ($i == $pager_current) { + $variables['current'] = $i; + $items['pages'][$i]['attributes']->setAttribute('aria-current', 'page'); + } + } + // Add an ellipsis if there are further next pages. + if ($i < $pager_max + 1) { + $variables['ellipses']['next'] = TRUE; + } + + // Create the "next" and "last" links if we are not on the last page. + if ($current_page < ($pager_max - 1)) { + $items['next'] = []; + $items['next']['attributes'] = new Attribute(); + $options = [ + 'query' => $this->pagerManager->getUpdatedParameters($parameters, $element, $current_page + 1), + ]; + $items['next']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); + if (isset($tags[3])) { + $items['next']['text'] = $tags[3]; + } + + $items['last'] = []; + $items['last']['attributes'] = new Attribute(); + $options = [ + 'query' => $this->pagerManager->getUpdatedParameters($parameters, $element, $pager_max - 1), + ]; + $items['last']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); + if (isset($tags[4])) { + $items['last']['text'] = $tags[4]; + } + } + + $variables['items'] = $items; + $variables['heading_id'] = Html::getUniqueId('pagination-heading'); + $variables['pagination_heading_level'] = $variables['pager']['#pagination_heading_level'] ?? 'h4'; + if (!preg_match('/^h[1-6]$/', $variables['pagination_heading_level'])) { + $variables['pagination_heading_level'] = 'h4'; + } + + // The rendered link needs to play well with any other query parameter used + // on the page, like exposed filters, so for the cacheability all query + // parameters matter. + $variables['#cache']['contexts'][] = 'url.query_args'; + } + +} diff --git a/core/lib/Drupal/Core/Recipe/InputConfigurator.php b/core/lib/Drupal/Core/Recipe/InputConfigurator.php index cec8e588611c..3d1f871abbb9 100644 --- a/core/lib/Drupal/Core/Recipe/InputConfigurator.php +++ b/core/lib/Drupal/Core/Recipe/InputConfigurator.php @@ -171,12 +171,17 @@ final class InputConfigurator { * Returns the default value for an input definition. * * @param array $definition - * An input definition. Must contain a `source` element, which can be either - * 'config' or 'value'. If `source` is 'config', then there must also be a - * `config` element, which is a two-element indexed array containing - * (in order) the name of an extant config object, and a property path - * within that object. If `source` is 'value', then there must be a `value` - * element, which will be returned as-is. + * An input definition. Must contain a `source` element, which can be one + * of `config`, `env`, or `value`: + * - If `source` is `config`, there must also be a `config` element, which + * is a two-element indexed array containing (in order) the name of an + * extant config object, and a property path within that object. + * - If `source` is `env`, there must also be an `env` element, which is + * the name of an environment variable to return. The value will always + * be returned as a string. If the environment variable is not set, an + * empty string will be returned. + * - If `source` is 'value', then there must be a `value` element, which + * will be returned as-is. * * @return mixed * The default value. @@ -192,6 +197,17 @@ final class InputConfigurator { } return $config->get($key); } + elseif ($settings['source'] === 'env') { + // getenv() accepts NULL to return an array of all environment variables, + // but this makes no sense in a recipe. There is no valid situation where + // the name of the environment variable should be empty. + if (empty($settings['env'])) { + throw new \RuntimeException("The name of the environment variable cannot be empty."); + } + // If the variable doesn't exist, getenv() returns FALSE; we can represent + // that as an empty string. + return (string) getenv($settings['env']); + } return $settings['value']; } diff --git a/core/lib/Drupal/Core/Recipe/Recipe.php b/core/lib/Drupal/Core/Recipe/Recipe.php index 888f54e4f42c..1eb42b79343b 100644 --- a/core/lib/Drupal/Core/Recipe/Recipe.php +++ b/core/lib/Drupal/Core/Recipe/Recipe.php @@ -237,7 +237,7 @@ final class Recipe { 'default' => new Required([ new Collection([ 'source' => new Required([ - new Choice(['value', 'config']), + new Choice(['value', 'config', 'env']), ]), 'value' => new Optional(), 'config' => new Optional([ @@ -250,6 +250,10 @@ final class Recipe { ]), ]), ]), + 'env' => new Optional([ + new Type('string'), + new NotBlank(), + ]), ]), new Callback(self::validateDefaultValueDefinition(...)), ]), diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php index 0c63ce2ac495..5ce90c946550 100644 --- a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php +++ b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php @@ -88,7 +88,8 @@ class BareHtmlPageRenderer implements BareHtmlPageRendererInterface { * The page to attach to. */ public function systemPageAttachments(array &$page): void { - // Ensure the same CSS is loaded in template_preprocess_maintenance_page(). + // Ensure the same CSS is loaded in + // \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage(). $page['#attached']['library'][] = 'system/base'; if (\Drupal::service('router.admin_context')->isAdminRoute()) { $page['#attached']['library'][] = 'system/admin'; diff --git a/core/lib/Drupal/Core/Render/Element/Button.php b/core/lib/Drupal/Core/Render/Element/Button.php index a8f12e939636..95b78d9c4907 100644 --- a/core/lib/Drupal/Core/Render/Element/Button.php +++ b/core/lib/Drupal/Core/Render/Element/Button.php @@ -18,11 +18,15 @@ use Drupal\Core\Render\Element; * using JavaScript or other mechanisms. * * Properties: - * - #limit_validation_errors: An array of form element keys that will block + * + * @property $limit_validation_errors + * An array of form element keys that will block * form submission when validation for these elements or any child elements * fails. Specify an empty array to suppress all form validation errors. - * - #value: The text to be shown on the button. - * - #submit_button: This has a default value of TRUE. If set to FALSE, the + * @property $value + * The text to be shown on the button. + * @property $submit_button + * This has a default value of TRUE. If set to FALSE, the * 'type' attribute is set to 'button.' * * diff --git a/core/lib/Drupal/Core/Render/Element/Checkbox.php b/core/lib/Drupal/Core/Render/Element/Checkbox.php index 65be5d22bf10..220d1c8f9693 100644 --- a/core/lib/Drupal/Core/Render/Element/Checkbox.php +++ b/core/lib/Drupal/Core/Render/Element/Checkbox.php @@ -10,7 +10,9 @@ use Drupal\Core\Render\Element; * Provides a form element for a single checkbox. * * Properties: - * - #return_value: The value to return when the checkbox is checked. + * + * @property $return_value + * The value to return when the checkbox is checked. * * Usage example: * @code diff --git a/core/lib/Drupal/Core/Render/Element/Checkboxes.php b/core/lib/Drupal/Core/Render/Element/Checkboxes.php index 234f25aa9044..c713cc9cb531 100644 --- a/core/lib/Drupal/Core/Render/Element/Checkboxes.php +++ b/core/lib/Drupal/Core/Render/Element/Checkboxes.php @@ -9,7 +9,9 @@ use Drupal\Core\Render\Attribute\FormElement; * Provides a form element for a set of checkboxes. * * Properties: - * - #options: An associative array whose keys are the values returned for each + * + * @property $options + * An associative array whose keys are the values returned for each * checkbox, and whose values are the labels next to each checkbox. The * #options array cannot have a 0 key, as it would not be possible to discern * checked and unchecked states. diff --git a/core/lib/Drupal/Core/Render/Element/Color.php b/core/lib/Drupal/Core/Render/Element/Color.php index 254300d976f9..616ed9dbe08e 100644 --- a/core/lib/Drupal/Core/Render/Element/Color.php +++ b/core/lib/Drupal/Core/Render/Element/Color.php @@ -11,7 +11,9 @@ use Drupal\Component\Utility\Color as ColorUtility; * Provides a form element for choosing a color. * * Properties: - * - #default_value: Default value, in a format like #ffffff. + * + * @property $default_value + * Default value, in a format like #ffffff. * * Example usage: * @code diff --git a/core/lib/Drupal/Core/Render/Element/ComponentElement.php b/core/lib/Drupal/Core/Render/Element/ComponentElement.php index 62db902c0681..befd6aa269b6 100644 --- a/core/lib/Drupal/Core/Render/Element/ComponentElement.php +++ b/core/lib/Drupal/Core/Render/Element/ComponentElement.php @@ -11,11 +11,16 @@ use Drupal\Core\Security\DoTrustedCallbackTrait; * Provides a Single-Directory Component render element. * * Properties: - * - #component: The machine name of the component. - * - #variant: (optional) The variant to be used for the component. - * - #props: an associative array where the keys are the names of the + * + * @property $component + * The machine name of the component. + * @property $variant + * (optional) The variant to be used for the component. + * @property $props + * an associative array where the keys are the names of the * component props, and the values are the prop values. - * - #slots: an associative array where the keys are the slot names, and the + * @property $slots + * an associative array where the keys are the slot names, and the * values are the slot values. Expected slot values are renderable arrays. * - #propsAlter: an array of trusted callbacks. These are used to prepare the * context. Typical uses include replacing tokens in props. diff --git a/core/lib/Drupal/Core/Render/Element/Container.php b/core/lib/Drupal/Core/Render/Element/Container.php index d5a2092718e7..105f1413efdd 100644 --- a/core/lib/Drupal/Core/Render/Element/Container.php +++ b/core/lib/Drupal/Core/Render/Element/Container.php @@ -14,7 +14,9 @@ use Drupal\Core\Render\Element; * an HTML ID. * * Properties: - * - #optional: Indicates whether the container should render when it has no + * + * @property $optional + * Indicates whether the container should render when it has no * visible children. Defaults to FALSE. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/Date.php b/core/lib/Drupal/Core/Render/Element/Date.php index 8304167ae707..fbaeae4d600c 100644 --- a/core/lib/Drupal/Core/Render/Element/Date.php +++ b/core/lib/Drupal/Core/Render/Element/Date.php @@ -9,13 +9,18 @@ use Drupal\Core\Render\Element; * Provides a form element for date or time selection. * * Properties: - * - #attributes: An associative array containing: + * + * @property $attributes + * An associative array containing: * - type: The type of date field rendered, valid values include 'date', * 'time', 'datetime', and 'datetime-local'. - * - #date_date_format: The date format used in PHP formats. - * - #default_value: A string representing the date formatted as Y-m-d, or + * @property $date_date_format + * The date format used in PHP formats. + * @property $default_value + * A string representing the date formatted as Y-m-d, or * hh:mm for time. - * - #size: The size of the input element in characters. + * @property $size + * The size of the input element in characters. * * @code * $form['expiration'] = [ diff --git a/core/lib/Drupal/Core/Render/Element/Details.php b/core/lib/Drupal/Core/Render/Element/Details.php index 28e7396887d8..99a982727043 100644 --- a/core/lib/Drupal/Core/Render/Element/Details.php +++ b/core/lib/Drupal/Core/Render/Element/Details.php @@ -13,10 +13,14 @@ use Drupal\Core\Render\Element; * element, showing or hiding the contained elements. * * Properties: - * - #title: The title of the details container. Defaults to "Details". - * - #open: Indicates whether the container should be open by default. + * + * @property $title + * The title of the details container. Defaults to "Details". + * @property $open + * Indicates whether the container should be open by default. * Defaults to FALSE. - * - #summary_attributes: An array of attributes to apply to the <summary> + * @property $summary_attributes + * An array of attributes to apply to the <summary> * element. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/Dropbutton.php b/core/lib/Drupal/Core/Render/Element/Dropbutton.php index 20bb375ea879..4ac2d6dda8a5 100644 --- a/core/lib/Drupal/Core/Render/Element/Dropbutton.php +++ b/core/lib/Drupal/Core/Render/Element/Dropbutton.php @@ -17,9 +17,12 @@ use Drupal\Core\Render\Attribute\RenderElement; * element property #links to provide $variables['links'] for theming. * * Properties: - * - #links: An array of links to actions. See template_preprocess_links() for + * + * @property $links + * An array of links to actions. See template_preprocess_links() for * documentation the properties of links in this array. - * - #dropbutton_type: A string defining a type of dropbutton variant for + * @property $dropbutton_type + * A string defining a type of dropbutton variant for * styling proposes. Renders as class `dropbutton--#dropbutton_type`. * * Usage Example: diff --git a/core/lib/Drupal/Core/Render/Element/ElementInterface.php b/core/lib/Drupal/Core/Render/Element/ElementInterface.php index f7debae9efd4..dae671666c3a 100644 --- a/core/lib/Drupal/Core/Render/Element/ElementInterface.php +++ b/core/lib/Drupal/Core/Render/Element/ElementInterface.php @@ -41,6 +41,22 @@ interface ElementInterface extends PluginInspectionInterface, RenderCallbackInte public function getInfo(); /** + * Initialize storage. + * + * This will only have an effect the first time it is called, once it has + * been called, subsequent calls will not have an effect. + * Only the plugin manager should ever call this method. + * + * @param array $element + * The containing element. + * + * @return $this + * + * @internal + */ + public function initializeInternalStorage(array &$element): static; + + /** * Sets a form element's class attribute. * * Adds 'required' and 'error' classes as needed. @@ -52,4 +68,101 @@ interface ElementInterface extends PluginInspectionInterface, RenderCallbackInte */ public static function setAttributes(&$element, $class = []); + /** + * Returns a render array. + * + * @param string|null $wrapper_key + * An optional wrapper. + * + * @return array|\Drupal\Core\Render\Element\ElementInterface + * A render array. Make sure to take the return value as a reference. + * If $wrapper_key is not given then the stored render element is returned. + * If $wrapper_key is given then [$wrapper_key => &$element] is returned. + * The return value is typed with array|ElementInterface to prepare for + * Drupal 12, where the plan for this method is to return an + * ElementInterface object. If that plan goes through then in Drupal 13 + * support for render arrays will be dropped. + */ + public function &toRenderable(?string $wrapper_key = NULL): array|ElementInterface; + + /** + * Returns child elements. + * + * @return \Traversable<\Drupal\Core\Render\Element\ElementInterface> + * Keys will be children names, values are render objects. + */ + public function getChildren(): \Traversable; + + /** + * Gets a child. + * + * @param int|string|list<int|string> $name + * The name of the child. Can also be an integer. Or a list of these. + * It is an integer when the field API uses the delta for children. + * + * @return ?\Drupal\Core\Render\Element\ElementInterface + * The child render object. + */ + public function getChild(int|string|array $name): ?ElementInterface; + + /** + * Adds a child render element. + * + * @param int|string $name + * The name of the child. Can also be an integer when the child is a delta. + * @param array|\Drupal\Core\Render\Element\ElementInterface $child + * A render array or a render object. + * + * @return \Drupal\Core\Render\Element\ElementInterface + * The added child as a render object. + */ + public function addChild(int|string $name, ElementInterface|array &$child): ElementInterface; + + /** + * Creates a render object and attaches it to the current render object. + * + * @param int|string $name + * The name of the child. Can also be an integer. + * @param class-string<T> $class + * The class of the render object. + * @param array $configuration + * An array of configuration relevant to the render object. + * @param bool $copyProperties + * Copy properties (but not children) from the parent. This is useful for + * widgets for example. + * + * @return T + * The child render object. + * + * @template T of \Drupal\Core\Render\Element\ElementInterface + */ + public function createChild(int|string $name, string $class, array $configuration = [], bool $copyProperties = FALSE): ElementInterface; + + /** + * Removes a child. + * + * @param int|string $name + * The name of the child. Can also be an integer. + * + * @return ?\Drupal\Core\Render\Element\ElementInterface + * The removed render object if any, or NULL if the child could not be + * found. + */ + public function removeChild(int|string $name): ?ElementInterface; + + /** + * Change the type of the element. + * + * Changes only the #type all other properties and children are preserved. + * + * @param class-string<T> $class + * The class of the new render object. + * + * @return T + * The new render object. + * + * @template T of \Drupal\Core\Render\Element\ElementInterface + */ + public function changeType(string $class): ElementInterface; + } diff --git a/core/lib/Drupal/Core/Render/Element/Email.php b/core/lib/Drupal/Core/Render/Element/Email.php index 82e688ce38e2..debb2e10f446 100644 --- a/core/lib/Drupal/Core/Render/Element/Email.php +++ b/core/lib/Drupal/Core/Render/Element/Email.php @@ -10,9 +10,13 @@ use Drupal\Core\Render\Element; * Provides a form input element for entering an email address. * * Properties: - * - #default_value: An RFC-compliant email address. - * - #size: The size of the input element in characters. - * - #pattern: A string for the native HTML5 pattern attribute. + * + * @property $default_value + * An RFC-compliant email address. + * @property $size + * The size of the input element in characters. + * @property $pattern + * A string for the native HTML5 pattern attribute. * * Example usage: * @code diff --git a/core/lib/Drupal/Core/Render/Element/File.php b/core/lib/Drupal/Core/Render/Element/File.php index df50492a9ec4..9336eda06960 100644 --- a/core/lib/Drupal/Core/Render/Element/File.php +++ b/core/lib/Drupal/Core/Render/Element/File.php @@ -13,8 +13,11 @@ use Drupal\Core\Render\Element; * will automatically be added to the form element. * * Properties: - * - #multiple: A Boolean indicating whether multiple files may be uploaded. - * - #size: The size of the file input element in characters. + * + * @property $multiple + * A Boolean indicating whether multiple files may be uploaded. + * @property $size + * The size of the file input element in characters. * * The value of this form element will always be an array of * \Symfony\Component\HttpFoundation\File\UploadedFile objects, regardless of diff --git a/core/lib/Drupal/Core/Render/Element/FormElement.php b/core/lib/Drupal/Core/Render/Element/FormElement.php index 14ab865dd3b1..e991e7e7aaca 100644 --- a/core/lib/Drupal/Core/Render/Element/FormElement.php +++ b/core/lib/Drupal/Core/Render/Element/FormElement.php @@ -18,7 +18,8 @@ abstract class FormElement extends FormElementBase { * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, $plugin_definition) { - parent::__construct($configuration, $plugin_id, $plugin_definition); + $elementInfoManager = \Drupal::service('plugin.manager.element_info'); + parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager); @trigger_error('\Drupal\Core\Render\Element\FormElement is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Use \Drupal\Core\Render\Element\FormElementBase instead. See https://www.drupal.org/node/3436275', E_USER_DEPRECATED); } diff --git a/core/lib/Drupal/Core/Render/Element/FormElementBase.php b/core/lib/Drupal/Core/Render/Element/FormElementBase.php index 581607d716af..320ba9a75a1a 100644 --- a/core/lib/Drupal/Core/Render/Element/FormElementBase.php +++ b/core/lib/Drupal/Core/Render/Element/FormElementBase.php @@ -11,6 +11,15 @@ use Drupal\Core\Url; * * Form elements are a subset of render elements, representing elements for * HTML forms, which can be referenced in form arrays. See the + * + * @see \Drupal\Core\Render\Attribute\FormElement + * @see \Drupal\Core\Render\Element\FormElementInterface + * @see \Drupal\Core\Render\ElementInfoManager + * @see \Drupal\Core\Render\Element\RenderElementBase + * @see plugin_api + * + * @ingroup theme_render + * @see \Drupal\Core\Form\FormHelper::processStates() * @link theme_render Render API topic @endlink for an overview of render * arrays and render elements, and the @link form_api Form API topic @endlink * for an overview of forms and form arrays. @@ -30,69 +39,75 @@ use Drupal\Core\Url; * processing of form elements, besides those properties documented in * \Drupal\Core\Render\Element\RenderElementBase (for example: #prefix, * #suffix): - * - #after_build: (array) Array of callables or function names, which are - * called after the element is built. Arguments: $element, $form_state. - * - #ajax: (array) Array of elements to specify Ajax behavior. See - * the @link ajax Ajax API topic @endlink for more information. - * - #array_parents: (string[], read-only) Array of names of all the element's - * parents (including itself) in the render array. See also #parents, #tree. - * - #default_value: Default value for the element. See also #value. - * - #description: (string) Help or description text for the element. In an - * ideal user interface, the #title should be enough to describe the element, - * so most elements should not have a description; if you do need one, make - * sure it is translated. If it is not already wrapped in a safe markup - * object, it will be filtered for XSS safety. - * - #disabled: (bool) If TRUE, the element is shown but does not accept - * user input. - * - #element_validate: (array) Array of callables or function names, which - * are called to validate the input. Arguments: $element, $form_state, $form. - * - #field_prefix: (string) Prefix to display before the HTML input element. - * Should be translated, normally. If it is not already wrapped in a safe - * markup object, will be filtered for XSS safety. Note that the contents of - * this prefix are wrapped in a <span> element, so the value should not - * contain block level HTML. Any HTML added must be valid, i.e. any tags - * introduced inside this prefix must also be terminated within the prefix. - * - #field_suffix: (string) Suffix to display after the HTML input element. - * Should be translated, normally. If it is not already wrapped in a safe - * markup object, will be filtered for XSS safety. Note that the contents of - * this suffix are wrapped in a <span> element, so the value should not - * contain block level HTML. Any HTML must also be valid, i.e. any tags - * introduce inside this suffix must also be terminated within the suffix. - * - #value: (mixed) A value that cannot be edited by the user. - * - #has_garbage_value: (bool) Internal only. Set to TRUE to indicate that the - * #value property of an element should not be used or processed. - * - #input: (bool, internal) Whether or not the element accepts input. - * - #parents: (string[], read-only) Array of names of the element's parents - * for purposes of getting values out of $form_state. See also - * #array_parents, #tree. - * - #process: (array) Array of callables or function names, which are - * called during form building. Arguments: $element, $form_state, $form. - * - #processed: (bool, internal) Set to TRUE when the element is processed. - * - #required: (bool) Whether or not input is required on the element. - * - #states: (array) Information about JavaScript states, such as when to - * hide or show the element based on input on other elements. - * See \Drupal\Core\Form\FormHelper::processStates() for documentation. - * - #title: (string) Title of the form element. Should be translated. - * - #title_display: (string) Where and how to display the #title. Possible - * values: - * - before: Label goes before the element (default for most elements). - * - after: Label goes after the element (default for radio elements). - * - invisible: Label is there but is made invisible using CSS. - * - attribute: Make it the title attribute (hover tooltip). - * - #tree: (bool) TRUE if the values of this element and its children should - * be hierarchical in $form_state; FALSE if the values should be flat. - * See also #parents, #array_parents. - * - #value_callback: (callable) Callable or function name, which is called - * to transform the raw user input to the element's value. Arguments: - * $element, $input, $form_state. - * - * @see \Drupal\Core\Render\Attribute\FormElement - * @see \Drupal\Core\Render\Element\FormElementInterface - * @see \Drupal\Core\Render\ElementInfoManager - * @see \Drupal\Core\Render\Element\RenderElementBase - * @see plugin_api - * - * @ingroup theme_render + * @property array $after_build + * Array of callables or function names, which are called after the element + * is built. Arguments: $element, $form_state. + * @property array $ajax + * Array of elements to specify Ajax behavior. See the @link ajax Ajax API + * topic @endlink for more information. + * @property array<string> $array_parents + * Array of names of all the element's parents (including itself) in the + * render array. See also #parents, #tree. + * @property mixed $default_value + * Default value for the element. See also #value. + * @property scalar|\Stringable|\Drupal\Core\Render\RenderableInterface|array $description + * Help or description text for the element. In an ideal user interface, + * the #title should be enough to describe the element, so most elements + * should not have a description; if you do need one, make sure it is + * translated. It can be anything that Twig can print and will be filtered + * for XSS as necessary. + * @property bool $disabled + * If TRUE, the element is shown but does not accept user input. + * @property array<callable> $element_validate + * Array of callables or function names, which are called to validate the + * input. Arguments: $element, $form_state, $form. + * @property string $field_prefix + * Prefix to display before the HTML input element. Should be translated, + * normally. If it is not already wrapped in a safe markup object, will be + * filtered for XSS safety. Note that the contents of this prefix are + * wrapped in a <span> element, so the value should not contain block level + * HTML. Any HTML added must be valid, i.e. any tags introduced inside this + * prefix must also be terminated within the prefix. + * @property string $field_suffix + * Suffix to display after the HTML input element. Should be translated, + * normally. If it is not already wrapped in a safe markup object, will be + * filtered for XSS safety. Note that the contents of this suffix are + * wrapped in a <span> element, so the value should not contain block + * level HTML. Any HTML must also be valid, i.e. any tags introduce inside + * this suffix must also be terminated within the suffix. + * @property mixed $value + * A value that cannot be edited by the user. + * @property bool $has_garbage_value + * @internal + * Set to TRUE to indicate that the #value property of an + * element should not be used or processed. + * @property bool $input + * @internal + * Whether the element accepts input. + * @property array<string> $parents + * Array of names of the element's parents for purposes of getting values + * out of $form_state. See also #array_parents, #tree. + * @property array $process + * Array of callables or function names, which are called during form + * building. Arguments: $element, $form_state, $form. + * @property bool, internal $processed + * Set to TRUE when the element is processed. + * @property bool $required + * Whether input is required on the element. + * @property array $states + * Information about JavaScript states, such as when to hide or show the + * element based on input on other elements. + * @property string $title + * Title of the form element. Should be translated. + * @property \Drupal\Core\Render\Element\TitleDisplay $title_display + * Where and how to display the #title. + * @property bool $tree + * TRUE if the values of this element and its children should be hierarchical + * in $form_state; FALSE if the values should be flat. See also #parents, + * #array_parents. + * @property callable $value_callback + * Callable or function name, which is called to transform the raw user + * input to the element's value. Arguments: $element, $input, $form_state. */ abstract class FormElementBase extends RenderElementBase implements FormElementInterface { diff --git a/core/lib/Drupal/Core/Render/Element/Generic.php b/core/lib/Drupal/Core/Render/Element/Generic.php new file mode 100644 index 000000000000..4a0b6f09ebd9 --- /dev/null +++ b/core/lib/Drupal/Core/Render/Element/Generic.php @@ -0,0 +1,31 @@ +<?php + +namespace Drupal\Core\Render\Element; + +use Drupal\Core\Render\Attribute\RenderElement; + +/** + * Provides a generic, empty element. + * + * Manually creating this element is not necessary; however, the system + * often needs to convert render arrays that do not have a type. While + * arrays without a #type are valid PHP code, it is not possible to create + * an object without a class. + */ +#[RenderElement('generic')] +class Generic extends RenderElementBase { + + /** + * {@inheritdoc} + */ + public function getInfo() { + return []; + } + + /** + * {@inheritdoc} + */ + protected function setType(): void { + } + +} diff --git a/core/lib/Drupal/Core/Render/Element/Hidden.php b/core/lib/Drupal/Core/Render/Element/Hidden.php index db3b23a08cce..1c8856143e68 100644 --- a/core/lib/Drupal/Core/Render/Element/Hidden.php +++ b/core/lib/Drupal/Core/Render/Element/Hidden.php @@ -11,9 +11,12 @@ use Drupal\Core\Render\Element; * Specify either #default_value or #value but not both. * * Properties: - * - #default_value: The initial value of the form element. JavaScript may + * + * @property $default_value + * The initial value of the form element. JavaScript may * alter the value prior to submission. - * - #value: The value of the form element. The Form API ensures that this + * @property $value + * The value of the form element. The Form API ensures that this * value remains unchanged by the browser. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/HtmlTag.php b/core/lib/Drupal/Core/Render/Element/HtmlTag.php index 3b4dbd25d462..277f121df2cf 100644 --- a/core/lib/Drupal/Core/Render/Element/HtmlTag.php +++ b/core/lib/Drupal/Core/Render/Element/HtmlTag.php @@ -13,12 +13,17 @@ use Drupal\Core\Template\Attribute; * Provides a render element for any HTML tag, with properties and value. * * Properties: - * - #tag: The tag name to output. - * - #attributes: (array, optional) HTML attributes to apply to the tag. The + * + * @property $tag + * The tag name to output. + * @property $attributes + * (array, optional) HTML attributes to apply to the tag. The * attributes are escaped, see \Drupal\Core\Template\Attribute. - * - #value: (string|MarkupInterface, optional) The textual contents of the tag. + * @property $value + * (string|MarkupInterface, optional) The textual contents of the tag. * Strings will be XSS admin filtered. - * - #noscript: (bool, optional) When set to TRUE, the markup + * @property $noscript + * (bool, optional) When set to TRUE, the markup * (including any prefix or suffix) will be wrapped in a <noscript> element. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/Icon.php b/core/lib/Drupal/Core/Render/Element/Icon.php index d903f0d17006..e71771044e78 100644 --- a/core/lib/Drupal/Core/Render/Element/Icon.php +++ b/core/lib/Drupal/Core/Render/Element/Icon.php @@ -12,9 +12,13 @@ use Drupal\Core\Theme\Icon\IconDefinition; * Provides a render element to display an icon. * * Properties: - * - #pack_id: (string) Icon Pack provider plugin id. - * - #icon_id: (string) Name of the icon. - * - #settings: (array) Settings sent to the inline Twig template. + * + * @property $pack_id + * (string) Icon Pack provider plugin id. + * @property $icon_id + * (string) Name of the icon. + * @property $settings + * (array) Settings sent to the inline Twig template. * * Usage Example: * @code diff --git a/core/lib/Drupal/Core/Render/Element/InlineTemplate.php b/core/lib/Drupal/Core/Render/Element/InlineTemplate.php index 313bc4f13710..68f5fbf7091d 100644 --- a/core/lib/Drupal/Core/Render/Element/InlineTemplate.php +++ b/core/lib/Drupal/Core/Render/Element/InlineTemplate.php @@ -8,8 +8,11 @@ use Drupal\Core\Render\Attribute\RenderElement; * Provides a render element where the user supplies an in-line Twig template. * * Properties: - * - #template: The inline Twig template used to render the element. - * - #context: (array) The variables to substitute into the Twig template. + * + * @property $template + * The inline Twig template used to render the element. + * @property $context + * (array) The variables to substitute into the Twig template. * Each variable may be a string or a render array. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/Link.php b/core/lib/Drupal/Core/Render/Element/Link.php index 2a00e0526b68..b04704b1681d 100644 --- a/core/lib/Drupal/Core/Render/Element/Link.php +++ b/core/lib/Drupal/Core/Render/Element/Link.php @@ -14,8 +14,11 @@ use Drupal\Core\Url as CoreUrl; * Provides a link render element. * * Properties: - * - #title: The link text. - * - #url: \Drupal\Core\Url object containing URL information pointing to an + * + * @property $title + * The link text. + * @property $url + * \Drupal\Core\Url object containing URL information pointing to an * internal or external link. See \Drupal\Core\Utility\LinkGeneratorInterface. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/MachineName.php b/core/lib/Drupal/Core/Render/Element/MachineName.php index 11647cd02c57..93fbfe5be7f1 100644 --- a/core/lib/Drupal/Core/Render/Element/MachineName.php +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php @@ -21,7 +21,9 @@ use Drupal\Core\Render\Attribute\FormElement; * machine name form element. * * Properties: - * - #machine_name: An associative array containing: + * + * @property $machine_name + * An associative array containing: * - exists: A callable to invoke for checking whether a submitted machine * name value already exists. The arguments passed to the callback will be: * - The submitted value. @@ -49,9 +51,11 @@ use Drupal\Core\Render\Attribute\FormElement; * form element rather than in the suffix of the source element. The source * element must appear in the form structure before this element. Defaults * to FALSE. - * - #maxlength: (optional) Maximum allowed length of the machine name. Defaults + * @property $maxlength + * (optional) Maximum allowed length of the machine name. Defaults * to 64. - * - #disabled: (optional) Should be set to TRUE if an existing machine name + * @property $disabled + * (optional) Should be set to TRUE if an existing machine name * must not be changed after initial creation. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/MoreLink.php b/core/lib/Drupal/Core/Render/Element/MoreLink.php index 9d990942953d..57303585d5ba 100644 --- a/core/lib/Drupal/Core/Render/Element/MoreLink.php +++ b/core/lib/Drupal/Core/Render/Element/MoreLink.php @@ -8,7 +8,9 @@ use Drupal\Core\Render\Attribute\RenderElement; * Provides a link render element for a "more" link, like those used in blocks. * * Properties: - * - #title: The text of the link to generate (defaults to 'More'). + * + * @property $title + * The text of the link to generate (defaults to 'More'). * * See \Drupal\Core\Render\Element\Link for additional properties. * diff --git a/core/lib/Drupal/Core/Render/Element/Number.php b/core/lib/Drupal/Core/Render/Element/Number.php index 7096b2925beb..2be176fd363e 100644 --- a/core/lib/Drupal/Core/Render/Element/Number.php +++ b/core/lib/Drupal/Core/Render/Element/Number.php @@ -11,10 +11,15 @@ use Drupal\Component\Utility\Number as NumberUtility; * Provides a form element for numeric input, with special numeric validation. * * Properties: - * - #default_value: A valid floating point number. - * - #min: Minimum value. - * - #max: Maximum value. - * - #step: Ensures that the number is an even multiple of step, offset by #min + * + * @property $default_value + * A valid floating point number. + * @property $min + * Minimum value. + * @property $max + * Maximum value. + * @property $step + * Ensures that the number is an even multiple of step, offset by #min * if specified. A #min of 1 and a #step of 2 would allow values of 1, 3, 5, * etc. * diff --git a/core/lib/Drupal/Core/Render/Element/Page.php b/core/lib/Drupal/Core/Render/Element/Page.php index f271c6adfcf1..cc7cd16dc1ee 100644 --- a/core/lib/Drupal/Core/Render/Element/Page.php +++ b/core/lib/Drupal/Core/Render/Element/Page.php @@ -8,7 +8,7 @@ use Drupal\Core\Render\Attribute\RenderElement; * Provides a render element for the content of an HTML page. * * This represents the "main part" of the HTML page's body; see html.html.twig. - */ + */ #[RenderElement('page')] class Page extends RenderElementBase { diff --git a/core/lib/Drupal/Core/Render/Element/Pager.php b/core/lib/Drupal/Core/Render/Element/Pager.php index a31c7f2f3719..f6f4b05a3243 100644 --- a/core/lib/Drupal/Core/Render/Element/Pager.php +++ b/core/lib/Drupal/Core/Render/Element/Pager.php @@ -13,15 +13,22 @@ use Drupal\Core\Render\Attribute\RenderElement; * extend a select query with \Drupal\Core\Database\Query\PagerSelectExtender. * * Properties: - * - #element: (optional, int) The pager ID, to distinguish between multiple + * + * @property $element + * (optional, int) The pager ID, to distinguish between multiple * pagers on the same page (defaults to 0). - * - #pagination_heading_level: (optional) A heading level for the pager. - * - #parameters: (optional) An associative array of query string parameters to + * @property $pagination_heading_level + * (optional) A heading level for the pager. + * @property $parameters + * (optional) An associative array of query string parameters to * append to the pager. - * - #quantity: The maximum number of numbered page links to create (defaults + * @property $quantity + * The maximum number of numbered page links to create (defaults * to 9). - * - #tags: (optional) An array of labels for the controls in the pages. - * - #route_name: (optional) The name of the route to be used to build pager + * @property $tags + * (optional) An array of labels for the controls in the pages. + * @property $route_name + * (optional) The name of the route to be used to build pager * links. Defaults to '<none>', which will make links relative to the current * URL. This makes the page more effectively cacheable. * @@ -73,13 +80,14 @@ class Pager extends RenderElementBase { * The render array with cache contexts added. */ public static function preRenderPager(array $pager) { - // Note: the default pager theme process function - // template_preprocess_pager() also calls + // Note: the default pager theme preprocess function + // \Drupal\Core\Pager\PagerPreprocess::preprocessPager() also calls // \Drupal\Core\Pager\PagerManagerInterface::getUpdatedParameters(), which // maintains the existing query string. Therefore - // template_preprocess_pager() adds the 'url.query_args' cache context, - // which causes the more specific cache context below to be optimized away. - // In other themes, however, that may not be the case. + // \Drupal\Core\Pager\PagerPreprocess::preprocessPager() adds the + // 'url.query_args' cache context which causes the more specific cache + // context below to be optimized away. In other themes, however, that may + // not be the case. $pager['#cache']['contexts'][] = 'url.query_args.pagers:' . $pager['#element']; return $pager; } diff --git a/core/lib/Drupal/Core/Render/Element/Password.php b/core/lib/Drupal/Core/Render/Element/Password.php index 0c2e99d054b7..3b0b5d3a3785 100644 --- a/core/lib/Drupal/Core/Render/Element/Password.php +++ b/core/lib/Drupal/Core/Render/Element/Password.php @@ -10,8 +10,11 @@ use Drupal\Core\Render\Element; * Provides a form element for entering a password, with hidden text. * * Properties: - * - #size: The size of the input element in characters. - * - #pattern: A string for the native HTML5 pattern attribute. + * + * @property $size + * The size of the input element in characters. + * @property $pattern + * A string for the native HTML5 pattern attribute. * * Usage example: * @code diff --git a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php index 3ca411682a53..95a1677c7f42 100644 --- a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php +++ b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php @@ -12,7 +12,9 @@ use Drupal\Core\Render\Attribute\FormElement; * entered passwords match. * * Properties: - * - #size: The size of the input element in characters. + * + * @property $size + * The size of the input element in characters. * * Usage example: * @code diff --git a/core/lib/Drupal/Core/Render/Element/Radios.php b/core/lib/Drupal/Core/Render/Element/Radios.php index 7dc1815b36e7..08880ce1689f 100644 --- a/core/lib/Drupal/Core/Render/Element/Radios.php +++ b/core/lib/Drupal/Core/Render/Element/Radios.php @@ -10,7 +10,9 @@ use Drupal\Component\Utility\Html as HtmlUtility; * Provides a form element for a set of radio buttons. * * Properties: - * - #options: An associative array, where the keys are the returned values for + * + * @property $options + * An associative array, where the keys are the returned values for * each radio button, and the values are the labels next to each radio button. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/Range.php b/core/lib/Drupal/Core/Render/Element/Range.php index 0588dd555230..210b19c4ca34 100644 --- a/core/lib/Drupal/Core/Render/Element/Range.php +++ b/core/lib/Drupal/Core/Render/Element/Range.php @@ -12,8 +12,11 @@ use Drupal\Core\Render\Element; * Provides an HTML5 input element with type of "range". * * Properties: - * - #min: Minimum value (defaults to 0). - * - #max: Maximum value (defaults to 100). + * + * @property $min + * Minimum value (defaults to 0). + * @property $max + * Maximum value (defaults to 100). * Refer to \Drupal\Core\Render\Element\Number for additional properties. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php index c37338c3219f..fd29573aad11 100644 --- a/core/lib/Drupal/Core/Render/Element/RenderElement.php +++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php @@ -18,7 +18,8 @@ abstract class RenderElement extends RenderElementBase { * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, $plugin_definition) { - parent::__construct($configuration, $plugin_id, $plugin_definition); + $elementInfoManager = \Drupal::service('plugin.manager.element_info'); + parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager); @trigger_error('\Drupal\Core\Render\Element\RenderElement is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Use \Drupal\Core\Render\Element\RenderElementBase instead. See https://www.drupal.org/node/3436275', E_USER_DEPRECATED); } diff --git a/core/lib/Drupal/Core/Render/Element/RenderElementBase.php b/core/lib/Drupal/Core/Render/Element/RenderElementBase.php index 5ff3a5a0f98e..a2562144b2d8 100644 --- a/core/lib/Drupal/Core/Render/Element/RenderElementBase.php +++ b/core/lib/Drupal/Core/Render/Element/RenderElementBase.php @@ -2,12 +2,16 @@ namespace Drupal\Core\Render\Element; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\PluginBase; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Render\Element; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Url; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a base class for render element plugins. @@ -37,88 +41,110 @@ use Drupal\Core\Url; * \Drupal\Core\StringTranslation\TranslatableMarkup objects instead. * * Here is the list of the properties used during the rendering of all render - * elements: - * - #access: (bool or AccessResultInterface) + * elements. These are available as properties on the render element (handled + * by magic setter/getter) and also the render array starting with a # + * character. For example $element['#access'] or $elementObject->access. + * + * @property bool|\Drupal\Core\Access\AccessResultInterface $access * Whether the element is accessible or not. - * When the value is FALSE (if boolean) - * or the isAllowed() method returns FALSE (if AccessResultInterface), - * the element is not rendered and user-submitted values are not taken - * into consideration. - * - #access_callback: A callable or function name to call to check access. - * Argument: element. - * - #allowed_tags: (array) Array of allowed HTML tags for XSS filtering of - * #markup, #prefix, #suffix, etc. - * - #attached: (array) Array of attachments associated with the element. - * See the "Attaching libraries in render arrays" section of the + * When the value is FALSE (if boolean) or the isAllowed() method returns + * FALSE (if AccessResultInterface), the element is not rendered and + * user-submitted values are not taken into consideration. + * @property callable $access_callback + * A callable or function name to call to check access. Argument: element. + * @property array<string> $allowed_tags + * Array of allowed HTML tags for XSS filtering of #markup, #prefix, #suffix, + * etc. + * @property array $attached + * Array of attachments associated with the element. See the "Attaching + * libraries in render arrays" section of the * @link theme_render Render API topic @endlink for an overview, and * \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments - * for a list of what this can contain. Besides this list, it may also contain - * a 'placeholders' element; see the Placeholders section of the + * for a list of what this can contain. Besides this list, it may also + * contain a 'placeholders' element; see the Placeholders section of the * @link theme_render Render API topic @endlink for an overview. - * - #attributes: (array) HTML attributes for the element. The first-level - * keys are the attribute names, such as 'class', and the attributes are - * usually given as an array of string values to apply to that attribute - * (the rendering system will concatenate them together into a string in - * the HTML output). - * - #cache: (array) Cache information. See the Caching section of the + * @property array $attributes + * HTML attributes for the element. The first-level keys are the attribute + * names, such as 'class', and the attributes are usually given as an array + * of string values to apply to that attribute (the rendering system will + * concatenate them together into a string in the HTML output). + * @property array $cache + * Cache information. See the Caching section of the * @link theme_render Render API topic @endlink for more information. - * - #children: (array, internal) Array of child elements of this element. - * Set and used during the rendering process. - * - #create_placeholder: (bool) TRUE if the element has placeholders that - * are generated by #lazy_builder callbacks. Set internally during rendering - * in some cases. See also #attached. - * - #defaults_loaded: (bool) Set to TRUE during rendering when the defaults - * for the element #type have been added to the element. - * - #value: (mixed) A value that cannot be edited by the user. - * - #has_garbage_value: (bool) Internal only. Set to TRUE to indicate that the - * #value property of an element should not be used or processed. - * - #id: (string) The HTML ID on the element. This is automatically set for - * form elements, but not for all render elements; you can override the - * default value or add an ID by setting this property. - * - #lazy_builder: (array) Array whose first element is a lazy building - * callback (callable), and whose second is an array of scalar arguments to - * the callback. To use lazy building, the element array must be very - * simple: no properties except #lazy_builder, #cache, #weight, and - * #create_placeholder, and no children. A lazy builder callback typically - * generates #markup and/or placeholders; see the Placeholders section of the + * @property array $children + * Array of child elements of this element. Set and used during the + * rendering process. + * @property bool $create_placeholder + * TRUE if the element has placeholders that are generated by #lazy_builder + * callbacks. Set internally during rendering in some cases. See also + * #attached. + * @property bool $defaults_loaded + * Set to TRUE during rendering when the defaults for the element #type have + * been added to the element. + * @property mixed $value + * A value that cannot be edited by the user. + * @property bool $has_garbage_value + * @internal + * Set to TRUE to indicate that the #value property of an element should not + * be used or processed. + * @property string $id + * The HTML ID on the element. This is automatically set for form elements, + * but not for all render elements; you can override the default value or + * add an ID by setting this property. + * @property array<callable, array<scalar>> $lazy_builder + * Array whose first element is a lazy building callback (callable), and + * whose second is an array of scalar arguments to the callback. To use + * lazy building, the element array must be very simple: no properties + * except #lazy_builder, #cache, #weight, and #create_placeholder, and no + * children. A lazy builder callback typically generates #markup and/or + * placeholders; see the Placeholders section of the * @link theme_render Render API topic @endlink for information about * placeholders. - * - #markup: (string) During rendering, this will be set to the HTML markup - * output. It can also be set on input, as a fallback if there is no - * theming for the element. This will be filtered for XSS problems during - * rendering; see also #plain_text and #allowed_tags. - * - #plain_text: (string) Elements can set this instead of #markup. All HTML - * tags will be escaped in this text, and if both #plain_text and #markup - * are provided, #plain_text is used. - * - #post_render: (array) Array of callables or function names, which are - * called after the element is rendered. Arguments: rendered element string, - * children. - * - #pre_render: (array) Array of callables or function names, which are - * called just before the element is rendered. Argument: $element. - * Return value: an altered $element. - * - #prefix: (string) Text to render before the entire element output. See - * also #suffix. If it is not already wrapped in a safe markup object, will - * be filtered for XSS safety. - * - #printed: (bool, internal) Set to TRUE when an element and its children - * have been rendered. - * - #render_children: (bool, internal) Set to FALSE by the rendering process - * if the #theme call should be bypassed (normally, the theme is used to - * render the children). Set to TRUE by the rendering process if the children - * should be rendered by rendering each one separately and concatenating. - * - #suffix: (string) Text to render after the entire element output. See - * also #prefix. If it is not already wrapped in a safe markup object, will - * be filtered for XSS safety. - * - #theme: (string) Name of the theme hook to use to render the element. - * A default is generally set for elements; users of the element can - * override this (typically by adding __suggestion suffixes). - * - #theme_wrappers: (array) Array of theme hooks, which are invoked - * after the element and children are rendered, and before #post_render - * functions. - * - #type: (string) The machine name of the type of render/form element. - * - #weight: (float) The sort order for rendering, with lower numbers coming - * before higher numbers. Default if not provided is zero; elements with - * the same weight are rendered in the order they appear in the render - * array. + * @property string $markup + * During rendering, this will be set to the HTML markup output. It can also + * be set on input, as a fallback if there is no theming for the element. + * This will be filtered for XSS problems during rendering; see also + * #plain_text and #allowed_tags. + * @property string $plain_text + * Elements can set this instead of #markup. All HTML tags will be escaped + * in this text, and if both #plain_text and #markup are provided, + * #plain_text is used. + * @property array<callable> $post_render + * Array of callables or function names, which are called after the element + * is rendered. Arguments: rendered element string, children. + * @property array<callable> $pre_render + * Array of callables or function names, which are called just before the + * element is rendered. Argument: $element. Return value: an altered + * $element. + * @property string $prefix + * Text to render before the entire element output. See also #suffix. If it + * is not already wrapped in a safe markup object, will be filtered for XSS + * safety. + * @property bool $printed + * Set to TRUE when an element and its children have been rendered. + * @property bool $render_children + * @internal + * Set to FALSE by the rendering process if the #theme call should be + * bypassed (normally, the theme is used to render the children). Set to + * TRUE by the rendering process if the children should be rendered by + * rendering each one separately and concatenating. + * @property string $suffix + * Text to render after the entire element output. See also #prefix. If it + * is not already wrapped in a safe markup object, will be filtered for XSS + * safety. + * @property string $theme + * Name of the theme hook to use to render the element. A default is + * generally set for elements; users of the element can override this + * (typically by adding __suggestion suffixes). + * @property array<string> $theme_wrappers + * Array of theme hooks, which are invoked after the element and children + * are rendered, and before #post_render functions. + * @property string $type + * The machine name of the type of render/form element. + * @property float $weight + * The sort order for rendering, with lower numbers coming before higher + * numbers. Default if not provided is zero; elements with the same weight + * are rendered in the order they appear in the render array. * * @see \Drupal\Core\Render\Attribute\RenderElement * @see \Drupal\Core\Render\ElementInterface @@ -127,7 +153,60 @@ use Drupal\Core\Url; * * @ingroup theme_render */ -abstract class RenderElementBase extends PluginBase implements ElementInterface { +abstract class RenderElementBase extends PluginBase implements ElementInterface, ContainerFactoryPluginInterface { + + /** + * Constructs a new render element object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin ID for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Render\ElementInfoManagerInterface|null $elementInfoManager + * The element info manager. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, protected ?ElementInfoManagerInterface $elementInfoManager = NULL) { + if (!$this->elementInfoManager) { + @trigger_error('Calling ' . __METHOD__ . '() without the $elementInfoManager argument is deprecated in drupal:11.3.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/node/3526683', E_USER_DEPRECATED); + $this->elementInfoManager = \Drupal::service('plugin.manager.element_info'); + } + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('plugin.manager.element_info'), + ); + } + + /** + * The storage. + * + * @internal + */ + protected array $storage = []; + + /** + * The parent element. + * + * @var static + */ + protected ElementInterface $renderParent; + + /** + * The parent key. + * + * @var string + */ + protected string $renderParentName; /** * {@inheritdoc} @@ -474,4 +553,183 @@ abstract class RenderElementBase extends PluginBase implements ElementInterface return $element; } + /** + * {@inheritdoc} + */ + public function initializeInternalStorage(array &$element): static { + $this->storage = &$element; + $element['##object'] = $this; + $this->setType(); + return $this; + } + + /** + * Set type on initialize. + * + * There is no need to either call or override this method. + * + * @internal + */ + protected function setType(): void { + $this->storage['#type'] = $this->getPluginId(); + } + + /** + * {@inheritdoc} + */ + public function &toRenderable(?string $wrapper_key = NULL): array { + if ($wrapper_key) { + $return = [$wrapper_key => &$this->storage]; + return $return; + } + return $this->storage; + } + + /** + * Magic method: Sets a property value. + * + * @param string $name + * The name of a property. $value will be accessible with $this->name and + * also $element['#' . $name] where the element is the render array this + * object was created from. + * @param mixed $value + * The value. + */ + public function __set(string $name, $value): void { + $this->storage['#' . $name] = $value; + } + + /** + * Magic method: gets a property value. + * + * @param string $name + * The name of the property. $value is accessible with $this->name and + * also $element['#' . $name] where the element is the render array this + * object was created from. + * + * @return mixed + * The value. + */ + public function __get(string $name): mixed { + return $this->storage['#' . $name] ?? NULL; + } + + /** + * Magic method: unsets a property value. + * + * @param string $name + * The name of the property. This will unset both the object property + * $this->name and also the render key $element['#' . $name] where the + * element is the render array this object was created from. + */ + public function __unset(string $name): void { + unset($this->storage['#' . $name]); + } + + /** + * Magic method: checks if a property value is set. + * + * @param string $name + * The name of the property. Check whether the render key + * $element['#' . $name] is set where element is the render array this + * object was created from. This value is also accessible as $this->name. + * + * @return bool + * Whether it is set or not. + */ + public function __isset(string $name): bool { + return isset($this->storage['#' . $name]); + } + + /** + * {@inheritdoc} + */ + public function getChildren(): \Traversable { + foreach (Element::children($this->storage) as $key) { + yield $key => $this->elementInfoManager()->fromRenderable($this->storage[$key]); + } + } + + /** + * {@inheritdoc} + */ + public function getChild(int|string|array $name): ?ElementInterface { + $value = &NestedArray::getValue($this->storage, (array) $name, $exists); + return $exists ? $this->elementInfoManager()->fromRenderable($value) : NULL; + } + + /** + * {@inheritdoc} + */ + public function addChild(string|int $name, ElementInterface|array &$child): ElementInterface { + if ($name[0] === '#') { + throw new \LogicException('The name of children can not start with a #.'); + } + $childObject = $this->elementInfoManager()->fromRenderable($child); + $childObject->renderParent = $this; + $childObject->renderParentName = $name; + $this->storage[$name] = &$childObject->toRenderable(); + return $childObject; + } + + /** + * {@inheritdoc} + */ + public function createChild(int|string $name, string $class, array $configuration = [], bool $copyProperties = FALSE): ElementInterface { + $childObject = $this->elementInfoManager()->fromClass($class, $configuration); + $childObject = $this->addChild($name, $childObject); + if ($copyProperties) { + $childObject->storage += array_filter($this->storage, Element::property(...), \ARRAY_FILTER_USE_KEY); + } + return $childObject; + } + + /** + * {@inheritdoc} + */ + public function removeChild(int|string $name): ?ElementInterface { + $return = $this->storage[$name] ?? NULL; + unset($this->storage[$name]); + return $return ? $this->elementInfoManager()->fromRenderable($return) : NULL; + } + + /** + * {@inheritdoc} + */ + public function changeType(string $class): ElementInterface { + $this->storage['#type'] = $this->elementInfoManager()->getIdFromClass($class); + unset($this->storage['##object']); + return $this->elementInfoManager()->fromRenderable($this->storage); + } + + /** + * Returns the element info manager. + * + * @return \Drupal\Core\Render\ElementInfoManagerInterface + * The element info manager/ + */ + protected function elementInfoManager(): ElementInfoManagerInterface { + if (!$this->elementInfoManager) { + $this->elementInfoManager = \Drupal::service('plugin.manager.element_info'); + } + return $this->elementInfoManager; + } + + /** + * {@inheritdoc} + */ + public function __sleep(): array { + $vars = parent::__sleep(); + unset($this->storage['##object']); + return $vars; + } + + /** + * {@inheritdoc} + */ + public function __wakeup(): void { + parent::__wakeup(); + $this->storage['##object'] = $this; + } + } diff --git a/core/lib/Drupal/Core/Render/Element/Select.php b/core/lib/Drupal/Core/Render/Element/Select.php index 094b963212bd..c38582d4b23b 100644 --- a/core/lib/Drupal/Core/Render/Element/Select.php +++ b/core/lib/Drupal/Core/Render/Element/Select.php @@ -10,7 +10,9 @@ use Drupal\Core\Render\Element; * Provides a form element for a drop-down menu or scrolling selection box. * * Properties: - * - #options: An associative array of options for the select. Do not use + * + * @property $options + * An associative array of options for the select. Do not use * placeholders that sanitize data in any labels, as doing so will lead to * double-escaping. Each array value can be: * - A single translated string representing an HTML option element, where @@ -28,18 +30,22 @@ use Drupal\Core\Render\Element; * is ignored, and the contents of the 'option' property are interpreted as * an array of options to be merged with any other regular options and * option groups found in the outer array. - * - #sort_options: (optional) If set to TRUE (default is FALSE), sort the + * @property $sort_options + * (optional) If set to TRUE (default is FALSE), sort the * options by their labels, after rendering and translation is complete. * Can be set within an option group to sort that group. - * - #sort_start: (optional) Option index to start sorting at, where 0 is the + * @property $sort_start + * (optional) Option index to start sorting at, where 0 is the * first option. Can be used within an option group. If an empty option is * being added automatically (see #empty_option and #empty_value properties), * this defaults to 1 to keep the empty option at the top of the list. * Otherwise, it defaults to 0. - * - #empty_option: (optional) The label to show for the first default option. + * @property $empty_option + * (optional) The label to show for the first default option. * By default, the label is automatically set to "- Select -" for a required * field and "- None -" for an optional field. - * - #empty_value: (optional) The value for the first default option, which is + * @property $empty_value + * (optional) The value for the first default option, which is * used to determine whether the user submitted a value or not. * - If #required is TRUE, this defaults to '' (an empty string). Note that * if #empty_value is the same as a key in #options then the value of @@ -57,15 +63,19 @@ use Drupal\Core\Render\Element; * - If #required is not TRUE and this value is set (most commonly to an * empty string), then an extra option (see #empty_option above) * representing a "non-selection" is added with this as its value. - * - #multiple: (optional) Indicates whether one or more options can be + * @property $multiple + * (optional) Indicates whether one or more options can be * selected. Defaults to FALSE. - * - #default_value: Must be NULL or not set in case there is no value for the + * @property $default_value + * Must be NULL or not set in case there is no value for the * element yet, in which case a first default option is inserted by default. * Whether this first option is a valid option depends on whether the field * is #required or not. - * - #required: (optional) Whether the user needs to select an option (TRUE) + * @property $required + * (optional) Whether the user needs to select an option (TRUE) * or not (FALSE). Defaults to FALSE. - * - #size: The number of rows in the list that should be visible at one time. + * @property $size + * The number of rows in the list that should be visible at one time. * * Usage example: * @code diff --git a/core/lib/Drupal/Core/Render/Element/Submit.php b/core/lib/Drupal/Core/Render/Element/Submit.php index 980f4ff6c591..d47ca57cb668 100644 --- a/core/lib/Drupal/Core/Render/Element/Submit.php +++ b/core/lib/Drupal/Core/Render/Element/Submit.php @@ -11,10 +11,13 @@ use Drupal\Core\Render\Attribute\FormElement; * the form's submit handler. * * Properties: - * - #submit: Specifies an alternate callback for form submission when the + * + * @property $submit + * Specifies an alternate callback for form submission when the * submit button is pressed. Use '::methodName' format or an array containing * the object and method name (for example, [ $this, 'methodName'] ). - * - #value: The text to be shown on the button. + * @property $value + * The text to be shown on the button. * * Usage Example: * @code diff --git a/core/lib/Drupal/Core/Render/Element/Table.php b/core/lib/Drupal/Core/Render/Element/Table.php index 85111505e9bb..957b868ed63e 100644 --- a/core/lib/Drupal/Core/Render/Element/Table.php +++ b/core/lib/Drupal/Core/Render/Element/Table.php @@ -14,19 +14,27 @@ use Drupal\Component\Utility\Html as HtmlUtility; * context of a form. * * Properties: - * - #header: An array of table header labels. - * - #rows: An array of the rows to be displayed. Each row is either an array + * + * @property $header + * An array of table header labels. + * @property $rows + * An array of the rows to be displayed. Each row is either an array * of cell contents or an array of properties as described in table.html.twig * Alternatively specify the data for the table as child elements of the table * element. Table elements would contain rows elements that would in turn * contain column elements. - * - #empty: Text to display when no rows are present. - * - #responsive: Indicates whether to add the drupal.tableresponsive library + * @property $empty + * Text to display when no rows are present. + * @property $responsive + * Indicates whether to add the drupal.tableresponsive library * providing responsive tables. Defaults to TRUE. - * - #sticky: Indicates whether to make the table headers sticky at + * @property $sticky + * Indicates whether to make the table headers sticky at * the top of the page. Defaults to FALSE. - * - #footer: Table footer rows, in the same format as the #rows property. - * - #caption: A localized string for the <caption> tag. + * @property $footer + * Table footer rows, in the same format as the #rows property. + * @property $caption + * A localized string for the <caption> tag. * * Usage example 1: A simple form with an additional information table which * doesn't include any other form field. @@ -394,7 +402,7 @@ class Table extends FormElementBase { * @return array * Associative array of rendered child elements for a table. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments() * @see drupal_attach_tabledrag() */ diff --git a/core/lib/Drupal/Core/Render/Element/Tableselect.php b/core/lib/Drupal/Core/Render/Element/Tableselect.php index cb38a0722d22..297115a30151 100644 --- a/core/lib/Drupal/Core/Render/Element/Tableselect.php +++ b/core/lib/Drupal/Core/Render/Element/Tableselect.php @@ -12,13 +12,19 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; * Provides a form element for a table with radios or checkboxes in left column. * * Properties: - * - #header: An array of table header labels. - * - #options: An associative array where each key is the value returned when + * + * @property $header + * An array of table header labels. + * @property $options + * An associative array where each key is the value returned when * a user selects the radio button or checkbox, and each value is the row of * table data. - * - #empty: The message to display if table does not have any options. - * - #multiple: Set to FALSE to render the table with radios instead checkboxes. - * - #js_select: Set to FALSE if you don't want the select all checkbox added to + * @property $empty + * The message to display if table does not have any options. + * @property $multiple + * Set to FALSE to render the table with radios instead checkboxes. + * @property $js_select + * Set to FALSE if you don't want the select all checkbox added to * the header. * * Other properties of the \Drupal\Core\Render\Element\Table element are also diff --git a/core/lib/Drupal/Core/Render/Element/Tel.php b/core/lib/Drupal/Core/Render/Element/Tel.php index 9d5951e7d4e6..3d752708ebc9 100644 --- a/core/lib/Drupal/Core/Render/Element/Tel.php +++ b/core/lib/Drupal/Core/Render/Element/Tel.php @@ -12,8 +12,11 @@ use Drupal\Core\Render\Element; * validation. * * Properties: - * - #size: The size of the input element in characters. - * - #pattern: A string for the native HTML5 pattern attribute. + * + * @property $size + * The size of the input element in characters. + * @property $pattern + * A string for the native HTML5 pattern attribute. * * Usage example: * @code diff --git a/core/lib/Drupal/Core/Render/Element/Textarea.php b/core/lib/Drupal/Core/Render/Element/Textarea.php index c3fc60219244..9e41f15a891a 100644 --- a/core/lib/Drupal/Core/Render/Element/Textarea.php +++ b/core/lib/Drupal/Core/Render/Element/Textarea.php @@ -9,11 +9,16 @@ use Drupal\Core\Render\Attribute\FormElement; * Provides a form element for input of multiple-line text. * * Properties: - * - #rows: Number of rows in the text box. - * - #cols: Number of columns in the text box. - * - #resizable: Controls whether the text area is resizable. Allowed values + * + * @property $rows + * Number of rows in the text box. + * @property $cols + * Number of columns in the text box. + * @property $resizable + * Controls whether the text area is resizable. Allowed values * are "none", "vertical", "horizontal", or "both" (defaults to "vertical"). - * - #maxlength: The maximum amount of characters to accept as input. + * @property $maxlength + * The maximum amount of characters to accept as input. * * Usage example: * @code diff --git a/core/lib/Drupal/Core/Render/Element/Textfield.php b/core/lib/Drupal/Core/Render/Element/Textfield.php index de144ab4348d..47dbaf2b325a 100644 --- a/core/lib/Drupal/Core/Render/Element/Textfield.php +++ b/core/lib/Drupal/Core/Render/Element/Textfield.php @@ -10,13 +10,21 @@ use Drupal\Core\Render\Element; * Provides a one-line text field form element. * * Properties: - * - #maxlength: Maximum number of characters of input allowed. - * - #size: The size of the input element in characters. - * - #autocomplete_route_name: A route to be used as callback URL by the + * + * @property $maxlength + * Maximum number of characters of input allowed. + * @property $size + * The size of the input element in characters. + * @property $autocomplete_route_name + * A route to be used as callback URL by the * autocomplete JavaScript library. - * - #autocomplete_route_parameters: An array of parameters to be used in + * @property $autocomplete_route_parameters + * An array of parameters to be used in * conjunction with the route name. - * - #pattern: A string for the native HTML5 pattern attribute. + * @property $pattern + * A string for the native HTML5 pattern attribute. + * @property $placeholder + * A string to displayed in a textfield when it has no value. * * Usage example: * diff --git a/core/lib/Drupal/Core/Render/Element/TitleDisplay.php b/core/lib/Drupal/Core/Render/Element/TitleDisplay.php new file mode 100644 index 000000000000..d194fd1b27a8 --- /dev/null +++ b/core/lib/Drupal/Core/Render/Element/TitleDisplay.php @@ -0,0 +1,22 @@ +<?php + +namespace Drupal\Core\Render\Element; + +/** + * Defines how and where a title should be displayed for a form element. + */ +enum TitleDisplay: string { + + // Label goes before the element (default for most elements). + case Before = 'before'; + + // Label goes after the element (default for radio elements). + case After = 'after'; + + // Label is present in the markup but made invisible using CSS. + case Invisible = 'invisible'; + + // Label is set as the title attribute, displayed as a tooltip on hover. + case Attribute = 'attribute'; + +} diff --git a/core/lib/Drupal/Core/Render/Element/Url.php b/core/lib/Drupal/Core/Render/Element/Url.php index 7c5a9af2fbb4..1e7d499d8372 100644 --- a/core/lib/Drupal/Core/Render/Element/Url.php +++ b/core/lib/Drupal/Core/Render/Element/Url.php @@ -11,9 +11,13 @@ use Drupal\Core\Render\Element; * Provides a form element for input of a URL. * * Properties: - * - #default_value: A valid URL string. - * - #size: The size of the input element in characters. - * - #pattern: A string for the native HTML5 pattern attribute. + * + * @property $default_value + * A valid URL string. + * @property $size + * The size of the input element in characters. + * @property $pattern + * A string for the native HTML5 pattern attribute. * * Usage example: * @code diff --git a/core/lib/Drupal/Core/Render/Element/Value.php b/core/lib/Drupal/Core/Render/Element/Value.php index ce6efe9620ff..c1bb28348528 100644 --- a/core/lib/Drupal/Core/Render/Element/Value.php +++ b/core/lib/Drupal/Core/Render/Element/Value.php @@ -12,7 +12,9 @@ use Drupal\Core\Render\Attribute\FormElement; * in validation and submit processing. * * Properties: - * - #value: The value of the form element that cannot be edited by the user. + * + * @property $value + * The value of the form element that cannot be edited by the user. * * Usage Example: * @code diff --git a/core/lib/Drupal/Core/Render/Element/VerticalTabs.php b/core/lib/Drupal/Core/Render/Element/VerticalTabs.php index bf55119913b0..083b79018c0c 100644 --- a/core/lib/Drupal/Core/Render/Element/VerticalTabs.php +++ b/core/lib/Drupal/Core/Render/Element/VerticalTabs.php @@ -13,7 +13,9 @@ use Drupal\Core\Render\Element; * this element's name as vertical tabs. * * Properties: - * - #default_tab: The HTML ID of the rendered details element to be used as + * + * @property $default_tab + * The HTML ID of the rendered details element to be used as * the default tab. View the source of the rendered page to determine the ID. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/Weight.php b/core/lib/Drupal/Core/Render/Element/Weight.php index 89df69b1879f..3fa4e2c8a969 100644 --- a/core/lib/Drupal/Core/Render/Element/Weight.php +++ b/core/lib/Drupal/Core/Render/Element/Weight.php @@ -12,7 +12,9 @@ use Drupal\Core\Render\Attribute\FormElement; * the order. * * Properties: - * - #delta: The range of possible weight values used. A delta of 10 would + * + * @property $delta + * The range of possible weight values used. A delta of 10 would * indicate possible weight values between -10 and 10. * * Usage example: diff --git a/core/lib/Drupal/Core/Render/Element/Widget.php b/core/lib/Drupal/Core/Render/Element/Widget.php new file mode 100644 index 000000000000..ca5d87ea183e --- /dev/null +++ b/core/lib/Drupal/Core/Render/Element/Widget.php @@ -0,0 +1,26 @@ +<?php + +namespace Drupal\Core\Render\Element; + +use Drupal\Core\Render\Attribute\RenderElement; + +/** + * Provides a widget element. + * + * A form wrapper containing basic properties for the widget, attach + * the widget elements to this wrapper. This element renders to an empty + * string. + * + * @property $field_parents + * The 'parents' space for the field in the form. Most widgets can simply + * overlook this property. This identifies the location where the field + * values are placed within $form_state->getValues(), and is used to + * access processing information for the field through the + * WidgetBase::getWidgetState() and WidgetBase::setWidgetState() methods. + * @property $delta + * The order of this item in the array of sub-elements. (0, 1, 2, etc.) + */ +#[RenderElement('widget')] +class Widget extends Generic { + +} diff --git a/core/lib/Drupal/Core/Render/ElementInfoManager.php b/core/lib/Drupal/Core/Render/ElementInfoManager.php index 9f1c171cf569..a57aaf71b1fe 100644 --- a/core/lib/Drupal/Core/Render/ElementInfoManager.php +++ b/core/lib/Drupal/Core/Render/ElementInfoManager.php @@ -2,6 +2,9 @@ namespace Drupal\Core\Render; +use Drupal\Component\Plugin\Discovery\DiscoveryInterface; +use Drupal\Component\Plugin\Discovery\DiscoveryTrait; +use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; @@ -11,6 +14,7 @@ use Drupal\Core\PreWarm\PreWarmableInterface; use Drupal\Core\Render\Attribute\RenderElement; use Drupal\Core\Render\Element\ElementInterface; use Drupal\Core\Render\Element\FormElementInterface; +use Drupal\Core\Render\Element\Generic; use Drupal\Core\Theme\ThemeManagerInterface; /** @@ -36,6 +40,16 @@ class ElementInfoManager extends DefaultPluginManager implements ElementInfoMana protected $elementInfo; /** + * Class => plugin id mapping. + * + * More performant than reflecting runtime. + * + * @var array + * @internal + */ + protected array $reverseMapping = []; + + /** * Constructs an ElementInfoManager object. * * @param \Traversable $namespaces @@ -65,6 +79,79 @@ class ElementInfoManager extends DefaultPluginManager implements ElementInfoMana /** * {@inheritdoc} */ + protected function getDiscovery(): DiscoveryInterface { + $discovery = parent::getDiscovery(); + return new class ($discovery, $this->reverseMapping) implements DiscoveryInterface { + use DiscoveryTrait; + + public function __construct(protected DiscoveryInterface $decorated, protected array &$reverseMapping) {} + + public function getDefinitions(): array { + $definitions = $this->decorated->getDefinitions(); + foreach ($definitions as $element_type => $definition) { + $this->reverseMapping[$definition['class']] = $element_type; + } + return $definitions; + } + + }; + } + + /** + * {@inheritdoc} + */ + protected function getCachedDefinitions(): ?array { + if (!isset($this->definitions) && $cache = $this->cacheGet($this->cacheKey)) { + $this->definitions = $cache->data['definitions']; + $this->reverseMapping = $cache->data['reverse_mapping']; + } + return $this->definitions; + } + + /** + * {@inheritdoc} + */ + protected function setCachedDefinitions($definitions): void { + $data = [ + 'definitions' => $definitions, + 'reverse_mapping' => $this->reverseMapping, + ]; + $this->cacheSet($this->cacheKey, $data, Cache::PERMANENT, $this->cacheTags); + $this->definitions = $definitions; + } + + /** + * {@inheritdoc} + */ + public function clearCachedDefinitions(): void { + $this->elementInfo = NULL; + + $cids = []; + foreach ($this->themeHandler->listInfo() as $theme_name => $info) { + $cids[] = $this->getCid($theme_name); + } + + $this->cacheBackend->deleteMultiple($cids); + + parent::clearCachedDefinitions(); + } + + /** + * Returns the CID used to cache the element info. + * + * @param string $theme_name + * The theme name. + * + * @return string + * The cache ID. + */ + protected function getCid($theme_name): string { + return 'element_info_build:' . $theme_name; + } + + /** + * {@inheritdoc} + */ public function getInfo($type) { $theme_name = $this->themeManager->getActiveTheme()->getName(); if (!isset($this->elementInfo[$theme_name])) { @@ -102,7 +189,8 @@ class ElementInfoManager extends DefaultPluginManager implements ElementInfoMana // Otherwise, rebuild and cache. $info = []; - $previous_error_handler = set_error_handler(function ($severity, $message, $file, $line) use (&$previous_error_handler) { + $previous_error_handler = get_error_handler(); + set_error_handler(function ($severity, $message, $file, $line) use (&$previous_error_handler) { // Ignore deprecations while building element information. if ($severity === E_USER_DEPRECATED) { // Don't execute PHP internal error handler. @@ -144,37 +232,47 @@ class ElementInfoManager extends DefaultPluginManager implements ElementInfoMana * @return \Drupal\Core\Render\Element\ElementInterface * The render element plugin instance. */ - public function createInstance($plugin_id, array $configuration = []) { - return parent::createInstance($plugin_id, $configuration); + public function createInstance($plugin_id, array $configuration = [], &$element = []): ElementInterface { + $instance = parent::createInstance($plugin_id, $configuration); + assert($instance instanceof ElementInterface); + $instance->initializeInternalStorage($element); + return $instance; } /** * {@inheritdoc} */ - public function clearCachedDefinitions() { - $this->elementInfo = NULL; - - $cids = []; - foreach ($this->themeHandler->listInfo() as $theme_name => $info) { - $cids[] = $this->getCid($theme_name); + public function fromClass(string $class, array $configuration = []): ElementInterface { + $this->getDefinitions(); + if ($id = $this->getIdFromClass($class)) { + return $this->createInstance($id, $configuration); } + throw new \LogicException("$class is not a valid element class."); + } - $this->cacheBackend->deleteMultiple($cids); - - parent::clearCachedDefinitions(); + /** + * {@inheritdoc} + */ + public function getIdFromClass(string $class): ?string { + $this->getDefinitions(); + return $this->reverseMapping[$class] ?? NULL; } /** - * Returns the CID used to cache the element info. - * - * @param string $theme_name - * The theme name. - * - * @return string - * The cache ID. + * {@inheritdoc} */ - protected function getCid($theme_name) { - return 'element_info_build:' . $theme_name; + public function fromRenderable(ElementInterface|array &$element, string $class = Generic::class): ElementInterface { + if ($element instanceof ElementInterface) { + return $element; + } + if (isset($element['##object']) && $element['##object'] instanceof ElementInterface) { + return $element['##object']->initializeInternalStorage($element); + } + $type = $element['#type'] ?? $this->getIdFromClass($class); + if (!$type) { + throw new \LogicException('The element passed to ElementInfoManager::fromRenderable must have a #type or a valid class must be provided.'); + } + return $this->createInstance($type, element: $element); } } diff --git a/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php b/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php index ea6f21b849cd..e1a891f47643 100644 --- a/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php +++ b/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php @@ -3,11 +3,14 @@ namespace Drupal\Core\Render; use Drupal\Component\Plugin\Discovery\DiscoveryInterface; +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\Core\Render\Element\ElementInterface; +use Drupal\Core\Render\Element\Form; /** * Collects available render array element types. */ -interface ElementInfoManagerInterface extends DiscoveryInterface { +interface ElementInfoManagerInterface extends DiscoveryInterface, FactoryInterface { /** * Retrieves the default properties for the defined element type. @@ -61,4 +64,61 @@ interface ElementInfoManagerInterface extends DiscoveryInterface { */ public function getInfoProperty($type, $property_name, $default = NULL); + /** + * Creates a render object from a render array. + * + * @param \Drupal\Core\Render\Element\ElementInterface|array $element + * A render array or render objects. The latter is returned unchanged. + * @param class-string<T> $class + * The class of the render object being created. + * + * @return T + * A render object. + * + * @template T of ElementInterface + */ + public function fromRenderable(ElementInterface|array &$element, string $class = Form::class): ElementInterface; + + /** + * {@inheritdoc} + * + * @return \Drupal\Core\Render\Element\ElementInterface + * A fully configured render object. + */ + public function createInstance($plugin_id, array $configuration = []): ElementInterface; + + /** + * Creates a render object based on the provided class and configuration. + * + * @param class-string<T> $class + * The class of the render object being instantiated. + * @param array $configuration + * An array of configuration relevant to the render object. + * + * @return T + * A fully configured render object. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * If the render object cannot be created, such as if the class is invalid. + * + * @template T of ElementInterface + */ + public function fromClass(string $class, array $configuration = []): ElementInterface; + + /** + * Get the plugin ID from the class. + * + * Whenever possible, use the class type inference. Calling this method + * should not be necessary. + * + * @param string $class + * The class of an element object. + * + * @return ?string + * The plugin ID or null if not found. + * + * @internal + */ + public function getIdFromClass(string $class): ?string; + } diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 0a28501ae71c..6bb9fb6b1886 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -209,7 +209,13 @@ class Renderer implements RendererInterface { /** * {@inheritdoc} */ - public function render(&$elements, $is_root_call = FALSE) { + public function render(/* array */&$elements, $is_root_call = FALSE) { + + if (!is_array($elements)) { + trigger_error('Calling ' . __METHOD__ . ' with NULL is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Either pass an array or skip the call. See https://www.drupal.org/node/3534020.'); + return ''; + } + $context = $this->getCurrentRenderContext(); if (!isset($context)) { throw new \LogicException("Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead."); diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php index 020e594755fb..081545bd79dd 100644 --- a/core/lib/Drupal/Core/Render/RendererInterface.php +++ b/core/lib/Drupal/Core/Render/RendererInterface.php @@ -340,7 +340,7 @@ interface RendererInterface { * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments() * @see \Drupal\Core\Render\RendererInterface::renderRoot() */ - public function render(&$elements, $is_root_call = FALSE); + public function render(/* array */&$elements, $is_root_call = FALSE); /** * Checks whether a render context is active. diff --git a/core/lib/Drupal/Core/TempStore/Element/BreakLockLink.php b/core/lib/Drupal/Core/TempStore/Element/BreakLockLink.php index 4467632eeb77..7052aab1d5d0 100644 --- a/core/lib/Drupal/Core/TempStore/Element/BreakLockLink.php +++ b/core/lib/Drupal/Core/TempStore/Element/BreakLockLink.php @@ -7,6 +7,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Render\Attribute\RenderElement; use Drupal\Core\Render\Element\RenderElementBase; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Render\RendererInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -14,9 +15,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * Provides a link to break a tempstore lock. * * Properties: - * - #label: The label of the object that is locked. - * - #lock: \Drupal\Core\TempStore\Lock object. - * - #url: \Drupal\Core\Url object pointing to the break lock form. + * + * @property $label + * The label of the object that is locked. + * @property $lock + * \Drupal\Core\TempStore\Lock object. + * @property $url + * \Drupal\Core\Url object pointing to the break lock form. * * Usage example: * @code @@ -67,9 +72,19 @@ class BreakLockLink extends RenderElementBase implements ContainerFactoryPluginI * The entity type manager. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer. + * @param \Drupal\Core\Render\ElementInfoManagerInterface $elementInfoManager + * The element info manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, DateFormatterInterface $date_formatter, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer) { - parent::__construct($configuration, $plugin_id, $plugin_definition); + public function __construct( + array $configuration, + $plugin_id, + $plugin_definition, + DateFormatterInterface $date_formatter, + EntityTypeManagerInterface $entity_type_manager, + RendererInterface $renderer, + ElementInfoManagerInterface $elementInfoManager, + ) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager); $this->dateFormatter = $date_formatter; $this->entityTypeManager = $entity_type_manager; @@ -86,7 +101,8 @@ class BreakLockLink extends RenderElementBase implements ContainerFactoryPluginI $plugin_definition, $container->get('date.formatter'), $container->get('entity_type.manager'), - $container->get('renderer') + $container->get('renderer'), + $container->get('plugin.manager.element_info') ); } diff --git a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php index 00345c8b8461..144cea386ca7 100644 --- a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php +++ b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php @@ -85,7 +85,7 @@ trait FunctionalTestSetupTrait { // - The temporary directory is set and created by install_base_system(). // - The private file directory is created post install by // FunctionalTestSetupTrait::initConfig(). - // @see system_requirements() + // @see \Drupal\system\Install\SystemRequirements. // @see TestBase::prepareEnvironment() // @see install_base_system() // @see \Drupal\Core\Test\FunctionalTestSetupTrait::initConfig() @@ -197,8 +197,8 @@ trait FunctionalTestSetupTrait { protected function writeSettings(array $settings) { include_once DRUPAL_ROOT . '/core/includes/install.inc'; $filename = $this->siteDirectory . '/settings.php'; - // system_requirements() removes write permissions from settings.php - // whenever it is invoked. + // The system runtime_requirements hook removes write permissions from + // settings.php whenever it is invoked. // Not using File API; a potential error must trigger a PHP warning. chmod($filename, 0666); SettingsEditor::rewrite($filename, $settings); diff --git a/core/lib/Drupal/Core/Test/TestRunnerKernel.php b/core/lib/Drupal/Core/Test/TestRunnerKernel.php index e5ece5c07a2b..242f725b2602 100644 --- a/core/lib/Drupal/Core/Test/TestRunnerKernel.php +++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php @@ -5,7 +5,6 @@ namespace Drupal\Core\Test; use Drupal\Core\DrupalKernel; use Drupal\Core\Extension\Extension; use Drupal\Core\Site\Settings; -use Drupal\Core\Utility\Error; use Symfony\Component\HttpFoundation\Request; /** @@ -61,8 +60,7 @@ class TestRunnerKernel extends DrupalKernel { // Remove Drupal's error/exception handlers; they are designed for HTML // and there is no storage nor a (watchdog) logger here. - $currentErrorHandler = Error::currentErrorHandler(); - if (is_string($currentErrorHandler) && $currentErrorHandler === '_drupal_error_handler') { + if (get_error_handler() === '_drupal_error_handler') { restore_error_handler(); } restore_exception_handler(); diff --git a/core/lib/Drupal/Core/Theme/ImagePreprocess.php b/core/lib/Drupal/Core/Theme/ImagePreprocess.php new file mode 100644 index 000000000000..2f43ffe4c089 --- /dev/null +++ b/core/lib/Drupal/Core/Theme/ImagePreprocess.php @@ -0,0 +1,90 @@ +<?php + +namespace Drupal\Core\Theme; + +use Drupal\Core\File\FileUrlGeneratorInterface; +use Drupal\Core\Template\AttributeHelper; + +/** + * Image theme preprocess. + * + * @internal + */ +class ImagePreprocess { + + public function __construct(protected FileUrlGeneratorInterface $fileUrlGenerator) { + } + + /** + * Prepares variables for image templates. + * + * Default template: image.html.twig. + * + * @param array $variables + * An associative array containing: + * - uri: Either the path of the image file (relative to base_path()) or a + * full URL. + * - width: The width of the image (if known). + * - height: The height of the image (if known). + * - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0 + * always require an alt attribute. The HTML 5 draft allows the alt + * attribute to be omitted in some cases. Therefore, this variable + * defaults to an empty string, but can be set to NULL for the attribute + * to be omitted. Usually, neither omission nor an empty string satisfies + * accessibility requirements, so it is strongly encouraged for code + * building variables for image.html.twig templates to pass a meaningful + * value for this variable. + * - https://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8 + * - https://www.w3.org/TR/xhtml1/dtds.html + * - http://dev.w3.org/html5/spec/Overview.html#alt + * - title: The title text is displayed when the image is hovered in some + * popular browsers. + * - attributes: Associative array of attributes to be placed in the img + * tag. + * - srcset: Array of multiple URIs and sizes/multipliers. + * - sizes: The sizes attribute for viewport-based selection of images. + * phpcs:ignore + * - http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:viewport-based-selection-2 + */ + public function preprocessImage(array &$variables): void { + if (!empty($variables['uri'])) { + $variables['attributes']['src'] = $this->fileUrlGenerator->generateString($variables['uri']); + } + // Generate a srcset attribute conforming to the spec at + // https://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-srcset + if (!empty($variables['srcset'])) { + $srcset = []; + foreach ($variables['srcset'] as $src) { + // URI is mandatory. + $source = $this->fileUrlGenerator->generateString($src['uri']); + if (isset($src['width']) && !empty($src['width'])) { + $source .= ' ' . $src['width']; + } + elseif (isset($src['multiplier']) && !empty($src['multiplier'])) { + $source .= ' ' . $src['multiplier']; + } + $srcset[] = $source; + } + $variables['attributes']['srcset'] = implode(', ', $srcset); + } + + foreach (['width', 'height', 'alt', 'title', 'sizes'] as $key) { + if (isset($variables[$key])) { + // If the property has already been defined in the attributes, + // do not override, including NULL. + if (AttributeHelper::attributeExists($key, $variables['attributes'])) { + continue; + } + $variables['attributes'][$key] = $variables[$key]; + } + } + + // Without dimensions specified, layout shifts can occur, + // which are more noticeable on pages that take some time to load. + // As a result, only mark images as lazy load that have dimensions. + if (isset($variables['width'], $variables['height']) && !isset($variables['attributes']['loading'])) { + $variables['attributes']['loading'] = 'lazy'; + } + } + +} diff --git a/core/lib/Drupal/Core/Theme/ThemeCommonElements.php b/core/lib/Drupal/Core/Theme/ThemeCommonElements.php index 90994e47dca5..50755c302a70 100644 --- a/core/lib/Drupal/Core/Theme/ThemeCommonElements.php +++ b/core/lib/Drupal/Core/Theme/ThemeCommonElements.php @@ -4,8 +4,11 @@ declare(strict_types=1); namespace Drupal\Core\Theme; +use Drupal\Core\Breadcrumb\BreadcrumbPreprocess; use Drupal\Core\Datetime\DatePreprocess; use Drupal\Core\Field\FieldPreprocess; +use Drupal\Core\Menu\MenuPreprocess; +use Drupal\Core\Pager\PagerPreprocess; /** * Provide common theme render elements. @@ -35,6 +38,7 @@ class ThemeCommonElements { ], 'region' => [ 'render element' => 'elements', + 'initial preprocess' => ThemePreprocess::class . ':preprocessRegion', ], 'time' => [ 'variables' => [ @@ -99,11 +103,13 @@ class ThemeCommonElements { 'srcset' => [], 'style_name' => NULL, ], + 'initial preprocess' => ImagePreprocess::class . ':preprocessImage', ], 'breadcrumb' => [ 'variables' => [ 'links' => [], ], + 'initial preprocess' => BreadcrumbPreprocess::class . ':preprocessBreadcrumb', ], 'table' => [ 'variables' => [ @@ -117,11 +123,13 @@ class ThemeCommonElements { 'responsive' => TRUE, 'empty' => '', ], + 'initial preprocess' => ThemePreprocess::class . ':preprocessTable', ], 'tablesort_indicator' => [ 'variables' => [ 'style' => NULL, ], + 'initial preprocess' => ThemePreprocess::class . ':preprocessTablesortIndicator', ], 'mark' => [ 'variables' => [ @@ -138,6 +146,7 @@ class ThemeCommonElements { 'empty' => NULL, 'context' => [], ], + 'initial preprocess' => ThemePreprocess::class . ':preprocessItemList', ], 'feed_icon' => [ 'variables' => [ @@ -156,12 +165,13 @@ class ThemeCommonElements { 'indentation' => [ 'variables' => ['size' => 1], ], - // From theme.maintenance.inc. 'maintenance_page' => [ 'render element' => 'page', + 'initial preprocess' => ThemePreprocess::class . ':preprocessMaintenancePage', ], 'install_page' => [ 'render element' => 'page', + 'initial preprocess' => ThemePreprocess::class . ':preprocessInstallPage', ], 'maintenance_task_list' => [ 'variables' => [ @@ -169,6 +179,7 @@ class ThemeCommonElements { 'active' => NULL, 'variant' => NULL, ], + 'initial preprocess' => ThemePreprocess::class . ':preprocessMaintenanceTaskList', ], 'authorize_report' => [ 'variables' => [ @@ -181,6 +192,7 @@ class ThemeCommonElements { ], 'pager' => [ 'render element' => 'pager', + 'initial preprocess' => PagerPreprocess::class . ':preprocessPager', ], 'menu' => [ 'variables' => [ @@ -191,9 +203,11 @@ class ThemeCommonElements { ], 'menu_local_task' => [ 'render element' => 'element', + 'initial preprocess' => MenuPreprocess::class . ':preprocessMenuLocalTask', ], 'menu_local_action' => [ 'render element' => 'element', + 'initial preprocess' => MenuPreprocess::class . ':preprocessMenuLocalAction', ], 'menu_local_tasks' => [ 'variables' => [ diff --git a/core/lib/Drupal/Core/Theme/ThemePreprocess.php b/core/lib/Drupal/Core/Theme/ThemePreprocess.php index 2a93d5b06c91..f712e3d9ff98 100644 --- a/core/lib/Drupal/Core/Theme/ThemePreprocess.php +++ b/core/lib/Drupal/Core/Theme/ThemePreprocess.php @@ -5,15 +5,18 @@ namespace Drupal\Core\Theme; use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\Crypt; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Installer\InstallerKernel; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Path\CurrentPathStack; use Drupal\Core\Path\PathMatcherInterface; +use Drupal\Core\Render\Element; use Drupal\Core\Render\Markup; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Template\Attribute; use Drupal\Core\Url; +use Drupal\Core\Utility\TableSort; /** * Preprocess for common/core theme templates. @@ -356,4 +359,445 @@ class ThemePreprocess { } } + /** + * Prepares variables for maintenance page templates. + * + * Default template: maintenance-page.html.twig. + * + * @param array $variables + * An associative array containing: + * - content - An array of page content. + * + * @see system_page_attachments() + */ + public function preprocessMaintenancePage(array &$variables): void { + // @todo Rename the templates to page--maintenance + page--install. + $this->preprocessPage($variables); + + // @see system_page_attachments() + $variables['#attached']['library'][] = 'system/maintenance'; + + // Maintenance page and install page need branding info in variables because + // there is no blocks. + $site_config = $this->configFactory->get('system.site'); + $variables['logo'] = theme_get_setting('logo.url'); + $variables['site_name'] = $site_config->get('name'); + $variables['site_slogan'] = $site_config->get('slogan'); + + // Maintenance page and install page need page title in variable because + // there are no blocks. + $variables['title'] = $variables['page']['#title']; + } + + /** + * Prepares variables for install page templates. + * + * Default template: install-page.html.twig. + * + * @param array $variables + * An associative array containing: + * - content - An array of page content. + * + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() + */ + public function preprocessInstallPage(array &$variables): void { + $installer_active_task = NULL; + if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE === 'install' && InstallerKernel::installationAttempted()) { + $installer_active_task = $GLOBALS['install_state']['active_task']; + } + + $this->preprocessMaintenancePage($variables); + + // Override the site name that is displayed on the page, since Drupal is + // still in the process of being installed. + $distribution_name = drupal_install_profile_distribution_name(); + $variables['site_name'] = $distribution_name; + $variables['site_version'] = $installer_active_task ? drupal_install_profile_distribution_version() : ''; + } + + /** + * Prepares variables for region templates. + * + * Default template: region.html.twig. + * + * Prepares the values passed to the theme_region function to be passed into a + * pluggable template engine. Uses the region name to generate a template file + * suggestions. + * + * @param array $variables + * An associative array containing: + * - elements: An associative array containing properties of the region. + */ + public function preprocessRegion(array &$variables): void { + // Create the $content variable that templates expect. + $variables['content'] = $variables['elements']['#children']; + $variables['region'] = $variables['elements']['#region']; + } + + /** + * Prepares variables for table templates. + * + * Default template: table.html.twig. + * + * @param array $variables + * An associative array containing: + * - header: An array containing the table headers. Each element of the + * array can be either a localized string or an associative array with the + * following keys: + * - data: The localized title of the table column, as a string or render + * array. + * - field: The database field represented in the table column (required + * if user is to be able to sort on this column). + * - sort: A default sort order for this column ("asc" or "desc"). Only + * one column should be given a default sort order because table sorting + * only applies to one column at a time. + * - initial_click_sort: Set the initial sort of the column when clicked. + * Defaults to "asc". + * - class: An array of values for the 'class' attribute. In particular, + * the least important columns that can be hidden on narrow and medium + * width screens should have a 'priority-low' class, referenced with the + * RESPONSIVE_PRIORITY_LOW constant. Columns that should be shown on + * medium+ wide screens should be marked up with a class of + * 'priority-medium', referenced by with the RESPONSIVE_PRIORITY_MEDIUM + * constant. Themes may hide columns with one of these two classes on + * narrow viewports to save horizontal space. + * - Any HTML attributes, such as "colspan", to apply to the column header + * cell. + * - rows: An array of table rows. Every row is an array of cells, or an + * associative array with the following keys: + * - data: An array of cells. + * - Any HTML attributes, such as "class", to apply to the table row. + * - no_striping: A Boolean indicating that the row should receive no + * 'even / odd' styling. Defaults to FALSE. + * Each cell can be either a string or an associative array with the + * following keys: + * - data: The string or render array to display in the table cell. + * - header: Indicates this cell is a header. + * - Any HTML attributes, such as "colspan", to apply to the table cell. + * Here's an example for $rows: + * @code + * $rows = [ + * // Simple row + * [ + * 'Cell 1', 'Cell 2', 'Cell 3' + * ], + * // Row with attributes on the row and some of its cells. + * [ + * 'data' => ['Cell 1', ['data' => 'Cell 2', 'colspan' => 2]], 'class' => ['funky'] + * ], + * ]; + * @endcode + * - footer: An array of table rows which will be printed within a <tfoot> + * tag, in the same format as the rows element (see above). + * - attributes: An array of HTML attributes to apply to the table tag. + * - caption: A localized string to use for the <caption> tag. + * - colgroups: An array of column groups. Each element of the array can be + * either: + * - An array of columns, each of which is an associative array of HTML + * attributes applied to the <col> element. + * - An array of attributes applied to the <colgroup> element, which must + * include a "data" attribute. To add attributes to <col> elements, + * set the "data" attribute with an array of columns, each of which is + * an associative array of HTML attributes. + * Here's an example for $colgroup: + * @code + * $colgroup = [ + * // <colgroup> with one <col> element. + * [ + * [ + * 'class' => ['funky'], // Attribute for the <col> element. + * ], + * ], + * // <colgroup> with attributes and inner <col> elements. + * [ + * 'data' => [ + * [ + * 'class' => ['funky'], // Attribute for the <col> element. + * ], + * ], + * 'class' => ['jazzy'], // Attribute for the <colgroup> element. + * ], + * ]; + * @endcode + * These optional tags are used to group and set properties on columns + * within a table. For example, one may easily group three columns and + * apply same background style to all. + * - sticky: Use a "sticky" table header. + * - empty: The message to display in an extra row if table does not have + * any rows. + */ + public function preprocessTable(array &$variables): void { + // Format the table columns: + if (!empty($variables['colgroups'])) { + foreach ($variables['colgroups'] as &$colgroup) { + // Check if we're dealing with a simple or complex column + if (isset($colgroup['data'])) { + $cols = $colgroup['data']; + unset($colgroup['data']); + $colgroup_attributes = $colgroup; + } + else { + $cols = $colgroup; + $colgroup_attributes = []; + } + $colgroup = []; + $colgroup['attributes'] = new Attribute($colgroup_attributes); + $colgroup['cols'] = []; + + // Build columns. + if (is_array($cols) && !empty($cols)) { + foreach ($cols as $col_key => $col) { + $colgroup['cols'][$col_key]['attributes'] = new Attribute($col); + } + } + } + } + + // Build an associative array of responsive classes keyed by column. + $responsive_classes = []; + + // Format the table header: + $ts = []; + $header_columns = 0; + if (!empty($variables['header'])) { + $ts = TableSort::getContextFromRequest($variables['header'], \Drupal::request()); + + // Use a separate index with responsive classes as headers + // may be associative. + $responsive_index = -1; + foreach ($variables['header'] as $col_key => $cell) { + // Increase the responsive index. + $responsive_index++; + + if (!is_array($cell)) { + $header_columns++; + $cell_content = $cell; + $cell_attributes = new Attribute(); + $is_header = TRUE; + } + else { + if (isset($cell['colspan'])) { + $header_columns += $cell['colspan']; + } + else { + $header_columns++; + } + $cell_content = ''; + if (isset($cell['data'])) { + $cell_content = $cell['data']; + unset($cell['data']); + } + // Flag the cell as a header or not and remove the flag. + $is_header = $cell['header'] ?? TRUE; + unset($cell['header']); + + // Track responsive classes for each column as needed. Only the header + // cells for a column are marked up with the responsive classes by a + // module developer or themer. The responsive classes on the header + // cells must be transferred to the content cells. + if (!empty($cell['class']) && is_array($cell['class'])) { + if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) { + $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM; + } + elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) { + $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW; + } + } + + TableSort::header($cell_content, $cell, $variables['header'], $ts); + + // TableSort::header() removes the 'sort', 'initial_click_sort' and + // 'field' keys. + $cell_attributes = new Attribute($cell); + } + $variables['header'][$col_key] = []; + $variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td'; + $variables['header'][$col_key]['attributes'] = $cell_attributes; + $variables['header'][$col_key]['content'] = $cell_content; + } + } + $variables['header_columns'] = $header_columns; + + // Rows and footer have the same structure. + $sections = ['rows' , 'footer']; + foreach ($sections as $section) { + if (!empty($variables[$section])) { + foreach ($variables[$section] as $row_key => $row) { + $cells = $row; + $row_attributes = []; + + // Check if we're dealing with a simple or complex row + if (isset($row['data'])) { + $cells = $row['data']; + $variables['no_striping'] = $row['no_striping'] ?? FALSE; + + // Set the attributes array and exclude 'data' and 'no_striping'. + $row_attributes = $row; + unset($row_attributes['data']); + unset($row_attributes['no_striping']); + } + + // Build row. + $variables[$section][$row_key] = []; + $variables[$section][$row_key]['attributes'] = new Attribute($row_attributes); + $variables[$section][$row_key]['cells'] = []; + if (!empty($cells)) { + // Reset the responsive index. + $responsive_index = -1; + foreach ($cells as $col_key => $cell) { + // Increase the responsive index. + $responsive_index++; + + if (!is_array($cell)) { + $cell_content = $cell; + $cell_attributes = []; + $is_header = FALSE; + } + else { + $cell_content = ''; + if (isset($cell['data'])) { + $cell_content = $cell['data']; + unset($cell['data']); + } + + // Flag the cell as a header or not and remove the flag. + $is_header = !empty($cell['header']); + unset($cell['header']); + + $cell_attributes = $cell; + } + // Active table sort information. + if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) { + $variables[$section][$row_key]['cells'][$col_key]['active_table_sort'] = TRUE; + } + // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM + // class from header to cell as needed. + if (isset($responsive_classes[$responsive_index])) { + $cell_attributes['class'][] = $responsive_classes[$responsive_index]; + } + $variables[$section][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td'; + $variables[$section][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes); + $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content; + } + } + } + } + } + if (empty($variables['no_striping'])) { + $variables['attributes']['data-striping'] = 1; + } + } + + /** + * Prepares variables for tablesort indicators. + * + * Default template: tablesort-indicator.html.twig. + */ + public function preprocessTablesortIndicator(array &$variables): void { + $variables['#attached']['library'][] = 'core/drupal.tablesort'; + } + + /** + * Prepares variables for item list templates. + * + * Default template: item-list.html.twig. + * + * @param array $variables + * An associative array containing: + * - items: An array of items to be displayed in the list. Each item can be + * either a string or a render array. If #type, #theme, or #markup + * properties are not specified for child render arrays, they will be + * inherited from the parent list, allowing callers to specify larger + * nested lists without having to explicitly specify and repeat the + * render properties for all nested child lists. + * - title: A title to be prepended to the list. + * - list_type: The type of list to return (e.g. "ul", "ol"). + * - wrapper_attributes: HTML attributes to be applied to the list wrapper. + * + * @see https://www.drupal.org/node/1842756 + */ + public function preprocessItemList(array &$variables): void { + $variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']); + $variables['#attached']['library'][] = 'core/drupal.item-list'; + foreach ($variables['items'] as &$item) { + $attributes = []; + // If the item value is an array, then it is a render array. + if (is_array($item)) { + // List items support attributes via the '#wrapper_attributes' property. + if (isset($item['#wrapper_attributes'])) { + $attributes = $item['#wrapper_attributes']; + } + // Determine whether there are any child elements in the item that are + // not fully-specified render arrays. If there are any, then the child + // elements present nested lists and we automatically inherit the render + // array properties of the current list to them. + foreach (Element::children($item) as $key) { + $child = &$item[$key]; + // If this child element does not specify how it can be rendered, then + // we need to inherit the render properties of the current list. + if (!isset($child['#type']) && !isset($child['#theme']) && !isset($child['#markup'])) { + // Since item-list.html.twig supports both strings and render arrays + // as items, the items of the nested list may have been specified as + // the child elements of the nested list, instead of #items. For + // convenience, we automatically move them into #items. + if (!isset($child['#items'])) { + // This is the same condition as in + // \Drupal\Core\Render\Element::children(), which cannot be used + // here, since it triggers an error on string values. + foreach ($child as $child_key => $child_value) { + if (is_int($child_key) || $child_key === '' || $child_key[0] !== '#') { + $child['#items'][$child_key] = $child_value; + unset($child[$child_key]); + } + } + } + // Lastly, inherit the original theme variables of the current list. + $child['#theme'] = $variables['theme_hook_original']; + $child['#list_type'] = $variables['list_type']; + } + } + } + + // Set the item's value and attributes for the template. + $item = [ + 'value' => $item, + 'attributes' => new Attribute($attributes), + ]; + } + } + + /** + * Prepares variables for maintenance task list templates. + * + * Default template: maintenance-task-list.html.twig. + * + * @param array $variables + * An associative array containing: + * - items: An associative array of maintenance tasks. + * It's the caller's responsibility to ensure this array's items contain + * no dangerous HTML such as <script> tags. + * - active: The key for the currently active maintenance task. + */ + public function preprocessMaintenanceTaskList(array &$variables): void { + $items = $variables['items']; + $active = $variables['active']; + + $done = isset($items[$active]) || $active == NULL; + foreach ($items as $k => $item) { + $variables['tasks'][$k]['item'] = $item; + $variables['tasks'][$k]['attributes'] = new Attribute(); + if ($active == $k) { + $variables['tasks'][$k]['attributes']->addClass('is-active'); + $variables['tasks'][$k]['status'] = $this->t('active'); + $done = FALSE; + } + else { + if ($done) { + $variables['tasks'][$k]['attributes']->addClass('done'); + $variables['tasks'][$k]['status'] = $this->t('done'); + } + } + } + } + } diff --git a/core/lib/Drupal/Core/Update/UpdateHookRegistry.php b/core/lib/Drupal/Core/Update/UpdateHookRegistry.php index 19e64d987805..fa4ed8807b31 100644 --- a/core/lib/Drupal/Core/Update/UpdateHookRegistry.php +++ b/core/lib/Drupal/Core/Update/UpdateHookRegistry.php @@ -112,7 +112,7 @@ class UpdateHookRegistry { // possible functions which match '_update_'. We use preg_grep() here // since looping through all PHP functions can take significant page // execution time and this function is called on every administrative page - // via system_requirements(). + // via the system runtime_requirements hook. foreach (preg_grep('/_\d+$/', $functions['user']) as $function) { // If this function is a module update function, add it to the list of // module updates. diff --git a/core/lib/Drupal/Core/Utility/Error.php b/core/lib/Drupal/Core/Utility/Error.php index 459af44d8c51..e460179b6f47 100644 --- a/core/lib/Drupal/Core/Utility/Error.php +++ b/core/lib/Drupal/Core/Utility/Error.php @@ -211,11 +211,15 @@ class Error { * * @return callable|null * The current error handler as a callable, or NULL if none is set. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Use + * get_error_handler() instead. + * + * @see https://www.drupal.org/node/3529500 */ public static function currentErrorHandler(): ?callable { - $currentHandler = set_error_handler('var_dump'); - restore_error_handler(); - return $currentHandler; + @trigger_error(__METHOD__ . ' is deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Use get_error_handler() instead. See https://www.drupal.org/node/3529500', E_USER_DEPRECATED); + return get_error_handler(); } } diff --git a/core/lib/Drupal/Core/Validation/ConstraintManager.php b/core/lib/Drupal/Core/Validation/ConstraintManager.php index 00f3c862af45..56f55832dc5b 100644 --- a/core/lib/Drupal/Core/Validation/ConstraintManager.php +++ b/core/lib/Drupal/Core/Validation/ConstraintManager.php @@ -15,6 +15,7 @@ use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\IdenticalTo; use Symfony\Component\Validator\Constraints\Image; use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\PositiveOrZero; /** * Constraint plugin manager. @@ -123,6 +124,11 @@ class ConstraintManager extends DefaultPluginManager { 'class' => Image::class, 'type' => ['string'], ]); + $this->getDiscovery()->setDefinition('PositiveOrZero', [ + 'label' => new TranslatableMarkup('Positive or zero'), + 'class' => PositiveOrZero::class, + 'type' => ['integer'], + ]); $this->getDiscovery()->setDefinition('IdenticalTo', [ 'label' => new TranslatableMarkup('IdenticalTo'), 'class' => IdenticalTo::class, diff --git a/core/modules/announcements_feed/tests/src/Kernel/AnnounceTestBase.php b/core/modules/announcements_feed/tests/src/Kernel/AnnounceTestBase.php index b70ad5d6c275..e017014afe58 100644 --- a/core/modules/announcements_feed/tests/src/Kernel/AnnounceTestBase.php +++ b/core/modules/announcements_feed/tests/src/Kernel/AnnounceTestBase.php @@ -14,7 +14,7 @@ use GuzzleHttp\Psr7\Response; /** * Base class for Announce Kernel tests. */ -class AnnounceTestBase extends KernelTestBase { +abstract class AnnounceTestBase extends KernelTestBase { /** * {@inheritdoc} diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml index b73f96422317..97623ff523af 100644 --- a/core/modules/block/block.services.yml +++ b/core/modules/block/block.services.yml @@ -11,3 +11,4 @@ services: class: Drupal\block\BlockRepository arguments: ['@entity_type.manager', '@theme.manager', '@context.handler'] Drupal\block\BlockRepositoryInterface: '@block.repository' + Drupal\block\BlockConfigUpdater: ~ diff --git a/core/modules/block/src/BlockConfigUpdater.php b/core/modules/block/src/BlockConfigUpdater.php new file mode 100644 index 000000000000..317d19ddc64f --- /dev/null +++ b/core/modules/block/src/BlockConfigUpdater.php @@ -0,0 +1,85 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\block; + +/** + * Provides a BC layer for modules providing old configurations. + * + * @internal + */ +class BlockConfigUpdater { + + /** + * Flag determining whether deprecations should be triggered. + * + * @var bool + */ + protected bool $deprecationsEnabled = TRUE; + + /** + * Stores which deprecations were triggered. + * + * @var array + */ + protected array $triggeredDeprecations = []; + + /** + * Sets the deprecations enabling status. + * + * @param bool $enabled + * Whether deprecations should be enabled. + */ + public function setDeprecationsEnabled(bool $enabled): void { + $this->deprecationsEnabled = $enabled; + } + + /** + * Performs the required update. + * + * @param \Drupal\block\BlockInterface $block + * The block to update. + * + * @return bool + * Whether the block was updated. + */ + public function updateBlock(BlockInterface $block): bool { + $changed = FALSE; + if ($this->needsInfoStatusSettingsRemoved($block)) { + $settings = $block->get('settings'); + unset($settings['info'], $settings['status']); + $block->set('settings', $settings); + $changed = TRUE; + } + return $changed; + } + + /** + * Checks if the block contains deprecated info and status settings. + * + * @param \Drupal\block\BlockInterface $block + * The block to update. + * + * @return bool + * TRUE if the block has deprecated settings. + */ + public function needsInfoStatusSettingsRemoved(BlockInterface $block): bool { + if (!str_starts_with($block->getPluginId(), 'block_content')) { + return FALSE; + } + $settings = $block->get('settings'); + if (!isset($settings['info']) && !isset($settings['status'])) { + return FALSE; + } + + $deprecations_triggered = &$this->triggeredDeprecations['3426302'][$block->id()]; + if ($this->deprecationsEnabled && !$deprecations_triggered) { + $deprecations_triggered = TRUE; + @trigger_error('Block content blocks with the "status" and "info" settings is deprecated in drupal:11.3.0 and will be removed in drupal:12.0.0. They were unused, so there is no replacement. Profile, module and theme provided configuration should be updated. See https://www.drupal.org/node/3499836', E_USER_DEPRECATED); + } + + return TRUE; + } + +} diff --git a/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php b/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php index dc96d95e6996..cb94233ebaff 100644 --- a/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php +++ b/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Drupal\Tests\block\Kernel\Migrate\d6; use Drupal\block\Entity\Block; -use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase; use Drupal\block\Hook\BlockHooks; +use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase; /** * Tests migration of blocks to configuration entities. @@ -265,8 +265,6 @@ class MigrateBlockTest extends MigrateDrupal6TestBase { 'label' => 'Static Block', 'provider' => 'block_content', 'label_display' => 'visible', - 'status' => TRUE, - 'info' => '', 'view_mode' => 'full', ]; $this->assertEntity('block', $visibility, 'content', 'olivero', 0, $settings); @@ -283,8 +281,6 @@ class MigrateBlockTest extends MigrateDrupal6TestBase { 'label' => 'Another Static Block', 'provider' => 'block_content', 'label_display' => 'visible', - 'status' => TRUE, - 'info' => '', 'view_mode' => 'full', ]; // We expect this block to be disabled because '' is not a valid region, @@ -296,8 +292,6 @@ class MigrateBlockTest extends MigrateDrupal6TestBase { 'label' => '', 'provider' => 'block_content', 'label_display' => '0', - 'status' => TRUE, - 'info' => '', 'view_mode' => 'full', ]; $this->assertEntity('block_2', [], 'right', 'test_theme', -7, $settings); diff --git a/core/modules/block_content/block_content.post_update.php b/core/modules/block_content/block_content.post_update.php index 592b3ee97147..53021a078f6a 100644 --- a/core/modules/block_content/block_content.post_update.php +++ b/core/modules/block_content/block_content.post_update.php @@ -5,6 +5,10 @@ * Post update functions for Content Block. */ +use Drupal\block\BlockConfigUpdater; +use Drupal\block\BlockInterface; +use Drupal\Core\Config\Entity\ConfigEntityUpdater; + /** * Implements hook_removed_post_updates(). */ @@ -18,3 +22,16 @@ function block_content_removed_post_updates(): array { 'block_content_post_update_revision_type' => '11.0.0', ]; } + +/** + * Remove deprecated status and info keys from block_content blocks. + */ +function block_content_post_update_remove_block_content_status_info_keys(array &$sandbox = []): void { + /** @var \Drupal\block\BlockConfigUpdater $blockConfigUpdater */ + $blockConfigUpdater = \Drupal::service(BlockConfigUpdater::class); + $blockConfigUpdater->setDeprecationsEnabled(FALSE); + \Drupal::classResolver(ConfigEntityUpdater::class) + ->update($sandbox, 'block', function (BlockInterface $block) use ($blockConfigUpdater): bool { + return $blockConfigUpdater->needsInfoStatusSettingsRemoved($block); + }); +} diff --git a/core/modules/block_content/config/schema/block_content.schema.yml b/core/modules/block_content/config/schema/block_content.schema.yml index 8c4161df5b5e..6d7b2aeadc8f 100644 --- a/core/modules/block_content/config/schema/block_content.schema.yml +++ b/core/modules/block_content/config/schema/block_content.schema.yml @@ -32,12 +32,12 @@ block.settings.block_content:*: FullyValidatable: ~ mapping: # @see \Drupal\block_content\Plugin\Block\BlockContentBlock::defaultConfiguration() - # @todo Deprecate this in https://www.drupal.org/project/drupal/issues/3426302 status: + deprecated: "The 'status' setting for content blocks is deprecated in drupal:11.3.0 and is removed from drupal 12.0.0. It was unused, so there is no replacement. See https://www.drupal.org/node/3499836." type: boolean label: 'Status' - # @todo Deprecate this in https://www.drupal.org/project/drupal/issues/3426302 info: + deprecated: "The 'info' setting for content blocks is deprecated in drupal:11.3.0 and is removed from drupal 12.0.0. It was unused, so there is no replacement. See https://www.drupal.org/node/3499836." type: label label: 'Admin info' view_mode: diff --git a/core/modules/block_content/src/BlockContentTypeInterface.php b/core/modules/block_content/src/BlockContentTypeInterface.php index aaf7957b8952..9c12709b2a55 100644 --- a/core/modules/block_content/src/BlockContentTypeInterface.php +++ b/core/modules/block_content/src/BlockContentTypeInterface.php @@ -3,19 +3,12 @@ namespace Drupal\block_content; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\Core\Entity\EntityDescriptionInterface; use Drupal\Core\Entity\RevisionableEntityBundleInterface; /** * Provides an interface defining a block type entity. */ -interface BlockContentTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface { - - /** - * Returns the description of the block type. - * - * @return string - * The description of the type of this block. - */ - public function getDescription(); +interface BlockContentTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface, EntityDescriptionInterface { } diff --git a/core/modules/block_content/src/Entity/BlockContentType.php b/core/modules/block_content/src/Entity/BlockContentType.php index ecbc6c3866d2..fa6fe3955038 100644 --- a/core/modules/block_content/src/Entity/BlockContentType.php +++ b/core/modules/block_content/src/Entity/BlockContentType.php @@ -100,6 +100,13 @@ class BlockContentType extends ConfigEntityBundleBase implements BlockContentTyp /** * {@inheritdoc} */ + public function setDescription($description): static { + return $this->set('description', $description); + } + + /** + * {@inheritdoc} + */ public function shouldCreateNewRevision() { return $this->revision; } diff --git a/core/modules/block_content/src/Hook/BlockContentHooks.php b/core/modules/block_content/src/Hook/BlockContentHooks.php index 4eef9bb8580c..d960c0a22d68 100644 --- a/core/modules/block_content/src/Hook/BlockContentHooks.php +++ b/core/modules/block_content/src/Hook/BlockContentHooks.php @@ -2,6 +2,7 @@ namespace Drupal\block_content\Hook; +use Drupal\block\BlockConfigUpdater; use Drupal\block\BlockInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\block_content\BlockContentInterface; @@ -160,4 +161,15 @@ class BlockContentHooks { return $operations; } + /** + * Implements hook_ENTITY_TYPE_presave(). + */ + #[Hook('block_presave')] + public function blockPreSave(BlockInterface $block): void { + // Use an inline service since DI would require enabling the block module + // in any Kernel test that installs block_content. This is BC code so will + // be removed in Drupal 12 anyway. + \Drupal::service(BlockConfigUpdater::class)->updateBlock($block); + } + } diff --git a/core/modules/block_content/src/Plugin/Block/BlockContentBlock.php b/core/modules/block_content/src/Plugin/Block/BlockContentBlock.php index c42fb2f5de54..131a56b516f8 100644 --- a/core/modules/block_content/src/Plugin/Block/BlockContentBlock.php +++ b/core/modules/block_content/src/Plugin/Block/BlockContentBlock.php @@ -132,8 +132,6 @@ class BlockContentBlock extends BlockBase implements ContainerFactoryPluginInter */ public function defaultConfiguration() { return [ - 'status' => TRUE, - 'info' => '', 'view_mode' => 'full', ]; } diff --git a/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobar_gorilla.yml b/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobar_gorilla.yml index 998c43340ebb..481478095608 100644 --- a/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobar_gorilla.yml +++ b/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobar_gorilla.yml @@ -16,8 +16,6 @@ settings: label: 'Foobar Gorilla' label_display: visible provider: block_content - status: true - info: '' view_mode: full visibility: request_path: diff --git a/core/modules/block_content/tests/src/Functional/BlockContentTestBase.php b/core/modules/block_content/tests/src/Functional/BlockContentTestBase.php index d0a3794b978d..ffee97e46793 100644 --- a/core/modules/block_content/tests/src/Functional/BlockContentTestBase.php +++ b/core/modules/block_content/tests/src/Functional/BlockContentTestBase.php @@ -4,8 +4,7 @@ declare(strict_types=1); namespace Drupal\Tests\block_content\Functional; -use Drupal\block_content\Entity\BlockContent; -use Drupal\block_content\Entity\BlockContentType; +use Drupal\Tests\block_content\Traits\BlockContentCreationTrait; use Drupal\Tests\BrowserTestBase; /** @@ -13,6 +12,8 @@ use Drupal\Tests\BrowserTestBase; */ abstract class BlockContentTestBase extends BrowserTestBase { + use BlockContentCreationTrait; + /** * Profile to use. * @@ -64,80 +65,4 @@ abstract class BlockContentTestBase extends BrowserTestBase { $this->drupalPlaceBlock('local_actions_block'); } - /** - * Creates a content block. - * - * @param bool|string $title - * (optional) Title of block. When no value is given uses a random name. - * Defaults to FALSE. - * @param string $bundle - * (optional) Bundle name. Defaults to 'basic'. - * @param bool $save - * (optional) Whether to save the block. Defaults to TRUE. - * - * @return \Drupal\block_content\Entity\BlockContent - * Created content block. - */ - protected function createBlockContent($title = FALSE, $bundle = 'basic', $save = TRUE) { - $title = $title ?: $this->randomMachineName(); - $block_content = BlockContent::create([ - 'info' => $title, - 'type' => $bundle, - 'langcode' => 'en', - ]); - if ($block_content && $save === TRUE) { - $block_content->save(); - } - return $block_content; - } - - /** - * Creates a block type (bundle). - * - * @param array|string $values - * (deprecated) The variable $values as string is deprecated. Provide as an - * array as parameter. The value to create the block content type. If - * $values is an array it should be like: ['id' => 'foo', 'label' => 'Foo']. - * If $values is a string, it will be considered that it represents the - * label. - * @param bool $create_body - * Whether or not to create the body field. - * - * @return \Drupal\block_content\Entity\BlockContentType - * Created block type. - */ - protected function createBlockContentType($values, $create_body = FALSE) { - if (is_string($values)) { - @trigger_error('Using the variable $values as string is deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Provide an array as parameter. See https://www.drupal.org/node/3473739', E_USER_DEPRECATED); - } - if (is_array($values)) { - if (!isset($values['id'])) { - do { - $id = $this->randomMachineName(8); - } while (BlockContentType::load($id)); - } - else { - $id = $values['id']; - } - $values += [ - 'id' => $id, - 'label' => $id, - 'revision' => FALSE, - ]; - $bundle = BlockContentType::create($values); - } - else { - $bundle = BlockContentType::create([ - 'id' => $values, - 'label' => $values, - 'revision' => FALSE, - ]); - } - $bundle->save(); - if ($create_body) { - block_content_add_body_field($bundle->id()); - } - return $bundle; - } - } diff --git a/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php b/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php index fba8362cd2cd..2ec82c2195b8 100644 --- a/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php +++ b/core/modules/block_content/tests/src/Functional/BlockContentTypeTest.php @@ -58,14 +58,26 @@ class BlockContentTypeTest extends BlockContentTestBase { } /** - * Tests the order of the block content types on the add page. + * Tests the block types on the block/add page. */ - public function testBlockContentAddPageOrder(): void { - $this->createBlockContentType(['id' => 'bundle_1', 'label' => 'Bundle 1']); - $this->createBlockContentType(['id' => 'bundle_2', 'label' => 'Aaa Bundle 2']); + public function testBlockContentAddPage(): void { + $this->createBlockContentType([ + 'id' => 'bundle_1', + 'label' => 'Bundle 1', + 'description' => 'Bundle 1 description', + ]); + $this->createBlockContentType([ + 'id' => 'bundle_2', + 'label' => 'Aaa Bundle 2', + 'description' => 'Bundle 2 description', + ]); $this->drupalLogin($this->adminUser); $this->drupalGet('block/add'); + // Ensure bundles are ordered by their label, not id. $this->assertSession()->pageTextMatches('/Aaa Bundle 2(.*)Bundle 1/'); + // Block type descriptions should display. + $this->assertSession()->pageTextContains('Bundle 1 description'); + $this->assertSession()->pageTextContains('Bundle 2 description'); } /** diff --git a/core/modules/block_content/tests/src/Functional/Update/BlockContentStatusInfoUpdatePathTest.php b/core/modules/block_content/tests/src/Functional/Update/BlockContentStatusInfoUpdatePathTest.php new file mode 100644 index 000000000000..fb4bff9e63bc --- /dev/null +++ b/core/modules/block_content/tests/src/Functional/Update/BlockContentStatusInfoUpdatePathTest.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\block_content\Functional\Update; + +use Drupal\block\Entity\Block; +use Drupal\FunctionalTests\Update\UpdatePathTestBase; + +// cspell:ignore anotherblock + +/** + * Tests block_content_post_update_remove_block_content_status_info_keys. + * + * @group block_content + */ +class BlockContentStatusInfoUpdatePathTest extends UpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles(): void { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-10.3.0.filled.standard.php.gz', + ]; + } + + /** + * Tests block_content_post_update_remove_block_content_status_info_keys. + */ + public function testRunUpdates(): void { + $this->assertArrayHasKey('info', Block::load('anotherblock')->get('settings')); + $this->assertArrayHasKey('status', Block::load('anotherblock')->get('settings')); + + $this->runUpdates(); + + $this->assertArrayNotHasKey('info', Block::load('anotherblock')->get('settings')); + $this->assertArrayNotHasKey('status', Block::load('anotherblock')->get('settings')); + } + +} diff --git a/core/modules/block_content/tests/src/Functional/Views/BlockContentTestBase.php b/core/modules/block_content/tests/src/Functional/Views/BlockContentTestBase.php index 1b8af1195eec..04c54c637d1a 100644 --- a/core/modules/block_content/tests/src/Functional/Views/BlockContentTestBase.php +++ b/core/modules/block_content/tests/src/Functional/Views/BlockContentTestBase.php @@ -4,8 +4,7 @@ declare(strict_types=1); namespace Drupal\Tests\block_content\Functional\Views; -use Drupal\block_content\Entity\BlockContent; -use Drupal\block_content\Entity\BlockContentType; +use Drupal\Tests\block_content\Traits\BlockContentCreationTrait; use Drupal\Tests\views\Functional\ViewTestBase; /** @@ -13,6 +12,11 @@ use Drupal\Tests\views\Functional\ViewTestBase; */ abstract class BlockContentTestBase extends ViewTestBase { + use BlockContentCreationTrait { + createBlockContent as baseCreateBlockContent; + createBlockContentType as baseCreateBlockContentType; + } + /** * Admin user. * @@ -67,7 +71,7 @@ abstract class BlockContentTestBase extends ViewTestBase { 'type' => 'basic', 'langcode' => 'en', ]; - if ($block_content = BlockContent::create($values)) { + if ($block_content = $this->baseCreateBlockContent(save: FALSE, values: $values)) { $status = $block_content->save(); } $this->assertEquals(SAVED_NEW, $status, "Created block content {$block_content->label()}."); @@ -84,26 +88,7 @@ abstract class BlockContentTestBase extends ViewTestBase { * Created block type. */ protected function createBlockContentType(array $values = []) { - // Find a non-existent random type name. - if (!isset($values['id'])) { - do { - $id = $this->randomMachineName(8); - } while (BlockContentType::load($id)); - } - else { - $id = $values['id']; - } - $values += [ - 'id' => $id, - 'label' => $id, - 'revision' => FALSE, - ]; - $bundle = BlockContentType::create($values); - $status = $bundle->save(); - block_content_add_body_field($bundle->id()); - - $this->assertEquals(SAVED_NEW, $status, sprintf('Created block content type %s.', $bundle->id())); - return $bundle; + return $this->baseCreateBlockContentType($values, TRUE); } } diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentTest.php index bb0186431e9a..fe07b3d5652e 100644 --- a/core/modules/block_content/tests/src/Kernel/BlockContentTest.php +++ b/core/modules/block_content/tests/src/Kernel/BlockContentTest.php @@ -38,6 +38,19 @@ class BlockContentTest extends KernelTestBase { } /** + * Tests BlockContentType functionality. + */ + public function testBlockContentType(): void { + $type = BlockContentType::create([ + 'id' => 'foo', + 'label' => 'Foo', + ]); + $this->assertSame('', $type->getDescription()); + $type->setDescription('Test description'); + $this->assertSame('Test description', $type->getDescription()); + } + + /** * Tests the editing links for BlockContentBlock. */ public function testOperationLinks(): void { diff --git a/core/modules/block_content/tests/src/Traits/BlockContentCreationTrait.php b/core/modules/block_content/tests/src/Traits/BlockContentCreationTrait.php new file mode 100644 index 000000000000..0322a3ebf173 --- /dev/null +++ b/core/modules/block_content/tests/src/Traits/BlockContentCreationTrait.php @@ -0,0 +1,94 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\block_content\Traits; + +use Drupal\block_content\Entity\BlockContent; +use Drupal\block_content\Entity\BlockContentType; + +/** + * Provides methods for creating block_content entities and types. + */ +trait BlockContentCreationTrait { + + /** + * Creates a content block. + * + * @param bool|string $title + * (optional) Title of block. When no value is given uses a random name. + * Defaults to FALSE. + * @param string $bundle + * (optional) Bundle name. Defaults to 'basic'. + * @param bool $save + * (optional) Whether to save the block. Defaults to TRUE. + * @param array $values + * (optional) Additional values for the block_content entity. + * + * @return \Drupal\block_content\Entity\BlockContent + * Created content block. + */ + protected function createBlockContent(bool|string $title = FALSE, string $bundle = 'basic', bool $save = TRUE, array $values = []): BlockContent { + $title = $title ?: $this->randomMachineName(); + $values += [ + 'info' => $title, + 'type' => $bundle, + 'langcode' => 'en', + ]; + $block_content = BlockContent::create($values); + if ($block_content && $save === TRUE) { + $block_content->save(); + } + return $block_content; + } + + /** + * Creates a block type (bundle). + * + * @param array|string $values + * (deprecated) The variable $values as string is deprecated. Provide as an + * array as parameter. The value to create the block content type. If + * $values is an array it should be like: ['id' => 'foo', 'label' => 'Foo']. + * If $values is a string, it will be considered that it represents the + * label. + * @param bool $create_body + * Whether or not to create the body field. + * + * @return \Drupal\block_content\Entity\BlockContentType + * Created block type. + */ + protected function createBlockContentType(array|string $values, bool $create_body = FALSE): BlockContentType { + if (is_string($values)) { + @trigger_error('Using the variable $values as string is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Provide an array as parameter. See https://www.drupal.org/node/3473739', E_USER_DEPRECATED); + } + if (is_array($values)) { + if (!isset($values['id'])) { + do { + $id = $this->randomMachineName(8); + } while (BlockContentType::load($id)); + } + else { + $id = $values['id']; + } + $values += [ + 'id' => $id, + 'label' => $id, + 'revision' => FALSE, + ]; + $bundle = BlockContentType::create($values); + } + else { + $bundle = BlockContentType::create([ + 'id' => $values, + 'label' => $values, + 'revision' => FALSE, + ]); + } + $bundle->save(); + if ($create_body) { + block_content_add_body_field($bundle->id()); + } + return $bundle; + } + +} diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTestTestBase.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTestTestBase.php index 070ae0e90cad..a6230938d12b 100644 --- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTestTestBase.php +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTestTestBase.php @@ -13,12 +13,9 @@ use Symfony\Component\Validator\ConstraintViolationInterface; // cspell:ignore imageresize imageupload /** - * @coversDefaultClass \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Image - * @group ckeditor5 - * @group #slow * @internal */ -class ImageTestTestBase extends ImageTestBase { +abstract class ImageTestTestBase extends ImageTestBase { /** * The sample image File entity to embed. diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageUrlTestBase.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageUrlTestBase.php index 5d0d6c50c824..1638a44cd280 100644 --- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageUrlTestBase.php +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageUrlTestBase.php @@ -12,12 +12,9 @@ use Symfony\Component\Validator\ConstraintViolationInterface; // cspell:ignore imageresize /** - * @coversDefaultClass \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Image - * @group ckeditor5 - * @group #slow * @internal */ -class ImageUrlTestBase extends ImageTestBase { +abstract class ImageUrlTestBase extends ImageTestBase { /** * {@inheritdoc} diff --git a/core/modules/ckeditor5/tests/src/Unit/CKEditor5ImageControllerTest.php b/core/modules/ckeditor5/tests/src/Unit/CKEditor5ImageControllerTest.php new file mode 100644 index 000000000000..bb69c28c6fbf --- /dev/null +++ b/core/modules/ckeditor5/tests/src/Unit/CKEditor5ImageControllerTest.php @@ -0,0 +1,85 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\ckeditor5\Unit; + +use Drupal\ckeditor5\Controller\CKEditor5ImageController; +use Drupal\ckeditor5\Plugin\CKEditor5PluginManagerInterface; +use Drupal\Core\Entity\EntityConstraintViolationList; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\EntityTypeRepositoryInterface; +use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Lock\LockBackendInterface; +use Drupal\editor\EditorInterface; +use Drupal\file\Entity\File; +use Drupal\file\FileInterface; +use Drupal\file\Upload\FileUploadHandlerInterface; +use Drupal\Tests\UnitTestCase; +use Prophecy\Argument; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\HttpException; + +/** + * Tests CKEditor5ImageController. + * + * @group ckeditor5 + * @coversDefaultClass \Drupal\ckeditor5\Controller\CKEditor5ImageController + */ +final class CKEditor5ImageControllerTest extends UnitTestCase { + + /** + * Tests that upload fails correctly when the file is too large. + */ + public function testInvalidFile(): void { + $file_system = $this->prophesize(FileSystemInterface::class); + $file_system->move(Argument::any())->shouldNotBeCalled(); + $directory = 'public://'; + $file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)->willReturn(TRUE); + $file_system->getDestinationFilename(Argument::cetera())->willReturn('/tmp/foo.txt'); + $lock = $this->prophesize(LockBackendInterface::class); + $lock->acquire(Argument::any())->willReturn(TRUE); + $container = $this->prophesize(ContainerInterface::class); + $file_storage = $this->prophesize(EntityStorageInterface::class); + $file = $this->prophesize(FileInterface::class); + $violations = $this->prophesize(EntityConstraintViolationList::class); + $violations->count()->willReturn(0); + $file->validate()->willReturn($violations->reveal()); + $file_storage->create(Argument::any())->willReturn($file->reveal()); + $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class); + $entity_type_manager->getStorage('file')->willReturn($file_storage->reveal()); + $container->get('entity_type.manager')->willReturn($entity_type_manager->reveal()); + $entity_type_repository = $this->prophesize(EntityTypeRepositoryInterface::class); + $entity_type_repository->getEntityTypeFromClass(File::class)->willReturn('file'); + $container->get('entity_type.repository')->willReturn($entity_type_repository->reveal()); + \Drupal::setContainer($container->reveal()); + $controller = new CKEditor5ImageController( + $file_system->reveal(), + $this->prophesize(FileUploadHandlerInterface::class)->reveal(), + $lock->reveal(), + $this->prophesize(CKEditor5PluginManagerInterface::class)->reveal(), + ); + // We can't use vfsstream here because of how Symfony request works. + $file_uri = tempnam(sys_get_temp_dir(), 'tmp'); + $fp = fopen($file_uri, 'w'); + fwrite($fp, 'foo'); + fclose($fp); + $request = Request::create('/', files: [ + 'upload' => [ + 'name' => 'foo.txt', + 'type' => 'text/plain', + 'size' => 42, + 'tmp_name' => $file_uri, + 'error' => \UPLOAD_ERR_FORM_SIZE, + ], + ]); + $editor = $this->prophesize(EditorInterface::class); + $request->attributes->set('editor', $editor->reveal()); + $this->expectException(HttpException::class); + $this->expectExceptionMessage('The file "foo.txt" exceeds the upload limit defined in your form.'); + $controller->upload($request); + } + +} diff --git a/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml b/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml index 2fe777a1da3e..8ac15c8f1bce 100644 --- a/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml +++ b/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml @@ -16,8 +16,6 @@ settings: label: 'Shop for cheap now!' label_display: visible provider: block_content - status: true - info: '' view_mode: full visibility: request_path: diff --git a/core/modules/config/tests/src/FunctionalJavascript/ConfigExportTest.php b/core/modules/config/tests/src/FunctionalJavascript/ConfigExportTest.php index 2489d31ae958..de9bb6ce8897 100644 --- a/core/modules/config/tests/src/FunctionalJavascript/ConfigExportTest.php +++ b/core/modules/config/tests/src/FunctionalJavascript/ConfigExportTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace Drupal\Tests\config\FunctionalJavascript; -use Drupal\block_content\Entity\BlockContent; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use Drupal\Tests\block_content\Traits\BlockContentCreationTrait; /** * Tests the config export form. @@ -14,6 +14,8 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase; */ class ConfigExportTest extends WebDriverTestBase { + use BlockContentCreationTrait; + /** * {@inheritdoc} */ @@ -58,26 +60,6 @@ class ConfigExportTest extends WebDriverTestBase { } /** - * Creates test blocks. - * - * @param string $title - * Title of the block. - * - * @return \Drupal\block_content\Entity\BlockContent - * The created block content entity. - * - * @throws \Drupal\Core\Entity\EntityStorageException - */ - protected function createBlockContent($title) { - $block_content = BlockContent::create([ - 'info' => $title, - 'type' => 'basic', - ]); - $block_content->save(); - return $block_content; - } - - /** * Tests Ajax form functionality on the config export page. */ public function testAjaxOnExportPage(): void { diff --git a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php index 4e8e77ff7da2..5a130bf160c3 100644 --- a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php +++ b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php @@ -9,6 +9,7 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Session\AccountInterface; use Drupal\content_moderation\ModerationInformation; use Drupal\content_moderation\StateTransitionValidationInterface; @@ -66,6 +67,8 @@ class ModerationStateWidget extends OptionsSelectWidget { * Field settings. * @param array $third_party_settings * Third party settings. + * @param \Drupal\Core\Render\ElementInfoManagerInterface $elementInfoManager + * The element info manager. * @param \Drupal\Core\Session\AccountInterface $current_user * Current user service. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager @@ -75,8 +78,8 @@ class ModerationStateWidget extends OptionsSelectWidget { * @param \Drupal\content_moderation\StateTransitionValidationInterface $validator * Moderation state transition validation service. */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ModerationInformation $moderation_information, StateTransitionValidationInterface $validator) { - parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $elementInfoManager, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ModerationInformation $moderation_information, StateTransitionValidationInterface $validator) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager); $this->entityTypeManager = $entity_type_manager; $this->currentUser = $current_user; $this->moderationInformation = $moderation_information; @@ -93,6 +96,7 @@ class ModerationStateWidget extends OptionsSelectWidget { $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], + $container->get('plugin.manager.element_info'), $container->get('current_user'), $container->get('entity_type.manager'), $container->get('content_moderation.moderation_information'), diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php index 9055d44982bf..4fc259a760a1 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php +++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php @@ -7,6 +7,7 @@ use Drupal\Core\Field\Attribute\FieldWidget; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -31,8 +32,8 @@ class DateTimeDefaultWidget extends DateTimeWidgetBase { /** * {@inheritdoc} */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityStorageInterface $date_storage) { - parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $elementInfoManager, EntityStorageInterface $date_storage) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager); $this->dateStorage = $date_storage; } @@ -47,7 +48,8 @@ class DateTimeDefaultWidget extends DateTimeWidgetBase { $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], - $container->get('entity_type.manager')->getStorage('date_format') + $container->get('plugin.manager.element_info'), + $container->get('entity_type.manager')->getStorage('date_format'), ); } diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php index 04c6f4eaf1c2..b2f90d662e9a 100644 --- a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php +++ b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php @@ -7,6 +7,7 @@ use Drupal\Core\Field\Attribute\FieldWidget; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -31,8 +32,8 @@ class DateRangeDefaultWidget extends DateRangeWidgetBase { /** * {@inheritdoc} */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityStorageInterface $date_storage) { - parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $elementInfoManager, EntityStorageInterface $date_storage) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager); $this->dateStorage = $date_storage; } @@ -47,6 +48,7 @@ class DateRangeDefaultWidget extends DateRangeWidgetBase { $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], + $container->get('plugin.manager.element_info'), $container->get('entity_type.manager')->getStorage('date_format') ); } diff --git a/core/modules/field/tests/modules/field_third_party_test/src/Hook/FieldThirdPartyTestHooks.php b/core/modules/field/tests/modules/field_third_party_test/src/Hook/FieldThirdPartyTestHooks.php index 369575020135..dd9a4167ba53 100644 --- a/core/modules/field/tests/modules/field_third_party_test/src/Hook/FieldThirdPartyTestHooks.php +++ b/core/modules/field/tests/modules/field_third_party_test/src/Hook/FieldThirdPartyTestHooks.php @@ -9,6 +9,9 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\WidgetInterface; use Drupal\Core\Hook\Attribute\Hook; +use Drupal\Core\Render\Element\Number; +use Drupal\Core\Render\Element\Textfield; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; /** @@ -18,17 +21,17 @@ class FieldThirdPartyTestHooks { use StringTranslationTrait; + public function __construct(protected ElementInfoManagerInterface $elementInfoManager) {} + /** * Implements hook_field_widget_third_party_settings_form(). */ #[Hook('field_widget_third_party_settings_form')] public function fieldWidgetThirdPartySettingsForm(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state): array { - $element['field_test_widget_third_party_settings_form'] = [ - '#type' => 'textfield', - '#title' => $this->t('3rd party widget settings form'), - '#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'field_test_widget_third_party_settings_form'), - ]; - return $element; + $textfield = $this->elementInfoManager->fromClass(Textfield::class); + $textfield->title = $this->t('3rd party widget settings form'); + $textfield->default_value = $plugin->getThirdPartySetting('field_third_party_test', 'field_test_widget_third_party_settings_form'); + return $textfield->toRenderable('field_test_widget_third_party_settings_form'); } /** @@ -36,12 +39,10 @@ class FieldThirdPartyTestHooks { */ #[Hook('field_widget_third_party_settings_form')] public function fieldWidgetThirdPartySettingsFormAdditionalImplementation(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state): array { - $element['second_field_widget_third_party_settings_form'] = [ - '#type' => 'number', - '#title' => $this->t('Second 3rd party widget settings form'), - '#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'second_field_widget_third_party_settings_form'), - ]; - return $element; + $number = $this->elementInfoManager->fromClass(Number::class); + $number->title = $this->t('Second 3rd party widget settings form'); + $number->default_value = $plugin->getThirdPartySetting('field_third_party_test', 'second_field_widget_third_party_settings_form'); + return $number->toRenderable('second_field_widget_third_party_settings_form'); } /** @@ -57,12 +58,10 @@ class FieldThirdPartyTestHooks { */ #[Hook('field_formatter_third_party_settings_form')] public function fieldFormatterThirdPartySettingsForm(FormatterInterface $plugin, FieldDefinitionInterface $field_definition, $view_mode, $form, FormStateInterface $form_state): array { - $element['field_test_field_formatter_third_party_settings_form'] = [ - '#type' => 'textfield', - '#title' => $this->t('3rd party formatter settings form'), - '#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'field_test_field_formatter_third_party_settings_form'), - ]; - return $element; + $textfield = $this->elementInfoManager->fromClass(Textfield::class); + $textfield->title = $this->t('3rd party formatter settings form'); + $textfield->default_value = $plugin->getThirdPartySetting('field_third_party_test', 'field_test_field_formatter_third_party_settings_form'); + return $textfield->toRenderable('field_test_field_formatter_third_party_settings_form'); } /** @@ -70,12 +69,10 @@ class FieldThirdPartyTestHooks { */ #[Hook('field_formatter_third_party_settings_form')] public function fieldFormatterThirdPartySettingsFormAdditionalImplementation(FormatterInterface $plugin, FieldDefinitionInterface $field_definition, $view_mode, $form, FormStateInterface $form_state): array { - $element['second_field_formatter_third_party_settings_form'] = [ - '#type' => 'number', - '#title' => $this->t('Second 3rd party formatter settings form'), - '#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'second_field_formatter_third_party_settings_form'), - ]; - return $element; + $number = $this->elementInfoManager->fromClass(Number::class); + $number->title = $this->t('Second 3rd party formatter settings form'); + $number->default_value = $plugin->getThirdPartySetting('field_third_party_test', 'second_field_formatter_third_party_settings_form'); + return $number->toRenderable('second_field_formatter_third_party_settings_form'); } /** diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module index 8904dd925e57..67c049fa5057 100644 --- a/core/modules/field_ui/field_ui.module +++ b/core/modules/field_ui/field_ui.module @@ -5,6 +5,7 @@ */ use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Theme\ThemePreprocess; use Drupal\field_ui\FieldUI; /** @@ -18,7 +19,7 @@ use Drupal\field_ui\FieldUI; * rendered as a table. */ function template_preprocess_field_ui_table(&$variables): void { - template_preprocess_table($variables); + \Drupal::service(ThemePreprocess::class)->preprocessTable($variables); } /** diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTestBase.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTestBase.php index 360e1f8ffa66..c8c99edf4f8d 100644 --- a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTestBase.php +++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTestBase.php @@ -15,7 +15,7 @@ use Drupal\Tests\field_ui\Traits\FieldUiTestTrait; /** * Tests the Field UI "Manage fields" screen. */ -class ManageFieldsFunctionalTestBase extends BrowserTestBase { +abstract class ManageFieldsFunctionalTestBase extends BrowserTestBase { use EntityReferenceFieldCreationTrait; use FieldUiTestTrait; diff --git a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php index a38e64ab53cc..bb4268ff389e 100644 --- a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php +++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php @@ -29,16 +29,10 @@ use Symfony\Component\Validator\ConstraintViolationListInterface; class FileWidget extends WidgetBase { /** - * The element info manager. - */ - protected ElementInfoManagerInterface $elementInfo; - - /** * {@inheritdoc} */ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $element_info) { - parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); - $this->elementInfo = $element_info; + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $element_info); } /** @@ -238,7 +232,7 @@ class FileWidget extends WidgetBase { // Essentially we use the managed_file type, extended with some // enhancements. - $element_info = $this->elementInfo->getInfo('managed_file'); + $element_info = $this->elementInfoManager->getInfo('managed_file'); $element += [ '#type' => 'managed_file', '#upload_location' => $items[$delta]->getUploadLocation(), diff --git a/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php b/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php index 1491510fd648..4bdcf5455f9d 100644 --- a/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php +++ b/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php @@ -14,14 +14,14 @@ class FileRequiredTestForm extends FileTestForm { /** * {@inheritdoc} */ - public function getFormId() { + public function getFormId(): string { return '_file_required_test_form'; } /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state) { + public function buildForm(array $form, FormStateInterface $form_state): array { $form = parent::buildForm($form, $form_state); $form['file_test_upload']['#required'] = TRUE; return $form; diff --git a/core/modules/file/tests/file_test/src/Form/FileTestForm.php b/core/modules/file/tests/file_test/src/Form/FileTestForm.php index d021a538f44c..902c675cdcf4 100644 --- a/core/modules/file/tests/file_test/src/Form/FileTestForm.php +++ b/core/modules/file/tests/file_test/src/Form/FileTestForm.php @@ -6,28 +6,28 @@ namespace Drupal\file_test\Form; use Drupal\Core\File\FileExists; use Drupal\Core\File\FileSystemInterface; -use Drupal\Core\Form\FormInterface; +use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; /** * File test form class. */ -class FileTestForm implements FormInterface { +class FileTestForm extends FormBase { use FileTestFormTrait; use StringTranslationTrait; /** * {@inheritdoc} */ - public function getFormId() { + public function getFormId(): string { return '_file_test_form'; } /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state) { + public function buildForm(array $form, FormStateInterface $form_state): array { $form = $this->baseForm($form, $form_state); @@ -42,12 +42,7 @@ class FileTestForm implements FormInterface { /** * {@inheritdoc} */ - public function validateForm(array &$form, FormStateInterface $form_state) {} - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { + public function submitForm(array &$form, FormStateInterface $form_state): void { // Process the upload and perform validation. Note: we're using the // form value for the $replace parameter. if (!$form_state->isValueEmpty('file_subdir')) { diff --git a/core/modules/filter/src/Element/TextFormat.php b/core/modules/filter/src/Element/TextFormat.php index 38ca133e3cd2..6b1fc25c3ffb 100644 --- a/core/modules/filter/src/Element/TextFormat.php +++ b/core/modules/filter/src/Element/TextFormat.php @@ -12,11 +12,15 @@ use Drupal\Core\Url; * Provides a text format render element. * * Properties: - * - #base_type: The form element #type to use for the 'value' element. + * + * @property $base_type + * The form element #type to use for the 'value' element. * 'textarea' by default. - * - #format: (optional) The text format ID to preselect. If omitted, the + * @property $format + * (optional) The text format ID to preselect. If omitted, the * default format for the current user will be used. - * - #allowed_formats: (optional) An array of text format IDs that are available + * @property $allowed_formats + * (optional) An array of text format IDs that are available * for this element. If omitted, all text formats that the current user has * access to will be allowed. * diff --git a/core/modules/jsonapi/jsonapi.api.php b/core/modules/jsonapi/jsonapi.api.php index 5b2f2002d25c..ca8c5ae993fb 100644 --- a/core/modules/jsonapi/jsonapi.api.php +++ b/core/modules/jsonapi/jsonapi.api.php @@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Access\AccessResult; +use Drupal\jsonapi\JsonApiFilter; /** * @defgroup jsonapi_architecture JSON:API Architecture @@ -263,10 +264,10 @@ use Drupal\Core\Access\AccessResult; * viewable. * - AccessResult::neutral() if the implementation has no opinion. * The supported subsets for which an access result may be returned are: - * - JSONAPI_FILTER_AMONG_ALL: all entities of the given type. - * - JSONAPI_FILTER_AMONG_PUBLISHED: all published entities of the given type. - * - JSONAPI_FILTER_AMONG_ENABLED: all enabled entities of the given type. - * - JSONAPI_FILTER_AMONG_OWN: all entities of the given type owned by the + * - JsonApiFilter::AMONG_ALL: all entities of the given type. + * - JsonApiFilter::AMONG_PUBLISHED: all published entities of the given type. + * - JsonApiFilter::AMONG_ENABLED: all enabled entities of the given type. + * - JsonApiFilter::AMONG_OWN: all entities of the given type owned by the * user for whom access is being checked. * See the documentation of the above constants for more information about * each subset. @@ -278,7 +279,7 @@ function hook_jsonapi_entity_filter_access(EntityTypeInterface $entity_type, Acc // by all entities of that type to users with that permission. if ($admin_permission = $entity_type->getAdminPermission()) { return ([ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission), + JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission), ]); } } @@ -305,9 +306,9 @@ function hook_jsonapi_entity_filter_access(EntityTypeInterface $entity_type, Acc */ function hook_jsonapi_ENTITY_TYPE_filter_access(EntityTypeInterface $entity_type, AccountInterface $account): array { return ([ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer llamas'), - JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view all published llamas'), - JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermissions($account, ['view own published llamas', 'view own unpublished llamas'], 'AND'), + JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer llamas'), + JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view all published llamas'), + JsonApiFilter::AMONG_OWN => AccessResult::allowedIfHasPermissions($account, ['view own published llamas', 'view own unpublished llamas'], 'AND'), ]); } diff --git a/core/modules/jsonapi/jsonapi.module b/core/modules/jsonapi/jsonapi.module index 69414af650b9..c512575305a4 100644 --- a/core/modules/jsonapi/jsonapi.module +++ b/core/modules/jsonapi/jsonapi.module @@ -11,6 +11,10 @@ * regardless of whether they are published or enabled, and regardless of * their owner. * + * @deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Use + * \Drupal\jsonapi\JsonApiFilter::AMONG_ALL instead. + * + * @see https://www.drupal.org/node/3495601 * @see hook_jsonapi_entity_filter_access() * @see hook_jsonapi_ENTITY_TYPE_filter_access() */ @@ -25,6 +29,10 @@ const JSONAPI_FILTER_AMONG_ALL = 'filter_among_all'; * This is used when an entity type has a "published" entity key and there's a * query condition for the value of that equaling 1. * + * @deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Use + * \Drupal\jsonapi\JsonApiFilter::AMONG_PUBLISHED instead. + * + * @see https://www.drupal.org/node/3495601 * @see hook_jsonapi_entity_filter_access() * @see hook_jsonapi_ENTITY_TYPE_filter_access() */ @@ -42,6 +50,10 @@ const JSONAPI_FILTER_AMONG_PUBLISHED = 'filter_among_published'; * For the User entity type, which does not have a "status" entity key, the * "status" field is used. * + * @deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Use + * \Drupal\jsonapi\JsonApiFilter::AMONG_ENABLED instead. + * + * @see https://www.drupal.org/node/3495601 * @see hook_jsonapi_entity_filter_access() * @see hook_jsonapi_ENTITY_TYPE_filter_access() */ @@ -64,6 +76,10 @@ const JSONAPI_FILTER_AMONG_ENABLED = 'filter_among_enabled'; * - The entity type has an "owner" entity key. * - There's a filter/query condition for the value equal to the user's ID. * + * @deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Use + * \Drupal\jsonapi\JsonApiFilter::AMONG_OWN instead. + * + * @see https://www.drupal.org/node/3495601 * @see hook_jsonapi_entity_filter_access() * @see hook_jsonapi_ENTITY_TYPE_filter_access() */ diff --git a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php index d59dca4ec03a..2888fbbec77c 100644 --- a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php +++ b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php @@ -12,6 +12,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\DataReferenceDefinitionInterface; +use Drupal\jsonapi\JsonApiFilter; use Drupal\jsonapi\Query\EntityCondition; use Drupal\jsonapi\Query\EntityConditionGroup; use Drupal\jsonapi\Query\Filter; @@ -323,12 +324,12 @@ class TemporaryQueryGuard { } /** - * Gets an access condition for the allowed JSONAPI_FILTER_AMONG_* subsets. + * Gets an access condition for the allowed JsonApiFilter::AMONG_* subsets. * - * If access is allowed for the JSONAPI_FILTER_AMONG_ALL subset, then no + * If access is allowed for the JsonApiFilter::AMONG_ALL subset, then no * conditions are returned. Otherwise, if access is allowed for - * JSONAPI_FILTER_AMONG_PUBLISHED, JSONAPI_FILTER_AMONG_ENABLED, or - * JSONAPI_FILTER_AMONG_OWN, then a condition group is returned for the union + * JsonApiFilter::AMONG_PUBLISHED, JsonApiFilter::AMONG_ENABLED, or + * JsonApiFilter::AMONG_OWN, then a condition group is returned for the union * of allowed subsets. If no subsets are allowed, then static::alwaysFalse() * is returned. * @@ -344,12 +345,12 @@ class TemporaryQueryGuard { * secure an entity query. */ protected static function getAccessConditionForKnownSubsets(EntityTypeInterface $entity_type, AccountInterface $account, CacheableMetadata $cacheability) { - // Get the combined access results for each JSONAPI_FILTER_AMONG_* subset. + // Get the combined access results for each JsonApiFilter::AMONG_* subset. $access_results = static::getAccessResultsFromEntityFilterHook($entity_type, $account); // No conditions are needed if access is allowed for all entities. - $cacheability->addCacheableDependency($access_results[JSONAPI_FILTER_AMONG_ALL]); - if ($access_results[JSONAPI_FILTER_AMONG_ALL]->isAllowed()) { + $cacheability->addCacheableDependency($access_results[JsonApiFilter::AMONG_ALL]); + if ($access_results[JsonApiFilter::AMONG_ALL]->isAllowed()) { return NULL; } @@ -363,7 +364,7 @@ class TemporaryQueryGuard { // The "published" subset. $published_field_name = $entity_type->getKey('published'); if ($published_field_name) { - $access_result = $access_results[JSONAPI_FILTER_AMONG_PUBLISHED]; + $access_result = $access_results[JsonApiFilter::AMONG_PUBLISHED]; $cacheability->addCacheableDependency($access_result); if ($access_result->isAllowed()) { $conditions[] = new EntityCondition($published_field_name, 1); @@ -375,7 +376,7 @@ class TemporaryQueryGuard { // @todo Remove ternary when the 'status' key is added to the User entity type. $status_field_name = $entity_type->id() === 'user' ? 'status' : $entity_type->getKey('status'); if ($status_field_name) { - $access_result = $access_results[JSONAPI_FILTER_AMONG_ENABLED]; + $access_result = $access_results[JsonApiFilter::AMONG_ENABLED]; $cacheability->addCacheableDependency($access_result); if ($access_result->isAllowed()) { $conditions[] = new EntityCondition($status_field_name, 1); @@ -387,7 +388,7 @@ class TemporaryQueryGuard { // @todo Remove ternary when the 'uid' key is added to the User entity type. $owner_field_name = $entity_type->id() === 'user' ? 'uid' : $entity_type->getKey('owner'); if ($owner_field_name) { - $access_result = $access_results[JSONAPI_FILTER_AMONG_OWN]; + $access_result = $access_results[JsonApiFilter::AMONG_OWN]; $cacheability->addCacheableDependency($access_result); if ($access_result->isAllowed()) { $cacheability->addCacheContexts(['user']); @@ -415,7 +416,7 @@ class TemporaryQueryGuard { } /** - * Gets the combined access result for each JSONAPI_FILTER_AMONG_* subset. + * Gets the combined access result for each JsonApiFilter::AMONG_* subset. * * This invokes hook_jsonapi_entity_filter_access() and * hook_jsonapi_ENTITY_TYPE_filter_access() and combines the results from all @@ -433,10 +434,10 @@ class TemporaryQueryGuard { protected static function getAccessResultsFromEntityFilterHook(EntityTypeInterface $entity_type, AccountInterface $account) { /** @var \Drupal\Core\Access\AccessResultInterface[] $combined_access_results */ $combined_access_results = [ - JSONAPI_FILTER_AMONG_ALL => AccessResult::neutral(), - JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::neutral(), - JSONAPI_FILTER_AMONG_ENABLED => AccessResult::neutral(), - JSONAPI_FILTER_AMONG_OWN => AccessResult::neutral(), + JsonApiFilter::AMONG_ALL => AccessResult::neutral(), + JsonApiFilter::AMONG_PUBLISHED => AccessResult::neutral(), + JsonApiFilter::AMONG_ENABLED => AccessResult::neutral(), + JsonApiFilter::AMONG_OWN => AccessResult::neutral(), ]; // Invoke hook_jsonapi_entity_filter_access() and diff --git a/core/modules/jsonapi/src/Hook/JsonapiHooks.php b/core/modules/jsonapi/src/Hook/JsonapiHooks.php index 7db5b77b0e80..d5f6d2540fcd 100644 --- a/core/modules/jsonapi/src/Hook/JsonapiHooks.php +++ b/core/modules/jsonapi/src/Hook/JsonapiHooks.php @@ -7,6 +7,7 @@ use Drupal\Core\Session\AccountInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\jsonapi\JsonApiFilter; use Drupal\jsonapi\Routing\Routes; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Hook\Attribute\Hook; @@ -107,7 +108,7 @@ class JsonapiHooks { // AccessResult::forbidden() from its implementation of this hook. if ($admin_permission = $entity_type->getAdminPermission()) { return [ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission), + JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission), ]; } return []; @@ -122,8 +123,8 @@ class JsonapiHooks { // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for // (isReusable()), so this does not have to. return [ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access block library'), - JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowed(), + JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access block library'), + JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowed(), ]; } @@ -136,8 +137,8 @@ class JsonapiHooks { // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for // (access to the commented entity), so this does not have to. return [ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer comments'), - JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access comments'), + JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer comments'), + JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access comments'), ]; } @@ -148,7 +149,7 @@ class JsonapiHooks { public function jsonapiEntityTestFilterAccess(EntityTypeInterface $entity_type, AccountInterface $account): array { // @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess() return [ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view test entity'), + JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view test entity'), ]; } @@ -161,7 +162,7 @@ class JsonapiHooks { // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for // (public OR owner), so this does not have to. return [ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access content'), + JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access content'), ]; } @@ -172,7 +173,7 @@ class JsonapiHooks { public function jsonapiMediaFilterAccess(EntityTypeInterface $entity_type, AccountInterface $account): array { // @see \Drupal\media\MediaAccessControlHandler::checkAccess() return [ - JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view media'), + JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view media'), ]; } @@ -184,30 +185,30 @@ class JsonapiHooks { // @see \Drupal\node\NodeAccessControlHandler::access() if ($account->hasPermission('bypass node access')) { return [ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowed()->cachePerPermissions(), + JsonApiFilter::AMONG_ALL => AccessResult::allowed()->cachePerPermissions(), ]; } if (!$account->hasPermission('access content')) { $forbidden = AccessResult::forbidden("The 'access content' permission is required.")->cachePerPermissions(); return [ - JSONAPI_FILTER_AMONG_ALL => $forbidden, - JSONAPI_FILTER_AMONG_OWN => $forbidden, - JSONAPI_FILTER_AMONG_PUBLISHED => $forbidden, + JsonApiFilter::AMONG_ALL => $forbidden, + JsonApiFilter::AMONG_OWN => $forbidden, + JsonApiFilter::AMONG_PUBLISHED => $forbidden, // For legacy reasons, the Node entity type has a "status" key, so // forbid this subset as well, even though it has no semantic meaning. - JSONAPI_FILTER_AMONG_ENABLED => $forbidden, + JsonApiFilter::AMONG_ENABLED => $forbidden, ]; } return [ - // @see \Drupal\node\NodeAccessControlHandler::checkAccess() - JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own unpublished content'), - // @see \Drupal\node\NodeGrantDatabaseStorage::access() - // Note that: - // - This is just for the default grant. Other node access conditions - // are added via the 'node_access' query tag. - // - Permissions were checked earlier in this function, so we must - // vary the cache by them. - JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowed()->cachePerPermissions(), + // @see \Drupal\node\NodeAccessControlHandler::checkAccess() + JsonApiFilter::AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own unpublished content'), + // @see \Drupal\node\NodeGrantDatabaseStorage::access() + // Note that: + // - This is just for the default grant. Other node access conditions + // are added via the 'node_access' query tag. + // - Permissions were checked earlier in this function, so we must + // vary the cache by them. + JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowed()->cachePerPermissions(), ]; } @@ -221,7 +222,7 @@ class JsonapiHooks { // "shortcut_set = $shortcut_set_storage->getDisplayedToUser($current_user)" // so this does not have to. return [ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer shortcuts')->orIf(AccessResult::allowedIfHasPermissions($account, [ + JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer shortcuts')->orIf(AccessResult::allowedIfHasPermissions($account, [ 'access shortcuts', 'customize shortcut links', ])), @@ -235,8 +236,8 @@ class JsonapiHooks { public function jsonapiTaxonomyTermFilterAccess(EntityTypeInterface $entity_type, AccountInterface $account): array { // @see \Drupal\taxonomy\TermAccessControlHandler::checkAccess() return [ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer taxonomy'), - JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access content'), + JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer taxonomy'), + JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access content'), ]; } @@ -249,8 +250,8 @@ class JsonapiHooks { // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for // (!isAnonymous()), so this does not have to. return [ - JSONAPI_FILTER_AMONG_OWN => AccessResult::allowed(), - JSONAPI_FILTER_AMONG_ENABLED => AccessResult::allowedIfHasPermission($account, 'access user profiles'), + JsonApiFilter::AMONG_OWN => AccessResult::allowed(), + JsonApiFilter::AMONG_ENABLED => AccessResult::allowedIfHasPermission($account, 'access user profiles'), ]; } @@ -261,8 +262,8 @@ class JsonapiHooks { public function jsonapiWorkspaceFilterAccess(EntityTypeInterface $entity_type, AccountInterface $account): array { // @see \Drupal\workspaces\WorkspaceAccessControlHandler::checkAccess() return [ - JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view any workspace'), - JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own workspace'), + JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view any workspace'), + JsonApiFilter::AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own workspace'), ]; } diff --git a/core/modules/jsonapi/src/JsonApiFilter.php b/core/modules/jsonapi/src/JsonApiFilter.php new file mode 100644 index 000000000000..c9ac90be7af7 --- /dev/null +++ b/core/modules/jsonapi/src/JsonApiFilter.php @@ -0,0 +1,77 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\jsonapi; + +/** + * JsonApi filter options. + */ +final class JsonApiFilter { + + /** + * Array key for denoting type-based filtering access. + * + * Array key for denoting access to filter among all entities of a given type, + * regardless of whether they are published or enabled, and regardless of + * their owner. + * + * @see hook_jsonapi_entity_filter_access() + * @see hook_jsonapi_ENTITY_TYPE_filter_access() + */ + const AMONG_ALL = 'filter_among_all'; + + /** + * Array key for denoting type-based published-only filtering access. + * + * Array key for denoting access to filter among all published entities of a + * given type, regardless of their owner. + * + * This is used when an entity type has a "published" entity key and there's a + * query condition for the value of that equaling 1. + * + * @see hook_jsonapi_entity_filter_access() + * @see hook_jsonapi_ENTITY_TYPE_filter_access() + */ + const AMONG_PUBLISHED = 'filter_among_published'; + + /** + * Array key for denoting type-based enabled-only filtering access. + * + * Array key for denoting access to filter among all enabled entities of a + * given type, regardless of their owner. + * + * This is used when an entity type has a "status" entity key and there's a + * query condition for the value of that equaling 1. + * + * For the User entity type, which does not have a "status" entity key, the + * "status" field is used. + * + * @see hook_jsonapi_entity_filter_access() + * @see hook_jsonapi_ENTITY_TYPE_filter_access() + */ + const AMONG_ENABLED = 'filter_among_enabled'; + + /** + * Array key for denoting type-based owned-only filtering access. + * + * Array key for denoting access to filter among all entities of a given type, + * regardless of whether they are published or enabled, so long as they are + * owned by the user for whom access is being checked. + * + * When filtering among User entities, this is used when access is being + * checked for an authenticated user and there's a query condition + * limiting the result set to just that user's entity object. + * + * When filtering among entities of another type, this is used when all of the + * following conditions are met: + * - Access is being checked for an authenticated user. + * - The entity type has an "owner" entity key. + * - There's a filter/query condition for the value equal to the user's ID. + * + * @see hook_jsonapi_entity_filter_access() + * @see hook_jsonapi_ENTITY_TYPE_filter_access() + */ + const AMONG_OWN = 'filter_among_own'; + +} diff --git a/core/modules/layout_builder/src/Element/LayoutBuilder.php b/core/modules/layout_builder/src/Element/LayoutBuilder.php index d673d24d1d38..2b128f931887 100644 --- a/core/modules/layout_builder/src/Element/LayoutBuilder.php +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php @@ -11,6 +11,7 @@ use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Render\Attribute\RenderElement; use Drupal\Core\Render\Element; use Drupal\Core\Render\Element\RenderElementBase; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Security\Attribute\TrustedCallback; use Drupal\Core\Url; use Drupal\layout_builder\Context\LayoutBuilderContextTrait; @@ -52,9 +53,11 @@ class LayoutBuilder extends RenderElementBase implements ContainerFactoryPluginI * The plugin implementation definition. * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher service. + * @param \Drupal\Core\Render\ElementInfoManagerInterface|null $elementInfoManager + * The element info manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EventDispatcherInterface $event_dispatcher) { - parent::__construct($configuration, $plugin_id, $plugin_definition); + public function __construct(array $configuration, $plugin_id, $plugin_definition, EventDispatcherInterface $event_dispatcher, ?ElementInfoManagerInterface $elementInfoManager = NULL) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager); $this->eventDispatcher = $event_dispatcher; } @@ -66,7 +69,8 @@ class LayoutBuilder extends RenderElementBase implements ContainerFactoryPluginI $configuration, $plugin_id, $plugin_definition, - $container->get('event_dispatcher') + $container->get('event_dispatcher'), + $container->get('plugin.manager.element_info') ); } diff --git a/core/modules/layout_builder/src/InlineBlockEntityOperations.php b/core/modules/layout_builder/src/InlineBlockEntityOperations.php index 16bda99a45cd..7a832ad284c6 100644 --- a/core/modules/layout_builder/src/InlineBlockEntityOperations.php +++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php @@ -134,8 +134,6 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface { * The parent entity. */ public function handleEntityDelete(EntityInterface $entity) { - // @todo In https://www.drupal.org/node/3008943 call - // \Drupal\layout_builder\LayoutEntityHelperTrait::isLayoutCompatibleEntity(). $this->usage->removeByLayoutEntity($entity); } diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTestBase.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTestBase.php index 94cb2455b5a4..75dac6efac20 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTestBase.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTestBase.php @@ -10,7 +10,7 @@ use Drupal\Tests\field_ui\Traits\FieldUiTestTrait; /** * Tests the Layout Builder UI. */ -class LayoutBuilderTestBase extends BrowserTestBase { +abstract class LayoutBuilderTestBase extends BrowserTestBase { use FieldUiTestTrait; diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php index 6931a733d96d..bd46767863ad 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace Drupal\Tests\layout_builder\FunctionalJavascript; -use Drupal\block_content\Entity\BlockContentType; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use Drupal\Tests\block_content\Traits\BlockContentCreationTrait; use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait; // cspell:ignore blockbasic @@ -15,6 +15,9 @@ use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait; */ abstract class InlineBlockTestBase extends WebDriverTestBase { + use BlockContentCreationTrait { + createBlockContentType as baseCreateBlockContentType; + } use ContextualLinkClickTrait; /** @@ -214,13 +217,11 @@ abstract class InlineBlockTestBase extends WebDriverTestBase { * The block type label. */ protected function createBlockContentType($id, $label) { - $bundle = BlockContentType::create([ + $this->baseCreateBlockContentType([ 'id' => $id, 'label' => $label, 'revision' => 1, - ]); - $bundle->save(); - block_content_add_body_field($bundle->id()); + ], TRUE); } } diff --git a/core/modules/link/tests/src/Kernel/LinkFormatterTest.php b/core/modules/link/tests/src/Kernel/LinkFormatterTest.php new file mode 100644 index 000000000000..8ebda26d9c58 --- /dev/null +++ b/core/modules/link/tests/src/Kernel/LinkFormatterTest.php @@ -0,0 +1,132 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\link\Kernel; + +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\KernelTests\Core\Entity\EntityKernelTestBase; +use Drupal\user\Entity\Role; +use Drupal\user\RoleInterface; + +/** + * Tests the Field Formatter for the link field type. + * + * @group link + */ +class LinkFormatterTest extends EntityKernelTestBase { + + /** + * Modules to enable. + * + * @var array + */ + protected static $modules = ['link']; + + /** + * The entity type used in this test. + * + * @var string + */ + protected string $entityType = 'entity_test'; + + /** + * The bundle used in this test. + * + * @var string + */ + protected string $bundle = 'entity_test'; + + /** + * The name of the field used in this test. + * + * @var string + */ + protected string $fieldName = 'field_test'; + + /** + * The entity to be tested. + * + * @var \Drupal\Core\Entity\EntityInterface + */ + protected $entity; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + // Use Stark theme for testing markup output. + \Drupal::service('theme_installer')->install(['stark']); + $this->config('system.theme')->set('default', 'stark')->save(); + $this->installEntitySchema('entity_test'); + // Grant the 'view test entity' permission. + $this->installConfig(['user']); + Role::load(RoleInterface::ANONYMOUS_ID) + ->grantPermission('view test entity') + ->save(); + + FieldStorageConfig::create([ + 'field_name' => $this->fieldName, + 'type' => 'link', + 'entity_type' => $this->entityType, + 'cardinality' => 1, + ])->save(); + + FieldConfig::create([ + 'field_name' => $this->fieldName, + 'entity_type' => $this->entityType, + 'bundle' => $this->bundle, + 'label' => 'Field test', + ])->save(); + } + + /** + * Tests the link formatters. + * + * @param string $formatter + * The name of the link formatter to test. + * + * @dataProvider providerLinkFormatter + */ + public function testLinkFormatter(string $formatter): void { + $entity = $this->container->get('entity_type.manager') + ->getStorage($this->entityType) + ->create([ + 'name' => $this->randomMachineName(), + $this->fieldName => [ + 'uri' => 'https://www.drupal.org/', + 'title' => 'Hello world', + 'options' => [ + 'attributes' => [ + 'class' => 'classy', + 'onmouseover' => 'alert(document.cookie)', + ], + ], + ], + ]); + $entity->save(); + + $build = $entity->get($this->fieldName)->view(['type' => $formatter]); + + $renderer = $this->container->get('renderer'); + $renderer->renderRoot($build[0]); + + $output = (string) $build[0]['#markup']; + $this->assertStringContainsString('<a href="https://www.drupal.org/" class="classy">', $output); + $this->assertStringNotContainsString('onmouseover=', $output); + } + + /** + * Data provider for ::testLinkFormatter. + */ + public static function providerLinkFormatter(): array { + return [ + 'default formatter' => ['link'], + 'separate link text and URL' => ['link_separate'], + ]; + } + +} diff --git a/core/modules/link/tests/src/Unit/AttributeXssTest.php b/core/modules/link/tests/src/Unit/AttributeXssTest.php new file mode 100644 index 000000000000..cbf01a40f606 --- /dev/null +++ b/core/modules/link/tests/src/Unit/AttributeXssTest.php @@ -0,0 +1,126 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\link\Unit; + +use Drupal\link\AttributeXss; +use Drupal\Tests\UnitTestCase; + +/** + * Tests AttributeXss. + * + * @group link + * @covers \Drupal\link\AttributeXss + */ +final class AttributeXssTest extends UnitTestCase { + + /** + * Covers ::sanitizeAttributes. + * + * @dataProvider providerSanitizeAttributes + */ + public function testSanitizeAttributes(array $attributes, array $expected): void { + self::assertSame($expected, AttributeXss::sanitizeAttributes($attributes)); + } + + /** + * Data provider for ::testSanitizeAttributes. + * + * @return \Generator + * Test cases. + */ + public static function providerSanitizeAttributes(): \Generator { + yield 'safe' => [ + ['class' => ['foo', 'bar'], 'data-biscuit' => TRUE], + ['class' => ['foo', 'bar'], 'data-biscuit' => TRUE], + ]; + + yield 'valueless' => [ + ['class' => ['foo', 'bar'], 'selected' => ''], + ['class' => ['foo', 'bar'], 'selected' => ''], + ]; + + yield 'empty names' => [ + ['class' => ['foo', 'bar'], '' => 'live', ' ' => TRUE], + ['class' => ['foo', 'bar']], + ]; + + yield 'only empty names' => [ + ['' => 'live', ' ' => TRUE], + [], + ]; + + yield 'valueless, mangled with a space' => [ + ['class' => ['foo', 'bar'], 'selected href' => 'http://example.com'], + ['class' => ['foo', 'bar'], 'selected' => 'selected', 'href' => 'http://example.com'], + ]; + + yield 'valueless, mangled with a space, blocked' => [ + ['class' => ['foo', 'bar'], 'selected onclick href' => 'http://example.com'], + ['class' => ['foo', 'bar'], 'selected' => 'selected', 'href' => 'http://example.com'], + ]; + + yield 'with encoding' => [ + ['class' => ['foo', 'bar'], 'data-how-good' => "It's the bee's knees"], + ['class' => ['foo', 'bar'], 'data-how-good' => "It's the bee's knees"], + ]; + + yield 'valueless, mangled with multiple spaces, blocked' => [ + ['class' => ['foo', 'bar'], 'selected onclick href' => 'http://example.com'], + ['class' => ['foo', 'bar'], 'selected' => 'selected', 'href' => 'http://example.com'], + ]; + + yield 'valueless, mangled with multiple spaces, blocked, mangled first' => [ + ['selected onclick href' => 'http://example.com', 'class' => ['foo', 'bar']], + ['selected' => 'selected', 'href' => 'http://example.com', 'class' => ['foo', 'bar']], + ]; + + yield 'valueless but with value' => [ + ['class' => ['foo', 'bar'], 'selected' => 'selected', 'href' => 'http://example.com'], + ['class' => ['foo', 'bar'], 'selected' => 'selected', 'href' => 'http://example.com'], + ]; + + yield 'valueless but with value, bad protocol' => [ + ['class' => ['foo', 'bar'], 'selected' => 'selected', 'href' => 'javascript:alert()'], + ['class' => ['foo', 'bar'], 'selected' => 'selected', 'href' => 'alert()'], + ]; + + yield 'valueless, mangled with a space and bad protocol' => [ + ['class' => ['foo', 'bar'], 'selected href' => 'javascript:alert()'], + ['class' => ['foo', 'bar'], 'selected' => 'selected', 'href' => 'alert()'], + ]; + + yield 'valueless, mangled with a space and bad protocol, repeated' => [ + ['class' => ['foo', 'bar'], 'selected href' => 'javascript:alert()', 'href' => 'http://example.com'], + ['class' => ['foo', 'bar'], 'selected' => 'selected', 'href' => 'alert()'], + ]; + + yield 'with a space' => [ + ['class' => ['foo', 'bar'], 'href' => \urlencode('some file.pdf')], + ['class' => ['foo', 'bar'], 'href' => 'some+file.pdf'], + ]; + + yield 'with an unencoded space' => [ + ['class' => ['foo', 'bar'], 'href' => 'some file.pdf'], + ['class' => ['foo', 'bar'], 'href' => 'some file.pdf'], + ]; + + yield 'xss onclick' => [ + ['class' => ['foo', 'bar'], 'onclick' => 'alert("whoop");'], + ['class' => ['foo', 'bar']], + ]; + + yield 'xss onclick, valueless, mangled with a space' => [ + ['class' => ['foo', 'bar'], 'selected onclick href' => 'http://example.com'], + ['class' => ['foo', 'bar'], 'selected' => 'selected', 'href' => 'http://example.com'], + ]; + + yield 'xss protocol' => [ + ['class' => ['foo', 'bar'], 'src' => 'javascript:alert("whoop");'], + ['class' => ['foo', 'bar'], 'src' => 'alert("whoop");'], + ]; + + } + +} diff --git a/core/modules/media/src/OEmbed/UrlResolver.php b/core/modules/media/src/OEmbed/UrlResolver.php index a672c5933d05..562dc430f772 100644 --- a/core/modules/media/src/OEmbed/UrlResolver.php +++ b/core/modules/media/src/OEmbed/UrlResolver.php @@ -141,7 +141,7 @@ class UrlResolver implements UrlResolverInterface { return $this->resourceFetcher->fetchResource($resource_url)->getProvider(); } - throw new ResourceException('No matching provider found.', $url); + throw new ResourceException("No matching oEmbed provider found for resource: \"{$url}\"", $url); } /** diff --git a/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php b/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php index 5fa53b2b9e92..0230cc19b465 100644 --- a/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php +++ b/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php @@ -7,6 +7,8 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\Plugin\Field\FieldWidget\StringTextfieldWidget; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element\ElementInterface; +use Drupal\Core\Render\Element\Widget; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\media\Entity\MediaType; use Drupal\media\Plugin\media\Source\OEmbedInterface; @@ -28,24 +30,35 @@ class OEmbedWidget extends StringTextfieldWidget { /** * {@inheritdoc} */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - $element = parent::formElement($items, $delta, $element, $form, $form_state); + public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface { + $widget = parent::singleElementObject($items, $delta, $widget, $form, $form_state); + $value = $widget->getChild('value'); + $value->description = $this->getValueDescription($items, $value->description); + return $widget; + } + /** + * Merges description and provider messages. + * + * @param \Drupal\Core\Field\FieldItemListInterface $items + * FieldItemList containing the values to be edited. + * @param scalar|\Stringable|\Drupal\Core\Render\RenderableInterface|array $description + * The description on the form element. + * + * @return string|array + * The description on the value child. + */ + protected function getValueDescription(FieldItemListInterface $items, mixed $description): string|array { /** @var \Drupal\media\Plugin\media\Source\OEmbedInterface $source */ $source = $items->getEntity()->getSource(); $message = $this->t('You can link to media from the following services: @providers', ['@providers' => implode(', ', $source->getProviders())]); - - if (!empty($element['value']['#description'])) { - $element['value']['#description'] = [ + if ($description) { + return [ '#theme' => 'item_list', - '#items' => [$element['value']['#description'], $message], + '#items' => [$description, $message], ]; } - else { - $element['value']['#description'] = $message; - } - - return $element; + return $message; } /** diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterTestBase.php b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterTestBase.php index 57d5278ee1d8..097ddb6b3102 100644 --- a/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterTestBase.php +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterTestBase.php @@ -9,7 +9,7 @@ use Drupal\filter\Entity\FilterFormat; /** * Base class for media embed filter configuration tests. */ -class MediaEmbedFilterTestBase extends MediaJavascriptTestBase { +abstract class MediaEmbedFilterTestBase extends MediaJavascriptTestBase { /** * {@inheritdoc} diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php index 3b36ee5e3773..33ffe1a39920 100644 --- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php +++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php @@ -18,6 +18,7 @@ use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Security\TrustedCallbackInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -78,6 +79,8 @@ class MediaLibraryWidget extends WidgetBase implements TrustedCallbackInterface * The widget settings. * @param array $third_party_settings * Any third party settings. + * @param \Drupal\Core\Render\ElementInfoManagerInterface $elementInfoManager + * The element info manager. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * Entity type manager service. * @param \Drupal\Core\Session\AccountInterface $current_user @@ -85,8 +88,8 @@ class MediaLibraryWidget extends WidgetBase implements TrustedCallbackInterface * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user, ModuleHandlerInterface $module_handler) { - parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $elementInfoManager, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user, ModuleHandlerInterface $module_handler) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager); $this->entityTypeManager = $entity_type_manager; $this->currentUser = $current_user; $this->moduleHandler = $module_handler; @@ -102,6 +105,7 @@ class MediaLibraryWidget extends WidgetBase implements TrustedCallbackInterface $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], + $container->get('plugin.manager.element_info'), $container->get('entity_type.manager'), $container->get('current_user'), $container->get('module_handler') diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetOEmbedTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetOEmbedTest.php index 46277c473e6f..1d53128b7047 100644 --- a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetOEmbedTest.php +++ b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetOEmbedTest.php @@ -124,10 +124,10 @@ class WidgetOEmbedTest extends MediaLibraryTestBase { // assertWaitOnAjaxRequest() required for input "id" attributes to // consistently match their label's "for" attribute. $assert_session->assertWaitOnAjaxRequest(); - $this->waitForText('No matching provider found.'); + $this->waitForText('No matching oEmbed provider found for resource:'); // Assert we can not add a video ID that doesn't exist. We need to use a // video ID that will not be filtered by the regex, because otherwise the - // message 'No matching provider found.' will be returned. + // message 'No matching oEmbed provider found for resource: "..."' will be returned. $page->fillField('Add Type Five via URL', 'https://www.youtube.com/watch?v=PWjcqE3QKBg1'); $page->pressButton('Add'); // assertWaitOnAjaxRequest() required for input "id" attributes to @@ -312,10 +312,10 @@ class WidgetOEmbedTest extends MediaLibraryTestBase { // assertWaitOnAjaxRequest() required for input "id" attributes to // consistently match their label's "for" attribute. $assert_session->assertWaitOnAjaxRequest(); - $this->waitForText('No matching provider found.'); + $this->waitForText('No matching oEmbed provider found for resource:'); // Assert we can not add a video ID that doesn't exist. We need to use a // video ID that will not be filtered by the regex, because otherwise the - // message 'No matching provider found.' will be returned. + // message 'No matching oEmbed provider found for resource: "..."' will be returned. $page->fillField('Add Type Five via URL', 'https://www.youtube.com/watch?v=PWjcqE3QKBg1'); $page->pressButton('Add'); // assertWaitOnAjaxRequest() required for input "id" attributes to diff --git a/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php b/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php index 34e0a0eee043..751b93dd92d5 100644 --- a/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php +++ b/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Drupal\Tests\menu_link_content\Kernel; +use Drupal\Core\Link; use Drupal\Core\Menu\MenuTreeParameters; use Drupal\entity_test\Entity\EntityTestExternal; use Drupal\KernelTests\KernelTestBase; @@ -477,4 +478,32 @@ class MenuLinksTest extends KernelTestBase { static::assertIsArray($build); } + /** + * Assert that attributes are filtered. + */ + public function testXssFiltering(): void { + $options = [ + 'menu_name' => 'menu-test', + 'bundle' => 'menu_link_content', + 'link' => [ + [ + 'uri' => 'https://www.drupal.org/', + 'options' => [ + 'attributes' => [ + 'class' => 'classy', + 'onmouseover' => 'alert(document.cookie)', + ], + ], + ], + ], + 'title' => 'Link test', + ]; + $link = MenuLinkContent::create($options); + $link->save(); + assert($link instanceof MenuLinkContent); + $output = Link::fromTextAndUrl($link->getTitle(), $link->getUrlObject())->toString()->getGeneratedLink(); + $this->assertStringContainsString('<a href="https://www.drupal.org/" class="classy">', $output); + $this->assertStringNotContainsString('onmouseover=', $output); + } + } diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php index 09e7c99f898c..f36dcea82063 100644 --- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php +++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php @@ -293,6 +293,38 @@ class MenuUiHooks { } /** + * Implements hook_ENTITY_TYPE_delete(). + */ + #[Hook('menu_delete')] + public function menuDelete(EntityInterface $entity): void { + if (!$this->entityTypeManager->hasDefinition('node_type')) { + return; + } + + // Remove the menu from content type third party settings. + $menu_id = $entity->id(); + $parent_prefix = $menu_id . ':'; + $storage = $this->entityTypeManager->getStorage('node_type'); + foreach ($storage->loadMultiple() as $content_type) { + $third_party_settings = $original_third_party_settings = $content_type->getThirdPartySettings('menu_ui'); + if (isset($third_party_settings['available_menus']) && in_array($menu_id, $third_party_settings['available_menus'])) { + $key = array_search($menu_id, $third_party_settings['available_menus']); + if ($key !== FALSE) { + unset($third_party_settings['available_menus'][$key]); + } + $content_type->setThirdPartySetting('menu_ui', 'available_menus', $third_party_settings['available_menus']); + } + if (isset($third_party_settings['parent']) && substr($third_party_settings['parent'], 0, strlen($parent_prefix)) == $parent_prefix) { + $third_party_settings['parent'] = ''; + $content_type->setThirdPartySetting('menu_ui', 'parent', $third_party_settings['parent']); + } + if ($third_party_settings != $original_third_party_settings) { + $content_type->save(); + } + } + } + + /** * Implements hook_system_breadcrumb_alter(). */ #[Hook('system_breadcrumb_alter')] diff --git a/core/modules/menu_ui/tests/src/Kernel/MenuDeleteTest.php b/core/modules/menu_ui/tests/src/Kernel/MenuDeleteTest.php new file mode 100644 index 000000000000..3bb81874387c --- /dev/null +++ b/core/modules/menu_ui/tests/src/Kernel/MenuDeleteTest.php @@ -0,0 +1,86 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\menu_ui\Kernel; + +use Drupal\KernelTests\KernelTestBase; +use Drupal\menu_ui\Hook\MenuUiHooks; +use Drupal\node\Entity\NodeType; +use Drupal\system\Entity\Menu; + +/** + * Tests the menu_delete hook. + * + * @group menu_ui + */ +class MenuDeleteTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['node', 'menu_ui', 'system']; + + /** + * @covers \Drupal\menu_ui\Hook\MenuUiHooks::menuDelete + * @dataProvider providerMenuDelete + */ + public function testMenuDelete($settings, $expected): void { + $menu = Menu::create([ + 'id' => 'mock', + 'label' => $this->randomMachineName(16), + 'description' => 'Description text', + ]); + $menu->save(); + $content_type = NodeType::create([ + 'status' => TRUE, + 'dependencies' => [ + 'module' => ['menu_ui'], + ], + 'third_party_settings' => [ + 'menu_ui' => $settings, + ], + 'name' => 'Test type', + 'type' => 'test_type', + ]); + $content_type->save(); + $this->assertEquals($settings['available_menus'], $content_type->getThirdPartySetting('menu_ui', 'available_menus')); + $this->assertEquals($settings['parent'], $content_type->getThirdPartySetting('menu_ui', 'parent')); + + $hooks = new MenuUiHooks(\Drupal::entityTypeManager()); + $hooks->menuDelete($menu); + + $content_type = NodeType::load('test_type'); + $this->assertEquals($expected['available_menus'], $content_type->getThirdPartySetting('menu_ui', 'available_menus')); + $this->assertEquals($expected['parent'], $content_type->getThirdPartySetting('menu_ui', 'parent')); + } + + /** + * Provides data for testMenuDelete(). + */ + public static function providerMenuDelete(): array { + return [ + [ + ['available_menus' => ['mock'], 'parent' => 'mock:'], + ['available_menus' => [], 'parent' => ''], + ], + [ + ['available_menus' => ['mock'], 'parent' => 'mock:menu_link_content:e0cd7689-016e-43e4-af8f-7ce82801ab95'], + ['available_menus' => [], 'parent' => ''], + ], + [ + ['available_menus' => ['main', 'mock'], 'parent' => 'mock:'], + ['available_menus' => ['main'], 'parent' => ''], + ], + [ + ['available_menus' => ['main'], 'parent' => 'main:'], + ['available_menus' => ['main'], 'parent' => 'main:'], + ], + [ + ['available_menus' => ['main'], 'parent' => 'main:menu_link_content:e0cd7689-016e-43e4-af8f-7ce82801ab95'], + ['available_menus' => ['main'], 'parent' => 'main:menu_link_content:e0cd7689-016e-43e4-af8f-7ce82801ab95'], + ], + ]; + } + +} diff --git a/core/modules/migrate/migrate.api.php b/core/modules/migrate/migrate.api.php index 1e58b0090ff2..5d2af7db180e 100644 --- a/core/modules/migrate/migrate.api.php +++ b/core/modules/migrate/migrate.api.php @@ -156,7 +156,7 @@ function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, Migr if ($migration->id() == 'd6_filter_formats') { $value = $source->getDatabase()->query('SELECT [value] FROM {variable} WHERE [name] = :name', [':name' => 'my_module_filter_foo_' . $row->getSourceProperty('format')])->fetchField(); if ($value) { - $row->setSourceProperty('settings:my_module:foo', unserialize($value)); + $row->setSourceProperty('settings:my_module:foo', unserialize($value, ['allowed_classes' => FALSE])); } } } @@ -179,7 +179,7 @@ function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, Migr function hook_migrate_MIGRATION_ID_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) { $value = $source->getDatabase()->query('SELECT [value] FROM {variable} WHERE [name] = :name', [':name' => 'my_module_filter_foo_' . $row->getSourceProperty('format')])->fetchField(); if ($value) { - $row->setSourceProperty('settings:my_module:foo', unserialize($value)); + $row->setSourceProperty('settings:my_module:foo', unserialize($value, ['allowed_classes' => FALSE])); } } diff --git a/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php b/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php index 9adf60b46ffe..30cc28562e8d 100644 --- a/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php +++ b/core/modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php @@ -65,7 +65,7 @@ class AnnotatedClassDiscoveryAutomatedProviders extends AnnotatedClassDiscovery if (isset($cached['id'])) { // Explicitly unserialize this to create a new object // instance. - $definitions[$cached['id']] = unserialize($cached['content']); + $definitions[$cached['id']] = unserialize($cached['content'], ['allowed_classes' => FALSE]); } continue; } diff --git a/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php b/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php index dc70496282f3..a5351c748620 100644 --- a/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php +++ b/core/modules/migrate/src/Plugin/migrate/source/ConfigEntity.php @@ -71,7 +71,8 @@ class ConfigEntity extends SqlBase { * {@inheritdoc} */ public function prepareRow(Row $row) { - $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'))); + // @see \Drupal\Core\Config\DatabaseStorage::decode() + $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'), ['allowed_classes' => FALSE])); return parent::prepareRow($row); } diff --git a/core/modules/migrate/tests/src/Functional/MigrateMessageTestBase.php b/core/modules/migrate/tests/src/Functional/MigrateMessageTestBase.php index 6885ba378e9c..84dddc3c1825 100644 --- a/core/modules/migrate/tests/src/Functional/MigrateMessageTestBase.php +++ b/core/modules/migrate/tests/src/Functional/MigrateMessageTestBase.php @@ -12,10 +12,8 @@ use Drupal\migrate\Plugin\MigrationInterface; /** * Provides base class for testing migrate messages. - * - * @group migrate */ -class MigrateMessageTestBase extends BrowserTestBase { +abstract class MigrateMessageTestBase extends BrowserTestBase { /** * {@inheritdoc} diff --git a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php index ba9ab78cff66..ed223601abb3 100644 --- a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php +++ b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php @@ -14,7 +14,7 @@ use Drupal\Tests\UnitTestCase; /** * Base test class for entity migration destination functionality. */ -class EntityTestBase extends UnitTestCase { +abstract class EntityTestBase extends UnitTestCase { /** * The migration entity. diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php index 64ac2031a3fc..06d9bbccc974 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php @@ -173,7 +173,7 @@ abstract class DrupalSqlBase extends SqlBase implements DependentPluginInterface catch (\Exception) { $result = FALSE; } - return $result !== FALSE ? unserialize($result) : $default; + return $result !== FALSE ? unserialize($result, ['allowed_classes' => ['stdClass']]) : $default; } /** diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php index 0b83a099afb2..f99cd69e8983 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php @@ -129,7 +129,10 @@ class Variable extends DrupalSqlBase { // Create an ID field so we can record migration in the map table. // Arbitrarily, use the first variable name. $values['id'] = reset($this->variables); - return $values + array_map('unserialize', $this->prepareQuery()->execute()->fetchAllKeyed()); + return $values + array_map( + fn($data) => unserialize($data, ['allowed_classes' => FALSE]), + $this->prepareQuery()->execute()->fetchAllKeyed(), + ); } /** diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php index ee9b6268ebbf..e2bcbddc8d3c 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php @@ -66,7 +66,7 @@ class VariableMultiRow extends DrupalSqlBase { */ public function prepareRow(Row $row) { if ($value = $row->getSourceProperty('value')) { - $row->setSourceProperty('value', unserialize($value)); + $row->setSourceProperty('value', unserialize($value, ['allowed_classes' => FALSE])); } return parent::prepareRow($row); } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php index 11323f4bf7a0..f9e6ef61d75c 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php @@ -78,7 +78,7 @@ class VariableTranslation extends DrupalSqlBase { foreach ($result as $i18n_variable) { foreach ($values as $key => $value) { if ($values[$key]['language'] === $i18n_variable->language) { - $values[$key][$i18n_variable->name] = unserialize($i18n_variable->value); + $values[$key][$i18n_variable->name] = unserialize($i18n_variable->value, ['allowed_classes' => FALSE]); break; } } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php index 56121db822ac..82d638fa3d8f 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php @@ -78,7 +78,7 @@ class VariableTranslation extends DrupalSqlBase { foreach ($values as $key => $value) { if ($values[$key]['language'] === $variable_store['realm_key']) { if ($variable_store['serialized']) { - $values[$key][$variable_store['name']] = unserialize($variable_store['value']); + $values[$key][$variable_store['name']] = unserialize($variable_store['value'], ['allowed_classes' => FALSE]); break; } else { diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php index b1d409f4017a..1bb5b4f51bf5 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php @@ -88,7 +88,8 @@ class Config extends DrupalSqlBase { * {@inheritdoc} */ public function prepareRow(Row $row) { - $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'))); + // @see \Drupal\Core\Config\DatabaseStorage::decode() + $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'), ['allowed_classes' => FALSE])); return parent::prepareRow($row); } diff --git a/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php index a28dada3e9cc..c6e76787f316 100644 --- a/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php @@ -286,7 +286,7 @@ class ReviewForm extends MigrateUpgradeFormBase { foreach ($migration_state as $source_machine_name => $destination_modules) { $data = NULL; if (isset($this->systemData['module'][$source_machine_name]['info'])) { - $data = unserialize($this->systemData['module'][$source_machine_name]['info']); + $data = unserialize($this->systemData['module'][$source_machine_name]['info'], ['allowed_classes' => FALSE]); } $source_module_name = $data['name'] ?? $source_machine_name; // Get the names of all the destination modules. diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php index 759f59b50a84..a6633fed5e9f 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php @@ -269,7 +269,7 @@ abstract class MigrateUpgradeTestBase extends BrowserTestBase { // Convert $source_id into a keyless array so that // \Drupal\migrate\Plugin\migrate\id_map\Sql::getSourceHash() works as // expected. - $source_id_values = array_values(unserialize($source_id)); + $source_id_values = array_values(unserialize($source_id, ['allowed_classes' => FALSE])); $row = $id_map->getRowBySource($source_id_values); $destination = serialize($id_map->currentDestination()); $message = "Migration of $source_id to $destination as part of the {$migration->id()} migration. The source row status is " . $row['source_row_status']; diff --git a/core/modules/mysqli/mysqli.install b/core/modules/mysqli/mysqli.install deleted file mode 100644 index 7f1147d63adb..000000000000 --- a/core/modules/mysqli/mysqli.install +++ /dev/null @@ -1,78 +0,0 @@ -<?php - -/** - * @file - * Install, update and uninstall functions for the mysqli module. - */ - -use Drupal\Core\Database\Database; -use Drupal\Core\Extension\Requirement\RequirementSeverity; -use Drupal\Core\Render\Markup; - -/** - * Implements hook_requirements(). - */ -function mysqli_requirements($phase): array { - $requirements = []; - - if ($phase === 'runtime') { - // Test with MySql databases. - if (Database::isActiveConnection()) { - $connection = Database::getConnection(); - // Only show requirements when MySQLi is the default database connection. - if (!($connection->driver() === 'mysqli' && $connection->getProvider() === 'mysqli')) { - return []; - } - - $query = $connection->isMariaDb() ? 'SELECT @@SESSION.tx_isolation' : 'SELECT @@SESSION.transaction_isolation'; - - $isolation_level = $connection->query($query)->fetchField(); - - $tables_missing_primary_key = []; - $tables = $connection->schema()->findTables('%'); - foreach ($tables as $table) { - $primary_key_column = Database::getConnection()->query("SHOW KEYS FROM {" . $table . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name'); - if (empty($primary_key_column)) { - $tables_missing_primary_key[] = $table; - } - } - - $description = []; - if ($isolation_level == 'READ-COMMITTED') { - if (empty($tables_missing_primary_key)) { - $severity_level = RequirementSeverity::OK; - } - else { - $severity_level = RequirementSeverity::Error; - } - } - else { - if ($isolation_level == 'REPEATABLE-READ') { - $severity_level = RequirementSeverity::Warning; - } - else { - $severity_level = RequirementSeverity::Error; - $description[] = t('This is not supported by Drupal.'); - } - $description[] = t('The recommended level for Drupal is "READ COMMITTED".'); - } - - if (!empty($tables_missing_primary_key)) { - $description[] = t('For this to work correctly, all tables must have a primary key. The following table(s) do not have a primary key: @tables.', ['@tables' => implode(', ', $tables_missing_primary_key)]); - } - - $description[] = t('See the <a href=":performance_doc">setting MySQL transaction isolation level</a> page for more information.', [ - ':performance_doc' => 'https://www.drupal.org/docs/system-requirements/setting-the-mysql-transaction-isolation-level', - ]); - - $requirements['mysql_transaction_level'] = [ - 'title' => t('Transaction isolation level'), - 'severity' => $severity_level, - 'value' => $isolation_level, - 'description' => Markup::create(implode(' ', $description)), - ]; - } - } - - return $requirements; -} diff --git a/core/modules/mysqli/src/Hook/MysqliHooks.php b/core/modules/mysqli/src/Hook/MysqliHooks.php index 5fae187d16c7..340b17373a12 100644 --- a/core/modules/mysqli/src/Hook/MysqliHooks.php +++ b/core/modules/mysqli/src/Hook/MysqliHooks.php @@ -2,7 +2,10 @@ namespace Drupal\mysqli\Hook; +use Drupal\Core\Database\Database; +use Drupal\Core\Extension\Requirement\RequirementSeverity; use Drupal\Core\Hook\Attribute\Hook; +use Drupal\Core\Render\Markup; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -29,4 +32,71 @@ class MysqliHooks { return NULL; } + /** + * Implements hook_runtime_requirements(). + */ + #[Hook('runtime_requirements')] + public function runtimeRequirements(): array { + $requirements = []; + + // Test with MySql databases. + if (Database::isActiveConnection()) { + $connection = Database::getConnection(); + // Only show requirements when MySQLi is the default database connection. + if (!($connection->driver() === 'mysqli' && $connection->getProvider() === 'mysqli')) { + return []; + } + + $query = $connection->isMariaDb() ? 'SELECT @@SESSION.tx_isolation' : 'SELECT @@SESSION.transaction_isolation'; + + $isolation_level = $connection->query($query)->fetchField(); + + $tables_missing_primary_key = []; + $tables = $connection->schema()->findTables('%'); + foreach ($tables as $table) { + $primary_key_column = Database::getConnection()->query("SHOW KEYS FROM {" . $table . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name'); + if (empty($primary_key_column)) { + $tables_missing_primary_key[] = $table; + } + } + + $description = []; + if ($isolation_level == 'READ-COMMITTED') { + if (empty($tables_missing_primary_key)) { + $severity_level = RequirementSeverity::OK; + } + else { + $severity_level = RequirementSeverity::Error; + } + } + else { + if ($isolation_level == 'REPEATABLE-READ') { + $severity_level = RequirementSeverity::Warning; + } + else { + $severity_level = RequirementSeverity::Error; + $description[] = $this->t('This is not supported by Drupal.'); + } + $description[] = $this->t('The recommended level for Drupal is "READ COMMITTED".'); + } + + if (!empty($tables_missing_primary_key)) { + $description[] = $this->t('For this to work correctly, all tables must have a primary key. The following table(s) do not have a primary key: @tables.', ['@tables' => implode(', ', $tables_missing_primary_key)]); + } + + $description[] = $this->t('See the <a href=":performance_doc">setting MySQL transaction isolation level</a> page for more information.', [ + ':performance_doc' => 'https://www.drupal.org/docs/system-requirements/setting-the-mysql-transaction-isolation-level', + ]); + + $requirements['mysql_transaction_level'] = [ + 'title' => $this->t('Transaction isolation level'), + 'severity' => $severity_level, + 'value' => $isolation_level, + 'description' => Markup::create(implode(' ', $description)), + ]; + } + + return $requirements; + } + } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index f14d843faa10..6841f24b96b1 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -36,8 +36,12 @@ use Drupal\node\NodeTypeInterface; * @return array|false * A renderable array containing a list of linked node titles fetched from * $result, or FALSE if there are no rows in $result. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. There is no replacement. + * @see https://www.drupal.org/node/3531959 */ function node_title_list(StatementInterface $result, $title = NULL) { + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3531959', E_USER_DEPRECATED); $items = []; $num_rows = FALSE; $nids = []; @@ -121,8 +125,12 @@ function node_get_type_label(NodeInterface $node) { * * @return string * The node type description. + * + * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Use $node_type->getDescription() instead. + * @see https://www.drupal.org/node/3531945 */ function node_type_get_description(NodeTypeInterface $node_type) { + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Use $node_type->getDescription() instead. See https://www.drupal.org/node/3531945', E_USER_DEPRECATED); return $node_type->getDescription(); } @@ -325,10 +333,11 @@ function template_preprocess_node(&$variables): void { // $variables['content'] is more flexible and consistent. $submitted_configurable = $node->getFieldDefinition('created')->isDisplayConfigurable('view') || $node->getFieldDefinition('uid')->isDisplayConfigurable('view'); if (!$skip_custom_preprocessing || !$submitted_configurable) { - $variables['date'] = \Drupal::service('renderer')->render($variables['elements']['created']); - unset($variables['elements']['created']); - $variables['author_name'] = \Drupal::service('renderer')->render($variables['elements']['uid']); - unset($variables['elements']['uid']); + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = \Drupal::service('renderer'); + $variables['date'] = !empty($variables['elements']['created']) ? $renderer->render($variables['elements']['created']) : ''; + $variables['author_name'] = !empty($variables['elements']['uid']) ? $renderer->render($variables['elements']['uid']) : ''; + unset($variables['elements']['created'], $variables['elements']['uid']); } if (isset($variables['elements']['title']) && (!$skip_custom_preprocessing || !$node->getFieldDefinition('title')->isDisplayConfigurable('view'))) { diff --git a/core/modules/node/src/Form/NodeForm.php b/core/modules/node/src/Form/NodeForm.php index d739aa7de8fc..295e9ab78ce0 100644 --- a/core/modules/node/src/Form/NodeForm.php +++ b/core/modules/node/src/Form/NodeForm.php @@ -10,6 +10,7 @@ use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Drupal\Core\Utility\Error; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -163,7 +164,7 @@ class NodeForm extends ContentEntityForm { $form['meta']['author'] = [ '#type' => 'item', '#title' => $this->t('Author'), - '#markup' => $node->getOwner()?->getAccountName(), + '#markup' => $node->getOwner()?->getDisplayName(), '#wrapper_attributes' => ['class' => ['entity-meta__author']], ]; @@ -278,21 +279,22 @@ class NodeForm extends ContentEntityForm { public function save(array $form, FormStateInterface $form_state) { $node = $this->entity; $insert = $node->isNew(); - $node->save(); - $node_link = $node->toLink($this->t('View'))->toString(); - $context = ['@type' => $node->getType(), '%title' => $node->label(), 'link' => $node_link]; - $t_args = ['@type' => node_get_type_label($node), '%title' => $node->access('view') ? $node->toLink()->toString() : $node->label()]; - - if ($insert) { - $this->logger('content')->info('@type: added %title.', $context); - $this->messenger()->addStatus($this->t('@type %title has been created.', $t_args)); - } - else { - $this->logger('content')->info('@type: updated %title.', $context); - $this->messenger()->addStatus($this->t('@type %title has been updated.', $t_args)); - } - if ($node->id()) { + try { + $node->save(); + $node_link = $node->toLink($this->t('View'))->toString(); + $context = ['@type' => $node->getType(), '%title' => $node->label(), 'link' => $node_link]; + $t_args = ['@type' => node_get_type_label($node), '%title' => $node->access('view') ? $node->toLink()->toString() : $node->label()]; + + if ($insert) { + $this->logger('content')->info('@type: added %title.', $context); + $this->messenger()->addStatus($this->t('@type %title has been created.', $t_args)); + } + else { + $this->logger('content')->info('@type: updated %title.', $context); + $this->messenger()->addStatus($this->t('@type %title has been updated.', $t_args)); + } + $form_state->setValue('nid', $node->id()); $form_state->set('nid', $node->id()); if ($node->access('view')) { @@ -310,10 +312,15 @@ class NodeForm extends ContentEntityForm { $store = $this->tempStoreFactory->get('node_preview'); $store->delete($node->uuid()); } - else { + catch (\Exception $e) { // In the unlikely case something went wrong on save, the node will be - // rebuilt and node form redisplayed the same way as in preview. - $this->messenger()->addError($this->t('The post could not be saved.')); + // rebuilt and node form redisplayed. + $this->messenger()->addError($this->t('The content could not be saved. Contact the site administrator if the problem persists.')); + // It's likely that this exception is an EntityStorageException in which + // case we won't have the actual backtrace available. Attempt to get the + // previous exception if available to include the backtrace. + $e = $e->getPrevious() ?: $e; + \Drupal::logger('node')->error('%type saving node form: @message in %function (line %line of %file) @backtrace_string.', Error::decodeException($e)); $form_state->setRebuild(); } } diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php index eea6cc100127..fbaab21a2119 100644 --- a/core/modules/node/src/NodeGrantDatabaseStorage.php +++ b/core/modules/node/src/NodeGrantDatabaseStorage.php @@ -112,6 +112,13 @@ class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface { if (count($grants) > 0) { $query->condition($grants); } + if ($query->execute()->fetchField()) { + $access_result = AccessResult::allowed(); + } + else { + $access_result = AccessResult::neutral(); + } + $access_result->addCacheContexts(['user.node_grants:' . $operation]); // Only the 'view' node grant can currently be cached; the others currently // don't have any cacheability metadata. Hopefully, we can add that in the @@ -119,20 +126,10 @@ class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface { // cases. For now, this must remain marked as uncacheable, even when it is // theoretically cacheable, because we don't have the necessary metadata to // know it for a fact. - $set_cacheability = function (AccessResult $access_result) use ($operation) { - $access_result->addCacheContexts(['user.node_grants:' . $operation]); - if ($operation !== 'view') { - $access_result->setCacheMaxAge(0); - } - return $access_result; - }; - - if ($query->execute()->fetchField()) { - return $set_cacheability(AccessResult::allowed()); - } - else { - return $set_cacheability(AccessResult::neutral()); + if ($operation !== 'view') { + $access_result->setCacheMaxAge(0); } + return $access_result; } /** diff --git a/core/modules/node/src/NodeGrantDatabaseStorageInterface.php b/core/modules/node/src/NodeGrantDatabaseStorageInterface.php index 5e81e1d04d0b..d343a2f350b4 100644 --- a/core/modules/node/src/NodeGrantDatabaseStorageInterface.php +++ b/core/modules/node/src/NodeGrantDatabaseStorageInterface.php @@ -42,8 +42,8 @@ interface NodeGrantDatabaseStorageInterface { * @param string $base_table * The base table of the query. * - * @return int - * Status of the access check. + * @return void + * No return value. */ public function alterQuery($query, array $tables, $operation, AccountInterface $account, $base_table); diff --git a/core/modules/node/tests/src/Functional/NodeCreationTest.php b/core/modules/node/tests/src/Functional/NodeCreationTest.php index 6930bb86f96f..7e99e3ba2ec1 100644 --- a/core/modules/node/tests/src/Functional/NodeCreationTest.php +++ b/core/modules/node/tests/src/Functional/NodeCreationTest.php @@ -311,6 +311,21 @@ class NodeCreationTest extends NodeTestBase { } /** + * Tests exception handling when saving a node through the form. + */ + public function testNodeCreateExceptionHandling(): void { + $this->drupalGet('node/add/page'); + + $this->submitForm([ + 'title[0][value]' => 'testing_transaction_exception', + 'body[0][value]' => $this->randomMachineName(16), + ], 'Save'); + + $this->assertSession()->pageTextNotContains('The website encountered an unexpected error.'); + $this->assertSession()->pageTextContains('The content could not be saved. Contact the site administrator if the problem persists.'); + } + + /** * Gets the watchdog IDs of the records with the rollback exception message. * * @return int[] diff --git a/core/modules/node/tests/src/Functional/NodeEditFormTest.php b/core/modules/node/tests/src/Functional/NodeEditFormTest.php index dc47998c9093..f661ae18ebbb 100644 --- a/core/modules/node/tests/src/Functional/NodeEditFormTest.php +++ b/core/modules/node/tests/src/Functional/NodeEditFormTest.php @@ -251,10 +251,15 @@ class NodeEditFormTest extends NodeTestBase { $edit['body[0][value]'] = $this->randomMachineName(16); $this->submitForm($edit, 'Save'); + // Enable user_hooks_test to test the users display name is visible on the + // edit form. + \Drupal::service('module_installer')->install(['user_hooks_test']); + \Drupal::keyValue('user_hooks_test')->set('user_format_name_alter', TRUE); $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); - $this->drupalGet("node/" . $node->id() . "/edit"); + $this->drupalGet($node->toUrl('edit-form')); $this->assertSession()->pageTextContains('Published'); $this->assertSession()->pageTextContains($this->container->get('date.formatter')->format($node->getChangedTime(), 'short')); + $this->assertSession()->responseContains('<em>' . $this->adminUser->id() . '</em>'); } /** diff --git a/core/modules/package_manager/src/PathExcluder/SiteConfigurationExcluder.php b/core/modules/package_manager/src/PathExcluder/SiteConfigurationExcluder.php index 9408e874c978..f8f23d0b86ac 100644 --- a/core/modules/package_manager/src/PathExcluder/SiteConfigurationExcluder.php +++ b/core/modules/package_manager/src/PathExcluder/SiteConfigurationExcluder.php @@ -43,7 +43,7 @@ class SiteConfigurationExcluder implements EventSubscriberInterface { // Exclude site-specific settings files, which are always in the web root. // By default, Drupal core will always try to write-protect these files. - // @see system_requirements() + // @see \Drupal\system\Hook\SystemRequirementsHooks $settings_files = [ 'settings.php', 'settings.local.php', diff --git a/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php b/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php index 18c87b11956b..4cc4405d4c81 100644 --- a/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php +++ b/core/modules/package_manager/tests/src/Build/PackageInstallSubmoduleTest.php @@ -4,13 +4,15 @@ declare(strict_types=1); namespace Drupal\Tests\package_manager\Build; +use PHPUnit\Framework\Attributes\Group; + /** * Tests installing packages in a stage directory. * - * @group package_manager - * @group #slow * @internal */ +#[Group('package_manager')] +#[Group('#slow')] class PackageInstallSubmoduleTest extends TemplateProjectTestBase { /** diff --git a/core/modules/package_manager/tests/src/Build/PackageInstallTest.php b/core/modules/package_manager/tests/src/Build/PackageInstallTest.php index 362343eaa91a..283c6aefa2cf 100644 --- a/core/modules/package_manager/tests/src/Build/PackageInstallTest.php +++ b/core/modules/package_manager/tests/src/Build/PackageInstallTest.php @@ -4,13 +4,15 @@ declare(strict_types=1); namespace Drupal\Tests\package_manager\Build; +use PHPUnit\Framework\Attributes\Group; + /** * Tests installing packages in a stage directory. * - * @group package_manager - * @group #slow * @internal */ +#[Group('package_manager')] +#[Group('#slow')] class PackageInstallTest extends TemplateProjectTestBase { /** diff --git a/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php b/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php index 2b9ef4aa894e..da6fc5eae320 100644 --- a/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php +++ b/core/modules/package_manager/tests/src/Build/PackageUpdateTest.php @@ -5,14 +5,15 @@ declare(strict_types=1); namespace Drupal\Tests\package_manager\Build; use Drupal\package_manager_test_api\ControllerSandboxManager; +use PHPUnit\Framework\Attributes\Group; /** * Tests updating packages in a stage directory. * - * @group package_manager - * @group #slow * @internal */ +#[Group('package_manager')] +#[Group('#slow')] class PackageUpdateTest extends TemplateProjectTestBase { /** diff --git a/core/modules/package_manager/tests/src/Kernel/ComposerPluginsValidatorTestBase.php b/core/modules/package_manager/tests/src/Kernel/ComposerPluginsValidatorTestBase.php index d9b91b1a7605..3100ce4bbf7a 100644 --- a/core/modules/package_manager/tests/src/Kernel/ComposerPluginsValidatorTestBase.php +++ b/core/modules/package_manager/tests/src/Kernel/ComposerPluginsValidatorTestBase.php @@ -12,10 +12,9 @@ use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\ValidationResult; /** - * @group package_manager * @internal */ -class ComposerPluginsValidatorTestBase extends PackageManagerKernelTestBase { +abstract class ComposerPluginsValidatorTestBase extends PackageManagerKernelTestBase { /** * Tests composer plugins are validated during pre-create. diff --git a/core/modules/pgsql/tests/src/Kernel/EntityQueryServiceDeprecation.php b/core/modules/pgsql/tests/src/Kernel/EntityQueryServiceDeprecationTest.php index db2c1b88ea37..043b5838d5ab 100644 --- a/core/modules/pgsql/tests/src/Kernel/EntityQueryServiceDeprecation.php +++ b/core/modules/pgsql/tests/src/Kernel/EntityQueryServiceDeprecationTest.php @@ -8,17 +8,20 @@ use Drupal\Core\Entity\Query\Sql\QueryFactory as BaseQueryFactory; use Drupal\Core\Entity\Query\Sql\pgsql\QueryFactory as DeprecatedQueryFactory; use Drupal\KernelTests\KernelTestBase; use Drupal\pgsql\EntityQuery\QueryFactory; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; /** * Tests the move of the 'pgsql.entity.query.sql' service. */ -class EntityQueryServiceDeprecation extends KernelTestBase { +#[Group('Database')] +#[Group('pgsql')] +class EntityQueryServiceDeprecationTest extends KernelTestBase { /** * Tests that the core provided service is deprecated. - * - * @group legacy */ + #[IgnoreDeprecations] public function testPostgresServiceDeprecated(): void { $running_driver = $this->container->get('database')->driver(); if ($running_driver === 'pgsql') { diff --git a/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php b/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php index b296446affbd..07bee95122cd 100644 --- a/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php +++ b/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php @@ -54,7 +54,7 @@ trait CookieResourceTestTrait { /** * {@inheritdoc} */ - protected function initAuthentication() { + protected function initAuthentication(): void { $user_login_url = Url::fromRoute('user.login.http') ->setRouteParameter('_format', static::$format); @@ -93,7 +93,7 @@ trait CookieResourceTestTrait { /** * {@inheritdoc} */ - protected function assertResponseWhenMissingAuthentication($method, ResponseInterface $response) { + protected function assertResponseWhenMissingAuthentication($method, ResponseInterface $response): void { // Requests needing cookie authentication but missing it results in a 403 // response. The cookie authentication mechanism sets no response message. // Hence, effectively, this is just the 403 response that one gets as the @@ -121,7 +121,7 @@ trait CookieResourceTestTrait { /** * {@inheritdoc} */ - protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) { + protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options): void { // X-CSRF-Token request header is unnecessary for safe and side effect-free // HTTP methods. No need for additional assertions. // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayTestBase.php b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayTestBase.php index 15b14a771e89..4b7ba9ca6980 100644 --- a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayTestBase.php +++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayTestBase.php @@ -11,7 +11,7 @@ use Drupal\Tests\system\FunctionalJavascript\OffCanvasTestBase; /** * Base class for Settings Tray tests. */ -class SettingsTrayTestBase extends OffCanvasTestBase { +abstract class SettingsTrayTestBase extends OffCanvasTestBase { use ContextualLinkClickTrait; diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 88f34652b986..cfd2f82952cc 100644 --- a/core/modules/system/config/schema/system.schema.yml +++ b/core/modules/system/config/schema/system.schema.yml @@ -69,14 +69,14 @@ system.cron: type: integer label: 'Requirements warning period' constraints: - # @see system_requirements() + # @see \Drupal\system\Hook\SystemRequirementsHooks Range: min: 60 requirements_error: type: integer label: 'Requirements error period' constraints: - # @see system_requirements() + # @see \Drupal\system\Hook\SystemRequirementsHooks Range: min: 300 logging: diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php index b340555c6289..fc253ac2f7cf 100644 --- a/core/modules/system/src/Controller/SystemController.php +++ b/core/modules/system/src/Controller/SystemController.php @@ -280,8 +280,6 @@ class SystemController extends ControllerBase { continue; } - // @todo Add logic for not displaying hidden modules in - // https://drupal.org/node/3117829. $module_name = $modules[$dependency]->info['name']; $theme->module_dependencies_list[$dependency] = $modules[$dependency]->status ? $this->t('@module_name', ['@module_name' => $module_name]) : $this->t('@module_name (<span class="admin-disabled">disabled</span>)', ['@module_name' => $module_name]); diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index 90dc9ead38b7..13297f3578c3 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -375,8 +375,6 @@ class ModulesListForm extends FormBase { // If this module requires other modules, add them to the array. /** @var \Drupal\Core\Extension\Dependency $dependency_object */ foreach ($module->requires as $dependency => $dependency_object) { - // @todo Add logic for not displaying hidden modules in - // https://drupal.org/node/3117829. if ($incompatible = $this->checkDependencyMessage($modules, $dependency, $dependency_object)) { $row['#requires'][$dependency] = $incompatible; $row['enable']['#disabled'] = TRUE; diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php index c999f37fe23f..97f76abe40e3 100644 --- a/core/modules/system/src/Form/ModulesUninstallForm.php +++ b/core/modules/system/src/Form/ModulesUninstallForm.php @@ -106,7 +106,8 @@ class ModulesUninstallForm extends FormBase { include_once DRUPAL_ROOT . '/core/includes/install.inc'; // Get a list of all available modules that can be uninstalled. - $uninstallable = array_filter($this->moduleExtensionList->getList(), function ($module) { + $modules = $this->moduleExtensionList->getList(); + $uninstallable = array_filter($modules, function ($module) { return empty($module->info['required']) && $module->status; }); @@ -199,7 +200,13 @@ class ModulesUninstallForm extends FormBase { // we can allow this module to be uninstalled. foreach (array_keys($module->required_by) as $dependent) { if ($this->updateRegistry->getInstalledVersion($dependent) !== $this->updateRegistry::SCHEMA_UNINSTALLED) { - $form['modules'][$module->getName()]['#required_by'][] = $dependent; + $module_name = $modules[$dependent]->info['name']; + if ($dependent != strtolower(str_replace(' ', '_', $module_name))) { + $form['modules'][$module->getName()]['#required_by'][] = $module_name . " (" . $dependent . ")"; + } + else { + $form['modules'][$module->getName()]['#required_by'][] = $module_name; + } $form['uninstall'][$module->getName()]['#disabled'] = TRUE; } } diff --git a/core/modules/system/src/Hook/PageAttachmentsHook.php b/core/modules/system/src/Hook/PageAttachmentsHook.php index fb6335f90c31..3f271571ede1 100644 --- a/core/modules/system/src/Hook/PageAttachmentsHook.php +++ b/core/modules/system/src/Hook/PageAttachmentsHook.php @@ -19,7 +19,7 @@ final class PageAttachmentsHook { /** * Implements hook_page_attachments(). * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() * @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter */ #[Hook('page_attachments')] diff --git a/core/modules/system/src/Hook/SystemHooks.php b/core/modules/system/src/Hook/SystemHooks.php index 800a1d718f37..e1be05077e29 100644 --- a/core/modules/system/src/Hook/SystemHooks.php +++ b/core/modules/system/src/Hook/SystemHooks.php @@ -339,7 +339,7 @@ class SystemHooks { \Drupal::service('file.htaccess_writer')->ensure(); if (\Drupal::config('system.advisories')->get('enabled')) { // Fetch the security advisories so that they will be pre-fetched during - // _system_advisories_requirements() and system_page_top(). + // systemAdvisoriesRequirements() and system_page_top(). /** @var \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher $fetcher */ $fetcher = \Drupal::service('system.sa_fetcher'); $fetcher->getSecurityAdvisories(); diff --git a/core/modules/system/src/Hook/SystemRequirementsHooks.php b/core/modules/system/src/Hook/SystemRequirementsHooks.php new file mode 100644 index 000000000000..49d318eb9bff --- /dev/null +++ b/core/modules/system/src/Hook/SystemRequirementsHooks.php @@ -0,0 +1,29 @@ +<?php + +namespace Drupal\system\Hook; + +use Drupal\Core\Hook\Attribute\Hook; +use Drupal\system\Install\Requirements\SystemRequirements; + +/** + * Requirements hook implementations for system module. + */ +class SystemRequirementsHooks { + + /** + * Implements hook_update_requirements(). + */ + #[Hook('update_requirements')] + public function updateRequirements(): array { + return SystemRequirements::checkRequirements('update'); + } + + /** + * Implements hook_runtime_requirements(). + */ + #[Hook('runtime_requirements')] + public function runtimeRequirements(): array { + return SystemRequirements::checkRequirements('runtime'); + } + +} diff --git a/core/modules/system/src/Install/Requirements/SystemRequirements.php b/core/modules/system/src/Install/Requirements/SystemRequirements.php new file mode 100644 index 000000000000..ee6a1d7a8028 --- /dev/null +++ b/core/modules/system/src/Install/Requirements/SystemRequirements.php @@ -0,0 +1,1665 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\system\Install\Requirements; + +use Drupal\Core\Extension\InstallRequirementsInterface; +use Drupal\Component\FileSystem\FileSystem as FileSystemComponent; +use Drupal\Component\Utility\Bytes; +use Drupal\Component\Utility\Environment; +use Drupal\Component\Utility\OpCodeCache; +use Drupal\Component\Utility\Unicode; +use Drupal\Core\Database\Database; +use Drupal\Core\DrupalKernel; +use Drupal\Core\Extension\ExtensionLifecycle; +use Drupal\Core\Extension\Requirement\RequirementSeverity; +use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Link; +use Drupal\Core\Render\Markup; +use Drupal\Core\Site\Settings; +use Drupal\Core\StreamWrapper\PrivateStream; +use Drupal\Core\StreamWrapper\PublicStream; +use Drupal\Core\StringTranslation\ByteSizeMarkup; +use Drupal\Core\StringTranslation\PluralTranslatableMarkup; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\Url; +use Drupal\Core\Utility\Error; +use Drupal\Core\Utility\PhpRequirements; +use Psr\Http\Client\ClientExceptionInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Install time requirements for the system module. + */ +class SystemRequirements implements InstallRequirementsInterface { + + /** + * {@inheritdoc} + */ + public static function getRequirements(): array { + return self::checkRequirements('install'); + } + + /** + * Check requirements for a given phase. + * + * @param string $phase + * The phase in which requirements are checked, as documented in + * hook_runtime_requirements() and hook_update_requirements(). + * + * @return array + * An associative array of requirements, as documented in + * hook_runtime_requirements() and hook_update_requirements(). + */ + public static function checkRequirements(string $phase): array { + global $install_state; + + // Get the current default PHP requirements for this version of Drupal. + $minimum_supported_php = PhpRequirements::getMinimumSupportedPhp(); + + // Reset the extension lists. + /** @var \Drupal\Core\Extension\ModuleExtensionList $module_extension_list */ + $module_extension_list = \Drupal::service('extension.list.module'); + $module_extension_list->reset(); + /** @var \Drupal\Core\Extension\ThemeExtensionList $theme_extension_list */ + $theme_extension_list = \Drupal::service('extension.list.theme'); + $theme_extension_list->reset(); + $requirements = []; + + // Report Drupal version + if ($phase == 'runtime') { + $requirements['drupal'] = [ + 'title' => t('Drupal'), + 'value' => \Drupal::VERSION, + 'severity' => RequirementSeverity::Info, + 'weight' => -10, + ]; + + // Display the currently active installation profile, if the site + // is not running the default installation profile. + $profile = \Drupal::installProfile(); + if ($profile != 'standard' && !empty($profile)) { + $info = $module_extension_list->getExtensionInfo($profile); + $requirements['install_profile'] = [ + 'title' => t('Installation profile'), + 'value' => t('%profile_name (%profile%version)', [ + '%profile_name' => $info['name'], + '%profile' => $profile, + '%version' => !empty($info['version']) ? '-' . $info['version'] : '', + ]), + 'severity' => RequirementSeverity::Info, + 'weight' => -9, + ]; + } + + // Gather all obsolete and experimental modules being enabled. + $obsolete_extensions = []; + $deprecated_modules = []; + $experimental_modules = []; + $enabled_modules = \Drupal::moduleHandler()->getModuleList(); + foreach ($enabled_modules as $module => $data) { + $info = $module_extension_list->getExtensionInfo($module); + if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])) { + if ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) { + $experimental_modules[$module] = $info['name']; + } + elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) { + $deprecated_modules[] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']]; + } + elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) { + $obsolete_extensions[$module] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']]; + } + } + } + + // Warn if any experimental modules are installed. + if (!empty($experimental_modules)) { + $requirements['experimental_modules'] = [ + 'title' => t('Experimental modules installed'), + 'value' => t('Experimental modules found: %module_list. <a href=":url">Experimental modules</a> are provided for testing purposes only. Use at your own risk.', ['%module_list' => implode(', ', $experimental_modules), ':url' => 'https://www.drupal.org/core/experimental']), + 'severity' => RequirementSeverity::Warning, + ]; + } + // Warn if any deprecated modules are installed. + if (!empty($deprecated_modules)) { + foreach ($deprecated_modules as $deprecated_module) { + $deprecated_modules_link_list[] = (string) Link::fromTextAndUrl($deprecated_module['name'], Url::fromUri($deprecated_module['lifecycle_link']))->toString(); + } + $requirements['deprecated_modules'] = [ + 'title' => t('Deprecated modules installed'), + 'value' => t('Deprecated modules found: %module_list.', [ + '%module_list' => Markup::create(implode(', ', $deprecated_modules_link_list)), + ]), + 'severity' => RequirementSeverity::Warning, + ]; + } + + // Gather all obsolete and experimental themes being installed. + $experimental_themes = []; + $deprecated_themes = []; + $installed_themes = \Drupal::service('theme_handler')->listInfo(); + foreach ($installed_themes as $theme => $data) { + $info = $theme_extension_list->getExtensionInfo($theme); + if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])) { + if ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) { + $experimental_themes[$theme] = $info['name']; + } + elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) { + $deprecated_themes[] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']]; + } + elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) { + $obsolete_extensions[$theme] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']]; + } + } + } + + // Warn if any experimental themes are installed. + if (!empty($experimental_themes)) { + $requirements['experimental_themes'] = [ + 'title' => t('Experimental themes installed'), + 'value' => t('Experimental themes found: %theme_list. Experimental themes are provided for testing purposes only. Use at your own risk.', ['%theme_list' => implode(', ', $experimental_themes)]), + 'severity' => RequirementSeverity::Warning, + ]; + } + + // Warn if any deprecated themes are installed. + if (!empty($deprecated_themes)) { + foreach ($deprecated_themes as $deprecated_theme) { + $deprecated_themes_link_list[] = (string) Link::fromTextAndUrl($deprecated_theme['name'], Url::fromUri($deprecated_theme['lifecycle_link']))->toString(); + + } + $requirements['deprecated_themes'] = [ + 'title' => t('Deprecated themes installed'), + 'value' => t('Deprecated themes found: %theme_list.', [ + '%theme_list' => Markup::create(implode(', ', $deprecated_themes_link_list)), + ]), + 'severity' => RequirementSeverity::Warning, + ]; + } + + // Warn if any obsolete extensions (themes or modules) are installed. + if (!empty($obsolete_extensions)) { + foreach ($obsolete_extensions as $obsolete_extension) { + $obsolete_extensions_link_list[] = (string) Link::fromTextAndUrl($obsolete_extension['name'], Url::fromUri($obsolete_extension['lifecycle_link']))->toString(); + } + $requirements['obsolete_extensions'] = [ + 'title' => t('Obsolete extensions installed'), + 'value' => t('Obsolete extensions found: %extensions. Obsolete extensions are provided only so that they can be uninstalled cleanly. You should immediately <a href=":uninstall_url">uninstall these extensions</a> since they may be removed in a future release.', [ + '%extensions' => Markup::create(implode(', ', $obsolete_extensions_link_list)), + ':uninstall_url' => Url::fromRoute('system.modules_uninstall')->toString(), + ]), + 'severity' => RequirementSeverity::Warning, + ]; + } + self::systemAdvisoriesRequirements($requirements); + } + + // Web server information. + $request_object = \Drupal::request(); + $software = $request_object->server->get('SERVER_SOFTWARE'); + $requirements['webserver'] = [ + 'title' => t('Web server'), + 'value' => $software, + ]; + + // Tests clean URL support. + if ($phase == 'install' && $install_state['interactive'] && !$request_object->query->has('rewrite') && str_contains($software, 'Apache')) { + // If the Apache rewrite module is not enabled, Apache version must be >= + // 2.2.16 because of the FallbackResource directive in the root .htaccess + // file. Since the Apache version reported by the server is dependent on + // the ServerTokens setting in httpd.conf, we may not be able to + // determine if a given config is valid. Thus we are unable to use + // version_compare() as we need have three possible outcomes: the version + // of Apache is greater than 2.2.16, is less than 2.2.16, or cannot be + // determined accurately. In the first case, we encourage the use of + // mod_rewrite; in the second case, we raise an error regarding the + // minimum Apache version; in the third case, we raise a warning that the + // current version of Apache may not be supported. + $rewrite_warning = FALSE; + $rewrite_error = FALSE; + $apache_version_string = 'Apache'; + + // Determine the Apache version number: major, minor and revision. + if (preg_match('/Apache\/(\d+)\.?(\d+)?\.?(\d+)?/', $software, $matches)) { + $apache_version_string = $matches[0]; + + // Major version number + if ($matches[1] < 2) { + $rewrite_error = TRUE; + } + elseif ($matches[1] == 2) { + if (!isset($matches[2])) { + $rewrite_warning = TRUE; + } + elseif ($matches[2] < 2) { + $rewrite_error = TRUE; + } + elseif ($matches[2] == 2) { + if (!isset($matches[3])) { + $rewrite_warning = TRUE; + } + elseif ($matches[3] < 16) { + $rewrite_error = TRUE; + } + } + } + } + else { + $rewrite_warning = TRUE; + } + + if ($rewrite_warning) { + $requirements['apache_version'] = [ + 'title' => t('Apache version'), + 'value' => $apache_version_string, + 'severity' => RequirementSeverity::Warning, + 'description' => t('Due to the settings for ServerTokens in httpd.conf, it is impossible to accurately determine the version of Apache running on this server. The reported value is @reported, to run Drupal without mod_rewrite, a minimum version of 2.2.16 is needed.', ['@reported' => $apache_version_string]), + ]; + } + + if ($rewrite_error) { + $requirements['Apache version'] = [ + 'title' => t('Apache version'), + 'value' => $apache_version_string, + 'severity' => RequirementSeverity::Error, + 'description' => t('The minimum version of Apache needed to run Drupal without mod_rewrite enabled is 2.2.16. See the <a href=":link">enabling clean URLs</a> page for more information on mod_rewrite.', [':link' => 'https://www.drupal.org/docs/8/clean-urls-in-drupal-8']), + ]; + } + + if (!$rewrite_error && !$rewrite_warning) { + $requirements['rewrite_module'] = [ + 'title' => t('Clean URLs'), + 'value' => t('Disabled'), + 'severity' => RequirementSeverity::Warning, + 'description' => t('Your server is capable of using clean URLs, but it is not enabled. Using clean URLs gives an improved user experience and is recommended. <a href=":link">Enable clean URLs</a>', [':link' => 'https://www.drupal.org/docs/8/clean-urls-in-drupal-8']), + ]; + } + } + + // Verify the user is running a supported PHP version. + // If the site is running a recommended version of PHP, just display it + // as an informational message on the status report. This will be overridden + // with an error or warning if the site is running older PHP versions for + // which Drupal has already or will soon drop support. + $phpversion = $phpversion_label = phpversion(); + if ($phase === 'runtime') { + $phpversion_label = t('@phpversion (<a href=":url">more information</a>)', [ + '@phpversion' => $phpversion, + ':url' => (new Url('system.php'))->toString(), + ]); + } + $requirements['php'] = [ + 'title' => t('PHP'), + 'value' => $phpversion_label, + ]; + + // Check if the PHP version is below what Drupal supports. + if (version_compare($phpversion, $minimum_supported_php) < 0) { + $requirements['php']['description'] = t('Your PHP installation is too old. Drupal requires at least PHP %version. It is recommended to upgrade to PHP version %recommended or higher for the best ongoing support. See <a href="http://php.net/supported-versions.php">PHP\'s version support documentation</a> and the <a href=":php_requirements">Drupal PHP requirements</a> page for more information.', + [ + '%version' => $minimum_supported_php, + '%recommended' => \Drupal::RECOMMENDED_PHP, + ':php_requirements' => 'https://www.drupal.org/docs/system-requirements/php-requirements', + ] + ); + + // If the PHP version is also below the absolute minimum allowed, it's not + // safe to continue with the requirements check, and should always be an + // error. + if (version_compare($phpversion, \Drupal::MINIMUM_PHP) < 0) { + $requirements['php']['severity'] = RequirementSeverity::Error; + return $requirements; + } + // Otherwise, the message should be an error at runtime, and a warning + // during installation or update. + $requirements['php']['severity'] = ($phase === 'runtime') ? RequirementSeverity::Error : RequirementSeverity::Warning; + } + // For PHP versions that are still supported but no longer recommended, + // inform users of what's recommended, allowing them to take action before + // it becomes urgent. + elseif ($phase === 'runtime' && version_compare($phpversion, \Drupal::RECOMMENDED_PHP) < 0) { + $requirements['php']['description'] = t('It is recommended to upgrade to PHP version %recommended or higher for the best ongoing support. See <a href="http://php.net/supported-versions.php">PHP\'s version support documentation</a> and the <a href=":php_requirements">Drupal PHP requirements</a> page for more information.', ['%recommended' => \Drupal::RECOMMENDED_PHP, ':php_requirements' => 'https://www.drupal.org/docs/system-requirements/php-requirements']); + $requirements['php']['severity'] = RequirementSeverity::Info; + } + + // Test for PHP extensions. + $requirements['php_extensions'] = [ + 'title' => t('PHP extensions'), + ]; + + $missing_extensions = []; + $required_extensions = [ + 'date', + 'dom', + 'filter', + 'gd', + 'hash', + 'json', + 'pcre', + 'pdo', + 'session', + 'SimpleXML', + 'SPL', + 'tokenizer', + 'xml', + 'zlib', + ]; + foreach ($required_extensions as $extension) { + if (!extension_loaded($extension)) { + $missing_extensions[] = $extension; + } + } + + if (!empty($missing_extensions)) { + $description = t('Drupal requires you to enable the PHP extensions in the following list (see the <a href=":system_requirements">system requirements page</a> for more information):', [ + ':system_requirements' => 'https://www.drupal.org/docs/system-requirements', + ]); + + // We use twig inline_template to avoid twig's autoescape. + $description = [ + '#type' => 'inline_template', + '#template' => '{{ description }}{{ missing_extensions }}', + '#context' => [ + 'description' => $description, + 'missing_extensions' => [ + '#theme' => 'item_list', + '#items' => $missing_extensions, + ], + ], + ]; + + $requirements['php_extensions']['value'] = t('Disabled'); + $requirements['php_extensions']['severity'] = RequirementSeverity::Error; + $requirements['php_extensions']['description'] = $description; + } + else { + $requirements['php_extensions']['value'] = t('Enabled'); + } + + if ($phase == 'install' || $phase == 'runtime') { + // Check to see if OPcache is installed. + if (!OpCodeCache::isEnabled()) { + $requirements['php_opcache'] = [ + 'value' => t('Not enabled'), + 'severity' => RequirementSeverity::Warning, + 'description' => t('PHP OPcode caching can improve your site\'s performance considerably. It is <strong>highly recommended</strong> to have <a href="http://php.net/manual/opcache.installation.php" target="_blank">OPcache</a> installed on your server.'), + ]; + } + else { + $requirements['php_opcache']['value'] = t('Enabled'); + } + $requirements['php_opcache']['title'] = t('PHP OPcode caching'); + } + + // Check to see if APCu is installed and configured correctly. + if ($phase == 'runtime' && PHP_SAPI != 'cli') { + $requirements['php_apcu_enabled']['title'] = t('PHP APCu caching'); + $requirements['php_apcu_available']['title'] = t('PHP APCu available caching'); + if (extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN)) { + $memory_info = apcu_sma_info(TRUE); + $apcu_actual_size = ByteSizeMarkup::create($memory_info['seg_size'] * $memory_info['num_seg']); + $apcu_recommended_size = '32 MB'; + $requirements['php_apcu_enabled']['value'] = t('Enabled (@size)', ['@size' => $apcu_actual_size]); + if (Bytes::toNumber(ini_get('apc.shm_size')) * ini_get('apc.shm_segments') < Bytes::toNumber($apcu_recommended_size)) { + $requirements['php_apcu_enabled']['severity'] = RequirementSeverity::Warning; + $requirements['php_apcu_enabled']['description'] = t('Depending on your configuration, Drupal can run with a @apcu_size APCu limit. However, a @apcu_default_size APCu limit (the default) or above is recommended, especially if your site uses additional custom or contributed modules.', [ + '@apcu_size' => $apcu_actual_size, + '@apcu_default_size' => $apcu_recommended_size, + ]); + } + else { + $memory_available = $memory_info['avail_mem'] / ($memory_info['seg_size'] * $memory_info['num_seg']); + if ($memory_available < 0.1) { + $requirements['php_apcu_available']['severity'] = RequirementSeverity::Error; + $requirements['php_apcu_available']['description'] = t('APCu is using over 90% of its allotted memory (@apcu_actual_size). To improve APCu performance, consider increasing this limit.', [ + '@apcu_actual_size' => $apcu_actual_size, + ]); + } + elseif ($memory_available < 0.25) { + $requirements['php_apcu_available']['severity'] = RequirementSeverity::Warning; + $requirements['php_apcu_available']['description'] = t('APCu is using over 75% of its allotted memory (@apcu_actual_size). To improve APCu performance, consider increasing this limit.', [ + '@apcu_actual_size' => $apcu_actual_size, + ]); + } + else { + $requirements['php_apcu_available']['severity'] = RequirementSeverity::OK; + } + $requirements['php_apcu_available']['value'] = t('Memory available: @available.', [ + '@available' => ByteSizeMarkup::create($memory_info['avail_mem']), + ]); + } + } + else { + $requirements['php_apcu_enabled'] += [ + 'value' => t('Not enabled'), + 'severity' => RequirementSeverity::Info, + 'description' => t('PHP APCu caching can improve your site\'s performance considerably. It is <strong>highly recommended</strong> to have <a href="https://www.php.net/manual/apcu.installation.php" target="_blank">APCu</a> installed on your server.'), + ]; + } + } + + if ($phase != 'update') { + // Test whether we have a good source of random bytes. + $requirements['php_random_bytes'] = [ + 'title' => t('Random number generation'), + ]; + try { + $bytes = random_bytes(10); + if (strlen($bytes) != 10) { + throw new \Exception("Tried to generate 10 random bytes, generated '" . strlen($bytes) . "'"); + } + $requirements['php_random_bytes']['value'] = t('Successful'); + } + catch (\Exception $e) { + // If /dev/urandom is not available on a UNIX-like system, check whether + // open_basedir restrictions are the cause. + $open_basedir_blocks_urandom = FALSE; + if (DIRECTORY_SEPARATOR === '/' && !@is_readable('/dev/urandom')) { + $open_basedir = ini_get('open_basedir'); + if ($open_basedir) { + $open_basedir_paths = explode(PATH_SEPARATOR, $open_basedir); + $open_basedir_blocks_urandom = !array_intersect(['/dev', '/dev/', '/dev/urandom'], $open_basedir_paths); + } + } + $args = [ + ':drupal-php' => 'https://www.drupal.org/docs/system-requirements/php-requirements', + '%exception_message' => $e->getMessage(), + ]; + if ($open_basedir_blocks_urandom) { + $requirements['php_random_bytes']['description'] = t('Drupal is unable to generate highly randomized numbers, which means certain security features like password reset URLs are not as secure as they should be. Instead, only a slow, less-secure fallback generator is available. The most likely cause is that open_basedir restrictions are in effect and /dev/urandom is not on the allowed list. See the <a href=":drupal-php">system requirements</a> page for more information. %exception_message', $args); + } + else { + $requirements['php_random_bytes']['description'] = t('Drupal is unable to generate highly randomized numbers, which means certain security features like password reset URLs are not as secure as they should be. Instead, only a slow, less-secure fallback generator is available. See the <a href=":drupal-php">system requirements</a> page for more information. %exception_message', $args); + } + $requirements['php_random_bytes']['value'] = t('Less secure'); + $requirements['php_random_bytes']['severity'] = RequirementSeverity::Error; + } + } + + if ($phase === 'runtime' && PHP_SAPI !== 'cli') { + if (!function_exists('fastcgi_finish_request') && !function_exists('litespeed_finish_request') && !ob_get_status()) { + $requirements['output_buffering'] = [ + 'title' => t('Output Buffering'), + 'error_value' => t('Not enabled'), + 'severity' => RequirementSeverity::Warning, + 'description' => t('<a href="https://www.php.net/manual/en/function.ob-start.php">Output buffering</a> is not enabled. This may degrade Drupal\'s performance. You can enable output buffering by default <a href="https://www.php.net/manual/en/outcontrol.configuration.php#ini.output-buffering">in your PHP settings</a>.'), + ]; + } + } + + if ($phase == 'install' || $phase == 'update') { + // Test for PDO (database). + $requirements['database_extensions'] = [ + 'title' => t('Database support'), + ]; + + // Make sure PDO is available. + $database_ok = extension_loaded('pdo'); + if (!$database_ok) { + $pdo_message = t('Your web server does not appear to support PDO (PHP Data Objects). Ask your hosting provider if they support the native PDO extension. See the <a href=":link">system requirements</a> page for more information.', [ + ':link' => 'https://www.drupal.org/docs/system-requirements/php-requirements#database', + ]); + } + else { + // Make sure at least one supported database driver exists. + if (empty(Database::getDriverList()->getInstallableList())) { + $database_ok = FALSE; + $pdo_message = t('Your web server does not appear to support any common PDO database extensions. Check with your hosting provider to see if they support PDO (PHP Data Objects) and offer any databases that <a href=":drupal-databases">Drupal supports</a>.', [ + ':drupal-databases' => 'https://www.drupal.org/docs/system-requirements/database-server-requirements', + ]); + } + // Make sure the native PDO extension is available, not the older PEAR + // version. (See install_verify_pdo() for details.) + if (!defined('PDO::ATTR_DEFAULT_FETCH_MODE')) { + $database_ok = FALSE; + $pdo_message = t('Your web server seems to have the wrong version of PDO installed. Drupal requires the PDO extension from PHP core. This system has the older PECL version. See the <a href=":link">system requirements</a> page for more information.', [ + ':link' => 'https://www.drupal.org/docs/system-requirements/php-requirements#database', + ]); + } + } + + if (!$database_ok) { + $requirements['database_extensions']['value'] = t('Disabled'); + $requirements['database_extensions']['severity'] = RequirementSeverity::Error; + $requirements['database_extensions']['description'] = $pdo_message; + } + else { + $requirements['database_extensions']['value'] = t('Enabled'); + } + } + + if ($phase === 'runtime' || $phase === 'update') { + // Database information. + $class = Database::getConnection()->getConnectionOptions()['namespace'] . '\\Install\\Tasks'; + /** @var \Drupal\Core\Database\Install\Tasks $tasks */ + $tasks = new $class(); + $requirements['database_system'] = [ + 'title' => t('Database system'), + 'value' => $tasks->name(), + ]; + $requirements['database_system_version'] = [ + 'title' => t('Database system version'), + 'value' => Database::getConnection()->version(), + ]; + + $errors = $tasks->engineVersionRequirementsCheck(); + $error_count = count($errors); + if ($error_count > 0) { + $error_message = [ + '#theme' => 'item_list', + '#items' => $errors, + // Use the comma-list style to display a single error without bullets. + '#context' => ['list_style' => $error_count === 1 ? 'comma-list' : ''], + ]; + $requirements['database_system_version']['severity'] = RequirementSeverity::Error; + $requirements['database_system_version']['description'] = $error_message; + } + } + + if ($phase === 'runtime' || $phase === 'update') { + // Test database JSON support. + $requirements['database_support_json'] = [ + 'title' => t('Database support for JSON'), + 'severity' => RequirementSeverity::OK, + 'value' => t('Available'), + 'description' => t('Drupal requires databases that support JSON storage.'), + ]; + + if (!Database::getConnection()->hasJson()) { + $requirements['database_support_json']['value'] = t('Not available'); + $requirements['database_support_json']['severity'] = RequirementSeverity::Error; + } + } + + // Test PHP memory_limit + $memory_limit = ini_get('memory_limit'); + $requirements['php_memory_limit'] = [ + 'title' => t('PHP memory limit'), + 'value' => $memory_limit == -1 ? t('-1 (Unlimited)') : $memory_limit, + ]; + + if (!Environment::checkMemoryLimit(\Drupal::MINIMUM_PHP_MEMORY_LIMIT, $memory_limit)) { + $description = []; + if ($phase == 'install') { + $description['phase'] = t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the installation process.', ['%memory_minimum_limit' => \Drupal::MINIMUM_PHP_MEMORY_LIMIT]); + } + elseif ($phase == 'update') { + $description['phase'] = t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the update process.', ['%memory_minimum_limit' => \Drupal::MINIMUM_PHP_MEMORY_LIMIT]); + } + elseif ($phase == 'runtime') { + $description['phase'] = t('Depending on your configuration, Drupal can run with a %memory_limit PHP memory limit. However, a %memory_minimum_limit PHP memory limit or above is recommended, especially if your site uses additional custom or contributed modules.', ['%memory_limit' => $memory_limit, '%memory_minimum_limit' => \Drupal::MINIMUM_PHP_MEMORY_LIMIT]); + } + + if (!empty($description['phase'])) { + if ($php_ini_path = get_cfg_var('cfg_file_path')) { + $description['memory'] = t('Increase the memory limit by editing the memory_limit parameter in the file %configuration-file and then restart your web server (or contact your system administrator or hosting provider for assistance).', ['%configuration-file' => $php_ini_path]); + } + else { + $description['memory'] = t('Contact your system administrator or hosting provider for assistance with increasing your PHP memory limit.'); + } + + $handbook_link = t('For more information, see the online handbook entry for <a href=":memory-limit">increasing the PHP memory limit</a>.', [':memory-limit' => 'https://www.drupal.org/node/207036']); + + $description = [ + '#type' => 'inline_template', + '#template' => '{{ description_phase }} {{ description_memory }} {{ handbook }}', + '#context' => [ + 'description_phase' => $description['phase'], + 'description_memory' => $description['memory'], + 'handbook' => $handbook_link, + ], + ]; + + $requirements['php_memory_limit']['description'] = $description; + $requirements['php_memory_limit']['severity'] = RequirementSeverity::Warning; + } + } + + // Test if configuration files and directory are writable. + if ($phase == 'runtime') { + $conf_errors = []; + // Find the site path. Kernel service is not always available at this + // point, but is preferred, when available. + if (\Drupal::hasService('kernel')) { + $site_path = \Drupal::getContainer()->getParameter('site.path'); + } + else { + $site_path = DrupalKernel::findSitePath(Request::createFromGlobals()); + } + // Allow system administrators to disable permissions hardening for the + // site directory. This allows additional files in the site directory to + // be updated when they are managed in a version control system. + if (Settings::get('skip_permissions_hardening')) { + $error_value = t('Protection disabled'); + // If permissions hardening is disabled, then only show a warning for a + // writable file, as a reminder, rather than an error. + $file_protection_severity = RequirementSeverity::Warning; + } + else { + $error_value = t('Not protected'); + // In normal operation, writable files or directories are an error. + $file_protection_severity = RequirementSeverity::Error; + if (!drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir')) { + $conf_errors[] = t("The directory %file is not protected from modifications and poses a security risk. You must change the directory's permissions to be non-writable.", ['%file' => $site_path]); + } + } + foreach (['settings.php', 'settings.local.php', 'services.yml'] as $conf_file) { + $full_path = $site_path . '/' . $conf_file; + if (file_exists($full_path) && !drupal_verify_install_file($full_path, FILE_EXIST | FILE_READABLE | FILE_NOT_WRITABLE, 'file', !Settings::get('skip_permissions_hardening'))) { + $conf_errors[] = t("The file %file is not protected from modifications and poses a security risk. You must change the file's permissions to be non-writable.", ['%file' => $full_path]); + } + } + if (!empty($conf_errors)) { + if (count($conf_errors) == 1) { + $description = $conf_errors[0]; + } + else { + // We use twig inline_template to avoid double escaping. + $description = [ + '#type' => 'inline_template', + '#template' => '{{ configuration_error_list }}', + '#context' => [ + 'configuration_error_list' => [ + '#theme' => 'item_list', + '#items' => $conf_errors, + ], + ], + ]; + } + $requirements['configuration_files'] = [ + 'value' => $error_value, + 'severity' => $file_protection_severity, + 'description' => $description, + ]; + } + else { + $requirements['configuration_files'] = [ + 'value' => t('Protected'), + ]; + } + $requirements['configuration_files']['title'] = t('Configuration files'); + } + + // Test the contents of the .htaccess files. + if ($phase == 'runtime' && Settings::get('auto_create_htaccess', TRUE)) { + // Try to write the .htaccess files first, to prevent false alarms in + // case (for example) the /tmp directory was wiped. + /** @var \Drupal\Core\File\HtaccessWriterInterface $htaccessWriter */ + $htaccessWriter = \Drupal::service("file.htaccess_writer"); + $htaccessWriter->ensure(); + foreach ($htaccessWriter->defaultProtectedDirs() as $protected_dir) { + $htaccess_file = $protected_dir->getPath() . '/.htaccess'; + // Check for the string which was added to the recommended .htaccess + // file in the latest security update. + if (!file_exists($htaccess_file) || !($contents = @file_get_contents($htaccess_file)) || !str_contains($contents, 'Drupal_Security_Do_Not_Remove_See_SA_2013_003')) { + $url = 'https://www.drupal.org/SA-CORE-2013-003'; + $requirements[$htaccess_file] = [ + // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString + 'title' => new TranslatableMarkup($protected_dir->getTitle()), + 'value' => t('Not fully protected'), + 'severity' => RequirementSeverity::Error, + 'description' => t('See <a href=":url">@url</a> for information about the recommended .htaccess file which should be added to the %directory directory to help protect against arbitrary code execution.', [':url' => $url, '@url' => $url, '%directory' => $protected_dir->getPath()]), + ]; + } + } + } + + // Report cron status. + if ($phase == 'runtime') { + $cron_config = \Drupal::config('system.cron'); + // Cron warning threshold defaults to two days. + $threshold_warning = $cron_config->get('threshold.requirements_warning'); + // Cron error threshold defaults to two weeks. + $threshold_error = $cron_config->get('threshold.requirements_error'); + + // Determine when cron last ran. + $cron_last = \Drupal::state()->get('system.cron_last'); + if (!is_numeric($cron_last)) { + $cron_last = \Drupal::state()->get('install_time', 0); + } + + // Determine severity based on time since cron last ran. + $severity = RequirementSeverity::Info; + $request_time = \Drupal::time()->getRequestTime(); + if ($request_time - $cron_last > $threshold_error) { + $severity = RequirementSeverity::Error; + } + elseif ($request_time - $cron_last > $threshold_warning) { + $severity = RequirementSeverity::Warning; + } + + // Set summary and description based on values determined above. + $summary = t('Last run @time ago', ['@time' => \Drupal::service('date.formatter')->formatTimeDiffSince($cron_last)]); + + $requirements['cron'] = [ + 'title' => t('Cron maintenance tasks'), + 'severity' => $severity, + 'value' => $summary, + ]; + if ($severity != RequirementSeverity::Info) { + $requirements['cron']['description'][] = [ + [ + '#markup' => t('Cron has not run recently.'), + '#suffix' => ' ', + ], + [ + '#markup' => t('For more information, see the online handbook entry for <a href=":cron-handbook">configuring cron jobs</a>.', [':cron-handbook' => 'https://www.drupal.org/docs/administering-a-drupal-site/cron-automated-tasks/cron-automated-tasks-overview']), + '#suffix' => ' ', + ], + ]; + } + $requirements['cron']['description'][] = [ + [ + '#type' => 'link', + '#prefix' => '(', + '#title' => t('more information'), + '#suffix' => ')', + '#url' => Url::fromRoute('system.cron_settings'), + ], + [ + '#prefix' => '<span class="cron-description__run-cron">', + '#suffix' => '</span>', + '#type' => 'link', + '#title' => t('Run cron'), + '#url' => Url::fromRoute('system.run_cron'), + ], + ]; + } + if ($phase != 'install') { + $directories = [ + PublicStream::basePath(), + // By default no private files directory is configured. For private + // files to be secure the admin needs to provide a path outside the + // webroot. + PrivateStream::basePath(), + \Drupal::service('file_system')->getTempDirectory(), + ]; + } + + // During an install we need to make assumptions about the file system + // unless overrides are provided in settings.php. + if ($phase == 'install') { + $directories = []; + if ($file_public_path = Settings::get('file_public_path')) { + $directories[] = $file_public_path; + } + else { + // If we are installing Drupal, the settings.php file might not exist + // yet in the intended site directory, so don't require it. + $request = Request::createFromGlobals(); + $site_path = DrupalKernel::findSitePath($request); + $directories[] = $site_path . '/files'; + } + if ($file_private_path = Settings::get('file_private_path')) { + $directories[] = $file_private_path; + } + if (Settings::get('file_temp_path')) { + $directories[] = Settings::get('file_temp_path'); + } + else { + // If the temporary directory is not overridden use an appropriate + // temporary path for the system. + $directories[] = FileSystemComponent::getOsTemporaryDirectory(); + } + } + + // Check the config directory if it is defined in settings.php. If it isn't + // defined, the installer will create a valid config directory later, but + // during runtime we must always display an error. + $config_sync_directory = Settings::get('config_sync_directory'); + if (!empty($config_sync_directory)) { + // If we're installing Drupal try and create the config sync directory. + if (!is_dir($config_sync_directory) && $phase == 'install') { + \Drupal::service('file_system')->prepareDirectory($config_sync_directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); + } + if (!is_dir($config_sync_directory)) { + if ($phase == 'install') { + $description = t('An automated attempt to create the directory %directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', ['%directory' => $config_sync_directory, ':handbook_url' => 'https://www.drupal.org/server-permissions']); + } + else { + $description = t('The directory %directory does not exist.', ['%directory' => $config_sync_directory]); + } + $requirements['config sync directory'] = [ + 'title' => t('Configuration sync directory'), + 'description' => $description, + 'severity' => RequirementSeverity::Error, + ]; + } + } + if ($phase != 'install' && empty($config_sync_directory)) { + $requirements['config sync directory'] = [ + 'title' => t('Configuration sync directory'), + 'value' => t('Not present'), + 'description' => t("Your %file file must define the %setting setting as a string containing the directory in which configuration files can be found.", ['%file' => $site_path . '/settings.php', '%setting' => "\$settings['config_sync_directory']"]), + 'severity' => RequirementSeverity::Error, + ]; + } + + $requirements['file system'] = [ + 'title' => t('File system'), + ]; + + $error = ''; + // For installer, create the directories if possible. + foreach ($directories as $directory) { + if (!$directory) { + continue; + } + if ($phase == 'install') { + \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); + } + $is_writable = is_writable($directory); + $is_directory = is_dir($directory); + if (!$is_writable || !$is_directory) { + $description = ''; + $requirements['file system']['value'] = t('Not writable'); + if (!$is_directory) { + $error = t('The directory %directory does not exist.', ['%directory' => $directory]); + } + else { + $error = t('The directory %directory is not writable.', ['%directory' => $directory]); + } + // The files directory requirement check is done only during install and + // runtime. + if ($phase == 'runtime') { + $description = t('You may need to set the correct directory at the <a href=":admin-file-system">file system settings page</a> or change the current directory\'s permissions so that it is writable.', [':admin-file-system' => Url::fromRoute('system.file_system_settings')->toString()]); + } + elseif ($phase == 'install') { + // For the installer UI, we need different wording. 'value' will + // be treated as version, so provide none there. + $description = t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', [':handbook_url' => 'https://www.drupal.org/server-permissions']); + $requirements['file system']['value'] = ''; + } + if (!empty($description)) { + $description = [ + '#type' => 'inline_template', + '#template' => '{{ error }} {{ description }}', + '#context' => [ + 'error' => $error, + 'description' => $description, + ], + ]; + $requirements['file system']['description'] = $description; + $requirements['file system']['severity'] = RequirementSeverity::Error; + } + } + else { + // This function can be called before the config_cache table has been + // created. + if ($phase == 'install' || \Drupal::config('system.file')->get('default_scheme') == 'public') { + $requirements['file system']['value'] = t('Writable (<em>public</em> download method)'); + } + else { + $requirements['file system']['value'] = t('Writable (<em>private</em> download method)'); + } + } + } + + // See if updates are available in update.php. + if ($phase == 'runtime') { + $requirements['update'] = [ + 'title' => t('Database updates'), + 'value' => t('Up to date'), + ]; + + // Check installed modules. + $has_pending_updates = FALSE; + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + foreach (\Drupal::moduleHandler()->getModuleList() as $module => $filename) { + $updates = $update_registry->getAvailableUpdates($module); + if ($updates) { + $default = $update_registry->getInstalledVersion($module); + if (max($updates) > $default) { + $has_pending_updates = TRUE; + break; + } + } + } + if (!$has_pending_updates) { + /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */ + $post_update_registry = \Drupal::service('update.post_update_registry'); + $missing_post_update_functions = $post_update_registry->getPendingUpdateFunctions(); + if (!empty($missing_post_update_functions)) { + $has_pending_updates = TRUE; + } + } + + if ($has_pending_updates) { + $requirements['update']['severity'] = RequirementSeverity::Error; + $requirements['update']['value'] = t('Out of date'); + $requirements['update']['description'] = t('Some modules have database schema updates to install. You should run the <a href=":update">database update script</a> immediately.', [':update' => Url::fromRoute('system.db_update')->toString()]); + } + + $requirements['entity_update'] = [ + 'title' => t('Entity/field definitions'), + 'value' => t('Up to date'), + ]; + // Verify that no entity updates are pending. + if ($change_list = \Drupal::entityDefinitionUpdateManager()->getChangeSummary()) { + $build = []; + foreach ($change_list as $entity_type_id => $changes) { + $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id); + $build[] = [ + '#theme' => 'item_list', + '#title' => $entity_type->getLabel(), + '#items' => $changes, + ]; + } + + $entity_update_issues = \Drupal::service('renderer')->renderInIsolation($build); + $requirements['entity_update']['severity'] = RequirementSeverity::Error; + $requirements['entity_update']['value'] = t('Mismatched entity and/or field definitions'); + $requirements['entity_update']['description'] = t('The following changes were detected in the entity type and field definitions. @updates', ['@updates' => $entity_update_issues]); + } + } + + // Display the deployment identifier if set. + if ($phase == 'runtime') { + if ($deployment_identifier = Settings::get('deployment_identifier')) { + $requirements['deployment identifier'] = [ + 'title' => t('Deployment identifier'), + 'value' => $deployment_identifier, + 'severity' => RequirementSeverity::Info, + ]; + } + } + + // Verify the update.php access setting + if ($phase == 'runtime') { + if (Settings::get('update_free_access')) { + $requirements['update access'] = [ + 'value' => t('Not protected'), + 'severity' => RequirementSeverity::Error, + 'description' => t('The update.php script is accessible to everyone without authentication check, which is a security risk. You must change the @settings_name value in your settings.php back to FALSE.', ['@settings_name' => '$settings[\'update_free_access\']']), + ]; + } + else { + $requirements['update access'] = [ + 'value' => t('Protected'), + ]; + } + $requirements['update access']['title'] = t('Access to update.php'); + } + + // Display an error if a newly introduced dependency in a module is not + // resolved. + if ($phase === 'update' || $phase === 'runtime') { + $create_extension_incompatibility_list = function (array $extension_names, PluralTranslatableMarkup $description, PluralTranslatableMarkup $title, TranslatableMarkup|string $message = '', TranslatableMarkup|string $additional_description = '') { + if ($message === '') { + $message = new TranslatableMarkup('Review the <a href=":url"> suggestions for resolving this incompatibility</a> to repair your installation, and then re-run update.php.', [':url' => 'https://www.drupal.org/docs/updating-drupal/troubleshooting-database-updates']); + } + // Use an inline twig template to: + // - Concatenate MarkupInterface objects and preserve safeness. + // - Use the item_list theme for the extension list. + $template = [ + '#type' => 'inline_template', + '#template' => '{{ description }}{{ extensions }}{{ additional_description }}<br>', + '#context' => [ + 'extensions' => [ + '#theme' => 'item_list', + ], + ], + ]; + $template['#context']['extensions']['#items'] = $extension_names; + $template['#context']['description'] = $description; + $template['#context']['additional_description'] = $additional_description; + return [ + 'title' => $title, + 'value' => [ + 'list' => $template, + 'handbook_link' => [ + '#markup' => $message, + ], + ], + 'severity' => RequirementSeverity::Error, + ]; + }; + $profile = \Drupal::installProfile(); + $files = $module_extension_list->getList(); + $files += $theme_extension_list->getList(); + $core_incompatible_extensions = []; + $php_incompatible_extensions = []; + foreach ($files as $extension_name => $file) { + // Ignore uninstalled extensions and installation profiles. + if (!$file->status || $extension_name == $profile) { + continue; + } + + $name = $file->info['name']; + if (!empty($file->info['core_incompatible'])) { + $core_incompatible_extensions[$file->info['type']][] = $name; + } + + // Check the extension's PHP version. + $php = (string) $file->info['php']; + if (version_compare($php, PHP_VERSION, '>')) { + $php_incompatible_extensions[$file->info['type']][] = $name; + } + + // Check the module's required modules. + /** @var \Drupal\Core\Extension\Dependency $requirement */ + foreach ($file->requires as $requirement) { + $required_module = $requirement->getName(); + // Check if the module exists. + if (!isset($files[$required_module])) { + $requirements["$extension_name-$required_module"] = [ + 'title' => t('Unresolved dependency'), + 'description' => t('@name requires this module.', ['@name' => $name]), + 'value' => t('@required_name (Missing)', ['@required_name' => $required_module]), + 'severity' => RequirementSeverity::Error, + ]; + continue; + } + // Check for an incompatible version. + $required_file = $files[$required_module]; + $required_name = $required_file->info['name']; + // Remove CORE_COMPATIBILITY- only from the start of the string. + $version = preg_replace('/^(' . \Drupal::CORE_COMPATIBILITY . '\-)/', '', $required_file->info['version'] ?? ''); + if (!$requirement->isCompatible($version)) { + $requirements["$extension_name-$required_module"] = [ + 'title' => t('Unresolved dependency'), + 'description' => t('@name requires this module and version. Currently using @required_name version @version', ['@name' => $name, '@required_name' => $required_name, '@version' => $version]), + 'value' => t('@required_name (Version @compatibility required)', ['@required_name' => $required_name, '@compatibility' => $requirement->getConstraintString()]), + 'severity' => RequirementSeverity::Error, + ]; + continue; + } + } + } + if (!empty($core_incompatible_extensions['module'])) { + $requirements['module_core_incompatible'] = $create_extension_incompatibility_list( + $core_incompatible_extensions['module'], + new PluralTranslatableMarkup( + count($core_incompatible_extensions['module']), + 'The following module is installed, but it is incompatible with Drupal @version:', + 'The following modules are installed, but they are incompatible with Drupal @version:', + ['@version' => \Drupal::VERSION] + ), + new PluralTranslatableMarkup( + count($core_incompatible_extensions['module']), + 'Incompatible module', + 'Incompatible modules' + ) + ); + } + if (!empty($core_incompatible_extensions['theme'])) { + $requirements['theme_core_incompatible'] = $create_extension_incompatibility_list( + $core_incompatible_extensions['theme'], + new PluralTranslatableMarkup( + count($core_incompatible_extensions['theme']), + 'The following theme is installed, but it is incompatible with Drupal @version:', + 'The following themes are installed, but they are incompatible with Drupal @version:', + ['@version' => \Drupal::VERSION] + ), + new PluralTranslatableMarkup( + count($core_incompatible_extensions['theme']), + 'Incompatible theme', + 'Incompatible themes' + ) + ); + } + if (!empty($php_incompatible_extensions['module'])) { + $requirements['module_php_incompatible'] = $create_extension_incompatibility_list( + $php_incompatible_extensions['module'], + new PluralTranslatableMarkup( + count($php_incompatible_extensions['module']), + 'The following module is installed, but it is incompatible with PHP @version:', + 'The following modules are installed, but they are incompatible with PHP @version:', + ['@version' => phpversion()] + ), + new PluralTranslatableMarkup( + count($php_incompatible_extensions['module']), + 'Incompatible module', + 'Incompatible modules' + ) + ); + } + if (!empty($php_incompatible_extensions['theme'])) { + $requirements['theme_php_incompatible'] = $create_extension_incompatibility_list( + $php_incompatible_extensions['theme'], + new PluralTranslatableMarkup( + count($php_incompatible_extensions['theme']), + 'The following theme is installed, but it is incompatible with PHP @version:', + 'The following themes are installed, but they are incompatible with PHP @version:', + ['@version' => phpversion()] + ), + new PluralTranslatableMarkup( + count($php_incompatible_extensions['theme']), + 'Incompatible theme', + 'Incompatible themes' + ) + ); + } + + $extension_config = \Drupal::configFactory()->get('core.extension'); + + // Look for removed core modules. + $is_removed_module = function ($extension_name) use ($module_extension_list) { + return !$module_extension_list->exists($extension_name) + && array_key_exists($extension_name, DRUPAL_CORE_REMOVED_MODULE_LIST); + }; + $removed_modules = array_filter(array_keys($extension_config->get('module')), $is_removed_module); + if (!empty($removed_modules)) { + $list = []; + foreach ($removed_modules as $removed_module) { + $list[] = t('<a href=":url">@module</a>', [ + ':url' => "https://www.drupal.org/project/$removed_module", + '@module' => DRUPAL_CORE_REMOVED_MODULE_LIST[$removed_module], + ]); + } + $requirements['removed_module'] = $create_extension_incompatibility_list( + $list, + new PluralTranslatableMarkup( + count($removed_modules), + 'You must add the following contributed module and reload this page.', + 'You must add the following contributed modules and reload this page.' + ), + new PluralTranslatableMarkup( + count($removed_modules), + 'Removed core module', + 'Removed core modules' + ), + new TranslatableMarkup( + 'For more information read the <a href=":url">documentation on deprecated modules.</a>', + [':url' => 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-modules'] + ), + new PluralTranslatableMarkup( + count($removed_modules), + 'This module is installed on your site but is no longer provided by Core.', + 'These modules are installed on your site but are no longer provided by Core.' + ), + ); + } + + // Look for removed core themes. + $is_removed_theme = function ($extension_name) use ($theme_extension_list) { + return !$theme_extension_list->exists($extension_name) + && array_key_exists($extension_name, DRUPAL_CORE_REMOVED_THEME_LIST); + }; + $removed_themes = array_filter(array_keys($extension_config->get('theme')), $is_removed_theme); + if (!empty($removed_themes)) { + $list = []; + foreach ($removed_themes as $removed_theme) { + $list[] = t('<a href=":url">@theme</a>', [ + ':url' => "https://www.drupal.org/project/$removed_theme", + '@theme' => DRUPAL_CORE_REMOVED_THEME_LIST[$removed_theme], + ]); + } + $requirements['removed_theme'] = $create_extension_incompatibility_list( + $list, + new PluralTranslatableMarkup( + count($removed_themes), + 'You must add the following contributed theme and reload this page.', + 'You must add the following contributed themes and reload this page.' + ), + new PluralTranslatableMarkup( + count($removed_themes), + 'Removed core theme', + 'Removed core themes' + ), + new TranslatableMarkup( + 'For more information read the <a href=":url">documentation on deprecated themes.</a>', + [':url' => 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-themes'] + ), + new PluralTranslatableMarkup( + count($removed_themes), + 'This theme is installed on your site but is no longer provided by Core.', + 'These themes are installed on your site but are no longer provided by Core.' + ), + ); + } + + // Look for missing modules. + $is_missing_module = function ($extension_name) use ($module_extension_list) { + return !$module_extension_list->exists($extension_name) && !in_array($extension_name, array_keys(DRUPAL_CORE_REMOVED_MODULE_LIST), TRUE); + }; + $invalid_modules = array_filter(array_keys($extension_config->get('module')), $is_missing_module); + + if (!empty($invalid_modules)) { + $requirements['invalid_module'] = $create_extension_incompatibility_list( + $invalid_modules, + new PluralTranslatableMarkup( + count($invalid_modules), + 'The following module is marked as installed in the core.extension configuration, but it is missing:', + 'The following modules are marked as installed in the core.extension configuration, but they are missing:' + ), + new PluralTranslatableMarkup( + count($invalid_modules), + 'Missing or invalid module', + 'Missing or invalid modules' + ) + ); + } + + // Look for invalid themes. + $is_missing_theme = function ($extension_name) use (&$theme_extension_list) { + return !$theme_extension_list->exists($extension_name) && !in_array($extension_name, array_keys(DRUPAL_CORE_REMOVED_THEME_LIST), TRUE); + }; + $invalid_themes = array_filter(array_keys($extension_config->get('theme')), $is_missing_theme); + if (!empty($invalid_themes)) { + $requirements['invalid_theme'] = $create_extension_incompatibility_list( + $invalid_themes, + new PluralTranslatableMarkup( + count($invalid_themes), + 'The following theme is marked as installed in the core.extension configuration, but it is missing:', + 'The following themes are marked as installed in the core.extension configuration, but they are missing:' + ), + new PluralTranslatableMarkup( + count($invalid_themes), + 'Missing or invalid theme', + 'Missing or invalid themes' + ) + ); + } + } + + // Returns Unicode library status and errors. + $libraries = [ + Unicode::STATUS_SINGLEBYTE => t('Standard PHP'), + Unicode::STATUS_MULTIBYTE => t('PHP Mbstring Extension'), + Unicode::STATUS_ERROR => t('Error'), + ]; + $severities = [ + Unicode::STATUS_SINGLEBYTE => RequirementSeverity::Warning, + Unicode::STATUS_MULTIBYTE => NULL, + Unicode::STATUS_ERROR => RequirementSeverity::Error, + ]; + $failed_check = Unicode::check(); + $library = Unicode::getStatus(); + + $requirements['unicode'] = [ + 'title' => t('Unicode library'), + 'value' => $libraries[$library], + 'severity' => $severities[$library], + ]; + switch ($failed_check) { + case 'mb_strlen': + $requirements['unicode']['description'] = t('Operations on Unicode strings are emulated on a best-effort basis. Install the <a href="http://php.net/mbstring">PHP mbstring extension</a> for improved Unicode support.'); + break; + + case 'mbstring.encoding_translation': + $requirements['unicode']['description'] = t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.encoding_translation</em> setting. Refer to the <a href="http://php.net/mbstring">PHP mbstring documentation</a> for more information.'); + break; + } + + if ($phase == 'runtime') { + // Check for update status module. + if (!\Drupal::moduleHandler()->moduleExists('update')) { + $requirements['update status'] = [ + 'value' => t('Not enabled'), + 'severity' => RequirementSeverity::Warning, + 'description' => t('Update notifications are not enabled. It is <strong>highly recommended</strong> that you install the Update Status module from the <a href=":module">module administration page</a> in order to stay up-to-date on new releases. For more information, <a href=":update">Update status handbook page</a>.', [ + ':update' => 'https://www.drupal.org/documentation/modules/update', + ':module' => Url::fromRoute('system.modules_list')->toString(), + ]), + ]; + } + else { + $requirements['update status'] = [ + 'value' => t('Enabled'), + ]; + } + $requirements['update status']['title'] = t('Update notifications'); + + if (Settings::get('rebuild_access')) { + $requirements['rebuild access'] = [ + 'title' => t('Rebuild access'), + 'value' => t('Enabled'), + 'severity' => RequirementSeverity::Error, + 'description' => t('The rebuild_access setting is enabled in settings.php. It is recommended to have this setting disabled unless you are performing a rebuild.'), + ]; + } + } + + // Check if the SameSite cookie attribute is set to a valid value. Since + // this involves checking whether we are using a secure connection this + // only makes sense inside an HTTP request, not on the command line. + if ($phase === 'runtime' && PHP_SAPI !== 'cli') { + $samesite = ini_get('session.cookie_samesite') ?: t('Not set'); + // Check if the SameSite attribute is set to a valid value. If it is set + // to 'None' the request needs to be done over HTTPS. + $valid = match ($samesite) { + 'Lax', 'Strict' => TRUE, + 'None' => $request_object->isSecure(), + default => FALSE, + }; + $requirements['php_session_samesite'] = [ + 'title' => t('SameSite cookie attribute'), + 'value' => $samesite, + 'severity' => $valid ? RequirementSeverity::OK : RequirementSeverity::Warning, + 'description' => t('This attribute should be explicitly set to Lax, Strict or None. If set to None then the request must be made via HTTPS. See <a href=":url" target="_blank">PHP documentation</a>', [ + ':url' => 'https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-samesite', + ]), + ]; + } + + // See if trusted host names have been configured, and warn the user if they + // are not set. + if ($phase == 'runtime') { + $trusted_host_patterns = Settings::get('trusted_host_patterns'); + if (empty($trusted_host_patterns)) { + $requirements['trusted_host_patterns'] = [ + 'title' => t('Trusted Host Settings'), + 'value' => t('Not enabled'), + 'description' => t('The trusted_host_patterns setting is not configured in settings.php. This can lead to security vulnerabilities. It is <strong>highly recommended</strong> that you configure this. See <a href=":url">Protecting against HTTP HOST Header attacks</a> for more information.', [':url' => 'https://www.drupal.org/docs/installing-drupal/trusted-host-settings']), + 'severity' => RequirementSeverity::Error, + ]; + } + else { + $requirements['trusted_host_patterns'] = [ + 'title' => t('Trusted Host Settings'), + 'value' => t('Enabled'), + 'description' => t('The trusted_host_patterns setting is set to allow %trusted_host_patterns', ['%trusted_host_patterns' => implode(', ', $trusted_host_patterns)]), + ]; + } + } + + // When the database driver is provided by a module, then check that the + // providing module is installed. + if ($phase === 'runtime' || $phase === 'update') { + $connection = Database::getConnection(); + $provider = $connection->getProvider(); + if ($provider !== 'core' && !\Drupal::moduleHandler()->moduleExists($provider)) { + $autoload = $connection->getConnectionOptions()['autoload'] ?? ''; + if (str_contains($autoload, 'src/Driver/Database/')) { + $post_update_registry = \Drupal::service('update.post_update_registry'); + $pending_updates = $post_update_registry->getPendingUpdateInformation(); + if (!in_array('enable_provider_database_driver', array_keys($pending_updates['system']['pending'] ?? []), TRUE)) { + // Only show the warning when the post update function has run and + // the module that is providing the database driver is not + // installed. + $requirements['database_driver_provided_by_module'] = [ + 'title' => t('Database driver provided by module'), + 'value' => t('Not installed'), + 'description' => t('The current database driver is provided by the module: %module. The module is currently not installed. You should immediately <a href=":install">install</a> the module.', ['%module' => $provider, ':install' => Url::fromRoute('system.modules_list')->toString()]), + 'severity' => RequirementSeverity::Error, + ]; + } + } + } + } + + // Check xdebug.max_nesting_level, as some pages will not work if it is too + // low. + if (extension_loaded('xdebug')) { + // Setting this value to 256 was considered adequate on Xdebug 2.3 + // (see http://bugs.xdebug.org/bug_view_page.php?bug_id=00001100) + $minimum_nesting_level = 256; + $current_nesting_level = ini_get('xdebug.max_nesting_level'); + + if ($current_nesting_level < $minimum_nesting_level) { + $requirements['xdebug_max_nesting_level'] = [ + 'title' => t('Xdebug settings'), + 'value' => t('xdebug.max_nesting_level is set to %value.', ['%value' => $current_nesting_level]), + 'description' => t('Set <code>xdebug.max_nesting_level=@level</code> in your PHP configuration as some pages in your Drupal site will not work when this setting is too low.', ['@level' => $minimum_nesting_level]), + 'severity' => RequirementSeverity::Error, + ]; + } + } + + // Installations on Windows can run into limitations with MAX_PATH if the + // Drupal root directory is too deep in the filesystem. Generally this + // shows up in cached Twig templates and other public files with long + // directory or file names. There is no definite root directory depth below + // which Drupal is guaranteed to function correctly on Windows. Since + // problems are likely with more than 100 characters in the Drupal root + // path, show an error. + if (str_starts_with(PHP_OS, 'WIN')) { + $depth = strlen(realpath(DRUPAL_ROOT . '/' . PublicStream::basePath())); + if ($depth > 120) { + $requirements['max_path_on_windows'] = [ + 'title' => t('Windows installation depth'), + 'description' => t('The public files directory path is %depth characters. Paths longer than 120 characters will cause problems on Windows.', ['%depth' => $depth]), + 'severity' => RequirementSeverity::Error, + ]; + } + } + // Check to see if dates will be limited to 1901-2038. + if (PHP_INT_SIZE <= 4) { + $requirements['limited_date_range'] = [ + 'title' => t('Limited date range'), + 'value' => t('Your PHP installation has a limited date range.'), + 'description' => t('You are running on a system where PHP is compiled or limited to using 32-bit integers. This will limit the range of dates and timestamps to the years 1901-2038. Read about the <a href=":url">limitations of 32-bit PHP</a>.', [':url' => 'https://www.drupal.org/docs/system-requirements/limitations-of-32-bit-php']), + 'severity' => RequirementSeverity::Warning, + ]; + } + + // During installs from configuration don't support install profiles that + // implement hook_install. + if ($phase == 'install' && !empty($install_state['config_install_path'])) { + $install_hook = $install_state['parameters']['profile'] . '_install'; + if (function_exists($install_hook)) { + $requirements['config_install'] = [ + 'title' => t('Configuration install'), + 'value' => $install_state['parameters']['profile'], + 'description' => t('The selected profile has a hook_install() implementation and therefore can not be installed from configuration.'), + 'severity' => RequirementSeverity::Error, + ]; + } + } + + if ($phase === 'runtime') { + $settings = Settings::getAll(); + if (array_key_exists('install_profile', $settings)) { + // The following message is only informational because not all site + // owners have access to edit their settings.php as it may be + // controlled by their hosting provider. + $requirements['install_profile_in_settings'] = [ + 'title' => t('Install profile in settings'), + 'value' => t("Drupal 9 no longer uses the \$settings['install_profile'] value in settings.php and it should be removed."), + 'severity' => RequirementSeverity::Warning, + ]; + } + } + + // Ensure that no module has a current schema version that is lower than the + // one that was last removed. + if ($phase == 'update') { + $module_handler = \Drupal::moduleHandler(); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $module_list = []; + // hook_update_last_removed() is a procedural hook hook because we + // do not have classes loaded that would be needed. + // Simply inlining the old hook mechanism is better than making + // ModuleInstaller::invoke() public. + foreach ($module_handler->getModuleList() as $module => $extension) { + $function = $module . '_update_last_removed'; + if (function_exists($function)) { + $last_removed = $function(); + if ($last_removed && $last_removed > $update_registry->getInstalledVersion($module)) { + + /** @var \Drupal\Core\Extension\Extension $module_info */ + $module_info = $module_extension_list->get($module); + $module_list[$module] = [ + 'name' => $module_info->info['name'], + 'last_removed' => $last_removed, + 'installed_version' => $update_registry->getInstalledVersion($module), + ]; + } + } + } + + // If user module is in the list then only show a specific message for + // Drupal core. + if (isset($module_list['user'])) { + $requirements['user_update_last_removed'] = [ + 'title' => t('The version of Drupal you are trying to update from is too old'), + 'description' => t('Updating to Drupal @current_major is only supported from Drupal version @required_min_version or higher. If you are trying to update from an older version, first update to the latest version of Drupal @previous_major. (<a href=":url">Drupal upgrade guide</a>)', [ + '@current_major' => 10, + '@required_min_version' => '9.4.0', + '@previous_major' => 9, + ':url' => 'https://www.drupal.org/docs/upgrading-drupal/drupal-8-and-higher', + ]), + 'severity' => RequirementSeverity::Error, + ]; + } + else { + foreach ($module_list as $module => $data) { + $requirements[$module . '_update_last_removed'] = [ + 'title' => t('Unsupported schema version: @module', ['@module' => $data['name']]), + 'description' => t('The installed version of the %module module is too old to update. Update to an intermediate version first (last removed version: @last_removed_version, installed version: @installed_version).', [ + '%module' => $data['name'], + '@last_removed_version' => $data['last_removed'], + '@installed_version' => $data['installed_version'], + ]), + 'severity' => RequirementSeverity::Error, + ]; + } + } + // Also check post-updates. Only do this if we're not already showing an + // error for hook_update_N(). + $missing_updates = []; + if (empty($module_list)) { + $existing_updates = \Drupal::service('keyvalue')->get('post_update')->get('existing_updates', []); + $post_update_registry = \Drupal::service('update.post_update_registry'); + $modules = \Drupal::moduleHandler()->getModuleList(); + foreach ($modules as $module => $extension) { + $module_info = $module_extension_list->get($module); + $removed_post_updates = $post_update_registry->getRemovedPostUpdates($module); + if ($missing_updates = array_diff(array_keys($removed_post_updates), $existing_updates)) { + $versions = array_unique(array_intersect_key($removed_post_updates, array_flip($missing_updates))); + $description = new PluralTranslatableMarkup(count($versions), + 'The installed version of the %module module is too old to update. Update to a version prior to @versions first (missing updates: @missing_updates).', + 'The installed version of the %module module is too old to update. Update first to a version prior to all of the following: @versions (missing updates: @missing_updates).', + [ + '%module' => $module_info->info['name'], + '@missing_updates' => implode(', ', $missing_updates), + '@versions' => implode(', ', $versions), + ] + ); + $requirements[$module . '_post_update_removed'] = [ + 'title' => t('Missing updates for: @module', ['@module' => $module_info->info['name']]), + 'description' => $description, + 'severity' => RequirementSeverity::Error, + ]; + } + } + } + + if (empty($missing_updates)) { + foreach ($update_registry->getAllEquivalentUpdates() as $module => $equivalent_updates) { + $module_info = $module_extension_list->get($module); + foreach ($equivalent_updates as $future_update => $data) { + $future_update_function_name = $module . '_update_' . $future_update; + $ran_update_function_name = $module . '_update_' . $data['ran_update']; + // If an update was marked as an equivalent by a previous update, + // and both the previous update and the equivalent update are not + // found in the current code base, prevent updating. This indicates + // a site attempting to go 'backwards' in terms of database schema. + // @see \Drupal\Core\Update\UpdateHookRegistry::markFutureUpdateEquivalent() + if (!function_exists($ran_update_function_name) && !function_exists($future_update_function_name)) { + // If the module is provided by core prepend helpful text as the + // module does not exist in composer or Drupal.org. + if (str_starts_with($module_info->getPathname(), 'core/')) { + $future_version_string = 'Drupal Core ' . $data['future_version_string']; + } + else { + $future_version_string = $data['future_version_string']; + } + $requirements[$module . '_equivalent_update_missing'] = [ + 'title' => t('Missing updates for: @module', ['@module' => $module_info->info['name']]), + 'description' => t('The version of the %module module that you are attempting to update to is missing update @future_update (which was marked as an equivalent by @ran_update). Update to at least @future_version_string.', [ + '%module' => $module_info->info['name'], + '@ran_update' => $data['ran_update'], + '@future_update' => $future_update, + '@future_version_string' => $future_version_string, + ]), + 'severity' => RequirementSeverity::Error, + ]; + break; + } + } + } + } + } + + // Add warning when twig debug option is enabled. + if ($phase === 'runtime') { + $development_settings = \Drupal::keyValue('development_settings'); + $twig_debug = $development_settings->get('twig_debug', FALSE); + $twig_cache_disable = $development_settings->get('twig_cache_disable', FALSE); + if ($twig_debug || $twig_cache_disable) { + $requirements['twig_debug_enabled'] = [ + 'title' => t('Twig development mode'), + 'value' => t('Twig development mode settings are turned on. Go to @link to disable them.', [ + '@link' => Link::createFromRoute( + 'development settings page', + 'system.development_settings', + )->toString(), + ]), + 'severity' => RequirementSeverity::Warning, + ]; + } + $render_cache_disabled = $development_settings->get('disable_rendered_output_cache_bins', FALSE); + if ($render_cache_disabled) { + $requirements['render_cache_disabled'] = [ + 'title' => t('Markup caching disabled'), + 'value' => t('Render cache, dynamic page cache, and page cache are bypassed. Go to @link to enable them.', [ + '@link' => Link::createFromRoute( + 'development settings page', + 'system.development_settings', + )->toString(), + ]), + 'severity' => RequirementSeverity::Warning, + ]; + } + } + + return $requirements; + } + + /** + * Display requirements from security advisories. + * + * @param array[] $requirements + * The requirements array as specified in hook_requirements(). + */ + public static function systemAdvisoriesRequirements(array &$requirements): void { + if (!\Drupal::config('system.advisories')->get('enabled')) { + return; + } + + /** @var \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher $fetcher */ + $fetcher = \Drupal::service('system.sa_fetcher'); + try { + $advisories = $fetcher->getSecurityAdvisories(TRUE, 5); + } + catch (ClientExceptionInterface $exception) { + $requirements['system_advisories']['title'] = t('Critical security announcements'); + $requirements['system_advisories']['severity'] = RequirementSeverity::Warning; + $requirements['system_advisories']['description'] = ['#theme' => 'system_security_advisories_fetch_error_message']; + Error::logException(\Drupal::logger('system'), $exception, 'Failed to retrieve security advisory data.'); + return; + } + + if (!empty($advisories)) { + $advisory_links = []; + $severity = RequirementSeverity::Warning; + foreach ($advisories as $advisory) { + if (!$advisory->isPsa()) { + $severity = RequirementSeverity::Error; + } + $advisory_links[] = new Link($advisory->getTitle(), Url::fromUri($advisory->getUrl())); + } + $requirements['system_advisories']['title'] = t('Critical security announcements'); + $requirements['system_advisories']['severity'] = $severity; + $requirements['system_advisories']['description'] = [ + 'list' => [ + '#theme' => 'item_list', + '#items' => $advisory_links, + ], + ]; + if (\Drupal::moduleHandler()->moduleExists('help')) { + $requirements['system_advisories']['description']['help_link'] = Link::createFromRoute( + 'What are critical security announcements?', + 'help.page', ['name' => 'system'], + ['fragment' => 'security-advisories'] + )->toRenderable(); + } + } + } + +} diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 431651d08e20..5bbaa8a5436f 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -5,31 +5,9 @@ * Install, update and uninstall functions for the system module. */ -use Drupal\Component\FileSystem\FileSystem as FileSystemComponent; -use Drupal\Component\Utility\Bytes; use Drupal\Component\Utility\Crypt; -use Drupal\Component\Utility\Environment; -use Drupal\Component\Utility\OpCodeCache; -use Drupal\Component\Utility\Unicode; -use Drupal\Core\Database\Database; -use Drupal\Core\DrupalKernel; -use Drupal\Core\Extension\ExtensionLifecycle; -use Drupal\Core\Extension\Requirement\RequirementSeverity; -use Drupal\Core\File\FileSystemInterface; -use Drupal\Core\Link; -use Drupal\Core\Render\Markup; -use Drupal\Core\Site\Settings; -use Drupal\Core\StreamWrapper\PrivateStream; -use Drupal\Core\StreamWrapper\PublicStream; -use Drupal\Core\StringTranslation\ByteSizeMarkup; -use Drupal\Core\StringTranslation\PluralTranslatableMarkup; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Update\EquivalentUpdate; -use Drupal\Core\Url; -use Drupal\Core\Utility\Error; -use Drupal\Core\Utility\PhpRequirements; -use Psr\Http\Client\ClientExceptionInterface; -use Symfony\Component\HttpFoundation\Request; // cspell:ignore quickedit @@ -62,1565 +40,6 @@ const DRUPAL_CORE_REMOVED_THEME_LIST = [ ]; /** - * Implements hook_requirements(). - */ -function system_requirements($phase): array { - global $install_state; - - // Get the current default PHP requirements for this version of Drupal. - $minimum_supported_php = PhpRequirements::getMinimumSupportedPhp(); - - // Reset the extension lists. - /** @var \Drupal\Core\Extension\ModuleExtensionList $module_extension_list */ - $module_extension_list = \Drupal::service('extension.list.module'); - $module_extension_list->reset(); - /** @var \Drupal\Core\Extension\ThemeExtensionList $theme_extension_list */ - $theme_extension_list = \Drupal::service('extension.list.theme'); - $theme_extension_list->reset(); - $requirements = []; - - // Report Drupal version - if ($phase == 'runtime') { - $requirements['drupal'] = [ - 'title' => t('Drupal'), - 'value' => \Drupal::VERSION, - 'severity' => RequirementSeverity::Info, - 'weight' => -10, - ]; - - // Display the currently active installation profile, if the site - // is not running the default installation profile. - $profile = \Drupal::installProfile(); - if ($profile != 'standard' && !empty($profile)) { - $info = $module_extension_list->getExtensionInfo($profile); - $requirements['install_profile'] = [ - 'title' => t('Installation profile'), - 'value' => t('%profile_name (%profile%version)', [ - '%profile_name' => $info['name'], - '%profile' => $profile, - '%version' => !empty($info['version']) ? '-' . $info['version'] : '', - ]), - 'severity' => RequirementSeverity::Info, - 'weight' => -9, - ]; - } - - // Gather all obsolete and experimental modules being enabled. - $obsolete_extensions = []; - $deprecated_modules = []; - $experimental_modules = []; - $enabled_modules = \Drupal::moduleHandler()->getModuleList(); - foreach ($enabled_modules as $module => $data) { - $info = $module_extension_list->getExtensionInfo($module); - if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])) { - if ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) { - $experimental_modules[$module] = $info['name']; - } - elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) { - $deprecated_modules[] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']]; - } - elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) { - $obsolete_extensions[$module] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']]; - } - } - } - - // Warn if any experimental modules are installed. - if (!empty($experimental_modules)) { - $requirements['experimental_modules'] = [ - 'title' => t('Experimental modules installed'), - 'value' => t('Experimental modules found: %module_list. <a href=":url">Experimental modules</a> are provided for testing purposes only. Use at your own risk.', ['%module_list' => implode(', ', $experimental_modules), ':url' => 'https://www.drupal.org/core/experimental']), - 'severity' => RequirementSeverity::Warning, - ]; - } - // Warn if any deprecated modules are installed. - if (!empty($deprecated_modules)) { - foreach ($deprecated_modules as $deprecated_module) { - $deprecated_modules_link_list[] = (string) Link::fromTextAndUrl($deprecated_module['name'], Url::fromUri($deprecated_module['lifecycle_link']))->toString(); - } - $requirements['deprecated_modules'] = [ - 'title' => t('Deprecated modules installed'), - 'value' => t('Deprecated modules found: %module_list.', [ - '%module_list' => Markup::create(implode(', ', $deprecated_modules_link_list)), - ]), - 'severity' => RequirementSeverity::Warning, - ]; - } - - // Gather all obsolete and experimental themes being installed. - $experimental_themes = []; - $deprecated_themes = []; - $installed_themes = \Drupal::service('theme_handler')->listInfo(); - foreach ($installed_themes as $theme => $data) { - $info = $theme_extension_list->getExtensionInfo($theme); - if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])) { - if ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) { - $experimental_themes[$theme] = $info['name']; - } - elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) { - $deprecated_themes[] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']]; - } - elseif ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) { - $obsolete_extensions[$theme] = ['name' => $info['name'], 'lifecycle_link' => $info['lifecycle_link']]; - } - } - } - - // Warn if any experimental themes are installed. - if (!empty($experimental_themes)) { - $requirements['experimental_themes'] = [ - 'title' => t('Experimental themes installed'), - 'value' => t('Experimental themes found: %theme_list. Experimental themes are provided for testing purposes only. Use at your own risk.', ['%theme_list' => implode(', ', $experimental_themes)]), - 'severity' => RequirementSeverity::Warning, - ]; - } - - // Warn if any deprecated themes are installed. - if (!empty($deprecated_themes)) { - foreach ($deprecated_themes as $deprecated_theme) { - $deprecated_themes_link_list[] = (string) Link::fromTextAndUrl($deprecated_theme['name'], Url::fromUri($deprecated_theme['lifecycle_link']))->toString(); - - } - $requirements['deprecated_themes'] = [ - 'title' => t('Deprecated themes installed'), - 'value' => t('Deprecated themes found: %theme_list.', [ - '%theme_list' => Markup::create(implode(', ', $deprecated_themes_link_list)), - ]), - 'severity' => RequirementSeverity::Warning, - ]; - } - - // Warn if any obsolete extensions (themes or modules) are installed. - if (!empty($obsolete_extensions)) { - foreach ($obsolete_extensions as $obsolete_extension) { - $obsolete_extensions_link_list[] = (string) Link::fromTextAndUrl($obsolete_extension['name'], Url::fromUri($obsolete_extension['lifecycle_link']))->toString(); - } - $requirements['obsolete_extensions'] = [ - 'title' => t('Obsolete extensions installed'), - 'value' => t('Obsolete extensions found: %extensions. Obsolete extensions are provided only so that they can be uninstalled cleanly. You should immediately <a href=":uninstall_url">uninstall these extensions</a> since they may be removed in a future release.', [ - '%extensions' => Markup::create(implode(', ', $obsolete_extensions_link_list)), - ':uninstall_url' => Url::fromRoute('system.modules_uninstall')->toString(), - ]), - 'severity' => RequirementSeverity::Warning, - ]; - } - _system_advisories_requirements($requirements); - } - - // Web server information. - $request_object = \Drupal::request(); - $software = $request_object->server->get('SERVER_SOFTWARE'); - $requirements['webserver'] = [ - 'title' => t('Web server'), - 'value' => $software, - ]; - - // Tests clean URL support. - if ($phase == 'install' && $install_state['interactive'] && !$request_object->query->has('rewrite') && str_contains($software, 'Apache')) { - // If the Apache rewrite module is not enabled, Apache version must be >= - // 2.2.16 because of the FallbackResource directive in the root .htaccess - // file. Since the Apache version reported by the server is dependent on the - // ServerTokens setting in httpd.conf, we may not be able to determine if a - // given config is valid. Thus we are unable to use version_compare() as we - // need have three possible outcomes: the version of Apache is greater than - // 2.2.16, is less than 2.2.16, or cannot be determined accurately. In the - // first case, we encourage the use of mod_rewrite; in the second case, we - // raise an error regarding the minimum Apache version; in the third case, - // we raise a warning that the current version of Apache may not be - // supported. - $rewrite_warning = FALSE; - $rewrite_error = FALSE; - $apache_version_string = 'Apache'; - - // Determine the Apache version number: major, minor and revision. - if (preg_match('/Apache\/(\d+)\.?(\d+)?\.?(\d+)?/', $software, $matches)) { - $apache_version_string = $matches[0]; - - // Major version number - if ($matches[1] < 2) { - $rewrite_error = TRUE; - } - elseif ($matches[1] == 2) { - if (!isset($matches[2])) { - $rewrite_warning = TRUE; - } - elseif ($matches[2] < 2) { - $rewrite_error = TRUE; - } - elseif ($matches[2] == 2) { - if (!isset($matches[3])) { - $rewrite_warning = TRUE; - } - elseif ($matches[3] < 16) { - $rewrite_error = TRUE; - } - } - } - } - else { - $rewrite_warning = TRUE; - } - - if ($rewrite_warning) { - $requirements['apache_version'] = [ - 'title' => t('Apache version'), - 'value' => $apache_version_string, - 'severity' => RequirementSeverity::Warning, - 'description' => t('Due to the settings for ServerTokens in httpd.conf, it is impossible to accurately determine the version of Apache running on this server. The reported value is @reported, to run Drupal without mod_rewrite, a minimum version of 2.2.16 is needed.', ['@reported' => $apache_version_string]), - ]; - } - - if ($rewrite_error) { - $requirements['Apache version'] = [ - 'title' => t('Apache version'), - 'value' => $apache_version_string, - 'severity' => RequirementSeverity::Error, - 'description' => t('The minimum version of Apache needed to run Drupal without mod_rewrite enabled is 2.2.16. See the <a href=":link">enabling clean URLs</a> page for more information on mod_rewrite.', [':link' => 'https://www.drupal.org/docs/8/clean-urls-in-drupal-8']), - ]; - } - - if (!$rewrite_error && !$rewrite_warning) { - $requirements['rewrite_module'] = [ - 'title' => t('Clean URLs'), - 'value' => t('Disabled'), - 'severity' => RequirementSeverity::Warning, - 'description' => t('Your server is capable of using clean URLs, but it is not enabled. Using clean URLs gives an improved user experience and is recommended. <a href=":link">Enable clean URLs</a>', [':link' => 'https://www.drupal.org/docs/8/clean-urls-in-drupal-8']), - ]; - } - } - - // Verify the user is running a supported PHP version. - // If the site is running a recommended version of PHP, just display it - // as an informational message on the status report. This will be overridden - // with an error or warning if the site is running older PHP versions for - // which Drupal has already or will soon drop support. - $phpversion = $phpversion_label = phpversion(); - if ($phase === 'runtime') { - $phpversion_label = t('@phpversion (<a href=":url">more information</a>)', [ - '@phpversion' => $phpversion, - ':url' => (new Url('system.php'))->toString(), - ]); - } - $requirements['php'] = [ - 'title' => t('PHP'), - 'value' => $phpversion_label, - ]; - - // Check if the PHP version is below what Drupal supports. - if (version_compare($phpversion, $minimum_supported_php) < 0) { - $requirements['php']['description'] = t('Your PHP installation is too old. Drupal requires at least PHP %version. It is recommended to upgrade to PHP version %recommended or higher for the best ongoing support. See <a href="http://php.net/supported-versions.php">PHP\'s version support documentation</a> and the <a href=":php_requirements">Drupal PHP requirements</a> page for more information.', - [ - '%version' => $minimum_supported_php, - '%recommended' => \Drupal::RECOMMENDED_PHP, - ':php_requirements' => 'https://www.drupal.org/docs/system-requirements/php-requirements', - ] - ); - - // If the PHP version is also below the absolute minimum allowed, it's not - // safe to continue with the requirements check, and should always be an - // error. - if (version_compare($phpversion, \Drupal::MINIMUM_PHP) < 0) { - $requirements['php']['severity'] = RequirementSeverity::Error; - return $requirements; - } - // Otherwise, the message should be an error at runtime, and a warning - // during installation or update. - $requirements['php']['severity'] = ($phase === 'runtime') ? RequirementSeverity::Error : RequirementSeverity::Warning; - } - // For PHP versions that are still supported but no longer recommended, - // inform users of what's recommended, allowing them to take action before it - // becomes urgent. - elseif ($phase === 'runtime' && version_compare($phpversion, \Drupal::RECOMMENDED_PHP) < 0) { - $requirements['php']['description'] = t('It is recommended to upgrade to PHP version %recommended or higher for the best ongoing support. See <a href="http://php.net/supported-versions.php">PHP\'s version support documentation</a> and the <a href=":php_requirements">Drupal PHP requirements</a> page for more information.', ['%recommended' => \Drupal::RECOMMENDED_PHP, ':php_requirements' => 'https://www.drupal.org/docs/system-requirements/php-requirements']); - $requirements['php']['severity'] = RequirementSeverity::Info; - } - - // Test for PHP extensions. - $requirements['php_extensions'] = [ - 'title' => t('PHP extensions'), - ]; - - $missing_extensions = []; - $required_extensions = [ - 'date', - 'dom', - 'filter', - 'gd', - 'hash', - 'json', - 'pcre', - 'pdo', - 'session', - 'SimpleXML', - 'SPL', - 'tokenizer', - 'xml', - 'zlib', - ]; - foreach ($required_extensions as $extension) { - if (!extension_loaded($extension)) { - $missing_extensions[] = $extension; - } - } - - if (!empty($missing_extensions)) { - $description = t('Drupal requires you to enable the PHP extensions in the following list (see the <a href=":system_requirements">system requirements page</a> for more information):', [ - ':system_requirements' => 'https://www.drupal.org/docs/system-requirements', - ]); - - // We use twig inline_template to avoid twig's autoescape. - $description = [ - '#type' => 'inline_template', - '#template' => '{{ description }}{{ missing_extensions }}', - '#context' => [ - 'description' => $description, - 'missing_extensions' => [ - '#theme' => 'item_list', - '#items' => $missing_extensions, - ], - ], - ]; - - $requirements['php_extensions']['value'] = t('Disabled'); - $requirements['php_extensions']['severity'] = RequirementSeverity::Error; - $requirements['php_extensions']['description'] = $description; - } - else { - $requirements['php_extensions']['value'] = t('Enabled'); - } - - if ($phase == 'install' || $phase == 'runtime') { - // Check to see if OPcache is installed. - if (!OpCodeCache::isEnabled()) { - $requirements['php_opcache'] = [ - 'value' => t('Not enabled'), - 'severity' => RequirementSeverity::Warning, - 'description' => t('PHP OPcode caching can improve your site\'s performance considerably. It is <strong>highly recommended</strong> to have <a href="http://php.net/manual/opcache.installation.php" target="_blank">OPcache</a> installed on your server.'), - ]; - } - else { - $requirements['php_opcache']['value'] = t('Enabled'); - } - $requirements['php_opcache']['title'] = t('PHP OPcode caching'); - } - - // Check to see if APCu is installed and configured correctly. - if ($phase == 'runtime' && PHP_SAPI != 'cli') { - $requirements['php_apcu_enabled']['title'] = t('PHP APCu caching'); - $requirements['php_apcu_available']['title'] = t('PHP APCu available caching'); - if (extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN)) { - $memory_info = apcu_sma_info(TRUE); - $apcu_actual_size = ByteSizeMarkup::create($memory_info['seg_size'] * $memory_info['num_seg']); - $apcu_recommended_size = '32 MB'; - $requirements['php_apcu_enabled']['value'] = t('Enabled (@size)', ['@size' => $apcu_actual_size]); - if (Bytes::toNumber(ini_get('apc.shm_size')) * ini_get('apc.shm_segments') < Bytes::toNumber($apcu_recommended_size)) { - $requirements['php_apcu_enabled']['severity'] = RequirementSeverity::Warning; - $requirements['php_apcu_enabled']['description'] = t('Depending on your configuration, Drupal can run with a @apcu_size APCu limit. However, a @apcu_default_size APCu limit (the default) or above is recommended, especially if your site uses additional custom or contributed modules.', [ - '@apcu_size' => $apcu_actual_size, - '@apcu_default_size' => $apcu_recommended_size, - ]); - } - else { - $memory_available = $memory_info['avail_mem'] / ($memory_info['seg_size'] * $memory_info['num_seg']); - if ($memory_available < 0.1) { - $requirements['php_apcu_available']['severity'] = RequirementSeverity::Error; - $requirements['php_apcu_available']['description'] = t('APCu is using over 90% of its allotted memory (@apcu_actual_size). To improve APCu performance, consider increasing this limit.', [ - '@apcu_actual_size' => $apcu_actual_size, - ]); - } - elseif ($memory_available < 0.25) { - $requirements['php_apcu_available']['severity'] = RequirementSeverity::Warning; - $requirements['php_apcu_available']['description'] = t('APCu is using over 75% of its allotted memory (@apcu_actual_size). To improve APCu performance, consider increasing this limit.', [ - '@apcu_actual_size' => $apcu_actual_size, - ]); - } - else { - $requirements['php_apcu_available']['severity'] = RequirementSeverity::OK; - } - $requirements['php_apcu_available']['value'] = t('Memory available: @available.', [ - '@available' => ByteSizeMarkup::create($memory_info['avail_mem']), - ]); - } - } - else { - $requirements['php_apcu_enabled'] += [ - 'value' => t('Not enabled'), - 'severity' => RequirementSeverity::Info, - 'description' => t('PHP APCu caching can improve your site\'s performance considerably. It is <strong>highly recommended</strong> to have <a href="https://www.php.net/manual/apcu.installation.php" target="_blank">APCu</a> installed on your server.'), - ]; - } - } - - if ($phase != 'update') { - // Test whether we have a good source of random bytes. - $requirements['php_random_bytes'] = [ - 'title' => t('Random number generation'), - ]; - try { - $bytes = random_bytes(10); - if (strlen($bytes) != 10) { - throw new \Exception("Tried to generate 10 random bytes, generated '" . strlen($bytes) . "'"); - } - $requirements['php_random_bytes']['value'] = t('Successful'); - } - catch (\Exception $e) { - // If /dev/urandom is not available on a UNIX-like system, check whether - // open_basedir restrictions are the cause. - $open_basedir_blocks_urandom = FALSE; - if (DIRECTORY_SEPARATOR === '/' && !@is_readable('/dev/urandom')) { - $open_basedir = ini_get('open_basedir'); - if ($open_basedir) { - $open_basedir_paths = explode(PATH_SEPARATOR, $open_basedir); - $open_basedir_blocks_urandom = !array_intersect(['/dev', '/dev/', '/dev/urandom'], $open_basedir_paths); - } - } - $args = [ - ':drupal-php' => 'https://www.drupal.org/docs/system-requirements/php-requirements', - '%exception_message' => $e->getMessage(), - ]; - if ($open_basedir_blocks_urandom) { - $requirements['php_random_bytes']['description'] = t('Drupal is unable to generate highly randomized numbers, which means certain security features like password reset URLs are not as secure as they should be. Instead, only a slow, less-secure fallback generator is available. The most likely cause is that open_basedir restrictions are in effect and /dev/urandom is not on the allowed list. See the <a href=":drupal-php">system requirements</a> page for more information. %exception_message', $args); - } - else { - $requirements['php_random_bytes']['description'] = t('Drupal is unable to generate highly randomized numbers, which means certain security features like password reset URLs are not as secure as they should be. Instead, only a slow, less-secure fallback generator is available. See the <a href=":drupal-php">system requirements</a> page for more information. %exception_message', $args); - } - $requirements['php_random_bytes']['value'] = t('Less secure'); - $requirements['php_random_bytes']['severity'] = RequirementSeverity::Error; - } - } - - if ($phase === 'runtime' && PHP_SAPI !== 'cli') { - if (!function_exists('fastcgi_finish_request') && !function_exists('litespeed_finish_request') && !ob_get_status()) { - $requirements['output_buffering'] = [ - 'title' => t('Output Buffering'), - 'error_value' => t('Not enabled'), - 'severity' => RequirementSeverity::Warning, - 'description' => t('<a href="https://www.php.net/manual/en/function.ob-start.php">Output buffering</a> is not enabled. This may degrade Drupal\'s performance. You can enable output buffering by default <a href="https://www.php.net/manual/en/outcontrol.configuration.php#ini.output-buffering">in your PHP settings</a>.'), - ]; - } - } - - if ($phase == 'install' || $phase == 'update') { - // Test for PDO (database). - $requirements['database_extensions'] = [ - 'title' => t('Database support'), - ]; - - // Make sure PDO is available. - $database_ok = extension_loaded('pdo'); - if (!$database_ok) { - $pdo_message = t('Your web server does not appear to support PDO (PHP Data Objects). Ask your hosting provider if they support the native PDO extension. See the <a href=":link">system requirements</a> page for more information.', [ - ':link' => 'https://www.drupal.org/docs/system-requirements/php-requirements#database', - ]); - } - else { - // Make sure at least one supported database driver exists. - if (empty(Database::getDriverList()->getInstallableList())) { - $database_ok = FALSE; - $pdo_message = t('Your web server does not appear to support any common PDO database extensions. Check with your hosting provider to see if they support PDO (PHP Data Objects) and offer any databases that <a href=":drupal-databases">Drupal supports</a>.', [ - ':drupal-databases' => 'https://www.drupal.org/docs/system-requirements/database-server-requirements', - ]); - } - // Make sure the native PDO extension is available, not the older PEAR - // version. (See install_verify_pdo() for details.) - if (!defined('PDO::ATTR_DEFAULT_FETCH_MODE')) { - $database_ok = FALSE; - $pdo_message = t('Your web server seems to have the wrong version of PDO installed. Drupal requires the PDO extension from PHP core. This system has the older PECL version. See the <a href=":link">system requirements</a> page for more information.', [ - ':link' => 'https://www.drupal.org/docs/system-requirements/php-requirements#database', - ]); - } - } - - if (!$database_ok) { - $requirements['database_extensions']['value'] = t('Disabled'); - $requirements['database_extensions']['severity'] = RequirementSeverity::Error; - $requirements['database_extensions']['description'] = $pdo_message; - } - else { - $requirements['database_extensions']['value'] = t('Enabled'); - } - } - - if ($phase === 'runtime' || $phase === 'update') { - // Database information. - $class = Database::getConnection()->getConnectionOptions()['namespace'] . '\\Install\\Tasks'; - /** @var \Drupal\Core\Database\Install\Tasks $tasks */ - $tasks = new $class(); - $requirements['database_system'] = [ - 'title' => t('Database system'), - 'value' => $tasks->name(), - ]; - $requirements['database_system_version'] = [ - 'title' => t('Database system version'), - 'value' => Database::getConnection()->version(), - ]; - - $errors = $tasks->engineVersionRequirementsCheck(); - $error_count = count($errors); - if ($error_count > 0) { - $error_message = [ - '#theme' => 'item_list', - '#items' => $errors, - // Use the comma-list style to display a single error without bullets. - '#context' => ['list_style' => $error_count === 1 ? 'comma-list' : ''], - ]; - $requirements['database_system_version']['severity'] = RequirementSeverity::Error; - $requirements['database_system_version']['description'] = $error_message; - } - } - - if ($phase === 'runtime' || $phase === 'update') { - // Test database JSON support. - $requirements['database_support_json'] = [ - 'title' => t('Database support for JSON'), - 'severity' => RequirementSeverity::OK, - 'value' => t('Available'), - 'description' => t('Drupal requires databases that support JSON storage.'), - ]; - - if (!Database::getConnection()->hasJson()) { - $requirements['database_support_json']['value'] = t('Not available'); - $requirements['database_support_json']['severity'] = RequirementSeverity::Error; - } - } - - // Test PHP memory_limit - $memory_limit = ini_get('memory_limit'); - $requirements['php_memory_limit'] = [ - 'title' => t('PHP memory limit'), - 'value' => $memory_limit == -1 ? t('-1 (Unlimited)') : $memory_limit, - ]; - - if (!Environment::checkMemoryLimit(\Drupal::MINIMUM_PHP_MEMORY_LIMIT, $memory_limit)) { - $description = []; - if ($phase == 'install') { - $description['phase'] = t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the installation process.', ['%memory_minimum_limit' => \Drupal::MINIMUM_PHP_MEMORY_LIMIT]); - } - elseif ($phase == 'update') { - $description['phase'] = t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the update process.', ['%memory_minimum_limit' => \Drupal::MINIMUM_PHP_MEMORY_LIMIT]); - } - elseif ($phase == 'runtime') { - $description['phase'] = t('Depending on your configuration, Drupal can run with a %memory_limit PHP memory limit. However, a %memory_minimum_limit PHP memory limit or above is recommended, especially if your site uses additional custom or contributed modules.', ['%memory_limit' => $memory_limit, '%memory_minimum_limit' => \Drupal::MINIMUM_PHP_MEMORY_LIMIT]); - } - - if (!empty($description['phase'])) { - if ($php_ini_path = get_cfg_var('cfg_file_path')) { - $description['memory'] = t('Increase the memory limit by editing the memory_limit parameter in the file %configuration-file and then restart your web server (or contact your system administrator or hosting provider for assistance).', ['%configuration-file' => $php_ini_path]); - } - else { - $description['memory'] = t('Contact your system administrator or hosting provider for assistance with increasing your PHP memory limit.'); - } - - $handbook_link = t('For more information, see the online handbook entry for <a href=":memory-limit">increasing the PHP memory limit</a>.', [':memory-limit' => 'https://www.drupal.org/node/207036']); - - $description = [ - '#type' => 'inline_template', - '#template' => '{{ description_phase }} {{ description_memory }} {{ handbook }}', - '#context' => [ - 'description_phase' => $description['phase'], - 'description_memory' => $description['memory'], - 'handbook' => $handbook_link, - ], - ]; - - $requirements['php_memory_limit']['description'] = $description; - $requirements['php_memory_limit']['severity'] = RequirementSeverity::Warning; - } - } - - // Test if configuration files and directory are writable. - if ($phase == 'runtime') { - $conf_errors = []; - // Find the site path. Kernel service is not always available at this point, - // but is preferred, when available. - if (\Drupal::hasService('kernel')) { - $site_path = \Drupal::getContainer()->getParameter('site.path'); - } - else { - $site_path = DrupalKernel::findSitePath(Request::createFromGlobals()); - } - // Allow system administrators to disable permissions hardening for the site - // directory. This allows additional files in the site directory to be - // updated when they are managed in a version control system. - if (Settings::get('skip_permissions_hardening')) { - $error_value = t('Protection disabled'); - // If permissions hardening is disabled, then only show a warning for a - // writable file, as a reminder, rather than an error. - $file_protection_severity = RequirementSeverity::Warning; - } - else { - $error_value = t('Not protected'); - // In normal operation, writable files or directories are an error. - $file_protection_severity = RequirementSeverity::Error; - if (!drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir')) { - $conf_errors[] = t("The directory %file is not protected from modifications and poses a security risk. You must change the directory's permissions to be non-writable.", ['%file' => $site_path]); - } - } - foreach (['settings.php', 'settings.local.php', 'services.yml'] as $conf_file) { - $full_path = $site_path . '/' . $conf_file; - if (file_exists($full_path) && !drupal_verify_install_file($full_path, FILE_EXIST | FILE_READABLE | FILE_NOT_WRITABLE, 'file', !Settings::get('skip_permissions_hardening'))) { - $conf_errors[] = t("The file %file is not protected from modifications and poses a security risk. You must change the file's permissions to be non-writable.", ['%file' => $full_path]); - } - } - if (!empty($conf_errors)) { - if (count($conf_errors) == 1) { - $description = $conf_errors[0]; - } - else { - // We use twig inline_template to avoid double escaping. - $description = [ - '#type' => 'inline_template', - '#template' => '{{ configuration_error_list }}', - '#context' => [ - 'configuration_error_list' => [ - '#theme' => 'item_list', - '#items' => $conf_errors, - ], - ], - ]; - } - $requirements['configuration_files'] = [ - 'value' => $error_value, - 'severity' => $file_protection_severity, - 'description' => $description, - ]; - } - else { - $requirements['configuration_files'] = [ - 'value' => t('Protected'), - ]; - } - $requirements['configuration_files']['title'] = t('Configuration files'); - } - - // Test the contents of the .htaccess files. - if ($phase == 'runtime') { - // Try to write the .htaccess files first, to prevent false alarms in case - // (for example) the /tmp directory was wiped. - /** @var \Drupal\Core\File\HtaccessWriterInterface $htaccessWriter */ - $htaccessWriter = \Drupal::service("file.htaccess_writer"); - $htaccessWriter->ensure(); - foreach ($htaccessWriter->defaultProtectedDirs() as $protected_dir) { - $htaccess_file = $protected_dir->getPath() . '/.htaccess'; - // Check for the string which was added to the recommended .htaccess file - // in the latest security update. - if (!file_exists($htaccess_file) || !($contents = @file_get_contents($htaccess_file)) || !str_contains($contents, 'Drupal_Security_Do_Not_Remove_See_SA_2013_003')) { - $url = 'https://www.drupal.org/SA-CORE-2013-003'; - $requirements[$htaccess_file] = [ - // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString - 'title' => new TranslatableMarkup($protected_dir->getTitle()), - 'value' => t('Not fully protected'), - 'severity' => RequirementSeverity::Error, - 'description' => t('See <a href=":url">@url</a> for information about the recommended .htaccess file which should be added to the %directory directory to help protect against arbitrary code execution.', [':url' => $url, '@url' => $url, '%directory' => $protected_dir->getPath()]), - ]; - } - } - } - - // Report cron status. - if ($phase == 'runtime') { - $cron_config = \Drupal::config('system.cron'); - // Cron warning threshold defaults to two days. - $threshold_warning = $cron_config->get('threshold.requirements_warning'); - // Cron error threshold defaults to two weeks. - $threshold_error = $cron_config->get('threshold.requirements_error'); - - // Determine when cron last ran. - $cron_last = \Drupal::state()->get('system.cron_last'); - if (!is_numeric($cron_last)) { - $cron_last = \Drupal::state()->get('install_time', 0); - } - - // Determine severity based on time since cron last ran. - $severity = RequirementSeverity::Info; - $request_time = \Drupal::time()->getRequestTime(); - if ($request_time - $cron_last > $threshold_error) { - $severity = RequirementSeverity::Error; - } - elseif ($request_time - $cron_last > $threshold_warning) { - $severity = RequirementSeverity::Warning; - } - - // Set summary and description based on values determined above. - $summary = t('Last run @time ago', ['@time' => \Drupal::service('date.formatter')->formatTimeDiffSince($cron_last)]); - - $requirements['cron'] = [ - 'title' => t('Cron maintenance tasks'), - 'severity' => $severity, - 'value' => $summary, - ]; - if ($severity != RequirementSeverity::Info) { - $requirements['cron']['description'][] = [ - [ - '#markup' => t('Cron has not run recently.'), - '#suffix' => ' ', - ], - [ - '#markup' => t('For more information, see the online handbook entry for <a href=":cron-handbook">configuring cron jobs</a>.', [':cron-handbook' => 'https://www.drupal.org/docs/administering-a-drupal-site/cron-automated-tasks/cron-automated-tasks-overview']), - '#suffix' => ' ', - ], - ]; - } - $requirements['cron']['description'][] = [ - [ - '#type' => 'link', - '#prefix' => '(', - '#title' => t('more information'), - '#suffix' => ')', - '#url' => Url::fromRoute('system.cron_settings'), - ], - [ - '#prefix' => '<span class="cron-description__run-cron">', - '#suffix' => '</span>', - '#type' => 'link', - '#title' => t('Run cron'), - '#url' => Url::fromRoute('system.run_cron'), - ], - ]; - } - if ($phase != 'install') { - $directories = [ - PublicStream::basePath(), - // By default no private files directory is configured. For private files - // to be secure the admin needs to provide a path outside the webroot. - PrivateStream::basePath(), - \Drupal::service('file_system')->getTempDirectory(), - ]; - } - - // During an install we need to make assumptions about the file system - // unless overrides are provided in settings.php. - if ($phase == 'install') { - $directories = []; - if ($file_public_path = Settings::get('file_public_path')) { - $directories[] = $file_public_path; - } - else { - // If we are installing Drupal, the settings.php file might not exist yet - // in the intended site directory, so don't require it. - $request = Request::createFromGlobals(); - $site_path = DrupalKernel::findSitePath($request); - $directories[] = $site_path . '/files'; - } - if ($file_private_path = Settings::get('file_private_path')) { - $directories[] = $file_private_path; - } - if (Settings::get('file_temp_path')) { - $directories[] = Settings::get('file_temp_path'); - } - else { - // If the temporary directory is not overridden use an appropriate - // temporary path for the system. - $directories[] = FileSystemComponent::getOsTemporaryDirectory(); - } - } - - // Check the config directory if it is defined in settings.php. If it isn't - // defined, the installer will create a valid config directory later, but - // during runtime we must always display an error. - $config_sync_directory = Settings::get('config_sync_directory'); - if (!empty($config_sync_directory)) { - // If we're installing Drupal try and create the config sync directory. - if (!is_dir($config_sync_directory) && $phase == 'install') { - \Drupal::service('file_system')->prepareDirectory($config_sync_directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); - } - if (!is_dir($config_sync_directory)) { - if ($phase == 'install') { - $description = t('An automated attempt to create the directory %directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', ['%directory' => $config_sync_directory, ':handbook_url' => 'https://www.drupal.org/server-permissions']); - } - else { - $description = t('The directory %directory does not exist.', ['%directory' => $config_sync_directory]); - } - $requirements['config sync directory'] = [ - 'title' => t('Configuration sync directory'), - 'description' => $description, - 'severity' => RequirementSeverity::Error, - ]; - } - } - if ($phase != 'install' && empty($config_sync_directory)) { - $requirements['config sync directory'] = [ - 'title' => t('Configuration sync directory'), - 'value' => t('Not present'), - 'description' => t("Your %file file must define the %setting setting as a string containing the directory in which configuration files can be found.", ['%file' => $site_path . '/settings.php', '%setting' => "\$settings['config_sync_directory']"]), - 'severity' => RequirementSeverity::Error, - ]; - } - - $requirements['file system'] = [ - 'title' => t('File system'), - ]; - - $error = ''; - // For installer, create the directories if possible. - foreach ($directories as $directory) { - if (!$directory) { - continue; - } - if ($phase == 'install') { - \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); - } - $is_writable = is_writable($directory); - $is_directory = is_dir($directory); - if (!$is_writable || !$is_directory) { - $description = ''; - $requirements['file system']['value'] = t('Not writable'); - if (!$is_directory) { - $error = t('The directory %directory does not exist.', ['%directory' => $directory]); - } - else { - $error = t('The directory %directory is not writable.', ['%directory' => $directory]); - } - // The files directory requirement check is done only during install and - // runtime. - if ($phase == 'runtime') { - $description = t('You may need to set the correct directory at the <a href=":admin-file-system">file system settings page</a> or change the current directory\'s permissions so that it is writable.', [':admin-file-system' => Url::fromRoute('system.file_system_settings')->toString()]); - } - elseif ($phase == 'install') { - // For the installer UI, we need different wording. 'value' will - // be treated as version, so provide none there. - $description = t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', [':handbook_url' => 'https://www.drupal.org/server-permissions']); - $requirements['file system']['value'] = ''; - } - if (!empty($description)) { - $description = [ - '#type' => 'inline_template', - '#template' => '{{ error }} {{ description }}', - '#context' => [ - 'error' => $error, - 'description' => $description, - ], - ]; - $requirements['file system']['description'] = $description; - $requirements['file system']['severity'] = RequirementSeverity::Error; - } - } - else { - // This function can be called before the config_cache table has been - // created. - if ($phase == 'install' || \Drupal::config('system.file')->get('default_scheme') == 'public') { - $requirements['file system']['value'] = t('Writable (<em>public</em> download method)'); - } - else { - $requirements['file system']['value'] = t('Writable (<em>private</em> download method)'); - } - } - } - - // See if updates are available in update.php. - if ($phase == 'runtime') { - $requirements['update'] = [ - 'title' => t('Database updates'), - 'value' => t('Up to date'), - ]; - - // Check installed modules. - $has_pending_updates = FALSE; - /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ - $update_registry = \Drupal::service('update.update_hook_registry'); - foreach (\Drupal::moduleHandler()->getModuleList() as $module => $filename) { - $updates = $update_registry->getAvailableUpdates($module); - if ($updates) { - $default = $update_registry->getInstalledVersion($module); - if (max($updates) > $default) { - $has_pending_updates = TRUE; - break; - } - } - } - if (!$has_pending_updates) { - /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */ - $post_update_registry = \Drupal::service('update.post_update_registry'); - $missing_post_update_functions = $post_update_registry->getPendingUpdateFunctions(); - if (!empty($missing_post_update_functions)) { - $has_pending_updates = TRUE; - } - } - - if ($has_pending_updates) { - $requirements['update']['severity'] = RequirementSeverity::Error; - $requirements['update']['value'] = t('Out of date'); - $requirements['update']['description'] = t('Some modules have database schema updates to install. You should run the <a href=":update">database update script</a> immediately.', [':update' => Url::fromRoute('system.db_update')->toString()]); - } - - $requirements['entity_update'] = [ - 'title' => t('Entity/field definitions'), - 'value' => t('Up to date'), - ]; - // Verify that no entity updates are pending. - if ($change_list = \Drupal::entityDefinitionUpdateManager()->getChangeSummary()) { - $build = []; - foreach ($change_list as $entity_type_id => $changes) { - $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id); - $build[] = [ - '#theme' => 'item_list', - '#title' => $entity_type->getLabel(), - '#items' => $changes, - ]; - } - - $entity_update_issues = \Drupal::service('renderer')->renderInIsolation($build); - $requirements['entity_update']['severity'] = RequirementSeverity::Error; - $requirements['entity_update']['value'] = t('Mismatched entity and/or field definitions'); - $requirements['entity_update']['description'] = t('The following changes were detected in the entity type and field definitions. @updates', ['@updates' => $entity_update_issues]); - } - } - - // Display the deployment identifier if set. - if ($phase == 'runtime') { - if ($deployment_identifier = Settings::get('deployment_identifier')) { - $requirements['deployment identifier'] = [ - 'title' => t('Deployment identifier'), - 'value' => $deployment_identifier, - 'severity' => RequirementSeverity::Info, - ]; - } - } - - // Verify the update.php access setting - if ($phase == 'runtime') { - if (Settings::get('update_free_access')) { - $requirements['update access'] = [ - 'value' => t('Not protected'), - 'severity' => RequirementSeverity::Error, - 'description' => t('The update.php script is accessible to everyone without authentication check, which is a security risk. You must change the @settings_name value in your settings.php back to FALSE.', ['@settings_name' => '$settings[\'update_free_access\']']), - ]; - } - else { - $requirements['update access'] = [ - 'value' => t('Protected'), - ]; - } - $requirements['update access']['title'] = t('Access to update.php'); - } - - // Display an error if a newly introduced dependency in a module is not - // resolved. - if ($phase === 'update' || $phase === 'runtime') { - $create_extension_incompatibility_list = function (array $extension_names, PluralTranslatableMarkup $description, PluralTranslatableMarkup $title, TranslatableMarkup|string $message = '', TranslatableMarkup|string $additional_description = '') { - if ($message === '') { - $message = new TranslatableMarkup('Review the <a href=":url"> suggestions for resolving this incompatibility</a> to repair your installation, and then re-run update.php.', [':url' => 'https://www.drupal.org/docs/updating-drupal/troubleshooting-database-updates']); - } - // Use an inline twig template to: - // - Concatenate MarkupInterface objects and preserve safeness. - // - Use the item_list theme for the extension list. - $template = [ - '#type' => 'inline_template', - '#template' => '{{ description }}{{ extensions }}{{ additional_description }}<br>', - '#context' => [ - 'extensions' => [ - '#theme' => 'item_list', - ], - ], - ]; - $template['#context']['extensions']['#items'] = $extension_names; - $template['#context']['description'] = $description; - $template['#context']['additional_description'] = $additional_description; - return [ - 'title' => $title, - 'value' => [ - 'list' => $template, - 'handbook_link' => [ - '#markup' => $message, - ], - ], - 'severity' => RequirementSeverity::Error, - ]; - }; - $profile = \Drupal::installProfile(); - $files = $module_extension_list->getList(); - $files += $theme_extension_list->getList(); - $core_incompatible_extensions = []; - $php_incompatible_extensions = []; - foreach ($files as $extension_name => $file) { - // Ignore uninstalled extensions and installation profiles. - if (!$file->status || $extension_name == $profile) { - continue; - } - - $name = $file->info['name']; - if (!empty($file->info['core_incompatible'])) { - $core_incompatible_extensions[$file->info['type']][] = $name; - } - - // Check the extension's PHP version. - $php = $file->info['php']; - if (version_compare($php, PHP_VERSION, '>')) { - $php_incompatible_extensions[$file->info['type']][] = $name; - } - - // Check the module's required modules. - /** @var \Drupal\Core\Extension\Dependency $requirement */ - foreach ($file->requires as $requirement) { - $required_module = $requirement->getName(); - // Check if the module exists. - if (!isset($files[$required_module])) { - $requirements["$extension_name-$required_module"] = [ - 'title' => t('Unresolved dependency'), - 'description' => t('@name requires this module.', ['@name' => $name]), - 'value' => t('@required_name (Missing)', ['@required_name' => $required_module]), - 'severity' => RequirementSeverity::Error, - ]; - continue; - } - // Check for an incompatible version. - $required_file = $files[$required_module]; - $required_name = $required_file->info['name']; - // Remove CORE_COMPATIBILITY- only from the start of the string. - $version = preg_replace('/^(' . \Drupal::CORE_COMPATIBILITY . '\-)/', '', $required_file->info['version'] ?? ''); - if (!$requirement->isCompatible($version)) { - $requirements["$extension_name-$required_module"] = [ - 'title' => t('Unresolved dependency'), - 'description' => t('@name requires this module and version. Currently using @required_name version @version', ['@name' => $name, '@required_name' => $required_name, '@version' => $version]), - 'value' => t('@required_name (Version @compatibility required)', ['@required_name' => $required_name, '@compatibility' => $requirement->getConstraintString()]), - 'severity' => RequirementSeverity::Error, - ]; - continue; - } - } - } - if (!empty($core_incompatible_extensions['module'])) { - $requirements['module_core_incompatible'] = $create_extension_incompatibility_list( - $core_incompatible_extensions['module'], - new PluralTranslatableMarkup( - count($core_incompatible_extensions['module']), - 'The following module is installed, but it is incompatible with Drupal @version:', - 'The following modules are installed, but they are incompatible with Drupal @version:', - ['@version' => \Drupal::VERSION] - ), - new PluralTranslatableMarkup( - count($core_incompatible_extensions['module']), - 'Incompatible module', - 'Incompatible modules' - ) - ); - } - if (!empty($core_incompatible_extensions['theme'])) { - $requirements['theme_core_incompatible'] = $create_extension_incompatibility_list( - $core_incompatible_extensions['theme'], - new PluralTranslatableMarkup( - count($core_incompatible_extensions['theme']), - 'The following theme is installed, but it is incompatible with Drupal @version:', - 'The following themes are installed, but they are incompatible with Drupal @version:', - ['@version' => \Drupal::VERSION] - ), - new PluralTranslatableMarkup( - count($core_incompatible_extensions['theme']), - 'Incompatible theme', - 'Incompatible themes' - ) - ); - } - if (!empty($php_incompatible_extensions['module'])) { - $requirements['module_php_incompatible'] = $create_extension_incompatibility_list( - $php_incompatible_extensions['module'], - new PluralTranslatableMarkup( - count($php_incompatible_extensions['module']), - 'The following module is installed, but it is incompatible with PHP @version:', - 'The following modules are installed, but they are incompatible with PHP @version:', - ['@version' => phpversion()] - ), - new PluralTranslatableMarkup( - count($php_incompatible_extensions['module']), - 'Incompatible module', - 'Incompatible modules' - ) - ); - } - if (!empty($php_incompatible_extensions['theme'])) { - $requirements['theme_php_incompatible'] = $create_extension_incompatibility_list( - $php_incompatible_extensions['theme'], - new PluralTranslatableMarkup( - count($php_incompatible_extensions['theme']), - 'The following theme is installed, but it is incompatible with PHP @version:', - 'The following themes are installed, but they are incompatible with PHP @version:', - ['@version' => phpversion()] - ), - new PluralTranslatableMarkup( - count($php_incompatible_extensions['theme']), - 'Incompatible theme', - 'Incompatible themes' - ) - ); - } - - $extension_config = \Drupal::configFactory()->get('core.extension'); - - // Look for removed core modules. - $is_removed_module = function ($extension_name) use ($module_extension_list) { - return !$module_extension_list->exists($extension_name) - && array_key_exists($extension_name, DRUPAL_CORE_REMOVED_MODULE_LIST); - }; - $removed_modules = array_filter(array_keys($extension_config->get('module')), $is_removed_module); - if (!empty($removed_modules)) { - $list = []; - foreach ($removed_modules as $removed_module) { - $list[] = t('<a href=":url">@module</a>', [ - ':url' => "https://www.drupal.org/project/$removed_module", - '@module' => DRUPAL_CORE_REMOVED_MODULE_LIST[$removed_module], - ]); - } - $requirements['removed_module'] = $create_extension_incompatibility_list( - $list, - new PluralTranslatableMarkup( - count($removed_modules), - 'You must add the following contributed module and reload this page.', - 'You must add the following contributed modules and reload this page.' - ), - new PluralTranslatableMarkup( - count($removed_modules), - 'Removed core module', - 'Removed core modules' - ), - new TranslatableMarkup( - 'For more information read the <a href=":url">documentation on deprecated modules.</a>', - [':url' => 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-modules'] - ), - new PluralTranslatableMarkup( - count($removed_modules), - 'This module is installed on your site but is no longer provided by Core.', - 'These modules are installed on your site but are no longer provided by Core.' - ), - ); - } - - // Look for removed core themes. - $is_removed_theme = function ($extension_name) use ($theme_extension_list) { - return !$theme_extension_list->exists($extension_name) - && array_key_exists($extension_name, DRUPAL_CORE_REMOVED_THEME_LIST); - }; - $removed_themes = array_filter(array_keys($extension_config->get('theme')), $is_removed_theme); - if (!empty($removed_themes)) { - $list = []; - foreach ($removed_themes as $removed_theme) { - $list[] = t('<a href=":url">@theme</a>', [ - ':url' => "https://www.drupal.org/project/$removed_theme", - '@theme' => DRUPAL_CORE_REMOVED_THEME_LIST[$removed_theme], - ]); - } - $requirements['removed_theme'] = $create_extension_incompatibility_list( - $list, - new PluralTranslatableMarkup( - count($removed_themes), - 'You must add the following contributed theme and reload this page.', - 'You must add the following contributed themes and reload this page.' - ), - new PluralTranslatableMarkup( - count($removed_themes), - 'Removed core theme', - 'Removed core themes' - ), - new TranslatableMarkup( - 'For more information read the <a href=":url">documentation on deprecated themes.</a>', - [':url' => 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-themes'] - ), - new PluralTranslatableMarkup( - count($removed_themes), - 'This theme is installed on your site but is no longer provided by Core.', - 'These themes are installed on your site but are no longer provided by Core.' - ), - ); - } - - // Look for missing modules. - $is_missing_module = function ($extension_name) use ($module_extension_list) { - return !$module_extension_list->exists($extension_name) && !in_array($extension_name, array_keys(DRUPAL_CORE_REMOVED_MODULE_LIST), TRUE); - }; - $invalid_modules = array_filter(array_keys($extension_config->get('module')), $is_missing_module); - - if (!empty($invalid_modules)) { - $requirements['invalid_module'] = $create_extension_incompatibility_list( - $invalid_modules, - new PluralTranslatableMarkup( - count($invalid_modules), - 'The following module is marked as installed in the core.extension configuration, but it is missing:', - 'The following modules are marked as installed in the core.extension configuration, but they are missing:' - ), - new PluralTranslatableMarkup( - count($invalid_modules), - 'Missing or invalid module', - 'Missing or invalid modules' - ) - ); - } - - // Look for invalid themes. - $is_missing_theme = function ($extension_name) use (&$theme_extension_list) { - return !$theme_extension_list->exists($extension_name) && !in_array($extension_name, array_keys(DRUPAL_CORE_REMOVED_THEME_LIST), TRUE); - }; - $invalid_themes = array_filter(array_keys($extension_config->get('theme')), $is_missing_theme); - if (!empty($invalid_themes)) { - $requirements['invalid_theme'] = $create_extension_incompatibility_list( - $invalid_themes, - new PluralTranslatableMarkup( - count($invalid_themes), - 'The following theme is marked as installed in the core.extension configuration, but it is missing:', - 'The following themes are marked as installed in the core.extension configuration, but they are missing:' - ), - new PluralTranslatableMarkup( - count($invalid_themes), - 'Missing or invalid theme', - 'Missing or invalid themes' - ) - ); - } - } - - // Returns Unicode library status and errors. - $libraries = [ - Unicode::STATUS_SINGLEBYTE => t('Standard PHP'), - Unicode::STATUS_MULTIBYTE => t('PHP Mbstring Extension'), - Unicode::STATUS_ERROR => t('Error'), - ]; - $severities = [ - Unicode::STATUS_SINGLEBYTE => RequirementSeverity::Warning, - Unicode::STATUS_MULTIBYTE => NULL, - Unicode::STATUS_ERROR => RequirementSeverity::Error, - ]; - $failed_check = Unicode::check(); - $library = Unicode::getStatus(); - - $requirements['unicode'] = [ - 'title' => t('Unicode library'), - 'value' => $libraries[$library], - 'severity' => $severities[$library], - ]; - switch ($failed_check) { - case 'mb_strlen': - $requirements['unicode']['description'] = t('Operations on Unicode strings are emulated on a best-effort basis. Install the <a href="http://php.net/mbstring">PHP mbstring extension</a> for improved Unicode support.'); - break; - - case 'mbstring.encoding_translation': - $requirements['unicode']['description'] = t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.encoding_translation</em> setting. Refer to the <a href="http://php.net/mbstring">PHP mbstring documentation</a> for more information.'); - break; - } - - if ($phase == 'runtime') { - // Check for update status module. - if (!\Drupal::moduleHandler()->moduleExists('update')) { - $requirements['update status'] = [ - 'value' => t('Not enabled'), - 'severity' => RequirementSeverity::Warning, - 'description' => t('Update notifications are not enabled. It is <strong>highly recommended</strong> that you install the Update Status module from the <a href=":module">module administration page</a> in order to stay up-to-date on new releases. For more information, <a href=":update">Update status handbook page</a>.', [ - ':update' => 'https://www.drupal.org/documentation/modules/update', - ':module' => Url::fromRoute('system.modules_list')->toString(), - ]), - ]; - } - else { - $requirements['update status'] = [ - 'value' => t('Enabled'), - ]; - } - $requirements['update status']['title'] = t('Update notifications'); - - if (Settings::get('rebuild_access')) { - $requirements['rebuild access'] = [ - 'title' => t('Rebuild access'), - 'value' => t('Enabled'), - 'severity' => RequirementSeverity::Error, - 'description' => t('The rebuild_access setting is enabled in settings.php. It is recommended to have this setting disabled unless you are performing a rebuild.'), - ]; - } - } - - // Check if the SameSite cookie attribute is set to a valid value. Since this - // involves checking whether we are using a secure connection this only makes - // sense inside an HTTP request, not on the command line. - if ($phase === 'runtime' && PHP_SAPI !== 'cli') { - $samesite = ini_get('session.cookie_samesite') ?: t('Not set'); - // Check if the SameSite attribute is set to a valid value. If it is set to - // 'None' the request needs to be done over HTTPS. - $valid = match ($samesite) { - 'Lax', 'Strict' => TRUE, - 'None' => $request_object->isSecure(), - default => FALSE, - }; - $requirements['php_session_samesite'] = [ - 'title' => t('SameSite cookie attribute'), - 'value' => $samesite, - 'severity' => $valid ? RequirementSeverity::OK : RequirementSeverity::Warning, - 'description' => t('This attribute should be explicitly set to Lax, Strict or None. If set to None then the request must be made via HTTPS. See <a href=":url" target="_blank">PHP documentation</a>', [ - ':url' => 'https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-samesite', - ]), - ]; - } - - // See if trusted host names have been configured, and warn the user if they - // are not set. - if ($phase == 'runtime') { - $trusted_host_patterns = Settings::get('trusted_host_patterns'); - if (empty($trusted_host_patterns)) { - $requirements['trusted_host_patterns'] = [ - 'title' => t('Trusted Host Settings'), - 'value' => t('Not enabled'), - 'description' => t('The trusted_host_patterns setting is not configured in settings.php. This can lead to security vulnerabilities. It is <strong>highly recommended</strong> that you configure this. See <a href=":url">Protecting against HTTP HOST Header attacks</a> for more information.', [':url' => 'https://www.drupal.org/docs/installing-drupal/trusted-host-settings']), - 'severity' => RequirementSeverity::Error, - ]; - } - else { - $requirements['trusted_host_patterns'] = [ - 'title' => t('Trusted Host Settings'), - 'value' => t('Enabled'), - 'description' => t('The trusted_host_patterns setting is set to allow %trusted_host_patterns', ['%trusted_host_patterns' => implode(', ', $trusted_host_patterns)]), - ]; - } - } - - // When the database driver is provided by a module, then check that the - // providing module is installed. - if ($phase === 'runtime' || $phase === 'update') { - $connection = Database::getConnection(); - $provider = $connection->getProvider(); - if ($provider !== 'core' && !\Drupal::moduleHandler()->moduleExists($provider)) { - $autoload = $connection->getConnectionOptions()['autoload'] ?? ''; - if (str_contains($autoload, 'src/Driver/Database/')) { - $post_update_registry = \Drupal::service('update.post_update_registry'); - $pending_updates = $post_update_registry->getPendingUpdateInformation(); - if (!in_array('enable_provider_database_driver', array_keys($pending_updates['system']['pending'] ?? []), TRUE)) { - // Only show the warning when the post update function has run and - // the module that is providing the database driver is not installed. - $requirements['database_driver_provided_by_module'] = [ - 'title' => t('Database driver provided by module'), - 'value' => t('Not installed'), - 'description' => t('The current database driver is provided by the module: %module. The module is currently not installed. You should immediately <a href=":install">install</a> the module.', ['%module' => $provider, ':install' => Url::fromRoute('system.modules_list')->toString()]), - 'severity' => RequirementSeverity::Error, - ]; - } - } - } - } - - // Check xdebug.max_nesting_level, as some pages will not work if it is too - // low. - if (extension_loaded('xdebug')) { - // Setting this value to 256 was considered adequate on Xdebug 2.3 - // (see http://bugs.xdebug.org/bug_view_page.php?bug_id=00001100) - $minimum_nesting_level = 256; - $current_nesting_level = ini_get('xdebug.max_nesting_level'); - - if ($current_nesting_level < $minimum_nesting_level) { - $requirements['xdebug_max_nesting_level'] = [ - 'title' => t('Xdebug settings'), - 'value' => t('xdebug.max_nesting_level is set to %value.', ['%value' => $current_nesting_level]), - 'description' => t('Set <code>xdebug.max_nesting_level=@level</code> in your PHP configuration as some pages in your Drupal site will not work when this setting is too low.', ['@level' => $minimum_nesting_level]), - 'severity' => RequirementSeverity::Error, - ]; - } - } - - // Installations on Windows can run into limitations with MAX_PATH if the - // Drupal root directory is too deep in the filesystem. Generally this shows - // up in cached Twig templates and other public files with long directory or - // file names. There is no definite root directory depth below which Drupal is - // guaranteed to function correctly on Windows. Since problems are likely - // with more than 100 characters in the Drupal root path, show an error. - if (str_starts_with(PHP_OS, 'WIN')) { - $depth = strlen(realpath(DRUPAL_ROOT . '/' . PublicStream::basePath())); - if ($depth > 120) { - $requirements['max_path_on_windows'] = [ - 'title' => t('Windows installation depth'), - 'description' => t('The public files directory path is %depth characters. Paths longer than 120 characters will cause problems on Windows.', ['%depth' => $depth]), - 'severity' => RequirementSeverity::Error, - ]; - } - } - // Check to see if dates will be limited to 1901-2038. - if (PHP_INT_SIZE <= 4) { - $requirements['limited_date_range'] = [ - 'title' => t('Limited date range'), - 'value' => t('Your PHP installation has a limited date range.'), - 'description' => t('You are running on a system where PHP is compiled or limited to using 32-bit integers. This will limit the range of dates and timestamps to the years 1901-2038. Read about the <a href=":url">limitations of 32-bit PHP</a>.', [':url' => 'https://www.drupal.org/docs/system-requirements/limitations-of-32-bit-php']), - 'severity' => RequirementSeverity::Warning, - ]; - } - - // During installs from configuration don't support install profiles that - // implement hook_install. - if ($phase == 'install' && !empty($install_state['config_install_path'])) { - $install_hook = $install_state['parameters']['profile'] . '_install'; - if (function_exists($install_hook)) { - $requirements['config_install'] = [ - 'title' => t('Configuration install'), - 'value' => $install_state['parameters']['profile'], - 'description' => t('The selected profile has a hook_install() implementation and therefore can not be installed from configuration.'), - 'severity' => RequirementSeverity::Error, - ]; - } - } - - if ($phase === 'runtime') { - $settings = Settings::getAll(); - if (array_key_exists('install_profile', $settings)) { - // The following message is only informational because not all site owners - // have access to edit their settings.php as it may be controlled by their - // hosting provider. - $requirements['install_profile_in_settings'] = [ - 'title' => t('Install profile in settings'), - 'value' => t("Drupal 9 no longer uses the \$settings['install_profile'] value in settings.php and it should be removed."), - 'severity' => RequirementSeverity::Warning, - ]; - } - } - - // Ensure that no module has a current schema version that is lower than the - // one that was last removed. - if ($phase == 'update') { - $module_handler = \Drupal::moduleHandler(); - /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ - $update_registry = \Drupal::service('update.update_hook_registry'); - $module_list = []; - // hook_update_last_removed() is a procedural hook hook because we - // do not have classes loaded that would be needed. - // Simply inlining the old hook mechanism is better than making - // ModuleInstaller::invoke() public. - foreach ($module_handler->getModuleList() as $module => $extension) { - $function = $module . '_update_last_removed'; - if (function_exists($function)) { - $last_removed = $function(); - if ($last_removed && $last_removed > $update_registry->getInstalledVersion($module)) { - - /** @var \Drupal\Core\Extension\Extension $module_info */ - $module_info = $module_extension_list->get($module); - $module_list[$module] = [ - 'name' => $module_info->info['name'], - 'last_removed' => $last_removed, - 'installed_version' => $update_registry->getInstalledVersion($module), - ]; - } - } - } - - // If user module is in the list then only show a specific message for - // Drupal core. - if (isset($module_list['user'])) { - $requirements['user_update_last_removed'] = [ - 'title' => t('The version of Drupal you are trying to update from is too old'), - 'description' => t('Updating to Drupal @current_major is only supported from Drupal version @required_min_version or higher. If you are trying to update from an older version, first update to the latest version of Drupal @previous_major. (<a href=":url">Drupal upgrade guide</a>)', [ - '@current_major' => 10, - '@required_min_version' => '9.4.0', - '@previous_major' => 9, - ':url' => 'https://www.drupal.org/docs/upgrading-drupal/drupal-8-and-higher', - ]), - 'severity' => RequirementSeverity::Error, - ]; - } - else { - foreach ($module_list as $module => $data) { - $requirements[$module . '_update_last_removed'] = [ - 'title' => t('Unsupported schema version: @module', ['@module' => $data['name']]), - 'description' => t('The installed version of the %module module is too old to update. Update to an intermediate version first (last removed version: @last_removed_version, installed version: @installed_version).', [ - '%module' => $data['name'], - '@last_removed_version' => $data['last_removed'], - '@installed_version' => $data['installed_version'], - ]), - 'severity' => RequirementSeverity::Error, - ]; - } - } - // Also check post-updates. Only do this if we're not already showing an - // error for hook_update_N(). - $missing_updates = []; - if (empty($module_list)) { - $existing_updates = \Drupal::service('keyvalue')->get('post_update')->get('existing_updates', []); - $post_update_registry = \Drupal::service('update.post_update_registry'); - $modules = \Drupal::moduleHandler()->getModuleList(); - foreach ($modules as $module => $extension) { - $module_info = $module_extension_list->get($module); - $removed_post_updates = $post_update_registry->getRemovedPostUpdates($module); - if ($missing_updates = array_diff(array_keys($removed_post_updates), $existing_updates)) { - $versions = array_unique(array_intersect_key($removed_post_updates, array_flip($missing_updates))); - $description = new PluralTranslatableMarkup(count($versions), - 'The installed version of the %module module is too old to update. Update to a version prior to @versions first (missing updates: @missing_updates).', - 'The installed version of the %module module is too old to update. Update first to a version prior to all of the following: @versions (missing updates: @missing_updates).', - [ - '%module' => $module_info->info['name'], - '@missing_updates' => implode(', ', $missing_updates), - '@versions' => implode(', ', $versions), - ] - ); - $requirements[$module . '_post_update_removed'] = [ - 'title' => t('Missing updates for: @module', ['@module' => $module_info->info['name']]), - 'description' => $description, - 'severity' => RequirementSeverity::Error, - ]; - } - } - } - - if (empty($missing_updates)) { - foreach ($update_registry->getAllEquivalentUpdates() as $module => $equivalent_updates) { - $module_info = $module_extension_list->get($module); - foreach ($equivalent_updates as $future_update => $data) { - $future_update_function_name = $module . '_update_' . $future_update; - $ran_update_function_name = $module . '_update_' . $data['ran_update']; - // If an update was marked as an equivalent by a previous update, and - // both the previous update and the equivalent update are not found in - // the current code base, prevent updating. This indicates a site - // attempting to go 'backwards' in terms of database schema. - // @see \Drupal\Core\Update\UpdateHookRegistry::markFutureUpdateEquivalent() - if (!function_exists($ran_update_function_name) && !function_exists($future_update_function_name)) { - // If the module is provided by core prepend helpful text as the - // module does not exist in composer or Drupal.org. - if (str_starts_with($module_info->getPathname(), 'core/')) { - $future_version_string = 'Drupal Core ' . $data['future_version_string']; - } - else { - $future_version_string = $data['future_version_string']; - } - $requirements[$module . '_equivalent_update_missing'] = [ - 'title' => t('Missing updates for: @module', ['@module' => $module_info->info['name']]), - 'description' => t('The version of the %module module that you are attempting to update to is missing update @future_update (which was marked as an equivalent by @ran_update). Update to at least @future_version_string.', [ - '%module' => $module_info->info['name'], - '@ran_update' => $data['ran_update'], - '@future_update' => $future_update, - '@future_version_string' => $future_version_string, - ]), - 'severity' => RequirementSeverity::Error, - ]; - break; - } - } - } - } - } - - // Add warning when twig debug option is enabled. - if ($phase === 'runtime') { - $development_settings = \Drupal::keyValue('development_settings'); - $twig_debug = $development_settings->get('twig_debug', FALSE); - $twig_cache_disable = $development_settings->get('twig_cache_disable', FALSE); - if ($twig_debug || $twig_cache_disable) { - $requirements['twig_debug_enabled'] = [ - 'title' => t('Twig development mode'), - 'value' => t('Twig development mode settings are turned on. Go to @link to disable them.', [ - '@link' => Link::createFromRoute( - 'development settings page', - 'system.development_settings', - )->toString(), - ]), - 'severity' => RequirementSeverity::Warning, - ]; - } - $render_cache_disabled = $development_settings->get('disable_rendered_output_cache_bins', FALSE); - if ($render_cache_disabled) { - $requirements['render_cache_disabled'] = [ - 'title' => t('Markup caching disabled'), - 'value' => t('Render cache, dynamic page cache, and page cache are bypassed. Go to @link to enable them.', [ - '@link' => Link::createFromRoute( - 'development settings page', - 'system.development_settings', - )->toString(), - ]), - 'severity' => RequirementSeverity::Warning, - ]; - } - } - - return $requirements; -} - -/** * Implements hook_install(). */ function system_install(): void { @@ -1686,57 +105,6 @@ function system_update_11200(): void { } /** - * Display requirements from security advisories. - * - * @param array[] $requirements - * The requirements array as specified in hook_requirements(). - */ -function _system_advisories_requirements(array &$requirements): void { - if (!\Drupal::config('system.advisories')->get('enabled')) { - return; - } - - /** @var \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher $fetcher */ - $fetcher = \Drupal::service('system.sa_fetcher'); - try { - $advisories = $fetcher->getSecurityAdvisories(TRUE, 5); - } - catch (ClientExceptionInterface $exception) { - $requirements['system_advisories']['title'] = t('Critical security announcements'); - $requirements['system_advisories']['severity'] = RequirementSeverity::Warning; - $requirements['system_advisories']['description'] = ['#theme' => 'system_security_advisories_fetch_error_message']; - Error::logException(\Drupal::logger('system'), $exception, 'Failed to retrieve security advisory data.'); - return; - } - - if (!empty($advisories)) { - $advisory_links = []; - $severity = RequirementSeverity::Warning; - foreach ($advisories as $advisory) { - if (!$advisory->isPsa()) { - $severity = RequirementSeverity::Error; - } - $advisory_links[] = new Link($advisory->getTitle(), Url::fromUri($advisory->getUrl())); - } - $requirements['system_advisories']['title'] = t('Critical security announcements'); - $requirements['system_advisories']['severity'] = $severity; - $requirements['system_advisories']['description'] = [ - 'list' => [ - '#theme' => 'item_list', - '#items' => $advisory_links, - ], - ]; - if (\Drupal::moduleHandler()->moduleExists('help')) { - $requirements['system_advisories']['description']['help_link'] = Link::createFromRoute( - 'What are critical security announcements?', - 'help.page', ['name' => 'system'], - ['fragment' => 'security-advisories'] - )->toRenderable(); - } - } -} - -/** * Invalidate container because the module handler has changed. */ function system_update_11100(): void { diff --git a/core/modules/system/templates/details.html.twig b/core/modules/system/templates/details.html.twig index 20e4ea7193e3..dcb1cf354ce5 100644 --- a/core/modules/system/templates/details.html.twig +++ b/core/modules/system/templates/details.html.twig @@ -34,7 +34,11 @@ </div> {% endif %} - {{ description }} + {%- if description -%} + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + <div{{ description_attributes }}>{{ description }}</div> + {%- endif -%} + {{ children }} {{ value }} </details> diff --git a/core/modules/system/templates/image.html.twig b/core/modules/system/templates/image.html.twig index 6411eaa3d07b..1f6d19d6c3e7 100644 --- a/core/modules/system/templates/image.html.twig +++ b/core/modules/system/templates/image.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the img tag. * - style_name: (optional) The name of the image style applied. * - * @see template_preprocess_image() + * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage() * * @ingroup themeable */ diff --git a/core/modules/system/templates/install-page.html.twig b/core/modules/system/templates/install-page.html.twig index f6091fd3b956..d9144e6a154b 100644 --- a/core/modules/system/templates/install-page.html.twig +++ b/core/modules/system/templates/install-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_install_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessInstallPage() * * @ingroup themeable */ diff --git a/core/modules/system/templates/item-list.html.twig b/core/modules/system/templates/item-list.html.twig index 1462cf41ae0f..c2babdab978e 100644 --- a/core/modules/system/templates/item-list.html.twig +++ b/core/modules/system/templates/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() * * @ingroup themeable */ diff --git a/core/modules/system/templates/maintenance-page.html.twig b/core/modules/system/templates/maintenance-page.html.twig index 748ed5a3aa4a..06fb6065f7a4 100644 --- a/core/modules/system/templates/maintenance-page.html.twig +++ b/core/modules/system/templates/maintenance-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() * * @ingroup themeable */ diff --git a/core/modules/system/templates/menu-local-action.html.twig b/core/modules/system/templates/menu-local-action.html.twig index 0eb03a9534ab..e0280d5fcbc0 100644 --- a/core/modules/system/templates/menu-local-action.html.twig +++ b/core/modules/system/templates/menu-local-action.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the wrapper element. * - link: A rendered link element. * - * @see template_preprocess_menu_local_action() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalAction() * * @ingroup themeable */ diff --git a/core/modules/system/templates/menu-local-task.html.twig b/core/modules/system/templates/menu-local-task.html.twig index ec02a8d530c4..b2a743940a77 100644 --- a/core/modules/system/templates/menu-local-task.html.twig +++ b/core/modules/system/templates/menu-local-task.html.twig @@ -11,7 +11,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() * * @ingroup themeable */ diff --git a/core/modules/system/templates/pager.html.twig b/core/modules/system/templates/pager.html.twig index 199f0578dbdc..75047c1b95f2 100644 --- a/core/modules/system/templates/pager.html.twig +++ b/core/modules/system/templates/pager.html.twig @@ -28,7 +28,7 @@ * at the first page. * - next: Present if the visible list of pages ends before the last page. * - * @see template_preprocess_pager() + * @see \Drupal\Core\Pager\PagerPreprocess::preprocessPager() * * @ingroup themeable */ diff --git a/core/modules/system/templates/region.html.twig b/core/modules/system/templates/region.html.twig index 219e14b0a4be..ddcaaa192df4 100644 --- a/core/modules/system/templates/region.html.twig +++ b/core/modules/system/templates/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() * * @ingroup themeable */ diff --git a/core/modules/system/templates/table.html.twig b/core/modules/system/templates/table.html.twig index cfcb0bf976c0..6a73cc1152a8 100644 --- a/core/modules/system/templates/table.html.twig +++ b/core/modules/system/templates/table.html.twig @@ -38,7 +38,7 @@ * - no_striping: A boolean indicating that the row should receive no striping. * - header_columns: The number of columns in the header. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() * * @ingroup themeable */ diff --git a/core/modules/system/tests/modules/container_initialize/container_initialize.info.yml b/core/modules/system/tests/modules/container_initialize/container_initialize.info.yml new file mode 100644 index 000000000000..46411d2ea544 --- /dev/null +++ b/core/modules/system/tests/modules/container_initialize/container_initialize.info.yml @@ -0,0 +1,5 @@ +name: 'Container initialize' +type: module +description: 'Support module for HookCollectorPass testing.' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/container_initialize/container_initialize.module b/core/modules/system/tests/modules/container_initialize/container_initialize.module new file mode 100644 index 000000000000..5c8e0aff74ea --- /dev/null +++ b/core/modules/system/tests/modules/container_initialize/container_initialize.module @@ -0,0 +1,10 @@ +<?php + +/** + * @file + * Used to test bare container calls in .module files. + */ + +declare(strict_types=1); + +\Drupal::getContainer()->getParameter('site.path'); diff --git a/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php b/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php index f54ad8f9a9a3..c052c5a488fb 100644 --- a/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php +++ b/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php @@ -17,7 +17,8 @@ class Deprecated extends RenderElementBase { * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, $plugin_definition) { - parent::__construct($configuration, $plugin_id, $plugin_definition); + $elementInfoManager = \Drupal::service('plugin.manager.element_info'); + parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager); @trigger_error(__CLASS__ . ' is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/3068104', E_USER_DEPRECATED); } diff --git a/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php b/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php index ea72bd033fb9..53150495cb9b 100644 --- a/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php +++ b/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php @@ -4,8 +4,9 @@ declare(strict_types=1); namespace Drupal\element_info_test\Hook; -use Drupal\element_info_test\ElementInfoTestNumberBuilder; use Drupal\Core\Hook\Attribute\Hook; +use Drupal\element_info_test\ElementInfoTestNumberBuilder; +use Drupal\element_info_test\Render\Element\Details; /** * Hook implementations for element_info_test. @@ -30,6 +31,9 @@ class ElementInfoTestHooks { if (\Drupal::state()->get('hook_element_plugin_alter:remove_weight', FALSE)) { unset($definitions['weight']); } + + $definitions['details']['class'] = Details::class; + $definitions['details']['provider'] = 'element_info_test'; } } diff --git a/core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php b/core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php new file mode 100644 index 000000000000..8e4d84558ca4 --- /dev/null +++ b/core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php @@ -0,0 +1,57 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\element_info_test\Render\Element; + +use Drupal\Core\Render\Attribute\RenderElement; +use Drupal\Core\Render\Element; +use Drupal\Core\Render\Element\RenderElementBase; + +/** + * Provides a render element for a details element. + * + * Properties: + * + * @property $title + * The title of the details container. Defaults to "Details". + * @property $open + * Indicates whether the container should be open by default. + * Defaults to FALSE. + * @property $custom + * Confirm that this class has been swapped properly. + * @property $summary_attributes + * An array of attributes to apply to the <summary> + * element. + */ +#[RenderElement('details')] +class Details extends RenderElementBase { + + /** + * {@inheritdoc} + */ + public function getInfo(): array { + return [ + '#open' => FALSE, + '#summary_attributes' => [], + '#custom' => 'Custom', + ]; + } + + /** + * Adds form element theming to details. + * + * @param array $element + * An associative array containing the properties and children of the + * details. + * + * @return array + * The modified element. + */ + public static function preRenderDetails($element): array { + Element::setAttributes($element, ['custom']); + + return $element; + } + +} diff --git a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install b/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install deleted file mode 100644 index 483a1d01717e..000000000000 --- a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.install +++ /dev/null @@ -1,24 +0,0 @@ -<?php - -/** - * @file - * Experimental Test Requirements module to test hook_requirements(). - */ - -declare(strict_types=1); - -use Drupal\Core\Extension\Requirement\RequirementSeverity; - -/** - * Implements hook_requirements(). - */ -function experimental_module_requirements_test_requirements(): array { - $requirements = []; - if (\Drupal::state()->get('experimental_module_requirements_test_requirements', FALSE)) { - $requirements['experimental_module_requirements_test_requirements'] = [ - 'severity' => RequirementSeverity::Error, - 'description' => t('The Experimental Test Requirements module can not be installed.'), - ]; - } - return $requirements; -} diff --git a/core/modules/system/tests/modules/experimental_module_requirements_test/src/Install/Requirements/ExperimentalModuleRequirementsTestRequirements.php b/core/modules/system/tests/modules/experimental_module_requirements_test/src/Install/Requirements/ExperimentalModuleRequirementsTestRequirements.php new file mode 100644 index 000000000000..53834f77c0e1 --- /dev/null +++ b/core/modules/system/tests/modules/experimental_module_requirements_test/src/Install/Requirements/ExperimentalModuleRequirementsTestRequirements.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\experimental_module_requirements_test\Install\Requirements; + +use Drupal\Core\Extension\InstallRequirementsInterface; +use Drupal\Core\Extension\Requirement\RequirementSeverity; + +/** + * Install time requirements for the Experimental Requirements Test module. + */ +class ExperimentalModuleRequirementsTestRequirements implements InstallRequirementsInterface { + + /** + * {@inheritdoc} + */ + public static function getRequirements(): array { + $requirements = []; + if (\Drupal::state()->get('experimental_module_requirements_test_requirements', FALSE)) { + $requirements['experimental_module_requirements_test_requirements'] = [ + 'severity' => RequirementSeverity::Error, + 'description' => t('The Experimental Test Requirements module can not be installed.'), + ]; + } + return $requirements; + } + +} diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php index 9babda83ddc0..226eb705802d 100644 --- a/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestGroupDetailsForm.php @@ -48,6 +48,11 @@ class FormTestGroupDetailsForm extends FormBase { 'data-summary-attribute' => 'test', ], ]; + $form['description_attributes'] = [ + '#type' => 'details', + '#title' => 'Details element with description', + '#description' => 'I am a details description', + ]; return $form; } diff --git a/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php b/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php index cda2b92b3477..441ebaa1d128 100644 --- a/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php +++ b/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php @@ -6,6 +6,8 @@ namespace Drupal\form_test\Hook; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Hook\Attribute\Hook; +use Drupal\Core\Render\Element\Submit; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\form_test\Callbacks; @@ -16,6 +18,8 @@ class FormTestHooks { use StringTranslationTrait; + public function __construct(protected ElementInfoManagerInterface $elementInfoManager) {} + /** * Implements hook_form_FORM_ID_alter(). */ @@ -55,13 +59,10 @@ class FormTestHooks { */ #[Hook('form_user_register_form_alter')] public function formUserRegisterFormAlter(&$form, FormStateInterface $form_state) : void { - $form['test_rebuild'] = [ - '#type' => 'submit', - '#value' => $this->t('Rebuild'), - '#submit' => [ - [Callbacks::class, 'userRegisterFormRebuild'], - ], - ]; + $submit = $this->elementInfoManager->fromRenderable($form) + ->createChild('test_rebuild', Submit::class); + $submit->value = $this->t('Rebuild'); + $submit->submit = [[Callbacks::class, 'userRegisterFormRebuild']]; } /** @@ -69,11 +70,12 @@ class FormTestHooks { */ #[Hook('form_form_test_vertical_tabs_access_form_alter')] public function formFormTestVerticalTabsAccessFormAlter(&$form, &$form_state, $form_id) : void { - $form['vertical_tabs1']['#access'] = FALSE; - $form['vertical_tabs2']['#access'] = FALSE; - $form['tabs3']['#access'] = TRUE; - $form['fieldset1']['#access'] = FALSE; - $form['container']['#access'] = FALSE; + $element_object = $this->elementInfoManager->fromRenderable($form); + $element_object->getChild('vertical_tabs1')->access = FALSE; + $element_object->getChild('vertical_tabs2')->access = FALSE; + $element_object->getChild('tab3')->access = FALSE; + $element_object->getChild('fieldset1')->access = FALSE; + $element_object->getChild('container')->access = FALSE; } } diff --git a/core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php new file mode 100644 index 000000000000..b57e4c883972 --- /dev/null +++ b/core/modules/system/tests/modules/system_test/src/Controller/OptionalServiceSystemTestController.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\system_test\Controller; + +use Drupal\Core\Controller\ControllerBase; +use Drupal\dblog\Logger\DbLog; +use Symfony\Component\DependencyInjection\Attribute\Autowire; + +/** + * A controller that specifies an optional dependency. + */ +class OptionalServiceSystemTestController extends ControllerBase { + + public function __construct( + #[Autowire('logger.dblog')] + public readonly ?DbLog $dbLog, + ) {} + +} diff --git a/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php b/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php index 6c6f67d77eb8..1d953ff8c337 100644 --- a/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php +++ b/core/modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php @@ -79,27 +79,10 @@ class ThemeTestSubscriber implements EventSubscriberInterface { } /** - * Ensures that the theme registry was not initialized. - */ - public function onView(RequestEvent $event) { - $current_route = $this->currentRouteMatch->getRouteName(); - $entity_autocomplete_route = [ - 'system.entity_autocomplete', - ]; - - if (in_array($current_route, $entity_autocomplete_route)) { - if ($this->container->initialized('theme.registry')) { - throw new \Exception('registry initialized'); - } - } - } - - /** * {@inheritdoc} */ public static function getSubscribedEvents(): array { $events[KernelEvents::REQUEST][] = ['onRequest']; - $events[KernelEvents::VIEW][] = ['onView', -1000]; return $events; } diff --git a/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php b/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php index 77eaa48575b0..f570d8031761 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php @@ -62,20 +62,23 @@ class EntityAddUITest extends BrowserTestBase { $this->drupalGet('/entity_test_with_bundle/add'); $this->assertSession()->addressEquals('/entity_test_with_bundle/add/test'); - // Two bundles exist, confirm both are shown. + // Two bundles exist. Confirm both are shown and that they are ordered + // alphabetically by their labels, not by their IDs. EntityTestBundle::create([ 'id' => 'test2', - 'label' => 'Test2 label', + 'label' => 'Aaa Test2 label', 'description' => 'My test2 description', ])->save(); $this->drupalGet('/entity_test_with_bundle/add'); $this->assertSession()->linkExists('Test label'); - $this->assertSession()->linkExists('Test2 label'); + $this->assertSession()->linkExists('Aaa Test2 label'); $this->assertSession()->pageTextContains('My test description'); $this->assertSession()->pageTextContains('My test2 description'); - $this->clickLink('Test2 label'); + $this->assertSession()->pageTextMatches('/Aaa Test2 label(.*)Test label/'); + + $this->clickLink('Aaa Test2 label'); $this->drupalGet('/entity_test_with_bundle/add/test2'); $this->submitForm(['name[0][value]' => 'test name'], 'Save'); @@ -106,7 +109,7 @@ class EntityAddUITest extends BrowserTestBase { $this->drupalGet('/entity_test_with_bundle/add'); $this->assertSession()->statusCodeEquals(200); $this->assertSession()->linkExists('Test label'); - $this->assertSession()->linkExists('Test2 label'); + $this->assertSession()->linkExists('Aaa Test2 label'); $this->assertSession()->linkNotExists('Forbidden to create bundle'); $this->assertSession()->linkNotExists('Test3 label'); $this->clickLink('Test label'); @@ -129,7 +132,7 @@ class EntityAddUITest extends BrowserTestBase { $this->drupalGet('/entity_test_with_bundle/add'); $this->assertSession()->linkNotExists('Forbidden to create bundle'); $this->assertSession()->linkNotExists('Test label'); - $this->assertSession()->linkNotExists('Test2 label'); + $this->assertSession()->linkNotExists('Aaa Test2 label'); $this->assertSession()->linkNotExists('Test3 label'); $this->assertSession()->linkExists('Add a new test entity bundle.'); } diff --git a/core/modules/system/tests/src/Functional/Form/ElementTest.php b/core/modules/system/tests/src/Functional/Form/ElementTest.php index 4a9755fac7f5..0ebf9e4ce774 100644 --- a/core/modules/system/tests/src/Functional/Form/ElementTest.php +++ b/core/modules/system/tests/src/Functional/Form/ElementTest.php @@ -38,6 +38,7 @@ class ElementTest extends BrowserTestBase { $this->testFormAutocomplete(); $this->testFormElementErrors(); $this->testDetailsSummaryAttributes(); + $this->testDetailsDescriptionAttributes(); } /** @@ -230,4 +231,13 @@ class ElementTest extends BrowserTestBase { $this->assertSession()->elementExists('css', 'summary[data-summary-attribute="test"]'); } + /** + * Tests description attributes of details. + */ + protected function testDetailsDescriptionAttributes(): void { + $this->drupalGet('form-test/group-details'); + $this->assertSession()->elementExists('css', 'details[aria-describedby="edit-description-attributes--description"]'); + $this->assertSession()->elementExists('css', 'div[id="edit-description-attributes--description"]'); + } + } diff --git a/core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php b/core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php new file mode 100644 index 000000000000..59d69d1242cb --- /dev/null +++ b/core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php @@ -0,0 +1,61 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\system\Functional\Hook; + +use Drupal\Tests\BrowserTestBase; +use Drupal\Core\Url; + +/** + * Tests services in .module files. + * + * @group Hook + */ +class HookCollectorPassTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['container_initialize']; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * Tests installing a module with a Drupal container call outside functions. + * + * If this is removed then it needs to be moved to a test that installs modules through + * admin/modules. + */ + public function testContainerOutsideFunction(): void { + $settings['settings']['rebuild_access'] = (object) [ + 'value' => TRUE, + 'required' => TRUE, + ]; + + // This simulates installing the module and running a cache rebuild in a + // separate request. + $this->writeSettings($settings); + $this->rebuildAll(); + $this->drupalGet(Url::fromUri('base:core/rebuild.php')); + $this->assertSession()->pageTextNotContains('ContainerNotInitializedException'); + // Successful response from rebuild.php should redirect to the front page. + $this->assertSession()->addressEquals('/'); + + // If this file is removed then this test needs to be updated to trigger + // the container rebuild error from https://www.drupal.org/i/3505049 + $config_module_file = $this->root . '/core/modules/system/tests/modules/container_initialize/container_initialize.module'; + $this->assertFileExists($config_module_file, 'This test depends on a container call in a .module file'); + // Confirm that the file still has a bare container call. + $bare_container = "declare(strict_types=1); + +\Drupal::getContainer()->getParameter('site.path'); +"; + $file_content = file_get_contents($config_module_file); + $this->assertStringContainsString($bare_container, $file_content, 'container_initialize.module container test feature is missing.'); + } + +} diff --git a/core/modules/system/tests/src/Functional/Module/UninstallTest.php b/core/modules/system/tests/src/Functional/Module/UninstallTest.php index aeace5bb488b..6d1b93cc50db 100644 --- a/core/modules/system/tests/src/Functional/Module/UninstallTest.php +++ b/core/modules/system/tests/src/Functional/Module/UninstallTest.php @@ -22,7 +22,15 @@ class UninstallTest extends BrowserTestBase { /** * {@inheritdoc} */ - protected static $modules = ['module_test', 'user', 'views', 'node']; + protected static $modules = [ + 'ckeditor5', + 'filter', + 'module_test', + 'node', + 'user', + 'views', + 'views_ui', + ]; /** * {@inheritdoc} @@ -118,6 +126,13 @@ class UninstallTest extends BrowserTestBase { // Delete the node to allow node to be uninstalled. $node->delete(); + // Ensure dependent module full names are shown. + $this->assertSession()->pageTextContains('Required by: Views UI'); + // Ensure matching machine names do not display. + $this->assertSession()->pageTextNotContains('Required by: Views UI (views_ui)'); + // Ensure machine names that do not match do display. + $this->assertSession()->pageTextContains('Text Editor (editor)'); + // Uninstall module_test. $edit = []; $edit['uninstall[module_test]'] = TRUE; diff --git a/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php b/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php index 6e47278edadd..32487fa86045 100644 --- a/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php +++ b/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php @@ -92,8 +92,7 @@ class SitesDirectoryHardeningTest extends BrowserTestBase { * An array of system requirements. */ protected function checkSystemRequirements() { - \Drupal::moduleHandler()->loadInclude('system', 'install'); - return system_requirements('runtime'); + return \Drupal::moduleHandler()->invoke('system', 'runtime_requirements'); } /** diff --git a/core/modules/system/tests/src/Functional/System/StatusTest.php b/core/modules/system/tests/src/Functional/System/StatusTest.php index 972ac14fb2cb..b5e32759a73a 100644 --- a/core/modules/system/tests/src/Functional/System/StatusTest.php +++ b/core/modules/system/tests/src/Functional/System/StatusTest.php @@ -97,6 +97,8 @@ class StatusTest extends BrowserTestBase { $this->drupalGet('admin/reports/status/php'); $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('PHP'); + $this->assertSession()->pageTextNotContains('$_COOKIE'); $settings['settings']['sa_core_2023_004_phpinfo_flags'] = (object) [ 'value' => INFO_ALL, diff --git a/core/modules/system/tests/src/Functional/Theme/FastTest.php b/core/modules/system/tests/src/Functional/Theme/FastTest.php deleted file mode 100644 index 5cbb7d8f277b..000000000000 --- a/core/modules/system/tests/src/Functional/Theme/FastTest.php +++ /dev/null @@ -1,52 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\Tests\system\Functional\Theme; - -use Drupal\Tests\BrowserTestBase; -use Drupal\user\Entity\User; - -/** - * Tests autocompletion not loading registry. - * - * @group Theme - */ -class FastTest extends BrowserTestBase { - - /** - * {@inheritdoc} - */ - protected static $modules = ['theme_test']; - - /** - * {@inheritdoc} - */ - protected $defaultTheme = 'stark'; - - /** - * User allowed to access use profiles. - * - * @var \Drupal\user\Entity\User - */ - protected User $account; - - /** - * {@inheritdoc} - */ - protected function setUp(): void { - parent::setUp(); - $this->account = $this->drupalCreateUser(['access user profiles']); - } - - /** - * Tests access to user autocompletion and verify the correct results. - */ - public function testUserAutocomplete(): void { - $this->drupalLogin($this->account); - $this->drupalGet('user/autocomplete', ['query' => ['q' => $this->account->getAccountName()]]); - $this->assertSession()->responseContains($this->account->getAccountName()); - $this->assertSession()->pageTextNotContains('registry initialized'); - } - -} diff --git a/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php b/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php index ab42b418125a..8a2cfe22f03c 100644 --- a/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php +++ b/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Drupal\Tests\system\Unit\Pager; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Pager\PagerPreprocess; use Drupal\Core\Template\AttributeString; use Drupal\Tests\UnitTestCase; @@ -12,10 +13,17 @@ use Drupal\Tests\UnitTestCase; * Tests pager preprocessing. * * @group system + * + * @coversDefaultClass \Drupal\Core\Pager\PagerPreprocess */ class PreprocessPagerTest extends UnitTestCase { /** + * Pager preprocess instance. + */ + protected PagerPreprocess $pagerPreprocess; + + /** * {@inheritdoc} */ protected function setUp(): void { @@ -39,21 +47,19 @@ class PreprocessPagerTest extends UnitTestCase { $pager_manager->method('getPager')->willReturn($pager); $pager_manager->method('getUpdatedParameters')->willReturn(''); + $this->pagerPreprocess = new PagerPreprocess($pager_manager); + $container = new ContainerBuilder(); - $container->set('pager.manager', $pager_manager); $container->set('url_generator', $url_generator); - // template_preprocess_pager() renders translatable attribute values. - $container->set('string_translation', $this->getStringTranslationStub()); \Drupal::setContainer($container); } /** - * Tests template_preprocess_pager() when an empty #quantity is passed. + * Tests when an empty #quantity is passed. * - * @covers ::template_preprocess_pager + * @covers ::preprocessPager */ public function testQuantityNotSet(): void { - require_once $this->root . '/core/includes/theme.inc'; $variables = [ 'pager' => [ '#element' => '', @@ -63,18 +69,17 @@ class PreprocessPagerTest extends UnitTestCase { '#tags' => '', ], ]; - template_preprocess_pager($variables); + $this->pagerPreprocess->preprocessPager($variables); $this->assertEquals(['first', 'previous'], array_keys($variables['items'])); } /** - * Tests template_preprocess_pager() when a #quantity value is passed. + * Tests when a #quantity value is passed. * - * @covers ::template_preprocess_pager + * @covers ::preprocessPager */ public function testQuantitySet(): void { - require_once $this->root . '/core/includes/theme.inc'; $variables = [ 'pager' => [ '#element' => '2', @@ -84,7 +89,7 @@ class PreprocessPagerTest extends UnitTestCase { '#tags' => '', ], ]; - template_preprocess_pager($variables); + $this->pagerPreprocess->preprocessPager($variables); $this->assertEquals(['first', 'previous', 'pages'], array_keys($variables['items'])); /** @var \Drupal\Core\Template\AttributeString $attribute */ @@ -94,12 +99,11 @@ class PreprocessPagerTest extends UnitTestCase { } /** - * Tests template_preprocess_pager() when an empty #pagination_heading_level value is passed. + * Tests when an empty #pagination_heading_level value is passed. * - * @covers ::template_preprocess_pager + * @covers ::preprocessPager */ public function testEmptyPaginationHeadingLevelSet(): void { - require_once $this->root . '/core/includes/theme.inc'; $variables = [ 'pager' => [ '#element' => '2', @@ -110,18 +114,17 @@ class PreprocessPagerTest extends UnitTestCase { '#tags' => '', ], ]; - template_preprocess_pager($variables); + $this->pagerPreprocess->preprocessPager($variables); $this->assertEquals('h4', $variables['pagination_heading_level']); } /** - * Tests template_preprocess_pager() when no #pagination_heading_level is passed. + * Tests when no #pagination_heading_level is passed. * - * @covers ::template_preprocess_pager + * @covers ::preprocessPager */ public function testPaginationHeadingLevelNotSet(): void { - require_once $this->root . '/core/includes/theme.inc'; $variables = [ 'pager' => [ '#element' => '', @@ -131,18 +134,17 @@ class PreprocessPagerTest extends UnitTestCase { '#tags' => '', ], ]; - template_preprocess_pager($variables); + $this->pagerPreprocess->preprocessPager($variables); $this->assertEquals('h4', $variables['pagination_heading_level']); } /** - * Tests template_preprocess_pager() when a #pagination_heading_level value is passed. + * Tests when a #pagination_heading_level value is passed. * - * @covers ::template_preprocess_pager + * @covers ::preprocessPager */ public function testPaginationHeadingLevelSet(): void { - require_once $this->root . '/core/includes/theme.inc'; $variables = [ 'pager' => [ '#element' => '2', @@ -153,18 +155,17 @@ class PreprocessPagerTest extends UnitTestCase { '#tags' => '', ], ]; - template_preprocess_pager($variables); + $this->pagerPreprocess->preprocessPager($variables); $this->assertEquals('h5', $variables['pagination_heading_level']); } /** - * Test template_preprocess_pager() with an invalid #pagination_heading_level. + * Test with an invalid #pagination_heading_level. * - * @covers ::template_preprocess_pager + * @covers ::preprocessPager */ public function testPaginationHeadingLevelInvalid(): void { - require_once $this->root . '/core/includes/theme.inc'; $variables = [ 'pager' => [ '#element' => '2', @@ -175,7 +176,7 @@ class PreprocessPagerTest extends UnitTestCase { '#tags' => '', ], ]; - template_preprocess_pager($variables); + $this->pagerPreprocess->preprocessPager($variables); $this->assertEquals('h4', $variables['pagination_heading_level']); } diff --git a/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php b/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php index 16b7549681c1..289fbbd332cd 100644 --- a/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php +++ b/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php @@ -6,7 +6,10 @@ use Drupal\Core\Field\Attribute\FieldWidget; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\Plugin\Field\FieldWidget\StringTextfieldWidget; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element\ElementInterface; +use Drupal\Core\Render\Element\Widget; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\filter\Element\TextFormat; use Symfony\Component\Validator\ConstraintViolationInterface; /** @@ -22,20 +25,20 @@ class TextfieldWidget extends StringTextfieldWidget { /** * {@inheritdoc} */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - $main_widget = parent::formElement($items, $delta, $element, $form, $form_state); + public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface { + $widget = parent::singleElementObject($items, $delta, $widget, $form, $form_state); $allowed_formats = $this->getFieldSetting('allowed_formats'); - $element = $main_widget['value']; - $element['#type'] = 'text_format'; - $element['#format'] = $items[$delta]->format ?? NULL; - $element['#base_type'] = $main_widget['value']['#type']; - + $widget = $widget->getChild('value'); + $type = $widget->type; + $widget = $widget->changeType(TextFormat::class); + $widget->format = $items[$delta]->format ?? NULL; + $widget->base_type = $type; if ($allowed_formats && !$this->isDefaultValueWidget($form_state)) { - $element['#allowed_formats'] = $allowed_formats; + $widget->allowed_formats = $allowed_formats; } - return $element; + return $widget; } /** diff --git a/core/modules/update/src/Hook/UpdateHooks.php b/core/modules/update/src/Hook/UpdateHooks.php index 6c6c57b53e52..6577f7f1fc28 100644 --- a/core/modules/update/src/Hook/UpdateHooks.php +++ b/core/modules/update/src/Hook/UpdateHooks.php @@ -78,14 +78,6 @@ class UpdateHooks { $verbose = TRUE; break; } - // This loadInclude() is to ensure that the install API is available. - // Since we're loading an include of type 'install', this will also - // include core/includes/install.inc for us, which is where the - // REQUIREMENTS* constants are currently defined. - // @todo Remove this once those constants live in a better place. - // @see https://www.drupal.org/project/drupal/issues/2909480 - // @see https://www.drupal.org/project/drupal/issues/3410938 - \Drupal::moduleHandler()->loadInclude('update', 'install'); $status = \Drupal::moduleHandler()->invoke('update', 'runtime_requirements'); foreach (['core', 'contrib'] as $report_type) { $type = 'update_' . $report_type; diff --git a/core/modules/update/tests/src/Functional/UpdateSemverContribTestBase.php b/core/modules/update/tests/src/Functional/UpdateSemverContribTestBase.php index 2f5c7c038b93..bd554c0e8503 100644 --- a/core/modules/update/tests/src/Functional/UpdateSemverContribTestBase.php +++ b/core/modules/update/tests/src/Functional/UpdateSemverContribTestBase.php @@ -10,7 +10,7 @@ namespace Drupal\Tests\update\Functional; * This wires up the protected data from UpdateSemverTestBase for a contrib * module with semantic version releases. */ -class UpdateSemverContribTestBase extends UpdateSemverTestBase { +abstract class UpdateSemverContribTestBase extends UpdateSemverTestBase { /** * {@inheritdoc} diff --git a/core/modules/update/tests/src/Functional/UpdateSemverCoreTestBase.php b/core/modules/update/tests/src/Functional/UpdateSemverCoreTestBase.php index b9d3a46a68e8..f336562c4794 100644 --- a/core/modules/update/tests/src/Functional/UpdateSemverCoreTestBase.php +++ b/core/modules/update/tests/src/Functional/UpdateSemverCoreTestBase.php @@ -10,7 +10,7 @@ namespace Drupal\Tests\update\Functional; * This wires up the protected data from UpdateSemverTestBase for Drupal core * with semantic version releases. */ -class UpdateSemverCoreTestBase extends UpdateSemverTestBase { +abstract class UpdateSemverCoreTestBase extends UpdateSemverTestBase { /** * {@inheritdoc} diff --git a/core/modules/update/update.fetch.inc b/core/modules/update/update.fetch.inc index c8e4990d385d..12295b97d986 100644 --- a/core/modules/update/update.fetch.inc +++ b/core/modules/update/update.fetch.inc @@ -20,14 +20,6 @@ use Drupal\update\UpdateManagerInterface; #[ProceduralHookScanStop] function _update_cron_notify(): void { $update_config = \Drupal::config('update.settings'); - // This loadInclude() is to ensure that the install API is available. - // Since we're loading an include of type 'install', this will also - // include core/includes/install.inc for us, which is where the - // REQUIREMENTS* constants are currently defined. - // @todo Remove this once those constants live in a better place. - // @see https://www.drupal.org/project/drupal/issues/2909480 - // @see https://www.drupal.org/project/drupal/issues/3410938 - \Drupal::moduleHandler()->loadInclude('update', 'install'); $status = \Drupal::moduleHandler()->invoke('update', 'runtime_requirements'); $params = []; $notify_all = ($update_config->get('notification.threshold') == 'all'); diff --git a/core/modules/user/src/AccountForm.php b/core/modules/user/src/AccountForm.php index 881244f6f53c..d0c5e8d2d9b2 100644 --- a/core/modules/user/src/AccountForm.php +++ b/core/modules/user/src/AccountForm.php @@ -402,6 +402,7 @@ abstract class AccountForm extends ContentEntityForm implements TrustedCallbackI 'name', 'pass', 'mail', + 'roles', 'timezone', 'langcode', 'preferred_langcode', @@ -420,6 +421,7 @@ abstract class AccountForm extends ContentEntityForm implements TrustedCallbackI 'name', 'pass', 'mail', + 'roles', 'timezone', 'langcode', 'preferred_langcode', diff --git a/core/modules/user/src/Hook/UserRequirements.php b/core/modules/user/src/Hook/UserRequirements.php index f317ced58bc4..46155e55e3cb 100644 --- a/core/modules/user/src/Hook/UserRequirements.php +++ b/core/modules/user/src/Hook/UserRequirements.php @@ -49,6 +49,7 @@ class UserRequirements { $query->addExpression('LOWER(mail)', 'lower_mail'); $query->isNotNull('mail'); $query->groupBy('lower_mail'); + $query->groupBy('langcode'); $query->having('COUNT(uid) > :matches', [':matches' => 1]); $conflicts = $query->countQuery()->execute()->fetchField(); diff --git a/core/modules/user/tests/modules/user_form_test/src/Hook/UserFormTestHooks.php b/core/modules/user/tests/modules/user_form_test/src/Hook/UserFormTestHooks.php index f49008597f10..106199f413fe 100644 --- a/core/modules/user/tests/modules/user_form_test/src/Hook/UserFormTestHooks.php +++ b/core/modules/user/tests/modules/user_form_test/src/Hook/UserFormTestHooks.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Drupal\user_form_test\Hook; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Hook\Attribute\Hook; /** @@ -20,4 +21,14 @@ class UserFormTestHooks { $form['access']['#value'] = \Drupal::currentUser()->hasPermission('cancel other accounts'); } + /** + * Implements hook_entity_base_field_info_alter(). + */ + #[Hook('entity_base_field_info_alter')] + public function entityBaseFieldInfoAlter(&$fields, EntityTypeInterface $entity_type): void { + if ($entity_type->id() === 'user' && \Drupal::keyvalue('user_form_test')->get('user_form_test_constraint_roles_edit')) { + $fields['roles']->addConstraint('FieldWidgetConstraint'); + } + } + } diff --git a/core/modules/user/tests/src/Functional/UserEditTest.php b/core/modules/user/tests/src/Functional/UserEditTest.php index f9a8dfb1a75c..ff86fa249084 100644 --- a/core/modules/user/tests/src/Functional/UserEditTest.php +++ b/core/modules/user/tests/src/Functional/UserEditTest.php @@ -283,4 +283,21 @@ class UserEditTest extends BrowserTestBase { $this->assertSession()->fieldEnabled('edit-status-1'); } + /** + * Tests constraint violations are triggered on the user account form. + */ + public function testRolesValidation(): void { + $admin_user = $this->drupalCreateUser(['administer users']); + $this->drupalLogin($admin_user); + $this->drupalGet("user/" . $admin_user->id() . "/edit"); + $this->submitForm([], 'Save'); + $this->assertSession()->pageTextContains('The changes have been saved.'); + \Drupal::keyvalue('user_form_test')->set('user_form_test_constraint_roles_edit', TRUE); + \Drupal::service('module_installer')->install(['entity_test', 'user_form_test']); + $this->drupalGet("user/" . $admin_user->id() . "/edit"); + $this->submitForm([], 'Save'); + $this->assertSession()->pageTextContains('Widget constraint has failed.'); + $this->assertSession()->pageTextNotContains('The changes have been saved.'); + } + } diff --git a/core/modules/user/tests/src/Kernel/UserRequirementsTest.php b/core/modules/user/tests/src/Kernel/UserRequirementsTest.php index 146ab9c8b904..746370a15d61 100644 --- a/core/modules/user/tests/src/Kernel/UserRequirementsTest.php +++ b/core/modules/user/tests/src/Kernel/UserRequirementsTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Drupal\Tests\user\Kernel; use Drupal\KernelTests\KernelTestBase; +use Drupal\language\Entity\ConfigurableLanguage; use Drupal\Tests\user\Traits\UserCreationTrait; /** @@ -70,4 +71,21 @@ class UserRequirementsTest extends KernelTestBase { $this->assertArrayNotHasKey('conflicting emails', $output); } + /** + * Tests that the requirements check does not flag user translations. + */ + public function testTranslatedUserEmail(): void { + \Drupal::service('module_installer')->install(['language']); + ConfigurableLanguage::createFromLangcode('is')->save(); + + $output = $this->moduleHandler->invoke('user', 'runtime_requirements'); + $this->assertArrayNotHasKey('conflicting emails', $output); + + $user = $this->createUser([], 'User A', FALSE, ['mail' => 'unique@example.com']); + $user->addTranslation('is')->save(); + + $output = $this->moduleHandler->invoke('user', 'runtime_requirements'); + $this->assertArrayNotHasKey('conflicting emails', $output); + } + } diff --git a/core/modules/views/config/schema/views.data_types.schema.yml b/core/modules/views/config/schema/views.data_types.schema.yml index 0e011d73dc15..31c6b2f6479e 100644 --- a/core/modules/views/config/schema/views.data_types.schema.yml +++ b/core/modules/views/config/schema/views.data_types.schema.yml @@ -633,29 +633,33 @@ views_pager: offset: type: integer label: 'Offset' + constraints: + PositiveOrZero: [] pagination_heading_level: + # Validating against a string, but the list is populated by a protected + # property of the plugin. This could be a callback in the future. type: string label: 'Pager header element' items_per_page: type: integer label: 'Items per page' + constraints: + PositiveOrZero: [] views_pager_sql: type: views_pager label: 'SQL pager' mapping: - items_per_page: - type: integer - label: 'Items per page' - pagination_heading_level: - type: string - label: 'Pager header element' total_pages: type: integer label: 'Number of pages' + constraints: + PositiveOrZero: [] id: type: integer label: 'Pager ID' + constraints: + PositiveOrZero: [] tags: type: mapping label: 'Pager link labels' @@ -669,6 +673,8 @@ views_pager_sql: quantity: type: integer label: 'Number of pager links visible' + constraints: + PositiveOrZero: [] expose: type: mapping label: 'Exposed options' @@ -682,6 +688,11 @@ views_pager_sql: items_per_page_options: type: string label: 'Exposed items per page options' + constraints: + # Comma separated list of integers, with optional space in between. + Regex: + pattern: '/^(\d+)(,\s*\d+)*$/' + message: 'Per page should be a valid list of integers.' items_per_page_options_all: type: boolean label: 'Include all items option' diff --git a/core/modules/views/config/schema/views.pager.schema.yml b/core/modules/views/config/schema/views.pager.schema.yml index eb360227e029..072dbc5b87e0 100644 --- a/core/modules/views/config/schema/views.pager.schema.yml +++ b/core/modules/views/config/schema/views.pager.schema.yml @@ -33,3 +33,5 @@ views.pager.full: quantity: type: integer label: 'Number of pager links visible' + constraints: + PositiveOrZero: [] diff --git a/core/modules/views/src/Plugin/views/pager/Full.php b/core/modules/views/src/Plugin/views/pager/Full.php index 0176fc6e7f90..ed8f7d7566a2 100644 --- a/core/modules/views/src/Plugin/views/pager/Full.php +++ b/core/modules/views/src/Plugin/views/pager/Full.php @@ -79,8 +79,8 @@ class Full extends SqlBase { * {@inheritdoc} */ public function render($input) { - // The 0, 1, 3, 4 indexes are correct. See the template_preprocess_pager() - // documentation. + // The 0, 1, 3, 4 indexes are correct. See the + // \Drupal\Core\Pager\PagerPreprocess::preprocessPager() documentation. $tags = [ 0 => $this->options['tags']['first'], 1 => $this->options['tags']['previous'], diff --git a/core/modules/views/src/Plugin/views/pager/Mini.php b/core/modules/views/src/Plugin/views/pager/Mini.php index 0f95f7a0d2f6..e17aa7fabdd7 100644 --- a/core/modules/views/src/Plugin/views/pager/Mini.php +++ b/core/modules/views/src/Plugin/views/pager/Mini.php @@ -90,7 +90,8 @@ class Mini extends SqlBase { * {@inheritdoc} */ public function render($input) { - // The 1, 3 indexes are correct, see template_preprocess_pager(). + // The 1, 3 indexes are correct, see + // \Drupal\Core\Pager\PagerPreprocess::preprocessPager(). $tags = [ 1 => $this->options['tags']['previous'], 3 => $this->options['tags']['next'], diff --git a/core/modules/workspaces/src/Hook/FormOperations.php b/core/modules/workspaces/src/Hook/FormOperations.php index 61b775ea3cd0..6f91618e71a8 100644 --- a/core/modules/workspaces/src/Hook/FormOperations.php +++ b/core/modules/workspaces/src/Hook/FormOperations.php @@ -8,7 +8,10 @@ use Drupal\Core\Form\WorkspaceSafeFormInterface; use Drupal\Core\Hook\Attribute\Hook; use Drupal\Core\Render\Element; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\workspaces\Entity\Workspace; +use Drupal\workspaces\Negotiator\QueryParameterWorkspaceNegotiator; use Drupal\workspaces\WorkspaceManagerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * Defines a class for reacting to form operations. @@ -17,6 +20,8 @@ class FormOperations { public function __construct( protected WorkspaceManagerInterface $workspaceManager, + #[Autowire('@workspaces.negotiator.query_parameter')] + protected QueryParameterWorkspaceNegotiator $queryParameterNegotiator, ) {} /** @@ -24,8 +29,21 @@ class FormOperations { */ #[Hook('form_alter')] public function formAlter(array &$form, FormStateInterface $form_state, $form_id): void { + $active_workspace = $this->workspaceManager->getActiveWorkspace(); + + // Ensure that the form's initial workspace (if any) is used for the current + // request. + $form_workspace_id = $form_state->getUserInput()['active_workspace_id'] ?? NULL; + $form_workspace = $form_workspace_id + ? Workspace::load($form_workspace_id) + : NULL; + if ($form_workspace && (!$active_workspace || $active_workspace->id() != $form_workspace->id())) { + $this->workspaceManager->setActiveWorkspace($form_workspace, FALSE); + $active_workspace = $form_workspace; + } + // No alterations are needed if we're not in a workspace context. - if (!$this->workspaceManager->hasActiveWorkspace()) { + if (!$active_workspace) { return; } @@ -47,6 +65,17 @@ class FormOperations { ]; $this->addWorkspaceValidation($form); } + else { + // Persist the active workspace for the entire lifecycle of the form, + // including AJAX requests. + $form['active_workspace_id'] = [ + '#type' => 'hidden', + '#value' => $active_workspace->id(), + ]; + + $url_query_options = $this->queryParameterNegotiator->getQueryOptions($active_workspace->id()); + $this->setAjaxWorkspace($form, $url_query_options + ['persist' => FALSE]); + } } /** @@ -83,4 +112,29 @@ class FormOperations { } } + /** + * Ensures that the current workspace is persisted across AJAX interactions. + * + * @param array &$element + * An associative array containing the structure of the form. + * @param array $url_query_options + * An array of URL query options used by the query parameter workspace + * negotiator. + */ + protected function setAjaxWorkspace(array &$element, array $url_query_options): void { + // Recurse through all children if needed. + foreach (Element::children($element) as $key) { + if (isset($element[$key]) && $element[$key]) { + $this->setAjaxWorkspace($element[$key], $url_query_options); + } + } + + if (isset($element['#ajax']) && !isset($element['#ajax']['options']['query']['workspace'])) { + $element['#ajax']['options']['query'] = array_merge_recursive( + $url_query_options, + $element['#ajax']['options']['query'] ?? [], + ); + } + } + } diff --git a/core/modules/workspaces/src/Negotiator/QueryParameterWorkspaceNegotiator.php b/core/modules/workspaces/src/Negotiator/QueryParameterWorkspaceNegotiator.php index 1f0b688541ff..04efac2c3b12 100644 --- a/core/modules/workspaces/src/Negotiator/QueryParameterWorkspaceNegotiator.php +++ b/core/modules/workspaces/src/Negotiator/QueryParameterWorkspaceNegotiator.php @@ -4,6 +4,7 @@ namespace Drupal\workspaces\Negotiator; use Drupal\Component\Utility\Crypt; use Drupal\Core\Site\Settings; +use Drupal\workspaces\WorkspaceInterface; use Symfony\Component\HttpFoundation\Request; /** @@ -12,6 +13,11 @@ use Symfony\Component\HttpFoundation\Request; class QueryParameterWorkspaceNegotiator extends SessionWorkspaceNegotiator { /** + * Whether the negotiated workspace should be persisted. + */ + protected bool $persist = TRUE; + + /** * {@inheritdoc} */ public function applies(Request $request) { @@ -24,6 +30,8 @@ class QueryParameterWorkspaceNegotiator extends SessionWorkspaceNegotiator { * {@inheritdoc} */ public function getActiveWorkspaceId(Request $request): ?string { + $this->persist = (bool) $request->query->get('persist', TRUE); + $workspace_id = (string) $request->query->get('workspace'); $token = (string) $request->query->get('token'); $is_valid_token = hash_equals($this->getQueryToken($workspace_id), $token); @@ -36,6 +44,40 @@ class QueryParameterWorkspaceNegotiator extends SessionWorkspaceNegotiator { } /** + * {@inheritdoc} + */ + public function setActiveWorkspace(WorkspaceInterface $workspace) { + if ($this->persist) { + parent::setActiveWorkspace($workspace); + } + } + + /** + * {@inheritdoc} + */ + public function unsetActiveWorkspace() { + if ($this->persist) { + parent::unsetActiveWorkspace(); + } + } + + /** + * Returns the query options used by this negotiator. + * + * @param string $workspace_id + * A workspace ID. + * + * @return array + * An array of query options that can be used for a \Drupal\Core\Url object. + */ + public function getQueryOptions(string $workspace_id): array { + return [ + 'workspace' => $workspace_id, + 'token' => $this->getQueryToken($workspace_id), + ]; + } + + /** * Calculates a token based on a workspace ID. * * @param string $workspace_id diff --git a/core/modules/workspaces/src/WorkspaceManager.php b/core/modules/workspaces/src/WorkspaceManager.php index 5bb2dc454c3f..b0e9b8ac959e 100644 --- a/core/modules/workspaces/src/WorkspaceManager.php +++ b/core/modules/workspaces/src/WorkspaceManager.php @@ -9,24 +9,53 @@ use Drupal\Core\Session\AccountProxyInterface; use Drupal\Core\Site\Settings; use Drupal\Core\State\StateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\workspaces\Negotiator\WorkspaceNegotiatorInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; use Symfony\Component\HttpFoundation\RequestStack; /** * Provides the workspace manager. + * + * @property iterable $negotiators */ class WorkspaceManager implements WorkspaceManagerInterface { use StringTranslationTrait; /** - * The current active workspace or FALSE if there is no active workspace. + * The current active workspace. * - * @var \Drupal\workspaces\WorkspaceInterface|false + * The value is either a workspace object, FALSE if there is no active + * workspace, or NULL if the active workspace hasn't been determined yet. */ - protected $activeWorkspace; + protected WorkspaceInterface|false|null $activeWorkspace = NULL; - public function __construct(protected RequestStack $requestStack, protected EntityTypeManagerInterface $entityTypeManager, protected MemoryCacheInterface $entityMemoryCache, protected AccountProxyInterface $currentUser, protected StateInterface $state, protected LoggerInterface $logger, protected ClassResolverInterface $classResolver, protected WorkspaceAssociationInterface $workspaceAssociation, protected WorkspaceInformationInterface $workspaceInfo, protected array $negotiatorIds = []) { + /** + * An array of workspace negotiator services. + * + * @todo Remove in drupal:12.0.0. + */ + private array $collectedNegotiators = []; + + public function __construct( + protected RequestStack $requestStack, + protected EntityTypeManagerInterface $entityTypeManager, + protected MemoryCacheInterface $entityMemoryCache, + protected AccountProxyInterface $currentUser, + protected StateInterface $state, + #[Autowire(service: 'logger.channel.workspaces')] + protected LoggerInterface $logger, + #[AutowireIterator(tag: 'workspace_negotiator')] + protected $negotiators, + protected WorkspaceAssociationInterface $workspaceAssociation, + protected WorkspaceInformationInterface $workspaceInfo, + ) { + if ($negotiators instanceof ClassResolverInterface) { + @trigger_error('Passing the \'class_resolver\' service as the 7th argument to ' . __METHOD__ . ' is deprecated in drupal:11.3.0 and is unsupported in drupal:12.0.0. Use autowiring for the \'workspaces.manager\' service instead. See https://www.drupal.org/node/3532939', E_USER_DEPRECATED); + $this->negotiators = $this->collectedNegotiators; + } } /** @@ -43,10 +72,7 @@ class WorkspaceManager implements WorkspaceManagerInterface { if (!isset($this->activeWorkspace)) { $request = $this->requestStack->getCurrentRequest(); - foreach ($this->negotiatorIds as $negotiator_id) { - /** @var \Drupal\workspaces\Negotiator\WorkspaceIdNegotiatorInterface $negotiator */ - $negotiator = $this->classResolver->getInstanceFromDefinition($negotiator_id); - + foreach ($this->negotiators as $negotiator) { if ($negotiator->applies($request)) { if ($workspace_id = $negotiator->getActiveWorkspaceId($request)) { /** @var \Drupal\workspaces\WorkspaceInterface $negotiated_workspace */ @@ -79,16 +105,19 @@ class WorkspaceManager implements WorkspaceManagerInterface { /** * {@inheritdoc} */ - public function setActiveWorkspace(WorkspaceInterface $workspace) { + public function setActiveWorkspace(WorkspaceInterface $workspace, /* bool $persist = TRUE */) { + $persist = func_num_args() < 2 || func_get_arg(1); + $this->doSwitchWorkspace($workspace); - // Set the workspace on the proper negotiator. - $request = $this->requestStack->getCurrentRequest(); - foreach ($this->negotiatorIds as $negotiator_id) { - $negotiator = $this->classResolver->getInstanceFromDefinition($negotiator_id); - if ($negotiator->applies($request)) { - $negotiator->setActiveWorkspace($workspace); - break; + // Set the workspace on the first applicable negotiator. + if ($persist) { + $request = $this->requestStack->getCurrentRequest(); + foreach ($this->negotiators as $negotiator) { + if ($negotiator->applies($request)) { + $negotiator->setActiveWorkspace($workspace); + break; + } } } @@ -102,8 +131,7 @@ class WorkspaceManager implements WorkspaceManagerInterface { $this->doSwitchWorkspace(NULL); // Unset the active workspace on all negotiators. - foreach ($this->negotiatorIds as $negotiator_id) { - $negotiator = $this->classResolver->getInstanceFromDefinition($negotiator_id); + foreach ($this->negotiators as $negotiator) { $negotiator->unsetActiveWorkspace(); } @@ -253,4 +281,18 @@ class WorkspaceManager implements WorkspaceManagerInterface { } } + /** + * Adds a workspace negotiator service. + * + * @param \Drupal\workspaces\Negotiator\WorkspaceNegotiatorInterface $negotiator + * The negotiator to be added. + * + * @todo Remove in drupal:12.0.0. + * + * @internal + */ + public function addNegotiator(WorkspaceNegotiatorInterface $negotiator): void { + $this->collectedNegotiators[] = $negotiator; + } + } diff --git a/core/modules/workspaces/src/WorkspaceManagerInterface.php b/core/modules/workspaces/src/WorkspaceManagerInterface.php index a61d29f50d5f..8d8024c66207 100644 --- a/core/modules/workspaces/src/WorkspaceManagerInterface.php +++ b/core/modules/workspaces/src/WorkspaceManagerInterface.php @@ -24,20 +24,24 @@ interface WorkspaceManagerInterface { public function getActiveWorkspace(); /** - * Sets the active workspace via the workspace negotiators. + * Sets the active workspace. * * @param \Drupal\workspaces\WorkspaceInterface $workspace * The workspace to set as active. + * phpcs:ignore + * @param bool $persist + * (optional) Whether to persist this workspace in the first applicable + * negotiator. Defaults to TRUE. * * @return $this * * @throws \Drupal\workspaces\WorkspaceAccessException * Thrown when the current user doesn't have access to view the workspace. */ - public function setActiveWorkspace(WorkspaceInterface $workspace); + public function setActiveWorkspace(WorkspaceInterface $workspace, /* bool $persist = TRUE */); /** - * Unsets the active workspace via the workspace negotiators. + * Unsets the active workspace. * * @return $this */ diff --git a/core/modules/workspaces/tests/modules/workspaces_test/src/Form/ActiveWorkspaceTestForm.php b/core/modules/workspaces/tests/modules/workspaces_test/src/Form/ActiveWorkspaceTestForm.php new file mode 100644 index 000000000000..955cf4720385 --- /dev/null +++ b/core/modules/workspaces/tests/modules/workspaces_test/src/Form/ActiveWorkspaceTestForm.php @@ -0,0 +1,74 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\workspaces_test\Form; + +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Form\WorkspaceSafeFormInterface; +use Drupal\Core\KeyValueStore\KeyValueStoreInterface; +use Drupal\Core\Url; +use Drupal\workspaces\WorkspaceManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Form for testing the active workspace. + * + * @internal + */ +class ActiveWorkspaceTestForm extends FormBase implements WorkspaceSafeFormInterface { + + /** + * The workspace manager. + */ + protected WorkspaceManagerInterface $workspaceManager; + + /** + * The test key-value store. + */ + protected KeyValueStoreInterface $keyValue; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): static { + $instance = parent::create($container); + $instance->workspaceManager = $container->get('workspaces.manager'); + $instance->keyValue = $container->get('keyvalue')->get('ws_test'); + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return 'active_workspace_test_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $form['test'] = [ + '#type' => 'textfield', + '#ajax' => [ + 'url' => Url::fromRoute('workspaces_test.get_form'), + 'callback' => function () { + $this->keyValue->set('ajax_test_active_workspace', $this->workspaceManager->getActiveWorkspace()->id()); + return new AjaxResponse(); + }, + ], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + $this->keyValue->set('form_test_active_workspace', $this->workspaceManager->getActiveWorkspace()->id()); + } + +} diff --git a/core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.routing.yml b/core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.routing.yml new file mode 100644 index 000000000000..bdf7648db9cc --- /dev/null +++ b/core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.routing.yml @@ -0,0 +1,7 @@ +workspaces_test.get_form: + path: '/active-workspace-test-form' + defaults: + _title: 'Active Workspace Test Form' + _form: '\Drupal\workspaces_test\Form\ActiveWorkspaceTestForm' + requirements: + _access: 'TRUE' diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php new file mode 100644 index 000000000000..ac3e7b3a51ce --- /dev/null +++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceFormPersistenceTest.php @@ -0,0 +1,120 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\workspaces\Kernel; + +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\EventSubscriber\MainContentViewSubscriber; +use Drupal\Core\Form\FormBuilderInterface; +use Drupal\Core\Form\FormState; +use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\user\Traits\UserCreationTrait; +use Drupal\workspaces\Entity\Workspace; +use Drupal\workspaces_test\Form\ActiveWorkspaceTestForm; +use Symfony\Component\HttpFoundation\Request; + +/** + * Tests form persistence for the active workspace. + * + * @group workspaces + */ +class WorkspaceFormPersistenceTest extends KernelTestBase { + + use UserCreationTrait; + use WorkspaceTestTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'system', + 'user', + 'workspaces', + 'workspaces_test', + ]; + + /** + * The entity type manager. + */ + protected EntityTypeManagerInterface $entityTypeManager; + + /** + * The form builder. + */ + protected FormBuilderInterface $formBuilder; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->entityTypeManager = \Drupal::entityTypeManager(); + $this->formBuilder = \Drupal::formBuilder(); + + $this->installEntitySchema('user'); + $this->installEntitySchema('workspace'); + + Workspace::create(['id' => 'ham', 'label' => 'Ham'])->save(); + Workspace::create(['id' => 'cheese', 'label' => 'Cheese'])->save(); + + $this->setCurrentUser($this->createUser([ + 'view any workspace', + ])); + } + + /** + * Tests that the active workspace is persisted throughout a form's lifecycle. + */ + public function testFormPersistence(): void { + $form_arg = ActiveWorkspaceTestForm::class; + + $this->switchToWorkspace('ham'); + $form_state_1 = new FormState(); + $form_1 = $this->formBuilder->buildForm($form_arg, $form_state_1); + + $this->switchToWorkspace('cheese'); + $form_state_2 = new FormState(); + $this->formBuilder->buildForm($form_arg, $form_state_2); + + // Submit the second form and check the workspace in which it was submitted. + $this->formBuilder->submitForm($form_arg, $form_state_2); + $this->assertSame('cheese', $this->keyValue->get('ws_test')->get('form_test_active_workspace')); + + // Submit the first form and check the workspace in which it was submitted. + $this->formBuilder->submitForm($form_arg, $form_state_1); + $this->assertSame('ham', $this->keyValue->get('ws_test')->get('form_test_active_workspace')); + + // Reset the workspace manager service to simulate a new request and check + // that the second workspace is still active. + \Drupal::getContainer()->set('workspaces.manager', NULL); + $this->assertSame('cheese', \Drupal::service('workspaces.manager')->getActiveWorkspace()->id()); + + // Reset the workspace manager service again to prepare for a new request. + \Drupal::getContainer()->set('workspaces.manager', NULL); + + $request = Request::create( + $form_1['test']['#ajax']['url']->toString(), + 'POST', + [ + MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax', + ] + $form_1['test']['#attached']['drupalSettings']['ajax'][$form_1['test']['#id']]['submit'], + ); + \Drupal::service('http_kernel')->handle($request); + + $form_state_1->setTriggeringElement($form_1['test']); + \Drupal::service('form_ajax_response_builder')->buildResponse($request, $form_1, $form_state_1, []); + + // Check that the AJAX callback is executed in the initial workspace of its + // parent form. + $this->assertSame('ham', $this->keyValue->get('ws_test')->get('ajax_test_active_workspace')); + + // Reset the workspace manager service again and check that the AJAX request + // didn't change the persisted workspace. + \Drupal::getContainer()->set('workspaces.manager', NULL); + \Drupal::requestStack()->pop(); + $this->assertSame('cheese', \Drupal::service('workspaces.manager')->getActiveWorkspace()->id()); + } + +} diff --git a/core/modules/workspaces/workspaces.services.yml b/core/modules/workspaces/workspaces.services.yml index 1fd07d36d772..4abb1d1cae42 100644 --- a/core/modules/workspaces/workspaces.services.yml +++ b/core/modules/workspaces/workspaces.services.yml @@ -3,9 +3,9 @@ services: autoconfigure: true workspaces.manager: class: Drupal\workspaces\WorkspaceManager - arguments: ['@request_stack', '@entity_type.manager', '@entity.memory_cache', '@current_user', '@state', '@logger.channel.workspaces', '@class_resolver', '@workspaces.association', '@workspaces.information'] + autowire: true tags: - - { name: service_id_collector, tag: workspace_negotiator } + - { name: service_collector, call: addNegotiator, tag: workspace_negotiator } Drupal\workspaces\WorkspaceManagerInterface: '@workspaces.manager' workspaces.information: class: Drupal\workspaces\WorkspaceInformation diff --git a/core/profiles/demo_umami/config/optional/block.block.umami_banner_home.yml b/core/profiles/demo_umami/config/optional/block.block.umami_banner_home.yml index 024f70d48a64..8d5f31939e16 100644 --- a/core/profiles/demo_umami/config/optional/block.block.umami_banner_home.yml +++ b/core/profiles/demo_umami/config/optional/block.block.umami_banner_home.yml @@ -22,8 +22,6 @@ settings: label: 'Umami Home Banner' label_display: '0' provider: block_content - status: true - info: '' view_mode: full visibility: request_path: diff --git a/core/profiles/demo_umami/config/optional/block.block.umami_banner_recipes.yml b/core/profiles/demo_umami/config/optional/block.block.umami_banner_recipes.yml index 5176f5f1c90e..ac8d1830242e 100644 --- a/core/profiles/demo_umami/config/optional/block.block.umami_banner_recipes.yml +++ b/core/profiles/demo_umami/config/optional/block.block.umami_banner_recipes.yml @@ -22,8 +22,6 @@ settings: label: 'Umami Recipes Banner' label_display: '0' provider: block_content - status: true - info: '' view_mode: full visibility: request_path: diff --git a/core/profiles/demo_umami/config/optional/block.block.umami_disclaimer.yml b/core/profiles/demo_umami/config/optional/block.block.umami_disclaimer.yml index 6817c0e70942..a0bb4c75810a 100644 --- a/core/profiles/demo_umami/config/optional/block.block.umami_disclaimer.yml +++ b/core/profiles/demo_umami/config/optional/block.block.umami_disclaimer.yml @@ -21,7 +21,5 @@ settings: label: 'Umami disclaimer' label_display: '0' provider: block_content - status: true - info: '' view_mode: full visibility: { } diff --git a/core/profiles/demo_umami/config/optional/block.block.umami_footer_promo.yml b/core/profiles/demo_umami/config/optional/block.block.umami_footer_promo.yml index 8f80552d2c28..066f12a21853 100644 --- a/core/profiles/demo_umami/config/optional/block.block.umami_footer_promo.yml +++ b/core/profiles/demo_umami/config/optional/block.block.umami_footer_promo.yml @@ -21,7 +21,5 @@ settings: label: 'Umami footer promo' label_display: '0' provider: block_content - status: true - info: '' view_mode: full visibility: { } diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig index e9928fd77660..4940f047477e 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list--search-results.html.twig @@ -17,7 +17,7 @@ * results, the following data is set: * - plugin: The search plugin ID, for example "node_search". * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig index 20541b0b7e66..6e7b8e317b1d 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if context.list_style %} diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig index cdfe0bff7e73..d7e6459bd4a4 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/dataset/table.html.twig @@ -38,7 +38,7 @@ * - no_striping: A boolean indicating that the row should receive no striping. * - header_columns: The number of columns in the header. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() */ #} <table{{ attributes }}> diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig index 31f782bb60a8..90d955c180a6 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/field/image.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the img tag. * - style_name: (optional) The name of the image style applied. * - * @see template_preprocess_image() + * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage() */ #} {% diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig index 3f6e8ddf05c9..e2ae382b56fa 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/form/details.html.twig @@ -32,7 +32,8 @@ </div> {% endif %} {%- if description -%} - <div class="details-description">{{ description }}</div> + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + <div {{ description_attributes.addClass(['details-description']) }}>{{ description }}</div> {%- endif -%} {%- if children -%} {{ children }} diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig index 7463b0238ca3..edd2783619b8 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/layout/maintenance-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() */ #} <div class="layout-container"> diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig index 95e71cec37e4..a4e8cc0af72a 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/layout/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig b/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig index b1d11d5458c5..99122f1668e0 100644 --- a/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/components/navigation/menu-local-task.html.twig @@ -11,7 +11,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() */ #} <li{{ attributes.addClass('tabs__tab', is_active ? 'is-active') }}>{{ link }}</li> diff --git a/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig b/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig index a0b90e94cb6c..d55f70c54ae9 100644 --- a/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/layout/region--header.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/scripts/dev/commit-code-check.sh b/core/scripts/dev/commit-code-check.sh index 541692236263..42d2ac670ad8 100755 --- a/core/scripts/dev/commit-code-check.sh +++ b/core/scripts/dev/commit-code-check.sh @@ -25,6 +25,7 @@ contains_element() { return 1 } +MEMORY_UNLIMITED=0 CACHED=0 DRUPALCI=0 BRANCH="" @@ -58,12 +59,22 @@ while test $# -gt 0; do DRUPALCI=1 shift ;; + --memory-unlimited) + MEMORY_UNLIMITED=1 + shift + ;; *) break ;; esac done +memory_limit="" + +if [[ "$MEMORY_UNLIMITED" == "1" ]]; then + memory_limit="--memory-limit=-1" +fi + # Set up variables to make colored output simple. Color output is disabled on # DrupalCI because it is breaks reporting. # @todo https://www.drupal.org/project/drupalci_testbot/issues/3181869 @@ -238,11 +249,11 @@ printf "\n" # APCu is disabled to ensure that the composer classmap is not corrupted. if [[ $PHPSTAN_DIST_FILE_CHANGED == "1" ]] || [[ "$DRUPALCI" == "1" ]]; then printf "\nRunning PHPStan on *all* files.\n" - php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan.neon.dist" + php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan.neon.dist" $memory_limit else # Only run PHPStan on changed files locally. printf "\nRunning PHPStan on changed files.\n" - php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan-partial.neon" $ABS_FILES + php -d apc.enabled=0 -d apc.enable_cli=0 vendor/bin/phpstan analyze --no-progress --configuration="$TOP_LEVEL/core/phpstan-partial.neon" $ABS_FILES $memory_limit fi if [ "$?" -ne "0" ]; then diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh index a1a9d903af03..dd831ee38ad3 100755 --- a/core/scripts/run-tests.sh +++ b/core/scripts/run-tests.sh @@ -179,10 +179,18 @@ if (!Composer::upgradePHPUnitCheck(Version::id())) { echo "\n"; echo "Drupal test run\n\n"; -echo sprintf("Drupal Version: %s\n", \Drupal::VERSION); -echo sprintf("PHP Version: %s\n", \PHP_VERSION); -echo sprintf("PHP Binary: %s\n", $php ?? getenv('_')); -echo sprintf("PHPUnit Version: %s\n", Version::id()); +echo sprintf("Drupal Version: %s\n", \Drupal::VERSION); +echo sprintf("PHP Version: %s\n", \PHP_VERSION); +echo sprintf("PHP Binary: %s\n", $php ?? getenv('_')); +echo sprintf("PHPUnit Version: %s\n", Version::id()); +if ($args['dburl']) { + $sut_connection_info = Database::getConnectionInfo(); + $sut_tasks_class = $sut_connection_info['default']['namespace'] . "\\Install\\Tasks"; + $sut_installer = new $sut_tasks_class(); + $sut_connection = Database::getConnection(); + echo sprintf("Database: %s\n", (string) $sut_installer->name()); + echo sprintf("Database Version: %s\n", $sut_connection->version()); +} echo "-------------------------------\n"; echo "\n"; diff --git a/core/tests/Drupal/BuildTests/Command/GenerateThemeTest.php b/core/tests/Drupal/BuildTests/Command/GenerateThemeTest.php index ee21d5bedf52..e943a257ee26 100644 --- a/core/tests/Drupal/BuildTests/Command/GenerateThemeTest.php +++ b/core/tests/Drupal/BuildTests/Command/GenerateThemeTest.php @@ -8,6 +8,8 @@ use Drupal\BuildTests\QuickStart\QuickStartTestBase; use Drupal\Core\Command\GenerateTheme; use Drupal\Core\Serialization\Yaml; use Drupal\sqlite\Driver\Database\sqlite\Install\Tasks; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful; use Symfony\Component\Process\PhpExecutableFinder; @@ -15,11 +17,9 @@ use Symfony\Component\Process\Process; /** * Tests the generate-theme commands. - * - * @requires extension pdo_sqlite - * - * @group Command */ +#[Group('Command')] +#[RequiresPhpExtension('pdo_sqlite')] class GenerateThemeTest extends QuickStartTestBase { /** diff --git a/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php b/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php index 0042c55e1604..8be768ed98c8 100644 --- a/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php +++ b/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php @@ -6,16 +6,17 @@ namespace Drupal\BuildTests\Composer\Component; use Drupal\BuildTests\Composer\ComposerBuildTestBase; use Drupal\Composer\Composer; +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use Symfony\Component\Finder\Finder; /** * Try to install dependencies per component, using Composer. - * - * @group Composer - * @group Component - * - * @coversNothing */ +#[CoversNothing] +#[Group('Composer')] +#[Group('Component')] class ComponentsIsolatedBuildTest extends ComposerBuildTestBase { /** @@ -41,9 +42,8 @@ class ComponentsIsolatedBuildTest extends ComposerBuildTestBase { /** * Test whether components' composer.json can be installed in isolation. - * - * @dataProvider provideComponentPaths */ + #[DataProvider('provideComponentPaths')] public function testComponentComposerJson(string $component_path): void { // Only copy the components. Copy all of them because some of them depend on // each other. diff --git a/core/tests/Drupal/BuildTests/Composer/Component/ComponentsTaggedReleaseTest.php b/core/tests/Drupal/BuildTests/Composer/Component/ComponentsTaggedReleaseTest.php index 269edf126bef..b50aa973c59e 100644 --- a/core/tests/Drupal/BuildTests/Composer/Component/ComponentsTaggedReleaseTest.php +++ b/core/tests/Drupal/BuildTests/Composer/Component/ComponentsTaggedReleaseTest.php @@ -6,15 +6,16 @@ namespace Drupal\BuildTests\Composer\Component; use Drupal\BuildTests\Composer\ComposerBuildTestBase; use Drupal\Composer\Composer; +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; /** * Demonstrate that the Component generator responds to release tagging. - * - * @group Composer - * @group Component - * - * @coversNothing */ +#[CoversNothing] +#[Group('Composer')] +#[Group('Component')] class ComponentsTaggedReleaseTest extends ComposerBuildTestBase { /** @@ -37,9 +38,8 @@ class ComponentsTaggedReleaseTest extends ComposerBuildTestBase { /** * Validate release tagging and regeneration of dependencies. - * - * @dataProvider providerVersionConstraint */ + #[DataProvider('providerVersionConstraint')] public function testReleaseTagging(string $tag, string $constraint): void { $this->copyCodebase(); $drupal_root = $this->getWorkspaceDirectory(); diff --git a/core/tests/Drupal/BuildTests/Composer/ComposerBuildTestBase.php b/core/tests/Drupal/BuildTests/Composer/ComposerBuildTestBase.php index 7bdba7e4f4f2..5c563016f52d 100644 --- a/core/tests/Drupal/BuildTests/Composer/ComposerBuildTestBase.php +++ b/core/tests/Drupal/BuildTests/Composer/ComposerBuildTestBase.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Drupal\BuildTests\Composer; use Drupal\BuildTests\Framework\BuildTestBase; +use PHPUnit\Framework\Attributes\CoversNothing; use Symfony\Component\Finder\Finder; /** * Base class for Composer build tests. - * - * @coversNothing */ +#[CoversNothing] abstract class ComposerBuildTestBase extends BuildTestBase { /** diff --git a/core/tests/Drupal/BuildTests/Composer/ComposerValidateTest.php b/core/tests/Drupal/BuildTests/Composer/ComposerValidateTest.php index 661d159bf806..50c343354e18 100644 --- a/core/tests/Drupal/BuildTests/Composer/ComposerValidateTest.php +++ b/core/tests/Drupal/BuildTests/Composer/ComposerValidateTest.php @@ -6,10 +6,13 @@ namespace Drupal\BuildTests\Composer; use Drupal\BuildTests\Framework\BuildTestBase; use Drupal\Tests\Composer\ComposerIntegrationTrait; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; /** - * @group Composer + * Tests. */ +#[Group('Composer')] class ComposerValidateTest extends BuildTestBase { use ComposerIntegrationTrait; @@ -23,9 +26,7 @@ class ComposerValidateTest extends BuildTestBase { return $data; } - /** - * @dataProvider provideComposerJson - */ + #[DataProvider('provideComposerJson')] public function testValidateComposer($path): void { $this->executeCommand('composer validate --strict --no-check-all ' . $path); $this->assertCommandSuccessful(); diff --git a/core/tests/Drupal/Tests/Composer/Plugin/Unpack/Functional/UnpackRecipeTest.php b/core/tests/Drupal/BuildTests/Composer/Plugin/Unpack/Functional/UnpackRecipeTest.php index b74349256892..1397a78cf694 100644 --- a/core/tests/Drupal/Tests/Composer/Plugin/Unpack/Functional/UnpackRecipeTest.php +++ b/core/tests/Drupal/BuildTests/Composer/Plugin/Unpack/Functional/UnpackRecipeTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Drupal\Tests\Composer\Plugin\Unpack\Functional; +namespace Drupal\BuildTests\Composer\Plugin\Unpack\Functional; use Composer\InstalledVersions; use Composer\Util\Filesystem; diff --git a/core/tests/Drupal/BuildTests/Composer/Template/ComposerProjectTemplatesTest.php b/core/tests/Drupal/BuildTests/Composer/Template/ComposerProjectTemplatesTest.php index 22c364bc8790..f7155bbac018 100644 --- a/core/tests/Drupal/BuildTests/Composer/Template/ComposerProjectTemplatesTest.php +++ b/core/tests/Drupal/BuildTests/Composer/Template/ComposerProjectTemplatesTest.php @@ -8,6 +8,8 @@ use Composer\Json\JsonFile; use Composer\Semver\VersionParser; use Drupal\BuildTests\Composer\ComposerBuildTestBase; use Drupal\Composer\Composer; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; /** * Demonstrate that Composer project templates can be built as patched. @@ -21,9 +23,8 @@ use Drupal\Composer\Composer; * * This is because Composer only uses the packages.json file to resolve the * project template and not any other dependencies. - * - * @group Template */ +#[Group('Template')] class ComposerProjectTemplatesTest extends ComposerBuildTestBase { /** @@ -171,9 +172,7 @@ class ComposerProjectTemplatesTest extends ComposerBuildTestBase { } } - /** - * @dataProvider provideTemplateCreateProject - */ + #[DataProvider('provideTemplateCreateProject')] public function testTemplateCreateProject($project, $package_dir, $docroot_dir): void { // Make a working COMPOSER_HOME directory for setting global composer config $composer_home = $this->getWorkspaceDirectory() . '/composer-home'; diff --git a/core/tests/Drupal/BuildTests/Framework/Tests/BuildTestTest.php b/core/tests/Drupal/BuildTests/Framework/Tests/BuildTestTest.php index 6e29abe18ee0..7f97eb530274 100644 --- a/core/tests/Drupal/BuildTests/Framework/Tests/BuildTestTest.php +++ b/core/tests/Drupal/BuildTests/Framework/Tests/BuildTestTest.php @@ -6,13 +6,16 @@ namespace Drupal\BuildTests\Framework\Tests; use Drupal\BuildTests\Framework\BuildTestBase; use org\bovigo\vfs\vfsStream; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; /** - * @coversDefaultClass \Drupal\BuildTests\Framework\BuildTestBase - * @group Build + * Tests Drupal\BuildTests\Framework\BuildTestBase. */ +#[CoversClass(BuildTestBase::class)] +#[Group('Build')] class BuildTestTest extends BuildTestBase { /** @@ -34,7 +37,7 @@ class BuildTestTest extends BuildTestBase { } /** - * @covers ::copyCodebase + * @legacy-covers ::copyCodebase */ public function testCopyCodebase(): void { $test_directory = 'copied_codebase'; @@ -56,7 +59,7 @@ class BuildTestTest extends BuildTestBase { /** * Ensure we're not copying directories we wish to exclude. * - * @covers ::copyCodebase + * @legacy-covers ::copyCodebase */ public function testCopyCodebaseExclude(): void { // Create a virtual file system containing items that should be @@ -129,7 +132,7 @@ class BuildTestTest extends BuildTestBase { /** * Tests copying codebase when Drupal and Composer roots are different. * - * @covers ::copyCodebase + * @legacy-covers ::copyCodebase */ public function testCopyCodebaseDocRoot(): void { // Create a virtual file system containing items that should be @@ -206,7 +209,7 @@ class BuildTestTest extends BuildTestBase { } /** - * @covers ::findAvailablePort + * @legacy-covers ::findAvailablePort */ public function testPortMany(): void { $iterator = (new Finder())->in($this->getDrupalRoot()) @@ -234,7 +237,7 @@ class BuildTestTest extends BuildTestBase { } /** - * @covers ::standUpServer + * @legacy-covers ::standUpServer */ public function testStandUpServer(): void { // Stand up a server with working directory 'first'. diff --git a/core/tests/Drupal/BuildTests/Framework/Tests/HtRouterTest.php b/core/tests/Drupal/BuildTests/Framework/Tests/HtRouterTest.php index f34f5e7f896c..825cc6eaa468 100644 --- a/core/tests/Drupal/BuildTests/Framework/Tests/HtRouterTest.php +++ b/core/tests/Drupal/BuildTests/Framework/Tests/HtRouterTest.php @@ -4,18 +4,23 @@ declare(strict_types=1); namespace Drupal\BuildTests\Framework\Tests; +use Drupal\BuildTests\Framework\BuildTestBase; use Drupal\BuildTests\QuickStart\QuickStartTestBase; use Drupal\sqlite\Driver\Database\sqlite\Install\Tasks; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; /** - * @coversDefaultClass \Drupal\BuildTests\Framework\BuildTestBase - * @group Build - * @requires extension pdo_sqlite + * Tests Drupal\BuildTests\Framework\BuildTestBase. */ +#[CoversClass(BuildTestBase::class)] +#[Group('Build')] +#[RequiresPhpExtension('pdo_sqlite')] class HtRouterTest extends QuickStartTestBase { /** - * @covers ::instantiateServer + * @legacy-covers ::instantiateServer */ public function testHtRouter(): void { $sqlite = (new \PDO('sqlite::memory:'))->query('select sqlite_version()')->fetch()[0]; diff --git a/core/tests/Drupal/BuildTests/TestSiteApplication/InstallTest.php b/core/tests/Drupal/BuildTests/TestSiteApplication/InstallTest.php index bbcce3d2ca2a..59622ab504f9 100644 --- a/core/tests/Drupal/BuildTests/TestSiteApplication/InstallTest.php +++ b/core/tests/Drupal/BuildTests/TestSiteApplication/InstallTest.php @@ -6,13 +6,15 @@ namespace Drupal\BuildTests\TestSiteApplication; use Drupal\BuildTests\Framework\BuildTestBase; use Drupal\sqlite\Driver\Database\sqlite\Install\Tasks; +use PHPUnit\Framework\Attributes\Group; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Process\PhpExecutableFinder; /** - * @group Build - * @group TestSiteApplication + * Tests. */ +#[Group('Build')] +#[Group('TestSiteApplication')] class InstallTest extends BuildTestBase { public function testInstall(): void { diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php index d1c07c20124e..1211cfb351b7 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxCallbacksTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests Ajax callbacks on FAPI elements. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxCallbacksTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php index b2ae9386bd0a..1eff2c19bbee 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormCacheTest.php @@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\Core\Url; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the usage of form caching for AJAX forms. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxFormCacheTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php index c9a4b3ef2722..347e8ac86f1a 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormImageButtonTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the Ajax image buttons work with key press events. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxFormImageButtonTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php index a80a627a89cc..c11430fdebff 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Performs tests on AJAX forms in cached pages. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxFormPageCacheTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php index a8282b2b6cfe..6618ec6acede 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxInGroupTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests that form elements in groups work correctly with AJAX. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxInGroupTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php index 180381e45c1b..3f4543996f1f 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php @@ -8,12 +8,12 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\Tests\field_ui\Traits\FieldUiTestTrait; use Drupal\Tests\file\Functional\FileFieldCreationTrait; use Drupal\Tests\TestFileCreationTrait; +use PHPUnit\Framework\Attributes\Group; /** * Tests maintenance message during an AJAX call. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxMaintenanceModeTest extends WebDriverTestBase { use FieldUiTestTrait; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php index 03bd477123c1..284a3e35fa9b 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php @@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\Component\Utility\UrlHelper; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests AJAX responses. - * - * @group Ajax */ +#[Group('Ajax')] class AjaxTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php index cc81d142d453..877f255a6591 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Performs tests on AJAX framework commands. - * - * @group Ajax */ +#[Group('Ajax')] class CommandsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php index be434633e5ef..beab4c6a242c 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php @@ -7,14 +7,13 @@ namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\ajax_test\Controller\AjaxTestController; use Drupal\Core\Ajax\OpenModalDialogWithUrl; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; // cspell:ignore testdialog - /** * Performs tests on opening and manipulating dialogs via AJAX commands. - * - * @group Ajax */ +#[Group('Ajax')] class DialogTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php index 60fd1f40b95f..5040430abae4 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ElementValidationTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Various tests of AJAX behavior. - * - * @group Ajax */ +#[Group('Ajax')] class ElementValidationTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php index 7a9b91c52b90..2a27a09a987a 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FocusFirstCommandTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests setting focus via AJAX command. - * - * @group Ajax */ +#[Group('Ajax')] class FocusFirstCommandTest extends WebDriverTestBase { /** * {@inheritdoc} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php index 2ee6b8ddb2ce..68a9683a4906 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/FormValuesTest.php @@ -5,12 +5,13 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; /** * Tests that form values are properly delivered to AJAX callbacks. - * - * @group Ajax */ +#[Group('Ajax')] class FormValuesTest extends WebDriverTestBase { /** @@ -33,9 +34,8 @@ class FormValuesTest extends WebDriverTestBase { /** * Submits forms with select and checkbox elements via Ajax. - * - * @dataProvider formModeProvider */ + #[DataProvider('formModeProvider')] public function testSimpleAjaxFormValue($form_mode): void { $this->drupalGet('ajax_forms_test_get_form'); diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php index 69b49916163e..f18bf953dd82 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MessageCommandTest.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\ExpectationFailedException; /** * Tests adding messages via AJAX command. - * - * @group Ajax */ +#[Group('Ajax')] class MessageCommandTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php index 6a0026976a90..40ed7ac76704 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/MultiFormTest.php @@ -8,12 +8,12 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests AJAX-enabled forms when multiple instances of the form are on a page. - * - * @group Ajax */ +#[Group('Ajax')] class MultiFormTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php index 6b2a17d4c6eb..92905a0c08ba 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php @@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Ajax; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\hold_test\HoldTestHelper; +use PHPUnit\Framework\Attributes\Group; /** * Tests the throbber. - * - * @group Ajax */ +#[Group('Ajax')] class ThrobberTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php b/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php index 0a61c5f03cbe..adade6bc4208 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/AjaxWaitTest.php @@ -4,12 +4,14 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; + /** * Tests that unnecessary or untracked XHRs will cause a test failure. - * - * @group javascript - * @group legacy */ +#[Group('javascript')] +#[IgnoreDeprecations] class AjaxWaitTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php b/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php index 2f11f7525eed..74c88ac48eed 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/BrowserWithJavascriptTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Attributes\Group; /** * Tests if we can execute JavaScript in the browser. - * - * @group javascript */ +#[Group('javascript')] class BrowserWithJavascriptTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php index 5b77de188aa2..95847a5d2290 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Components/ComponentRenderTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Components; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the correct rendering of components. - * - * @group sdc */ +#[Group('sdc')] class ComponentRenderTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php index f16b300441de..3684ec3c4976 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/CsrfTokenRaceTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Core; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Test race condition for CSRF tokens for simultaneous requests. - * - * @group Session */ +#[Group('Session')] class CsrfTokenRaceTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php index 53b2c5dcb7a5..6e615f4b4f91 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffTest.php @@ -10,12 +10,12 @@ use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the 'timestamp' formatter when is used with time difference setting. - * - * @group Field */ +#[Group('Field')] class TimestampFormatterWithTimeDiffTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php index 0b5176182390..f35b5c72171b 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/TimestampFormatterWithTimeDiffViewsTest.php @@ -7,12 +7,12 @@ namespace Drupal\FunctionalJavascriptTests\Core\Field; use Drupal\entity_test\Entity\EntityTest; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\views\Tests\ViewTestData; +use PHPUnit\Framework\Attributes\Group; /** * Tests the timestamp formatter used with time difference setting in views. - * - * @group Field */ +#[Group('Field')] class TimestampFormatterWithTimeDiffViewsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php index 7c8af15a2e31..f538690daf99 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Core\Form; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests for form grouping elements. - * - * @group form */ +#[Group('form')] class FormGroupingElementsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php index 189af6e49e61..ac41f01653fe 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php @@ -6,6 +6,7 @@ namespace Drupal\FunctionalJavascriptTests\Core\Form; use Drupal\filter\Entity\FilterFormat; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the state of elements based on another elements. @@ -14,9 +15,8 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase; * module under 'system' (core/modules/system/tests/module/form_test). * * @see Drupal\form_test\Form\JavascriptStatesForm - * - * @group javascript */ +#[Group('javascript')] class JavascriptStatesTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php index ce4cf583cd8c..4acec8e11102 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php @@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Core; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\js_message_test\Controller\JSMessageTestController; +use PHPUnit\Framework\Attributes\Group; /** * Tests core/drupal.message library. - * - * @group Javascript */ +#[Group('Javascript')] class JsMessageTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php index 3df154e90500..b990be12549c 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Core; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests for the machine name field. - * - * @group field */ +#[Group('field')] class MachineNameTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php index 8adf7a5685a5..94387c04633e 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Session/SessionTest.php @@ -6,12 +6,12 @@ namespace Drupal\FunctionalJavascriptTests\Core\Session; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\menu_link_content\Entity\MenuLinkContent; +use PHPUnit\Framework\Attributes\Group; /** * Tests that sessions don't expire. - * - * @group session */ +#[Group('session')] class SessionTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php index abc6fd4c5ca4..2085b5518419 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogDeprecationsTest.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Dialog; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\IgnoreDeprecations; /** * Tests jQuery events deprecations. - * - * @group dialog */ +#[Group('dialog')] class DialogDeprecationsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php index ed73ac1ff706..abda27ed3b70 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Dialog/DialogPositionTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Dialog; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests the JavaScript functionality of the dialog position. - * - * @group dialog */ +#[Group('dialog')] class DialogPositionTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php b/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php index 971af632a993..18fe44d8c4c3 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php @@ -4,18 +4,18 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\EntityReference; -use Drupal\FunctionalJavascriptTests\WebDriverTestBase; -use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\entity_test\Entity\EntityTest; +use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait; use Drupal\Tests\node\Traits\ContentTypeCreationTrait; use Drupal\Tests\node\Traits\NodeCreationTrait; +use PHPUnit\Framework\Attributes\Group; /** * Tests the output of entity reference autocomplete widgets. - * - * @group entity_reference */ +#[Group('entity_reference')] class EntityReferenceAutocompleteWidgetTest extends WebDriverTestBase { use ContentTypeCreationTrait; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php index e45026d8960a..cb2fce7a36c1 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php @@ -4,12 +4,14 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; + /** * Tests Javascript deprecation notices. - * - * @group javascript - * @group legacy */ +#[Group('javascript')] +#[IgnoreDeprecations] class JavascriptDeprecationTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php index a310d4c9e6e4..c67e27551c07 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsSuppressionTest.php @@ -4,11 +4,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; +use PHPUnit\Framework\Attributes\Group; + /** * Tests that Drupal.throwError can be suppressed to allow a test to pass. - * - * @group javascript */ +#[Group('javascript')] class JavascriptErrorsSuppressionTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php index 3fb355523406..5f96e95dc2bb 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptErrorsTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Attributes\Group; /** * Tests that Drupal.throwError will cause a test failure. - * - * @group javascript */ +#[Group('javascript')] class JavascriptErrorsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php index 31ec8c91675b..8054a7395997 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptGetDrupalSettingsTest.php @@ -4,11 +4,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests; +use PHPUnit\Framework\Attributes\Group; + /** * Tests Drupal settings retrieval in WebDriverTestBase tests. - * - * @group javascript */ +#[Group('javascript')] class JavascriptGetDrupalSettingsTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php b/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php index ad9ba4f89d8e..19cc4bf3a401 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/MachineName/MachineNameTransliterationTest.php @@ -5,15 +5,15 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\MachineName; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; - use Drupal\language\Entity\ConfigurableLanguage; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; /** * Tests the machine name transliteration functionality. - * - * @group javascript - * @group #slow */ +#[Group('javascript')] +#[Group('#slow')] class MachineNameTransliterationTest extends WebDriverTestBase { /** @@ -46,9 +46,8 @@ class MachineNameTransliterationTest extends WebDriverTestBase { /** * Test for machine name transliteration functionality. - * - * @dataProvider machineNameInputOutput */ + #[DataProvider('machineNameInputOutput')] public function testMachineNameTransliterations($langcode, $input, $output): void { $page = $this->getSession()->getPage(); if ($langcode !== 'en') { diff --git a/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php b/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php index d624e03c00d9..60f7b44fe0f4 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php @@ -13,7 +13,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * * @ingroup testing */ -class PerformanceTestBase extends WebDriverTestBase { +abstract class PerformanceTestBase extends WebDriverTestBase { use PerformanceTestTrait; /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php b/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php index a9350a1976e6..0a14594369b6 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php @@ -7,12 +7,12 @@ namespace Drupal\FunctionalJavascriptTests\TableDrag; use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\ExpectationException; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests draggable table. - * - * @group javascript */ +#[Group('javascript')] class TableDragTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php index 1ab9d65b3be7..e598f5cc04a4 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/DrupalSelenium2DriverTest.php @@ -6,16 +6,18 @@ namespace Drupal\FunctionalJavascriptTests\Tests; use Behat\Mink\Driver\Selenium2Driver; use Drupal\entity_test\Entity\EntityTest; +use Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\Tests\file\Functional\FileFieldCreationTrait; use Drupal\Tests\TestFileCreationTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; /** * Tests the DrupalSelenium2Driver methods. - * - * @coversDefaultClass \Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver - * @group javascript */ +#[CoversClass(DrupalSelenium2Driver::class)] +#[Group('javascript')] class DrupalSelenium2DriverTest extends WebDriverTestBase { use TestFileCreationTrait; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php index 866f04e3a72a..a8018c7b013a 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSInteractionTest.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Tests; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; use WebDriver\Exception; /** * Tests fault tolerant interactions. - * - * @group javascript */ +#[Group('javascript')] class JSInteractionTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php index f200ed28ac98..6388bd0844e1 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php @@ -8,12 +8,12 @@ use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\ElementHtmlException; use Drupal\Component\Utility\Timer; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests for the JSWebAssert class. - * - * @group javascript */ +#[Group('javascript')] class JSWebAssertTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php index dbc7874c735f..62ae39c4db49 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php @@ -5,14 +5,14 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\Tests\block\FunctionalJavascript\BlockFilterTest; +use PHPUnit\Framework\Attributes\Group; /** * Runs BlockFilterTest in Claro. * - * @group block - * * @see \Drupal\Tests\block\FunctionalJavascript\BlockFilterTest. */ +#[Group('block')] class ClaroBlockFilterTest extends BlockFilterTest { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php index 85476468d815..c8e018ebfbe5 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php @@ -6,14 +6,14 @@ namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\entity_test\EntityTestHelper; use Drupal\Tests\field_ui\FunctionalJavascript\EntityDisplayTest; +use PHPUnit\Framework\Attributes\Group; /** * Runs EntityDisplayTest in Claro. * - * @group claro - * * @see \Drupal\Tests\field_ui\FunctionalJavascript\EntityDisplayTest. */ +#[Group('claro')] class ClaroEntityDisplayTest extends EntityDisplayTest { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php index 32f6256396b3..930c7f963a7b 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php @@ -5,14 +5,14 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\Tests\menu_ui\FunctionalJavascript\MenuUiJavascriptTest; +use PHPUnit\Framework\Attributes\Group; /** * Runs MenuUiJavascriptTest in Claro. * - * @group claro - * * @see \Drupal\Tests\menu_ui\FunctionalJavascript\MenuUiJavascriptTest; */ +#[Group('claro')] class ClaroMenuUiJavascriptTest extends MenuUiJavascriptTest { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php index c78ff99c80a3..7f110c1a4282 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroModalDisplayTest.php @@ -9,12 +9,12 @@ use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\media_library\FunctionalJavascript\MediaLibraryTestBase; use Drupal\Tests\TestFileCreationTrait; +use PHPUnit\Framework\Attributes\Group; /** * Tests that buttons in modals are not in their button pane. - * - * @group claro */ +#[Group('claro')] class ClaroModalDisplayTest extends MediaLibraryTestBase { use TestFileCreationTrait; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php index af01db8d2a8f..c090f658dc56 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroPasswordConfirmWidgetTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\Tests\user\FunctionalJavascript\PasswordConfirmWidgetTest; +use PHPUnit\Framework\Attributes\Group; /** * Tests the password confirm widget with Claro theme. - * - * @group claro */ +#[Group('claro')] class ClaroPasswordConfirmWidgetTest extends PasswordConfirmWidgetTest { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php index 9938ad22d095..7dda74a5348e 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroTableDragTest.php @@ -5,14 +5,14 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\FunctionalJavascriptTests\TableDrag\TableDragTest; +use PHPUnit\Framework\Attributes\Group; /** * Tests draggable tables with Claro theme. * - * @group claro - * * @see \Drupal\FunctionalJavascriptTests\TableDrag\TableDragTest */ +#[Group('claro')] class ClaroTableDragTest extends TableDragTest { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php index e752b79a65af..3f6c21ad8755 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsBulkOperationsTest.php @@ -7,12 +7,12 @@ namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\Tests\node\Traits\ContentTypeCreationTrait; use Drupal\Tests\node\Traits\NodeCreationTrait; +use PHPUnit\Framework\Attributes\Group; /** * Tests Claro's Views Bulk Operations form. - * - * @group claro */ +#[Group('claro')] class ClaroViewsBulkOperationsTest extends WebDriverTestBase { use ContentTypeCreationTrait; use NodeCreationTrait; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php index 28b3c46a9996..67474ad08a60 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroViewsUiTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Runs tests on Views UI using Claro. - * - * @group claro */ +#[Group('claro')] class ClaroViewsUiTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php index ee1261a354b0..24d3ca85af26 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroAvoidStorageUsingTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use PHPUnit\Framework\Attributes\Group; /** * Tests usage of localStorage. - * - * @group olivero */ +#[Group('olivero')] final class OliveroAvoidStorageUsingTest extends WebDriverTestBase { /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php index 6b0472702c0c..74278efdf4bc 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/OliveroMessagesTest.php @@ -6,14 +6,14 @@ namespace Drupal\FunctionalJavascriptTests\Theme; use Drupal\FunctionalJavascriptTests\Core\JsMessageTest; use Drupal\js_message_test\Controller\JSMessageTestController; +use PHPUnit\Framework\Attributes\Group; /** * Runs OliveroMessagesTest in Olivero. * - * @group olivero - * * @see \Drupal\FunctionalJavascriptTests\Core\JsMessageTest. */ +#[Group('olivero')] class OliveroMessagesTest extends JsMessageTest { /** diff --git a/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php b/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php index f7509fd72775..f280ad493a57 100644 --- a/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php +++ b/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php @@ -35,6 +35,7 @@ use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait; use Drupal\Tests\media\Traits\MediaTypeCreationTrait; use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait; use Drupal\user\UserInterface; +use Drupal\workspaces\Entity\Workspace; use Psr\Log\LogLevel; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -72,6 +73,7 @@ class ContentImportTest extends BrowserTestBase { 'system', 'taxonomy', 'user', + 'workspaces', ]; /** @@ -180,6 +182,22 @@ class ContentImportTest extends BrowserTestBase { ); }; $this->assertTrue($logger->hasRecordThatPasses($predicate, LogLevel::WARNING)); + + // Visit a page that is published in a non-live workspace; we should not be + // able to see it, because we don't have permission. + $node_in_workspace = $this->container->get(EntityRepositoryInterface::class) + ->loadEntityByUuid('node', '48475954-e878-439c-9d3d-226724a44269'); + $this->assertInstanceOf(NodeInterface::class, $node_in_workspace); + $node_url = $node_in_workspace->toUrl(); + $this->drupalGet($node_url); + $assert_session = $this->assertSession(); + $assert_session->statusCodeEquals(403); + // If we log in with administrative privileges (i.e., we can look at any + // workspace), we should be able to see it. + $this->drupalLogin($this->adminAccount); + $this->drupalGet($node_url); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains($node_in_workspace->label()); } /** @@ -303,6 +321,11 @@ class ContentImportTest extends BrowserTestBase { $this->assertInstanceOf(Section::class, $section); $this->assertCount(2, $section->getComponents()); $this->assertSame('system_powered_by_block', $section->getComponent('03b45f14-cf74-469a-8398-edf3383ce7fa')->getPluginId()); + + // Workspaces should have been imported with their parent references intact. + $workspaces = Workspace::loadMultiple(); + $this->assertArrayHasKey('test_workspace', $workspaces); + $this->assertSame('test_workspace', $workspaces['inner_test']?->parent->entity->id()); } /** diff --git a/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php b/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php index d33d7c4942ab..994ac39d2707 100644 --- a/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php +++ b/core/tests/Drupal/FunctionalTests/Installer/InstallerNonDefaultDatabaseDriverTest.php @@ -90,8 +90,8 @@ class InstallerNonDefaultDatabaseDriverTest extends InstallerTestBase { // uninstalled being dependencies of the "driver_test" module. $this->drupalGet('admin/modules/uninstall'); $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-driver-test"]', "The following reason prevents Contrib database driver test from being uninstalled: The module 'Contrib database driver test' is providing the database driver '{$this->testDriverName}'."); - $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-mysql"]', "The following reason prevents MySQL from being uninstalled: Required by: driver_test"); - $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-pgsql"]', "The following reason prevents PostgreSQL from being uninstalled: Required by: driver_test"); + $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-mysql"]', "The following reason prevents MySQL from being uninstalled: Required by: Contrib database driver test (driver_test)"); + $this->assertSession()->elementTextContains('xpath', '//tr[@data-drupal-selector="edit-pgsql"]', "The following reason prevents PostgreSQL from being uninstalled: Required by: Contrib database driver test (driver_test)"); } /** diff --git a/core/tests/Drupal/FunctionalTests/Installer/InstallerTestBase.php b/core/tests/Drupal/FunctionalTests/Installer/InstallerTestBase.php index c1694932a0f8..f1d2d507eeed 100644 --- a/core/tests/Drupal/FunctionalTests/Installer/InstallerTestBase.php +++ b/core/tests/Drupal/FunctionalTests/Installer/InstallerTestBase.php @@ -271,7 +271,7 @@ abstract class InstallerTestBase extends BrowserTestBase { * Override this method to test specific requirements warnings or errors * during the installer. * - * @see system_requirements() + * @see \Drupal\system\Install\SystemRequirements */ protected function setUpRequirementsProblem() { if (version_compare(phpversion(), PhpRequirements::getMinimumSupportedPhp()) < 0) { diff --git a/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php b/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php index 91cc24234e1a..9882d3d9ef0f 100644 --- a/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php +++ b/core/tests/Drupal/KernelTests/Core/Controller/ControllerBaseTest.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace Drupal\KernelTests\Core\Controller; +use Drupal\dblog\Logger\DbLog; use Drupal\KernelTests\KernelTestBase; use Drupal\system_test\Controller\BrokenSystemTestController; +use Drupal\system_test\Controller\OptionalServiceSystemTestController; use Drupal\system_test\Controller\SystemTestController; use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException; @@ -52,4 +54,17 @@ class ControllerBaseTest extends KernelTestBase { $this->container->get('class_resolver')->getInstanceFromDefinition(BrokenSystemTestController::class); } + /** + * @covers ::create + */ + public function testCreateOptional(): void { + $service = $this->container->get('class_resolver')->getInstanceFromDefinition(OptionalServiceSystemTestController::class); + $this->assertInstanceOf(OptionalServiceSystemTestController::class, $service); + $this->assertNull($service->dbLog); + $this->container->get('module_installer')->install(['dblog']); + $service = $this->container->get('class_resolver')->getInstanceFromDefinition(OptionalServiceSystemTestController::class); + $this->assertInstanceOf(OptionalServiceSystemTestController::class, $service); + $this->assertInstanceOf(DbLog::class, $service->dbLog); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php b/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php index cbda6e3d7f72..b7e31e06a59a 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php +++ b/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php @@ -38,7 +38,7 @@ use Drupal\Core\Database\TransactionOutOfOrderException; * is active, and mysqli does not fail when rolling back and no transaction * active. */ -class DriverSpecificTransactionTestBase extends DriverSpecificDatabaseTestBase { +abstract class DriverSpecificTransactionTestBase extends DriverSpecificDatabaseTestBase { /** * Keeps track of the post-transaction callback action executed. diff --git a/core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php b/core/tests/Drupal/KernelTests/Core/Datetime/DrupalDateTimeTest.php index cbef10d726ec..e1542a30c381 100644 --- a/core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php +++ b/core/tests/Drupal/KernelTests/Core/Datetime/DrupalDateTimeTest.php @@ -2,30 +2,39 @@ declare(strict_types=1); -namespace Drupal\Tests\system\Functional\Datetime; +namespace Drupal\KernelTests\Core\Datetime; use Drupal\Core\Datetime\DrupalDateTime; -use Drupal\Tests\BrowserTestBase; -use Drupal\user\Entity\User; +use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\user\Traits\UserCreationTrait; /** * Tests DrupalDateTime functionality. * * @group Datetime */ -class DrupalDateTimeTest extends BrowserTestBase { +class DrupalDateTimeTest extends KernelTestBase { + + use UserCreationTrait; /** * Set up required modules. * * @var string[] */ - protected static $modules = []; + protected static $modules = [ + 'system', + 'user', + ]; /** * {@inheritdoc} */ - protected $defaultTheme = 'stark'; + protected function setUp(): void { + parent::setUp(); + $this->installConfig(['system']); + $this->installEntitySchema('user'); + } /** * Tests that DrupalDateTime can detect the right timezone to use. @@ -68,17 +77,9 @@ class DrupalDateTimeTest extends BrowserTestBase { // Create user. $this->config('system.date')->set('timezone.user.configurable', 1)->save(); - $test_user = $this->drupalCreateUser([]); - $this->drupalLogin($test_user); - - // Set up the user with a different timezone than the site. - $edit = ['mail' => $test_user->getEmail(), 'timezone' => 'Asia/Manila']; - $this->drupalGet('user/' . $test_user->id() . '/edit'); - $this->submitForm($edit, 'Save'); - - // Reload the user and reset the timezone in AccountProxy::setAccount(). - \Drupal::entityTypeManager()->getStorage('user')->resetCache(); - $this->container->get('current_user')->setAccount(User::load($test_user->id())); + $this->setUpCurrentUser([ + 'timezone' => 'Asia/Manila', + ]); // Create a date object with an unspecified timezone, which should // end up using the user timezone. diff --git a/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php b/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php index 3be9b023b2f7..54d72e9fbff5 100644 --- a/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php +++ b/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php @@ -7,7 +7,6 @@ namespace Drupal\KernelTests\Core\DrupalKernel; use Composer\Autoload\ClassLoader; use Drupal\Core\DrupalKernel; use Drupal\Core\DrupalKernelInterface; -use Drupal\Core\Utility\Error; use Drupal\KernelTests\KernelTestBase; use org\bovigo\vfs\vfsStream; use Prophecy\Argument; @@ -27,8 +26,7 @@ class DrupalKernelTest extends KernelTestBase { * {@inheritdoc} */ protected function tearDown(): void { - $currentErrorHandler = Error::currentErrorHandler(); - if (is_string($currentErrorHandler) && $currentErrorHandler === '_drupal_error_handler') { + if (get_error_handler() === '_drupal_error_handler') { restore_error_handler(); } parent::tearDown(); diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityBundleEntityTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityBundleEntityTest.php new file mode 100644 index 000000000000..419d8bff4d43 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityBundleEntityTest.php @@ -0,0 +1,85 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\KernelTests\Core\Entity; + +use Drupal\entity_test\Entity\EntityTest; +use Drupal\entity_test\Entity\EntityTestBundle; +use Drupal\entity_test\Entity\EntityTestNoBundleWithLabel; +use Drupal\entity_test\Entity\EntityTestWithBundle; + +/** + * Tests the getBundleEntity() method. + * + * @coversDefaultClass \Drupal\Core\Entity\ContentEntityBase + * + * @group Entity + */ +class EntityBundleEntityTest extends EntityKernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['entity_test']; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->installEntitySchema('entity_test'); + $this->installEntitySchema('entity_test_with_bundle'); + $this->installEntitySchema('entity_test_no_bundle_with_label'); + } + + /** + * Tests an entity type with config entities for bundles. + * + * @covers ::getBundleEntity + */ + public function testWithConfigBundleEntity(): void { + $bundleEntity = EntityTestBundle::create([ + 'id' => 'bundle_alpha', + 'label' => 'Alpha', + ]); + $bundleEntity->save(); + + $entity = EntityTestWithBundle::create([ + 'type' => 'bundle_alpha', + 'name' => 'foo', + ]); + $entity->save(); + $this->assertEquals($bundleEntity->id(), $entity->getBundleEntity()->id()); + } + + /** + * Tests an entity type without config entities for bundles. + * + * EntityTest doesn't have bundles, but does have the bundle entity key. + * + * @covers ::getBundleEntity + */ + public function testWithoutBundleEntity(): void { + $entity = EntityTest::create([ + 'name' => 'foo', + ]); + $entity->save(); + $this->assertNull($entity->getBundleEntity()); + } + + /** + * Tests an entity type without the bundle entity key. + * + * @covers ::getBundleEntity + */ + public function testWithBundleKeyEntity(): void { + $entity = EntityTestNoBundleWithLabel::create([ + 'name' => 'foo', + ]); + $entity->save(); + $this->assertNull($entity->getBundleEntity()); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/File/FileSystemRequirementsTest.php b/core/tests/Drupal/KernelTests/Core/File/FileSystemRequirementsTest.php index 0cb098bfccab..ca1fab258f3c 100644 --- a/core/tests/Drupal/KernelTests/Core/File/FileSystemRequirementsTest.php +++ b/core/tests/Drupal/KernelTests/Core/File/FileSystemRequirementsTest.php @@ -45,8 +45,14 @@ class FileSystemRequirementsTest extends KernelTestBase { * An array of system requirements. */ protected function checkSystemRequirements() { + // This loadInclude() is to ensure that the install API is available. + // Since we're loading an include of type 'install', this will also + // include core/includes/install.inc for us, which is where + // drupal_verify_install_file() is currently defined. + // @todo Remove this once the function lives in a better place. + // @see https://www.drupal.org/project/drupal/issues/3526388 $this->container->get('module_handler')->loadInclude('system', 'install'); - return system_requirements('runtime'); + return \Drupal::moduleHandler()->invoke('system', 'runtime_requirements'); } } diff --git a/core/tests/Drupal/KernelTests/Core/File/HtaccessTest.php b/core/tests/Drupal/KernelTests/Core/File/HtaccessTest.php index dfe537e4d1fa..7d1e641f485a 100644 --- a/core/tests/Drupal/KernelTests/Core/File/HtaccessTest.php +++ b/core/tests/Drupal/KernelTests/Core/File/HtaccessTest.php @@ -92,6 +92,15 @@ class HtaccessTest extends KernelTestBase { } /** + * @covers ::write + */ + public function testHtaccessSaveDisabled(): void { + $this->setSetting('auto_create_htaccess', FALSE); + $this->assertTrue($this->htaccessWriter->write($this->public, FALSE)); + $this->assertFileDoesNotExist($this->public . '/.htaccess'); + } + + /** * Asserts expected file permissions for a given file. * * @param string $uri diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php index fdfa189b880b..8fc0c2013d29 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php @@ -319,4 +319,54 @@ YAML $recipe->input->collectAll($collector); } + /** + * Tests getting default input values from environment variables. + */ + public function testDefaultInputFromEnvironmentVariables(): void { + $this->config('system.site') + ->set('name', 'Hello Thar') + ->set('slogan', 'Very important') + ->save(); + + $recipe = $this->createRecipe(<<<YAML +name: 'Input from environment variables' +input: + name: + data_type: string + description: The name of the site. + default: + source: env + env: SITE_NAME + slogan: + data_type: string + description: The site slogan. + default: + source: env + env: SITE_SLOGAN +config: + actions: + system.site: + simpleConfigUpdate: + name: \${name} + slogan: \${slogan} +YAML + ); + putenv('SITE_NAME=Input Test'); + + // Mock a collector that only returns the default value. + $collector = $this->createMock(InputCollectorInterface::class); + $collector->expects($this->any()) + ->method('collectValue') + ->withAnyParameters() + ->willReturnArgument(2); + $recipe->input->collectAll($collector); + + RecipeRunner::processRecipe($recipe); + $config = $this->config('system.site'); + $this->assertSame('Input Test', $config->get('name')); + // There was no SITE_SLOGAN environment variable, so it should have been + // set to an empty string. + $this->assertSame('', $config->get('slogan')); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php index 62f14df4d202..64b4c17869f5 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php @@ -761,6 +761,36 @@ extra: YAML, NULL, ]; + yield 'input env variable name is not a string' => [ + <<<YAML +name: Bad input +input: + bad_news: + data_type: string + description: 'Bad default definition' + default: + source: env + env: -40 +YAML, + [ + '[input][bad_news][default][env]' => ['This value should be of type string.'], + ], + ]; + yield 'input env variable name is empty' => [ + <<<YAML +name: Bad input +input: + bad_news: + data_type: string + description: 'Bad default definition' + default: + source: env + env: '' +YAML, + [ + '[input][bad_news][default][env]' => ['This value should not be blank.'], + ], + ]; } /** diff --git a/core/tests/Drupal/KernelTests/Core/Render/Element/DeprecatedElementTest.php b/core/tests/Drupal/KernelTests/Core/Render/Element/DeprecatedElementTest.php index 09931b10f3a3..16c0a7fe12bf 100644 --- a/core/tests/Drupal/KernelTests/Core/Render/Element/DeprecatedElementTest.php +++ b/core/tests/Drupal/KernelTests/Core/Render/Element/DeprecatedElementTest.php @@ -43,7 +43,8 @@ class DeprecatedElementTest extends KernelTestBase { ], $info_manager->getInfo('deprecated_extends_form')); // Ensure the constructor is triggering a deprecation error. - $previous_error_handler = set_error_handler(function ($severity, $message, $file, $line) use (&$previous_error_handler) { + $previous_error_handler = get_error_handler(); + set_error_handler(function ($severity, $message, $file, $line) use (&$previous_error_handler) { // Convert deprecation error into a catchable exception. if ($severity === E_USER_DEPRECATED) { throw new \ErrorException($message, 0, $severity, $file, $line); @@ -84,7 +85,8 @@ class DeprecatedElementTest extends KernelTestBase { * Test use of static methods trigger deprecations. */ public function testDeprecatedStaticMethods(): void { - $previous_error_handler = set_error_handler(function ($severity, $message, $file, $line) use (&$previous_error_handler) { + $previous_error_handler = get_error_handler(); + set_error_handler(function ($severity, $message, $file, $line) use (&$previous_error_handler) { // Convert deprecation error into a catchable exception. if ($severity === E_USER_DEPRECATED) { throw new \ErrorException($message, 0, $severity, $file, $line); diff --git a/core/tests/Drupal/KernelTests/Core/Render/Element/PluginAlterTest.php b/core/tests/Drupal/KernelTests/Core/Render/Element/PluginAlterTest.php index 7ad8afa75be6..c300cd4c019f 100644 --- a/core/tests/Drupal/KernelTests/Core/Render/Element/PluginAlterTest.php +++ b/core/tests/Drupal/KernelTests/Core/Render/Element/PluginAlterTest.php @@ -23,7 +23,7 @@ class PluginAlterTest extends KernelTestBase { $info_manager = $this->container->get('plugin.manager.element_info'); $this->assertArrayHasKey('weight', $info_manager->getDefinitions()); - // @see element_info_test_element_plugin_alter() + // @see ElementInfoTestHooks::elementPluginAlter(). $this->container->get('state')->set('hook_element_plugin_alter:remove_weight', TRUE); // The definition will be cached. $this->assertArrayHasKey('weight', $info_manager->getDefinitions()); @@ -33,4 +33,27 @@ class PluginAlterTest extends KernelTestBase { $this->assertArrayNotHasKey('weight', $info_manager->getDefinitions()); } + /** + * Tests hook_element_plugin_alter(). + */ + public function testPluginClassSwap(): void { + $info_manager = $this->container->get('plugin.manager.element_info'); + $test_details = [ + '#type' => 'details', + '#title' => 'Title', + '#description' => 'Description', + '#open' => TRUE, + ]; + + // @see ElementInfoTestHooks::elementPluginAlter(). + $expected = [ + 'class' => 'Drupal\element_info_test\Render\Element\Details', + 'provider' => 'element_info_test', + 'id' => 'details', + ]; + $this->assertEquals($expected, $info_manager->getDefinitions()['details']); + \Drupal::service('renderer')->renderRoot($test_details); + $this->assertArrayHasKey('#custom', $test_details); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php b/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php index 00b7948f2a81..e36da16d9028 100644 --- a/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php +++ b/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php @@ -8,6 +8,7 @@ use Drupal\Core\Form\FormState; use Drupal\Core\Render\Element\Number; use Drupal\Core\Render\Element\Select; use Drupal\Core\Render\Element\Weight; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\element_info_test\ElementInfoTestNumberBuilder; use Drupal\KernelTests\KernelTestBase; @@ -40,7 +41,7 @@ class WeightTest extends KernelTestBase { $form_state = new FormState(); $complete_form = []; - $element_object = new Weight([], 'weight', []); + $element_object = new Weight([], 'weight', [], elementInfoManager: $this->createStub(ElementInfoManagerInterface::class)); $info = $element_object->getInfo(); $element += $info; diff --git a/core/tests/Drupal/TestTools/Extension/DeprecationBridge/ExpectDeprecationTrait.php b/core/tests/Drupal/TestTools/Extension/DeprecationBridge/ExpectDeprecationTrait.php index d60a4f3062c1..ed73ca8fd933 100644 --- a/core/tests/Drupal/TestTools/Extension/DeprecationBridge/ExpectDeprecationTrait.php +++ b/core/tests/Drupal/TestTools/Extension/DeprecationBridge/ExpectDeprecationTrait.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Drupal\TestTools\Extension\DeprecationBridge; -use Drupal\Core\Utility\Error; use Drupal\TestTools\ErrorHandler\TestErrorHandler; use PHPUnit\Framework\Attributes\After; use PHPUnit\Framework\Attributes\Before; @@ -41,7 +40,7 @@ trait ExpectDeprecationTrait { } DeprecationHandler::reset(); - set_error_handler(new TestErrorHandler(Error::currentErrorHandler(), $this)); + set_error_handler(new TestErrorHandler(get_error_handler(), $this)); } /** @@ -61,8 +60,7 @@ trait ExpectDeprecationTrait { // ::setUpErrorHandler() prior to the start of the test execution. If not, // the error handler was changed during the test execution but not properly // restored during ::tearDown(). - $handler = Error::currentErrorHandler(); - if (!$handler instanceof TestErrorHandler) { + if (!get_error_handler() instanceof TestErrorHandler) { throw new \RuntimeException(sprintf('%s registered its own error handler without restoring the previous one before or during tear down. This can cause unpredictable test results. Ensure the test cleans up after itself.', $this->name())); } restore_error_handler(); diff --git a/core/tests/Drupal/Tests/Component/Annotation/AnnotatedClassDiscoveryCachedTest.php b/core/tests/Drupal/Tests/Component/Annotation/AnnotatedClassDiscoveryCachedTest.php index 07a9fab674fb..9a2b78ebc775 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/AnnotatedClassDiscoveryCachedTest.php +++ b/core/tests/Drupal/Tests/Component/Annotation/AnnotatedClassDiscoveryCachedTest.php @@ -6,13 +6,17 @@ namespace Drupal\Tests\Component\Annotation; use Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery; use Drupal\Component\FileCache\FileCacheFactory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery - * @group Annotation - * @runTestsInSeparateProcesses + * Tests Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery. */ +#[CoversClass(AnnotatedClassDiscovery::class)] +#[Group('Annotation')] +#[RunTestsInSeparateProcesses] class AnnotatedClassDiscoveryCachedTest extends TestCase { /** @@ -30,7 +34,7 @@ class AnnotatedClassDiscoveryCachedTest extends TestCase { /** * Tests that getDefinitions() retrieves the file cache correctly. * - * @covers ::getDefinitions + * @legacy-covers ::getDefinitions */ public function testGetDefinitions(): void { // Path to the classes which we'll discover and parse annotation. diff --git a/core/tests/Drupal/Tests/Component/Annotation/AnnotatedClassDiscoveryTest.php b/core/tests/Drupal/Tests/Component/Annotation/AnnotatedClassDiscoveryTest.php index a83d030b2aff..96174c148422 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/AnnotatedClassDiscoveryTest.php +++ b/core/tests/Drupal/Tests/Component/Annotation/AnnotatedClassDiscoveryTest.php @@ -7,13 +7,17 @@ namespace Drupal\Tests\Component\Annotation; use Drupal\Component\Annotation\Plugin; use Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery; use Drupal\Component\FileCache\FileCacheFactory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery - * @group Annotation - * @runTestsInSeparateProcesses + * Tests Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery. */ +#[CoversClass(AnnotatedClassDiscovery::class)] +#[Group('Annotation')] +#[RunTestsInSeparateProcesses] class AnnotatedClassDiscoveryTest extends TestCase { /** @@ -28,8 +32,8 @@ class AnnotatedClassDiscoveryTest extends TestCase { } /** - * @covers ::__construct - * @covers ::getPluginNamespaces + * @legacy-covers ::__construct + * @legacy-covers ::getPluginNamespaces */ public function testGetPluginNamespaces(): void { $discovery = new AnnotatedClassDiscovery(['com/example' => [__DIR__]]); @@ -40,9 +44,9 @@ class AnnotatedClassDiscoveryTest extends TestCase { } /** - * @covers ::getDefinitions - * @covers ::prepareAnnotationDefinition - * @covers ::getAnnotationReader + * @legacy-covers ::getDefinitions + * @legacy-covers ::prepareAnnotationDefinition + * @legacy-covers ::getAnnotationReader */ public function testGetDefinitions(): void { $discovery = new AnnotatedClassDiscovery(['com\example' => [__DIR__ . '/Fixtures']]); diff --git a/core/tests/Drupal/Tests/Component/Annotation/AnnotationBaseTest.php b/core/tests/Drupal/Tests/Component/Annotation/AnnotationBaseTest.php index 80f3b31f82be..cb5d4727b4f7 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/AnnotationBaseTest.php +++ b/core/tests/Drupal/Tests/Component/Annotation/AnnotationBaseTest.php @@ -5,17 +5,20 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Annotation; use Drupal\Component\Annotation\AnnotationBase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Annotation\AnnotationBase - * @group Annotation + * Tests Drupal\Component\Annotation\AnnotationBase. */ +#[CoversClass(AnnotationBase::class)] +#[Group('Annotation')] class AnnotationBaseTest extends TestCase { /** - * @covers ::getProvider - * @covers ::setProvider + * @legacy-covers ::getProvider + * @legacy-covers ::setProvider */ public function testSetProvider(): void { $plugin = new AnnotationBaseStub(); @@ -24,7 +27,7 @@ class AnnotationBaseTest extends TestCase { } /** - * @covers ::getId + * @legacy-covers ::getId */ public function testGetId(): void { $plugin = new AnnotationBaseStub(); @@ -34,8 +37,8 @@ class AnnotationBaseTest extends TestCase { } /** - * @covers ::getClass - * @covers ::setClass + * @legacy-covers ::getClass + * @legacy-covers ::setClass */ public function testSetClass(): void { $plugin = new AnnotationBaseStub(); diff --git a/core/tests/Drupal/Tests/Component/Annotation/DocParserIgnoredClassesTest.php b/core/tests/Drupal/Tests/Component/Annotation/DocParserIgnoredClassesTest.php index c211193d1fd5..e4cc1f2b9218 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/DocParserIgnoredClassesTest.php +++ b/core/tests/Drupal/Tests/Component/Annotation/DocParserIgnoredClassesTest.php @@ -5,13 +5,15 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Annotation; use Drupal\Component\Annotation\Doctrine\DocParser; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Annotation\Doctrine\DocParser - * - * @group Annotation + * Tests Drupal\Component\Annotation\Doctrine\DocParser. */ +#[CoversClass(DocParser::class)] +#[Group('Annotation')] class DocParserIgnoredClassesTest extends TestCase { /** diff --git a/core/tests/Drupal/Tests/Component/Annotation/Doctrine/StaticReflectionParserTest.php b/core/tests/Drupal/Tests/Component/Annotation/Doctrine/StaticReflectionParserTest.php index ab5d242262d9..96b501928a67 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/Doctrine/StaticReflectionParserTest.php +++ b/core/tests/Drupal/Tests/Component/Annotation/Doctrine/StaticReflectionParserTest.php @@ -2,38 +2,39 @@ namespace Drupal\Tests\Component\Annotation\Doctrine; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\TestWith; use Drupal\Component\Annotation\Doctrine\StaticReflectionParser; use Drupal\Component\Annotation\Reflection\MockFileFinder; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Annotation\Doctrine\StaticReflectionParser - * - * @group Annotation + * Tests Drupal\Component\Annotation\Doctrine\StaticReflectionParser. */ +#[CoversClass(StaticReflectionParser::class)] +#[Group('Annotation')] class StaticReflectionParserTest extends TestCase { - /** - * @testWith ["AttributeClass", "\\Attribute", true] - * ["AttributeClass", "attribute", true] - * ["AttributeClass", "Attribute", true] - * ["AttributeClass", "\\DoesNotExist", false] - * ["Nonexistent", "NonexistentAttribute", false] - * ["MultipleAttributes", "Attribute", true] - * ["MultipleAttributes", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\AttributeClass", true] - * ["MultipleAttributes", "DoesNotExist", false] - * ["FullyQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true] - * ["Used", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true] - * ["UsedAs", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true] - * ["UsedAsQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true] - * ["Qualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true] - * ["Relative", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\SubDir\\SubDirAttribute", true] - * ["FullyQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true] - * ["Used", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true] - * ["UsedAs", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true] - * ["UsedAsQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true] - * ["Qualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true] - */ + #[TestWith(["AttributeClass", "\\Attribute", true])] + #[TestWith(["AttributeClass", "attribute", true])] + #[TestWith(["AttributeClass", "Attribute", true])] + #[TestWith(["AttributeClass", "\\DoesNotExist", false])] + #[TestWith(["Nonexistent", "NonexistentAttribute", false])] + #[TestWith(["MultipleAttributes", "Attribute", true])] + #[TestWith(["MultipleAttributes", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\AttributeClass", true])] + #[TestWith(["MultipleAttributes", "DoesNotExist", false])] + #[TestWith(["FullyQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true])] + #[TestWith(["Used", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true])] + #[TestWith(["UsedAs", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true])] + #[TestWith(["UsedAsQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true])] + #[TestWith(["Qualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true])] + #[TestWith(["Relative", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\SubDir\\SubDirAttribute", true])] + #[TestWith(["FullyQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true])] + #[TestWith(["Used", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true])] + #[TestWith(["UsedAs", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true])] + #[TestWith(["UsedAsQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true])] + #[TestWith(["Qualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true])] public function testAttribute(string $class, string $attribute_class, bool $expected): void { $finder = MockFileFinder::create(__DIR__ . '/Fixtures/Attribute/' . $class . '.php'); $parser = new StaticReflectionParser('\\Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\' . $class, $finder); diff --git a/core/tests/Drupal/Tests/Component/Annotation/MockFileFinderTest.php b/core/tests/Drupal/Tests/Component/Annotation/MockFileFinderTest.php index 6511a2c980c2..c227a276a1ec 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/MockFileFinderTest.php +++ b/core/tests/Drupal/Tests/Component/Annotation/MockFileFinderTest.php @@ -5,17 +5,20 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Annotation; use Drupal\Component\Annotation\Reflection\MockFileFinder; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Annotation\Reflection\MockFileFinder - * @group Annotation + * Tests Drupal\Component\Annotation\Reflection\MockFileFinder. */ +#[CoversClass(MockFileFinder::class)] +#[Group('Annotation')] class MockFileFinderTest extends TestCase { /** - * @covers ::create - * @covers ::findFile + * @legacy-covers ::create + * @legacy-covers ::findFile */ public function testFindFile(): void { $tmp = MockFileFinder::create('test_filename.txt'); diff --git a/core/tests/Drupal/Tests/Component/Annotation/Plugin/Discovery/AnnotationBridgeDecoratorTest.php b/core/tests/Drupal/Tests/Component/Annotation/Plugin/Discovery/AnnotationBridgeDecoratorTest.php index ab59964f3396..7852e14c1b1e 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/Plugin/Discovery/AnnotationBridgeDecoratorTest.php +++ b/core/tests/Drupal/Tests/Component/Annotation/Plugin/Discovery/AnnotationBridgeDecoratorTest.php @@ -8,19 +8,22 @@ use Drupal\Component\Annotation\Plugin; use Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator; use Drupal\Component\Plugin\Definition\PluginDefinition; use Drupal\Component\Plugin\Discovery\DiscoveryInterface; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; /** - * @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator - * @group Plugin + * Tests Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator. */ +#[CoversClass(AnnotationBridgeDecorator::class)] +#[Group('Plugin')] class AnnotationBridgeDecoratorTest extends TestCase { use ProphecyTrait; /** - * @covers ::getDefinitions + * @legacy-covers ::getDefinitions */ public function testGetDefinitions(): void { $definitions = []; diff --git a/core/tests/Drupal/Tests/Component/Annotation/PluginIdTest.php b/core/tests/Drupal/Tests/Component/Annotation/PluginIdTest.php index 17fb399a7264..4ff731f364f1 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/PluginIdTest.php +++ b/core/tests/Drupal/Tests/Component/Annotation/PluginIdTest.php @@ -5,16 +5,19 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Annotation; use Drupal\Component\Annotation\PluginID; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Annotation\PluginID - * @group Annotation + * Tests Drupal\Component\Annotation\PluginID. */ +#[CoversClass(PluginID::class)] +#[Group('Annotation')] class PluginIdTest extends TestCase { /** - * @covers ::get + * @legacy-covers ::get */ public function testGet(): void { // Assert plugin starts empty. @@ -37,7 +40,7 @@ class PluginIdTest extends TestCase { } /** - * @covers ::getId + * @legacy-covers ::getId */ public function testGetId(): void { $plugin = new PluginID(); diff --git a/core/tests/Drupal/Tests/Component/Annotation/PluginTest.php b/core/tests/Drupal/Tests/Component/Annotation/PluginTest.php index ad02c2ddba47..857044e4b6c9 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/PluginTest.php +++ b/core/tests/Drupal/Tests/Component/Annotation/PluginTest.php @@ -5,18 +5,21 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Annotation; use Drupal\Component\Annotation\Plugin; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Annotation\Plugin - * @group Annotation + * Tests Drupal\Component\Annotation\Plugin. */ +#[CoversClass(Plugin::class)] +#[Group('Annotation')] class PluginTest extends TestCase { /** - * @covers ::__construct - * @covers ::parse - * @covers ::get + * @legacy-covers ::__construct + * @legacy-covers ::parse + * @legacy-covers ::get */ public function testGet(): void { // Assert all values are accepted through constructor and default value is @@ -51,7 +54,7 @@ class PluginTest extends TestCase { } /** - * @covers ::getProvider + * @legacy-covers ::getProvider */ public function testGetProvider(): void { $plugin = new Plugin(['provider' => 'example']); @@ -59,7 +62,7 @@ class PluginTest extends TestCase { } /** - * @covers ::setProvider + * @legacy-covers ::setProvider */ public function testSetProvider(): void { $plugin = new Plugin([]); @@ -68,7 +71,7 @@ class PluginTest extends TestCase { } /** - * @covers ::getId + * @legacy-covers ::getId */ public function testGetId(): void { $plugin = new Plugin(['id' => 'example']); @@ -76,7 +79,7 @@ class PluginTest extends TestCase { } /** - * @covers ::getClass + * @legacy-covers ::getClass */ public function testGetClass(): void { $plugin = new Plugin(['class' => 'example']); @@ -84,7 +87,7 @@ class PluginTest extends TestCase { } /** - * @covers ::setClass + * @legacy-covers ::setClass */ public function testSetClass(): void { $plugin = new Plugin([]); diff --git a/core/tests/Drupal/Tests/Component/Assertion/InspectorTest.php b/core/tests/Drupal/Tests/Component/Assertion/InspectorTest.php index 0d3f5d221a32..2d02f97c5716 100644 --- a/core/tests/Drupal/Tests/Component/Assertion/InspectorTest.php +++ b/core/tests/Drupal/Tests/Component/Assertion/InspectorTest.php @@ -4,14 +4,18 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Assertion; -use PHPUnit\Framework\TestCase; use Drupal\Component\Assertion\Inspector; use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Assertion\Inspector - * @group Assertion + * Tests Drupal\Component\Assertion\Inspector. */ +#[CoversClass(Inspector::class)] +#[Group('Assertion')] class InspectorTest extends TestCase { use ExpectDeprecationTrait; @@ -19,9 +23,9 @@ class InspectorTest extends TestCase { /** * Tests asserting all members are strings. * - * @covers ::assertAllStrings - * @dataProvider providerTestAssertAllStrings + * @legacy-covers ::assertAllStrings */ + #[DataProvider('providerTestAssertAllStrings')] public function testAssertAllStrings($input, $expected): void { $this->assertSame($expected, Inspector::assertAllStrings($input)); } @@ -55,7 +59,7 @@ class InspectorTest extends TestCase { /** * Tests asserting all members are strings or objects with __toString(). * - * @covers ::assertAllStringable + * @legacy-covers ::assertAllStringable */ public function testAssertAllStringable(): void { $this->assertTrue(Inspector::assertAllStringable([])); @@ -67,7 +71,7 @@ class InspectorTest extends TestCase { /** * Tests asserting all members are arrays. * - * @covers ::assertAllArrays + * @legacy-covers ::assertAllArrays */ public function testAssertAllArrays(): void { $this->assertTrue(Inspector::assertAllArrays([])); @@ -78,7 +82,7 @@ class InspectorTest extends TestCase { /** * Tests asserting array is 0-indexed - the strict definition of array. * - * @covers ::assertStrictArray + * @legacy-covers ::assertStrictArray */ public function testAssertStrictArray(): void { $this->assertTrue(Inspector::assertStrictArray([])); @@ -89,7 +93,7 @@ class InspectorTest extends TestCase { /** * Tests asserting all members are strict arrays. * - * @covers ::assertAllStrictArrays + * @legacy-covers ::assertAllStrictArrays */ public function testAssertAllStrictArrays(): void { $this->assertTrue(Inspector::assertAllStrictArrays([])); @@ -100,7 +104,7 @@ class InspectorTest extends TestCase { /** * Tests asserting all members have specified keys. * - * @covers ::assertAllHaveKey + * @legacy-covers ::assertAllHaveKey */ public function testAssertAllHaveKey(): void { $this->assertTrue(Inspector::assertAllHaveKey([])); @@ -113,7 +117,7 @@ class InspectorTest extends TestCase { /** * Tests asserting all members are integers. * - * @covers ::assertAllIntegers + * @legacy-covers ::assertAllIntegers */ public function testAssertAllIntegers(): void { $this->assertTrue(Inspector::assertAllIntegers([])); @@ -125,7 +129,7 @@ class InspectorTest extends TestCase { /** * Tests asserting all members are floating point variables. * - * @covers ::assertAllFloat + * @legacy-covers ::assertAllFloat */ public function testAssertAllFloat(): void { $this->assertTrue(Inspector::assertAllFloat([])); @@ -138,7 +142,7 @@ class InspectorTest extends TestCase { /** * Tests asserting all members are callable. * - * @covers ::assertAllCallable + * @legacy-covers ::assertAllCallable */ public function testAllCallable(): void { $this->assertTrue(Inspector::assertAllCallable([ @@ -164,7 +168,7 @@ class InspectorTest extends TestCase { /** * Tests asserting all members are !empty(). * - * @covers ::assertAllNotEmpty + * @legacy-covers ::assertAllNotEmpty */ public function testAllNotEmpty(): void { $this->assertTrue(Inspector::assertAllNotEmpty([1, 'two'])); @@ -174,7 +178,7 @@ class InspectorTest extends TestCase { /** * Tests asserting all arguments are numbers or strings castable to numbers. * - * @covers ::assertAllNumeric + * @legacy-covers ::assertAllNumeric */ public function testAssertAllNumeric(): void { $this->assertTrue(Inspector::assertAllNumeric([1, '2', 3.14])); @@ -184,7 +188,7 @@ class InspectorTest extends TestCase { /** * Tests asserting strstr() or stristr() match. * - * @covers ::assertAllMatch + * @legacy-covers ::assertAllMatch */ public function testAssertAllMatch(): void { $this->assertTrue(Inspector::assertAllMatch('f', ['fee', 'fi', 'fo'])); @@ -198,7 +202,7 @@ class InspectorTest extends TestCase { /** * Tests asserting regular expression match. * - * @covers ::assertAllRegularExpressionMatch + * @legacy-covers ::assertAllRegularExpressionMatch */ public function testAssertAllRegularExpressionMatch(): void { $this->assertTrue(Inspector::assertAllRegularExpressionMatch('/f/i', ['fee', 'fi', 'fo'])); @@ -212,7 +216,7 @@ class InspectorTest extends TestCase { /** * Tests asserting all members are objects. * - * @covers ::assertAllObjects + * @legacy-covers ::assertAllObjects */ public function testAssertAllObjects(): void { $this->assertTrue(Inspector::assertAllObjects([new \ArrayObject(), new \ArrayObject()])); diff --git a/core/tests/Drupal/Tests/Component/ClassFinder/ClassFinderTest.php b/core/tests/Drupal/Tests/Component/ClassFinder/ClassFinderTest.php index 9653180cfa22..0b8322303fb2 100644 --- a/core/tests/Drupal/Tests/Component/ClassFinder/ClassFinderTest.php +++ b/core/tests/Drupal/Tests/Component/ClassFinder/ClassFinderTest.php @@ -6,16 +6,19 @@ namespace Drupal\Tests\Component\ClassFinder; use Composer\Autoload\ClassLoader; use Drupal\Component\ClassFinder\ClassFinder; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\ClassFinder\ClassFinder - * @group ClassFinder + * Tests Drupal\Component\ClassFinder\ClassFinder. */ +#[CoversClass(ClassFinder::class)] +#[Group('ClassFinder')] class ClassFinderTest extends TestCase { /** - * @covers ::findFile + * @legacy-covers ::findFile */ public function testFindFile(): void { $finder = new ClassFinder(); diff --git a/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php b/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php index ae46c3cc5a56..0a09e3adb4b8 100644 --- a/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php +++ b/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php @@ -5,12 +5,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Datetime; use Drupal\Component\Datetime\DateTimePlus; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Datetime\DateTimePlus - * @group Datetime + * Tests Drupal\Component\Datetime\DateTimePlus. */ +#[CoversClass(DateTimePlus::class)] +#[Group('Datetime')] class DateTimePlusTest extends TestCase { /** @@ -22,9 +26,8 @@ class DateTimePlusTest extends TestCase { * Timezone argument for DateTimePlus. * @param string $expected * Expected output from DateTimePlus::format(). - * - * @dataProvider providerTestDates */ + #[DataProvider('providerTestDates')] public function testDates($input, $timezone, $expected): void { $date = new DateTimePlus($input, $timezone); $value = $date->format('c'); @@ -44,9 +47,8 @@ class DateTimePlusTest extends TestCase { * Timezone argument for DateTimePlus. * @param string $expected * Expected output from DateTimePlus::format(). - * - * @dataProvider providerTestDateArrays */ + #[DataProvider('providerTestDateArrays')] public function testDateArrays($input, $timezone, $expected): void { $date = DateTimePlus::createFromArray($input, $timezone); $value = $date->format('c'); @@ -68,9 +70,8 @@ class DateTimePlusTest extends TestCase { * Absolute flag for DateTimePlus::diff method. * @param \DateInterval $expected * The expected result of the DateTimePlus::diff operation. - * - * @dataProvider providerTestDateDiff */ + #[DataProvider('providerTestDateDiff')] public function testDateDiff($input1, $input2, $absolute, \DateInterval $expected): void { $interval = $input1->diff($input2, $absolute); $this->assertEquals($interval, $expected); @@ -85,9 +86,8 @@ class DateTimePlusTest extends TestCase { * Date argument for DateTimePlus::diff method. * @param bool $absolute * Absolute flag for DateTimePlus::diff method. - * - * @dataProvider providerTestInvalidDateDiff */ + #[DataProvider('providerTestInvalidDateDiff')] public function testInvalidDateDiff($input1, $input2, $absolute): void { $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('Method Drupal\Component\Datetime\DateTimePlus::diff expects parameter 1 to be a \DateTime or \Drupal\Component\Datetime\DateTimePlus object'); @@ -103,9 +103,8 @@ class DateTimePlusTest extends TestCase { * Timezone argument for DateTimePlus. * @param string $class * The Exception subclass to expect to be thrown. - * - * @dataProvider providerTestInvalidDateArrays */ + #[DataProvider('providerTestInvalidDateArrays')] public function testInvalidDateArrays($input, $timezone, $class): void { $this->expectException($class); $this->assertInstanceOf( @@ -121,9 +120,8 @@ class DateTimePlusTest extends TestCase { * Input argument for DateTimePlus::checkArray(). * @param bool $expected * The expected result of DateTimePlus::checkArray(). - * - * @dataProvider providerTestCheckArray */ + #[DataProvider('providerTestCheckArray')] public function testCheckArray(array $array, $expected): void { $this->assertSame( $expected, @@ -157,9 +155,8 @@ class DateTimePlusTest extends TestCase { * DateTimePlus::getTimeZone()::getName(), after timezone transform. * - 'expected_transform_offset' - Expected output from * DateTimePlus::getOffset(), after timezone transform. - * - * @dataProvider providerTestTimestamp */ + #[DataProvider('providerTestTimestamp')] public function testTimestamp($input, array $initial, array $transform): void { // Initialize a new date object. $date = DateTimePlus::createFromTimestamp($input, $initial['timezone']); @@ -175,9 +172,8 @@ class DateTimePlusTest extends TestCase { * @see testTimestamp() * @param array $transform * @see testTimestamp() - * - * @dataProvider providerTestDateTimestamp */ + #[DataProvider('providerTestDateTimestamp')] public function testDateTimestamp($input, array $initial, array $transform): void { // Initialize a new date object. $date = new DateTimePlus($input, $initial['timezone']); @@ -240,9 +236,8 @@ class DateTimePlusTest extends TestCase { * Format argument for DateTimePlus::format(). * @param string $expected * Expected output from DateTimePlus::format(). - * - * @dataProvider providerTestDateFormat */ + #[DataProvider('providerTestDateFormat')] public function testDateFormat($input, $timezone, $format, $format_date, $expected): void { $date = DateTimePlus::createFromFormat($format, $input, $timezone); $value = $date->format($format_date); @@ -262,9 +257,8 @@ class DateTimePlusTest extends TestCase { * Message to print if no errors are thrown by the invalid dates. * @param string $class * The Exception subclass to expect to be thrown. - * - * @dataProvider providerTestInvalidDates */ + #[DataProvider('providerTestInvalidDates')] public function testInvalidDates($input, $timezone, $format, $message, $class): void { $this->expectException($class); DateTimePlus::createFromFormat($format, $input, $timezone); @@ -283,9 +277,8 @@ class DateTimePlusTest extends TestCase { * Expected timezone returned from DateTimePlus::getTimezone::getName(). * @param string $message * Message to print on test failure. - * - * @dataProvider providerTestDateTimezone */ + #[DataProvider('providerTestDateTimezone')] public function testDateTimezone($input, $timezone, $expected_timezone, $message): void { $date = new DateTimePlus($input, $timezone); $timezone = $date->getTimezone()->getName(); @@ -755,10 +748,9 @@ class DateTimePlusTest extends TestCase { * @param string[] $errors * An array of error messages. * - * @covers ::__construct - * - * @dataProvider providerTestInvalidConstructor + * @legacy-covers ::__construct */ + #[DataProvider('providerTestInvalidConstructor')] public function testInvalidConstructor($time, array $errors): void { $date = new DateTimePlus($time); @@ -870,7 +862,7 @@ class DateTimePlusTest extends TestCase { /** * Tests that object methods are chainable. * - * @covers ::__call + * @legacy-covers ::__call */ public function testChainable(): void { $date = new DateTimePlus('now', 'Australia/Sydney'); @@ -894,7 +886,7 @@ class DateTimePlusTest extends TestCase { /** * Tests that non-chainable methods work. * - * @covers ::__call + * @legacy-covers ::__call */ public function testChainableNonChainable(): void { $datetime1 = new DateTimePlus('2009-10-11 12:00:00'); @@ -907,7 +899,7 @@ class DateTimePlusTest extends TestCase { /** * Tests that chained calls to non-existent functions throw an exception. * - * @covers ::__call + * @legacy-covers ::__call */ public function testChainableNonCallable(): void { $this->expectException(\BadMethodCallException::class); @@ -917,7 +909,7 @@ class DateTimePlusTest extends TestCase { } /** - * @covers ::getPhpDateTime + * @legacy-covers ::getPhpDateTime */ public function testGetPhpDateTime(): void { $new_york = new \DateTimeZone('America/New_York'); diff --git a/core/tests/Drupal/Tests/Component/Datetime/TimeWithNoRequestTest.php b/core/tests/Drupal/Tests/Component/Datetime/TimeWithNoRequestTest.php index d4b02de5d30a..a394e1b99c98 100644 --- a/core/tests/Drupal/Tests/Component/Datetime/TimeWithNoRequestTest.php +++ b/core/tests/Drupal/Tests/Component/Datetime/TimeWithNoRequestTest.php @@ -5,17 +5,20 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Datetime; use Drupal\Component\Datetime\Time; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; /** * Tests that getRequest(Micro)Time works when no underlying request exists. - * - * @coversDefaultClass \Drupal\Component\Datetime\Time - * @group Datetime - * @group #slow - * @runTestsInSeparateProcesses - * @preserveGlobalState disabled */ +#[CoversClass(Time::class)] +#[Group('Datetime')] +#[Group('#slow')] +#[PreserveGlobalState(FALSE)] +#[RunTestsInSeparateProcesses] class TimeWithNoRequestTest extends TestCase { /** @@ -40,7 +43,7 @@ class TimeWithNoRequestTest extends TestCase { /** * Tests the getRequestTime method. * - * @covers ::getRequestTime + * @legacy-covers ::getRequestTime */ public function testGetRequestTimeImmutable(): void { $requestTime = $this->time->getRequestTime(); @@ -51,7 +54,7 @@ class TimeWithNoRequestTest extends TestCase { /** * Tests the getRequestMicroTime method. * - * @covers ::getRequestMicroTime + * @legacy-covers ::getRequestMicroTime */ public function testGetRequestMicroTimeImmutable(): void { $requestTime = $this->time->getRequestMicroTime(); diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php index 6f8880376327..b57d2740704b 100644 --- a/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php +++ b/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php @@ -4,9 +4,13 @@ declare(strict_types=1); namespace Drupal\Tests\Component\DependencyInjection; +use Drupal\Component\DependencyInjection\Container; use Drupal\Component\Utility\Crypt; use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; +use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -15,12 +19,12 @@ use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; -use Prophecy\Argument; /** - * @coversDefaultClass \Drupal\Component\DependencyInjection\Container - * @group DependencyInjection + * Tests Drupal\Component\DependencyInjection\Container. */ +#[CoversClass(Container::class)] +#[Group('DependencyInjection')] class ContainerTest extends TestCase { use ExpectDeprecationTrait; use ProphecyTrait; @@ -66,7 +70,7 @@ class ContainerTest extends TestCase { /** * Tests that passing a non-supported format throws an InvalidArgumentException. * - * @covers ::__construct + * @legacy-covers ::__construct */ public function testConstruct(): void { $container_definition = $this->getMockContainerDefinition(); @@ -78,7 +82,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::getParameter() works properly. * - * @covers ::getParameter + * @legacy-covers ::getParameter */ public function testGetParameter(): void { $this->assertEquals($this->containerDefinition['parameters']['some_config'], $this->container->getParameter('some_config'), 'Container parameter matches for %some_config%.'); @@ -88,9 +92,9 @@ class ContainerTest extends TestCase { /** * Tests that Container::getParameter() works for non-existing parameters. * - * @covers ::getParameter - * @covers ::getParameterAlternatives - * @covers ::getAlternatives + * @legacy-covers ::getParameter + * @legacy-covers ::getParameterAlternatives + * @legacy-covers ::getAlternatives */ public function testGetParameterIfNotFound(): void { $this->expectException(ParameterNotFoundException::class); @@ -100,7 +104,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::getParameter() works properly for NULL parameters. * - * @covers ::getParameter + * @legacy-covers ::getParameter */ public function testGetParameterIfNotFoundBecauseNull(): void { $this->expectException(ParameterNotFoundException::class); @@ -110,7 +114,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::hasParameter() works properly. * - * @covers ::hasParameter + * @legacy-covers ::hasParameter */ public function testHasParameter(): void { $this->assertTrue($this->container->hasParameter('some_config'), 'Container parameters include %some_config%.'); @@ -120,7 +124,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::setParameter() in an unfrozen case works properly. * - * @covers ::setParameter + * @legacy-covers ::setParameter */ public function testSetParameterWithUnfrozenContainer(): void { $container_definition = $this->containerDefinition; @@ -133,7 +137,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::setParameter() in a frozen case works properly. * - * @covers ::setParameter + * @legacy-covers ::setParameter */ public function testSetParameterWithFrozenContainer(): void { $this->container = new $this->containerClass($this->containerDefinition); @@ -144,8 +148,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() works properly. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGet(): void { $container = $this->container->get('service_container'); @@ -171,8 +175,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for non-shared services works properly. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForNonSharedService(): void { $service = $this->container->get('non_shared_service'); @@ -184,8 +188,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() works properly for class from parameters. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForClassFromParameter(): void { $container_definition = $this->containerDefinition; @@ -200,7 +204,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::set() works properly. * - * @covers ::set + * @legacy-covers ::set */ public function testSet(): void { $this->assertNull($this->container->get('new_id', ContainerInterface::NULL_ON_INVALID_REFERENCE)); @@ -213,7 +217,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::has() works properly. * - * @covers ::has + * @legacy-covers ::has */ public function testHas(): void { $this->assertTrue($this->container->has('other.service')); @@ -228,7 +232,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::has() for aliased services works properly. * - * @covers ::has + * @legacy-covers ::has */ public function testHasForAliasedService(): void { $service = $this->container->has('service.provider'); @@ -239,8 +243,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for circular dependencies works properly. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForCircularServices(): void { $this->expectException(ServiceCircularReferenceException::class); @@ -250,10 +254,10 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for non-existent services works properly. * - * @covers ::get - * @covers ::createService - * @covers ::getAlternatives - * @covers ::getServiceAlternatives + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::getAlternatives + * @legacy-covers ::getServiceAlternatives */ public function testGetForNonExistentService(): void { $this->expectException(ServiceNotFoundException::class); @@ -263,8 +267,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for a serialized definition works properly. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForSerializedServiceDefinition(): void { $container_definition = $this->containerDefinition; @@ -283,9 +287,9 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for non-existent parameters works properly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testGetForNonExistentParameterDependency(): void { $service = $this->container->get('service_parameter_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE); @@ -295,9 +299,9 @@ class ContainerTest extends TestCase { /** * Tests Container::get() with an exception due to missing parameter on the second call. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testGetForParameterDependencyWithExceptionOnSecondCall(): void { $service = $this->container->get('service_parameter_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE); @@ -312,9 +316,9 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for non-existent parameters works properly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testGetForNonExistentParameterDependencyWithException(): void { $this->expectException(InvalidArgumentException::class); @@ -324,9 +328,9 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for non-existent dependencies works properly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testGetForNonExistentServiceDependency(): void { $service = $this->container->get('service_dependency_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE); @@ -336,10 +340,10 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for non-existent dependencies works properly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters - * @covers ::getAlternatives + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters + * @legacy-covers ::getAlternatives */ public function testGetForNonExistentServiceDependencyWithException(): void { $this->expectException(ServiceNotFoundException::class); @@ -349,8 +353,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for non-existent services works properly. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForNonExistentServiceWhenUsingNull(): void { $this->assertNull($this->container->get('service_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE), 'Not found service does not throw exception.'); @@ -359,8 +363,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for NULL service works properly. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForNonExistentNULLService(): void { $this->expectException(ServiceNotFoundException::class); @@ -370,8 +374,8 @@ class ContainerTest extends TestCase { /** * Tests multiple Container::get() calls for non-existing dependencies work. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForNonExistentServiceMultipleTimes(): void { $container = new $this->containerClass(); @@ -383,9 +387,9 @@ class ContainerTest extends TestCase { /** * Tests multiple Container::get() calls with exception on the second time. * - * @covers ::get - * @covers ::createService - * @covers ::getAlternatives + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::getAlternatives */ public function testGetForNonExistentServiceWithExceptionOnSecondCall(): void { $this->assertNull($this->container->get('service_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE), 'Not found service does nto throw exception.'); @@ -396,8 +400,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for aliased services works properly. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForAliasedService(): void { $service = $this->container->get('service.provider'); @@ -408,8 +412,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for synthetic services works - if defined. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForSyntheticService(): void { $synthetic_service = new \stdClass(); @@ -421,8 +425,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for synthetic services throws an Exception if not defined. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForSyntheticServiceWithException(): void { $this->expectException(RuntimeException::class); @@ -432,8 +436,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for services with file includes works. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetWithFileInclude(): void { $this->container->get('container_test_file_service_test'); @@ -444,9 +448,9 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for various arguments lengths works. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testGetForInstantiationWithVariousArgumentLengths(): void { $args = []; @@ -460,8 +464,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for wrong factories works correctly. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForWrongFactory(): void { $this->expectException(RuntimeException::class); @@ -471,8 +475,8 @@ class ContainerTest extends TestCase { /** * Tests Container::get() for factories via services (Symfony 2.7.0). * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForFactoryService(): void { $factory_service = $this->container->get('factory_service'); @@ -483,8 +487,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for factories via class works (Symfony 2.7.0). * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForFactoryClass(): void { $service = $this->container->get('service.provider'); @@ -498,8 +502,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for configurable services throws an Exception. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForConfiguratorWithException(): void { $this->expectException(InvalidArgumentException::class); @@ -509,8 +513,8 @@ class ContainerTest extends TestCase { /** * Tests that Container::get() for configurable services works. * - * @covers ::get - * @covers ::createService + * @legacy-covers ::get + * @legacy-covers ::createService */ public function testGetForConfigurator(): void { $container = $this->container; @@ -532,9 +536,9 @@ class ContainerTest extends TestCase { /** * Tests that private services work correctly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testResolveServicesAndParametersForPrivateService(): void { $service = $this->container->get('service_using_private'); @@ -551,9 +555,9 @@ class ContainerTest extends TestCase { /** * Tests that private service sharing works correctly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testResolveServicesAndParametersForSharedPrivateService(): void { $service = $this->container->get('service_using_shared_private'); @@ -570,9 +574,9 @@ class ContainerTest extends TestCase { /** * Tests that services with an array of arguments work correctly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testResolveServicesAndParametersForArgumentsUsingDeepArray(): void { $service = $this->container->get('service_using_array'); @@ -583,9 +587,9 @@ class ContainerTest extends TestCase { /** * Tests that services that are optional work correctly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testResolveServicesAndParametersForOptionalServiceDependencies(): void { $service = $this->container->get('service_with_optional_dependency'); @@ -595,9 +599,9 @@ class ContainerTest extends TestCase { /** * Tests that services wrapped in a closure work correctly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testResolveServicesAndParametersForServiceReferencedViaServiceClosure(): void { $service = $this->container->get('service_within_service_closure'); @@ -610,9 +614,9 @@ class ContainerTest extends TestCase { /** * Tests that an invalid argument throw an Exception. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testResolveServicesAndParametersForInvalidArgument(): void { $this->expectException(InvalidArgumentException::class); @@ -622,9 +626,9 @@ class ContainerTest extends TestCase { /** * Tests that invalid arguments throw an Exception. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testResolveServicesAndParametersForInvalidArguments(): void { // In case the machine-optimized format is not used, we need to simulate the @@ -639,9 +643,9 @@ class ContainerTest extends TestCase { /** * Tests that a parameter that points to a service works correctly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testResolveServicesAndParametersForServiceInstantiatedFromParameter(): void { $service = $this->container->get('service.provider'); @@ -652,7 +656,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::initialized works correctly. * - * @covers ::initialized + * @legacy-covers ::initialized */ public function testInitialized(): void { $this->assertFalse($this->container->initialized('late.service'), 'Late service is not initialized.'); @@ -663,7 +667,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::initialized works correctly for aliases. * - * @covers ::initialized + * @legacy-covers ::initialized */ public function testInitializedForAliases(): void { $this->assertFalse($this->container->initialized('late.service_alias'), 'Late service is not initialized.'); @@ -674,7 +678,7 @@ class ContainerTest extends TestCase { /** * Tests that Container::getServiceIds() works properly. * - * @covers ::getServiceIds + * @legacy-covers ::getServiceIds */ public function testGetServiceIds(): void { $service_definition_keys = array_merge(['service_container'], array_keys($this->containerDefinition['services'])); @@ -691,9 +695,9 @@ class ContainerTest extends TestCase { /** * Tests that raw type services arguments are resolved correctly. * - * @covers ::get - * @covers ::createService - * @covers ::resolveServicesAndParameters + * @legacy-covers ::get + * @legacy-covers ::createService + * @legacy-covers ::resolveServicesAndParameters */ public function testResolveServicesAndParametersForRawArgument(): void { $this->assertEquals(['ccc'], $this->container->get('service_with_raw_argument')->getArguments()); @@ -715,7 +719,7 @@ class ContainerTest extends TestCase { /** * Tests Container::reset(). * - * @covers ::reset + * @legacy-covers ::reset */ public function testReset(): void { $this->assertFalse($this->container->initialized('late.service'), 'Late service is not initialized.'); diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/PhpArrayContainerTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/PhpArrayContainerTest.php index 99113913a7f2..ed02dc435ebd 100644 --- a/core/tests/Drupal/Tests/Component/DependencyInjection/PhpArrayContainerTest.php +++ b/core/tests/Drupal/Tests/Component/DependencyInjection/PhpArrayContainerTest.php @@ -4,12 +4,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\DependencyInjection; +use Drupal\Component\DependencyInjection\PhpArrayContainer; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * @coversDefaultClass \Drupal\Component\DependencyInjection\PhpArrayContainer - * @group DependencyInjection + * Tests Drupal\Component\DependencyInjection\PhpArrayContainer. */ +#[CoversClass(PhpArrayContainer::class)] +#[Group('DependencyInjection')] class PhpArrayContainerTest extends ContainerTest { /** diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/ReverseContainerTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/ReverseContainerTest.php index 2bdac0cc4d48..db16c20e1f81 100644 --- a/core/tests/Drupal/Tests/Component/DependencyInjection/ReverseContainerTest.php +++ b/core/tests/Drupal/Tests/Component/DependencyInjection/ReverseContainerTest.php @@ -5,6 +5,9 @@ declare(strict_types=1); namespace Drupal\Tests\Component\DependencyInjection; use Drupal\Component\DependencyInjection\ReverseContainer; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -13,15 +16,14 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; * * The reverse container uses a static to maintain information across * container rebuilds. - * - * @runTestsInSeparateProcesses - * @coversDefaultClass \Drupal\Component\DependencyInjection\ReverseContainer - * @group DependencyInjection */ +#[CoversClass(ReverseContainer::class)] +#[Group('DependencyInjection')] +#[RunTestsInSeparateProcesses] class ReverseContainerTest extends TestCase { /** - * @covers ::getId + * @legacy-covers ::getId */ public function testGetId(): void { $container = new ContainerBuilder(); @@ -37,7 +39,7 @@ class ReverseContainerTest extends TestCase { } /** - * @covers ::recordContainer + * @legacy-covers ::recordContainer */ public function testRecordContainer(): void { $container = new ContainerBuilder(); diff --git a/core/tests/Drupal/Tests/Component/Diff/DiffFormatterTest.php b/core/tests/Drupal/Tests/Component/Diff/DiffFormatterTest.php index 696dcb852032..cb53b3cee21e 100644 --- a/core/tests/Drupal/Tests/Component/Diff/DiffFormatterTest.php +++ b/core/tests/Drupal/Tests/Component/Diff/DiffFormatterTest.php @@ -6,15 +6,16 @@ namespace Drupal\Tests\Component\Diff; use Drupal\Component\Diff\Diff; use Drupal\Component\Diff\DiffFormatter; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Test DiffFormatter classes. - * - * @coversDefaultClass \Drupal\Component\Diff\DiffFormatter - * - * @group Diff */ +#[CoversClass(DiffFormatter::class)] +#[Group('Diff')] class DiffFormatterTest extends TestCase { /** @@ -47,9 +48,9 @@ class DiffFormatterTest extends TestCase { /** * Tests whether op classes returned by DiffEngine::diff() match expectations. * - * @covers ::format - * @dataProvider provideTestDiff + * @legacy-covers ::format */ + #[DataProvider('provideTestDiff')] public function testDiff($expected, $from, $to): void { $diff = new Diff($from, $to); $formatter = new DiffFormatter(); diff --git a/core/tests/Drupal/Tests/Component/Diff/DiffOpOutputBuilderTest.php b/core/tests/Drupal/Tests/Component/Diff/DiffOpOutputBuilderTest.php index 29143ca9b2b0..e68c1ad5f634 100644 --- a/core/tests/Drupal/Tests/Component/Diff/DiffOpOutputBuilderTest.php +++ b/core/tests/Drupal/Tests/Component/Diff/DiffOpOutputBuilderTest.php @@ -6,17 +6,20 @@ namespace Drupal\Tests\Component\Diff; use Drupal\Component\Diff\DiffOpOutputBuilder; use Drupal\Component\Diff\Engine\DiffOpAdd; -use Drupal\Component\Diff\Engine\DiffOpCopy; use Drupal\Component\Diff\Engine\DiffOpChange; +use Drupal\Component\Diff\Engine\DiffOpCopy; use Drupal\Component\Diff\Engine\DiffOpDelete; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use SebastianBergmann\Diff\Differ; /** - * @coversDefaultClass \Drupal\Component\Diff\DiffOpOutputBuilder - * - * @group Diff + * Tests Drupal\Component\Diff\DiffOpOutputBuilder. */ +#[CoversClass(DiffOpOutputBuilder::class)] +#[Group('Diff')] class DiffOpOutputBuilderTest extends TestCase { /** @@ -91,9 +94,9 @@ class DiffOpOutputBuilderTest extends TestCase { /** * Tests whether op classes returned match expectations. * - * @covers ::toOpsArray - * @dataProvider provideTestDiff + * @legacy-covers ::toOpsArray */ + #[DataProvider('provideTestDiff')] public function testToOpsArray(array $expected, array $from, array $to): void { $diffOpBuilder = new DiffOpOutputBuilder(); $differ = new Differ($diffOpBuilder); @@ -102,9 +105,9 @@ class DiffOpOutputBuilderTest extends TestCase { } /** - * @covers ::getDiff - * @dataProvider provideTestDiff + * @legacy-covers ::getDiff */ + #[DataProvider('provideTestDiff')] public function testGetDiff(array $expected, array $from, array $to): void { $differ = new Differ(new DiffOpOutputBuilder()); $diff = $differ->diff($from, $to); @@ -114,7 +117,7 @@ class DiffOpOutputBuilderTest extends TestCase { /** * Tests that two files can be successfully diffed. * - * @covers ::toOpsArray + * @legacy-covers ::toOpsArray */ public function testDiffInfiniteLoop(): void { $from = explode("\n", file_get_contents(__DIR__ . '/Engine/fixtures/file1.txt')); diff --git a/core/tests/Drupal/Tests/Component/Diff/Engine/HWLDFWordAccumulatorTest.php b/core/tests/Drupal/Tests/Component/Diff/Engine/HWLDFWordAccumulatorTest.php index eb006d115e83..27e9bbd38c49 100644 --- a/core/tests/Drupal/Tests/Component/Diff/Engine/HWLDFWordAccumulatorTest.php +++ b/core/tests/Drupal/Tests/Component/Diff/Engine/HWLDFWordAccumulatorTest.php @@ -4,28 +4,26 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Diff\Engine; -// cspell:ignore HWLDFWordAccumulator - use Drupal\Component\Diff\Engine\HWLDFWordAccumulator; +// cspell:ignore HWLDFWordAccumulator +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; // cspell:ignore wordword - /** * Test HWLDFWordAccumulator. - * - * @coversDefaultClass \Drupal\Component\Diff\Engine\HWLDFWordAccumulator - * - * @group Diff */ +#[CoversClass(HWLDFWordAccumulator::class)] +#[Group('Diff')] class HWLDFWordAccumulatorTest extends TestCase { /** * Verify that we only get back a NBSP from an empty accumulator. * - * @covers ::getLines - * * @see Drupal\Component\Diff\Engine\HWLDFWordAccumulator::NBSP + * @legacy-covers ::getLines */ public function testGetLinesEmpty(): void { $acc = new HWLDFWordAccumulator(); @@ -47,9 +45,9 @@ class HWLDFWordAccumulatorTest extends TestCase { } /** - * @covers ::addWords - * @dataProvider provideAddWords + * @legacy-covers ::addWords */ + #[DataProvider('provideAddWords')] public function testAddWords($expected, $words, $tag): void { $acc = new HWLDFWordAccumulator(); $acc->addWords($words, $tag); diff --git a/core/tests/Drupal/Tests/Component/Discovery/YamlDirectoryDiscoveryTest.php b/core/tests/Drupal/Tests/Component/Discovery/YamlDirectoryDiscoveryTest.php index 816def560ed5..0b6521acd64b 100644 --- a/core/tests/Drupal/Tests/Component/Discovery/YamlDirectoryDiscoveryTest.php +++ b/core/tests/Drupal/Tests/Component/Discovery/YamlDirectoryDiscoveryTest.php @@ -8,15 +8,15 @@ use Drupal\Component\Discovery\DiscoveryException; use Drupal\Component\Discovery\YamlDirectoryDiscovery; use Drupal\Component\FileCache\FileCacheFactory; use org\bovigo\vfs\vfsStream; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * YamlDirectoryDiscoveryTest component unit tests. - * - * @coversDefaultClass \Drupal\Component\Discovery\YamlDirectoryDiscovery - * - * @group Discovery */ +#[CoversClass(YamlDirectoryDiscovery::class)] +#[Group('Discovery')] class YamlDirectoryDiscoveryTest extends TestCase { /** @@ -30,7 +30,7 @@ class YamlDirectoryDiscoveryTest extends TestCase { /** * Tests YAML directory discovery. * - * @covers ::findAll + * @legacy-covers ::findAll */ public function testDiscovery(): void { vfsStream::setup('modules', NULL, [ @@ -102,7 +102,7 @@ class YamlDirectoryDiscoveryTest extends TestCase { /** * Tests YAML directory discovery with an alternate ID key. * - * @covers ::findAll + * @legacy-covers ::findAll */ public function testDiscoveryAlternateId(): void { vfsStream::setup('modules', NULL, [ @@ -124,8 +124,8 @@ class YamlDirectoryDiscoveryTest extends TestCase { /** * Tests YAML directory discovery with a missing ID key. * - * @covers ::findAll - * @covers ::getIdentifier + * @legacy-covers ::findAll + * @legacy-covers ::getIdentifier */ public function testDiscoveryNoIdException(): void { $this->expectException(DiscoveryException::class); @@ -146,7 +146,7 @@ class YamlDirectoryDiscoveryTest extends TestCase { /** * Tests YAML directory discovery with invalid YAML. * - * @covers ::findAll + * @legacy-covers ::findAll */ public function testDiscoveryInvalidYamlException(): void { $this->expectException(DiscoveryException::class); diff --git a/core/tests/Drupal/Tests/Component/Discovery/YamlDiscoveryTest.php b/core/tests/Drupal/Tests/Component/Discovery/YamlDiscoveryTest.php index a592b1cea485..cc662a528cc1 100644 --- a/core/tests/Drupal/Tests/Component/Discovery/YamlDiscoveryTest.php +++ b/core/tests/Drupal/Tests/Component/Discovery/YamlDiscoveryTest.php @@ -10,13 +10,13 @@ use Drupal\Component\Serialization\Exception\InvalidDataTypeException; use org\bovigo\vfs\vfsStream; use org\bovigo\vfs\vfsStreamDirectory; use org\bovigo\vfs\vfsStreamWrapper; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * YamlDiscovery component unit tests. - * - * @group Discovery */ +#[Group('Discovery')] class YamlDiscoveryTest extends TestCase { /** diff --git a/core/tests/Drupal/Tests/Component/DrupalComponentTest.php b/core/tests/Drupal/Tests/Component/DrupalComponentTest.php index 4ffa430c05b5..3e4295e18abf 100644 --- a/core/tests/Drupal/Tests/Component/DrupalComponentTest.php +++ b/core/tests/Drupal/Tests/Component/DrupalComponentTest.php @@ -6,13 +6,14 @@ namespace Drupal\Tests\Component; use org\bovigo\vfs\vfsStream; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * General tests for \Drupal\Component that can't go anywhere else. - * - * @group Component */ +#[Group('Component')] class DrupalComponentTest extends TestCase { /** @@ -40,9 +41,8 @@ class DrupalComponentTest extends TestCase { * * @param string $component_path * The path to the component. - * - * @dataProvider getComponents */ + #[DataProvider('getComponents')] public function testComponentLicense(string $component_path): void { $this->assertFileExists($component_path . DIRECTORY_SEPARATOR . 'LICENSE.txt'); $this->assertSame('e84dac1d9fbb5a4a69e38654ce644cea769aa76b', hash_file('sha1', $component_path . DIRECTORY_SEPARATOR . 'LICENSE.txt')); @@ -139,9 +139,9 @@ class DrupalComponentTest extends TestCase { } /** - * @covers \Drupal\Tests\Component\DrupalComponentTest::assertNoCoreUsage - * @dataProvider providerAssertNoCoreUsage + * @legacy-covers \Drupal\Tests\Component\DrupalComponentTest::assertNoCoreUsage */ + #[DataProvider('providerAssertNoCoreUsage')] public function testAssertNoCoreUsage($expected_pass, $file_data): void { // Set up a virtual file to read. $vfs_root = vfsStream::setup('root'); diff --git a/core/tests/Drupal/Tests/Component/FileCache/FileCacheFactoryTest.php b/core/tests/Drupal/Tests/Component/FileCache/FileCacheFactoryTest.php index 9cae565db968..503a80c4bb2b 100644 --- a/core/tests/Drupal/Tests/Component/FileCache/FileCacheFactoryTest.php +++ b/core/tests/Drupal/Tests/Component/FileCache/FileCacheFactoryTest.php @@ -5,15 +5,19 @@ declare(strict_types=1); namespace Drupal\Tests\Component\FileCache; use Drupal\Component\FileCache\FileCache; -use Drupal\Component\FileCache\NullFileCache; use Drupal\Component\FileCache\FileCacheFactory; +use Drupal\Component\FileCache\NullFileCache; use Drupal\Component\Utility\Random; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\FileCache\FileCacheFactory - * @group FileCache + * Tests Drupal\Component\FileCache\FileCacheFactory. */ +#[CoversClass(FileCacheFactory::class)] +#[Group('FileCache')] class FileCacheFactoryTest extends TestCase { /** @@ -36,7 +40,7 @@ class FileCacheFactoryTest extends TestCase { } /** - * @covers ::get + * @legacy-covers ::get */ public function testGet(): void { $file_cache = FileCacheFactory::get('test_foo_settings', []); @@ -57,7 +61,7 @@ class FileCacheFactoryTest extends TestCase { } /** - * @covers ::get + * @legacy-covers ::get */ public function testGetNoPrefix(): void { FileCacheFactory::setPrefix(NULL); @@ -67,7 +71,7 @@ class FileCacheFactoryTest extends TestCase { } /** - * @covers ::get + * @legacy-covers ::get */ public function testGetDisabledFileCache(): void { // Ensure the returned FileCache is an instance of FileCache::class. @@ -84,10 +88,9 @@ class FileCacheFactoryTest extends TestCase { } /** - * @covers ::get - * - * @dataProvider configurationDataProvider + * @legacy-covers ::get */ + #[DataProvider('configurationDataProvider')] public function testGetConfigurationOverrides($configuration, $arguments, $class): void { FileCacheFactory::setConfiguration($configuration); @@ -154,8 +157,8 @@ class FileCacheFactoryTest extends TestCase { } /** - * @covers ::getConfiguration - * @covers ::setConfiguration + * @legacy-covers ::getConfiguration + * @legacy-covers ::setConfiguration */ public function testGetSetConfiguration(): void { $configuration = FileCacheFactory::getConfiguration(); @@ -166,8 +169,8 @@ class FileCacheFactoryTest extends TestCase { } /** - * @covers ::getPrefix - * @covers ::setPrefix + * @legacy-covers ::getPrefix + * @legacy-covers ::setPrefix */ public function testGetSetPrefix(): void { // Random generator. diff --git a/core/tests/Drupal/Tests/Component/FileCache/FileCacheTest.php b/core/tests/Drupal/Tests/Component/FileCache/FileCacheTest.php index 88aa8e2f34cd..7128514f6543 100644 --- a/core/tests/Drupal/Tests/Component/FileCache/FileCacheTest.php +++ b/core/tests/Drupal/Tests/Component/FileCache/FileCacheTest.php @@ -5,12 +5,15 @@ declare(strict_types=1); namespace Drupal\Tests\Component\FileCache; use Drupal\Component\FileCache\FileCache; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\FileCache\FileCache - * @group FileCache + * Tests Drupal\Component\FileCache\FileCache. */ +#[CoversClass(FileCache::class)] +#[Group('FileCache')] class FileCacheTest extends TestCase { /** @@ -38,8 +41,8 @@ class FileCacheTest extends TestCase { } /** - * @covers ::get - * @covers ::__construct + * @legacy-covers ::get + * @legacy-covers ::__construct */ public function testGet(): void { // Test a cache miss. @@ -66,7 +69,7 @@ class FileCacheTest extends TestCase { } /** - * @covers ::getMultiple + * @legacy-covers ::getMultiple */ public function testGetMultiple(): void { // Test a cache miss. @@ -101,7 +104,7 @@ class FileCacheTest extends TestCase { } /** - * @covers ::set + * @legacy-covers ::set */ public function testSet(): void { $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . 'llama-23.txt'; @@ -122,7 +125,7 @@ class FileCacheTest extends TestCase { } /** - * @covers ::delete + * @legacy-covers ::delete */ public function testDelete(): void { $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . 'llama-23.txt'; diff --git a/core/tests/Drupal/Tests/Component/FileSecurity/FileSecurityTest.php b/core/tests/Drupal/Tests/Component/FileSecurity/FileSecurityTest.php index 60cedeb245cb..693194a66a2b 100644 --- a/core/tests/Drupal/Tests/Component/FileSecurity/FileSecurityTest.php +++ b/core/tests/Drupal/Tests/Component/FileSecurity/FileSecurityTest.php @@ -6,18 +6,19 @@ namespace Drupal\Tests\Component\FileSecurity; use Drupal\Component\FileSecurity\FileSecurity; use org\bovigo\vfs\vfsStream; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests the file security component. - * - * @coversDefaultClass \Drupal\Component\FileSecurity\FileSecurity - * @group FileSecurity */ +#[CoversClass(FileSecurity::class)] +#[Group('FileSecurity')] class FileSecurityTest extends TestCase { /** - * @covers ::writeHtaccess + * @legacy-covers ::writeHtaccess */ public function testWriteHtaccessPrivate(): void { vfsStream::setup('root'); @@ -30,7 +31,7 @@ class FileSecurityTest extends TestCase { } /** - * @covers ::writeHtaccess + * @legacy-covers ::writeHtaccess */ public function testWriteHtaccessPublic(): void { vfsStream::setup('root'); @@ -43,7 +44,7 @@ class FileSecurityTest extends TestCase { } /** - * @covers ::writeHtaccess + * @legacy-covers ::writeHtaccess */ public function testWriteHtaccessForceOverwrite(): void { vfsStream::setup('root'); @@ -56,7 +57,7 @@ class FileSecurityTest extends TestCase { } /** - * @covers ::writeHtaccess + * @legacy-covers ::writeHtaccess */ public function testWriteHtaccessFailure(): void { vfsStream::setup('root'); diff --git a/core/tests/Drupal/Tests/Component/FileSystem/RegexDirectoryIteratorTest.php b/core/tests/Drupal/Tests/Component/FileSystem/RegexDirectoryIteratorTest.php index 36749ade6dda..621ce108a5bf 100644 --- a/core/tests/Drupal/Tests/Component/FileSystem/RegexDirectoryIteratorTest.php +++ b/core/tests/Drupal/Tests/Component/FileSystem/RegexDirectoryIteratorTest.php @@ -6,18 +6,22 @@ namespace Drupal\Tests\Component\FileSystem; use Drupal\Component\FileSystem\RegexDirectoryIterator; use org\bovigo\vfs\vfsStream; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\FileSystem\RegexDirectoryIterator - * @group FileSystem + * Tests Drupal\Component\FileSystem\RegexDirectoryIterator. */ +#[CoversClass(RegexDirectoryIterator::class)] +#[Group('FileSystem')] class RegexDirectoryIteratorTest extends TestCase { /** - * @covers ::accept - * @dataProvider providerTestRegexDirectoryIterator + * @legacy-covers ::accept */ + #[DataProvider('providerTestRegexDirectoryIterator')] public function testRegexDirectoryIterator(array $directory, $regex, array $expected): void { vfsStream::setup('root', NULL, $directory); $iterator = new RegexDirectoryIterator(vfsStream::url('root'), $regex); diff --git a/core/tests/Drupal/Tests/Component/FrontMatter/FrontMatterTest.php b/core/tests/Drupal/Tests/Component/FrontMatter/FrontMatterTest.php index 90977d85fb60..618ed88d6fe2 100644 --- a/core/tests/Drupal/Tests/Component/FrontMatter/FrontMatterTest.php +++ b/core/tests/Drupal/Tests/Component/FrontMatter/FrontMatterTest.php @@ -7,15 +7,16 @@ namespace Drupal\Tests\Component\FrontMatter; use Drupal\Component\FrontMatter\Exception\FrontMatterParseException; use Drupal\Component\FrontMatter\FrontMatter; use Drupal\Component\Serialization\Yaml; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests front matter parsing helper methods. - * - * @group FrontMatter - * - * @coversDefaultClass \Drupal\Component\FrontMatter\FrontMatter */ +#[CoversClass(FrontMatter::class)] +#[Group('FrontMatter')] class FrontMatterTest extends TestCase { /** @@ -47,8 +48,8 @@ class FrontMatterTest extends TestCase { /** * Tests when a passed serializer doesn't implement the proper interface. * - * @covers ::__construct - * @covers ::create + * @legacy-covers ::__construct + * @legacy-covers ::create */ public function testFrontMatterSerializerException(): void { $this->expectException(\AssertionError::class); @@ -59,10 +60,10 @@ class FrontMatterTest extends TestCase { /** * Tests broken front matter. * - * @covers ::__construct - * @covers ::create - * @covers ::parse - * @covers \Drupal\Component\FrontMatter\Exception\FrontMatterParseException + * @legacy-covers ::__construct + * @legacy-covers ::create + * @legacy-covers ::parse + * @legacy-covers \Drupal\Component\FrontMatter\Exception\FrontMatterParseException */ public function testFrontMatterBroken(): void { $this->expectException(FrontMatterParseException::class); @@ -81,15 +82,14 @@ class FrontMatterTest extends TestCase { * @param string $content * The content to use for testing purposes. * - * @covers ::__construct - * @covers ::getContent - * @covers ::getData - * @covers ::getLine - * @covers ::create - * @covers ::parse - * - * @dataProvider providerFrontMatterData + * @legacy-covers ::__construct + * @legacy-covers ::getContent + * @legacy-covers ::getData + * @legacy-covers ::getLine + * @legacy-covers ::create + * @legacy-covers ::parse */ + #[DataProvider('providerFrontMatterData')] public function testFrontMatterData($yaml, $line, $content = self::SOURCE): void { $source = static::createFrontMatterSource($yaml, $content); $frontMatter = FrontMatter::create($source); diff --git a/core/tests/Drupal/Tests/Component/Gettext/PoHeaderTest.php b/core/tests/Drupal/Tests/Component/Gettext/PoHeaderTest.php index d5c97bdb8c7e..14797ad4b3b7 100644 --- a/core/tests/Drupal/Tests/Component/Gettext/PoHeaderTest.php +++ b/core/tests/Drupal/Tests/Component/Gettext/PoHeaderTest.php @@ -5,15 +5,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Gettext; use Drupal\Component\Gettext\PoHeader; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Unit tests for the Gettext PO file header handling features. * * @see Drupal\Component\Gettext\PoHeader. - * - * @group Gettext */ +#[Group('Gettext')] class PoHeaderTest extends TestCase { /** @@ -26,9 +27,8 @@ class PoHeaderTest extends TestCase { * The plural expression. * @param array $expected * Array of expected plural positions keyed by plural value. - * - * @dataProvider providerTestPluralsFormula */ + #[DataProvider('providerTestPluralsFormula')] public function testPluralsFormula($plural, $expected): void { $p = new PoHeader(); [, $new_plural] = $p->parsePluralForms($plural); diff --git a/core/tests/Drupal/Tests/Component/Gettext/PoItemTest.php b/core/tests/Drupal/Tests/Component/Gettext/PoItemTest.php new file mode 100644 index 000000000000..d8890bac7799 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Gettext/PoItemTest.php @@ -0,0 +1,87 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\Component\Gettext; + +use Drupal\Component\Gettext\PoItem; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\TestCase; + +/** + * Tests Drupal\Component\Gettext\PoItem. + */ +#[CoversClass(PoItem::class)] +#[Group('Gettext')] +class PoItemTest extends TestCase { + + /** + * @return array + * - Source string + * - Context (optional) + * - Translated string (optional) + * - Expected value + */ + public static function providerStrings(): array { + // cSpell:disable + return [ + [ + '', + NULL, + NULL, + 'msgid ""' . "\n" . 'msgstr ""' . "\n\n", + ], + // Translated String without contesxt. + [ + 'Next', + NULL, + 'Suivant', + 'msgid "Next"' . "\n" . 'msgstr "Suivant"' . "\n\n", + ], + // Translated string with context. + [ + 'Apr', + 'Abbreviated month name', + 'Avr', + 'msgctxt "Abbreviated month name"' . "\n" . 'msgid "Apr"' . "\n" . 'msgstr "Avr"' . "\n\n", + ], + // Translated string with placeholder. + [ + '%email is not a valid email address.', + NULL, + '%email n\'est pas une adresse de courriel valide.', + 'msgid "%email is not a valid email address."' . "\n" . 'msgstr "%email n\'est pas une adresse de courriel valide."' . "\n\n", + ], + // Translated Plural String without context. + [ + ['Installed theme', 'Installed themes'], + NULL, + ['Thème installé', 'Thèmes installés'], + 'msgid "Installed theme"' . "\n" . 'msgid_plural "Installed themes"' . "\n" . 'msgstr[0] "Thème installé"' . "\n" . 'msgstr[1] "Thèmes installés"' . "\n\n", + ], + ]; + // cSpell:enable + } + + #[DataProvider('providerStrings')] + public function testFormat($source, $context, $translation, $expected): void { + $item = new PoItem(); + + $item->setSource($source); + + if (is_array($source)) { + $item->setPlural(TRUE); + } + if (!empty($context)) { + $item->setContext($context); + } + if (!empty($translation)) { + $item->setTranslation($translation); + } + + $this->assertEquals($expected, (string) $item); + } + +} diff --git a/core/tests/Drupal/Tests/Component/Gettext/PoStreamWriterTest.php b/core/tests/Drupal/Tests/Component/Gettext/PoStreamWriterTest.php index 667c2129770a..d38147f21ed6 100644 --- a/core/tests/Drupal/Tests/Component/Gettext/PoStreamWriterTest.php +++ b/core/tests/Drupal/Tests/Component/Gettext/PoStreamWriterTest.php @@ -9,13 +9,17 @@ use Drupal\Component\Gettext\PoItem; use Drupal\Component\Gettext\PoStreamWriter; use org\bovigo\vfs\vfsStream; use org\bovigo\vfs\vfsStreamFile; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; /** - * @coversDefaultClass \Drupal\Component\Gettext\PoStreamWriter - * @group Gettext + * Tests Drupal\Component\Gettext\PoStreamWriter. */ +#[CoversClass(PoStreamWriter::class)] +#[Group('Gettext')] class PoStreamWriterTest extends TestCase { use ProphecyTrait; @@ -51,7 +55,7 @@ class PoStreamWriterTest extends TestCase { } /** - * @covers ::getURI + * @legacy-covers ::getURI */ public function testGetUriException(): void { $this->expectException(\Exception::class); @@ -61,9 +65,9 @@ class PoStreamWriterTest extends TestCase { } /** - * @covers ::writeItem - * @dataProvider providerWriteData + * @legacy-covers ::writeItem */ + #[DataProvider('providerWriteData')] public function testWriteItem($poContent, $expected, $long): void { if ($long) { $this->expectException(\Exception::class); @@ -105,7 +109,7 @@ class PoStreamWriterTest extends TestCase { } /** - * @covers ::close + * @legacy-covers ::close */ public function testCloseException(): void { $this->expectException(\Exception::class); diff --git a/core/tests/Drupal/Tests/Component/Graph/GraphTest.php b/core/tests/Drupal/Tests/Component/Graph/GraphTest.php index 61002b0199b3..cd0f46a3a1a9 100644 --- a/core/tests/Drupal/Tests/Component/Graph/GraphTest.php +++ b/core/tests/Drupal/Tests/Component/Graph/GraphTest.php @@ -5,12 +5,15 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Graph; use Drupal\Component\Graph\Graph; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Graph\Graph - * @group Graph + * Tests Drupal\Component\Graph\Graph. */ +#[CoversClass(Graph::class)] +#[Group('Graph')] class GraphTest extends TestCase { /** diff --git a/core/tests/Drupal/Tests/Component/HttpFoundation/SecuredRedirectResponseTest.php b/core/tests/Drupal/Tests/Component/HttpFoundation/SecuredRedirectResponseTest.php index 4fa007b1121c..5c5fc82e6339 100644 --- a/core/tests/Drupal/Tests/Component/HttpFoundation/SecuredRedirectResponseTest.php +++ b/core/tests/Drupal/Tests/Component/HttpFoundation/SecuredRedirectResponseTest.php @@ -5,23 +5,24 @@ declare(strict_types=1); namespace Drupal\Tests\Component\HttpFoundation; use Drupal\Component\HttpFoundation\SecuredRedirectResponse; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\RedirectResponse; /** * Test secure redirect base class. - * - * @group Routing - * @coversDefaultClass \Drupal\Component\HttpFoundation\SecuredRedirectResponse */ +#[CoversClass(SecuredRedirectResponse::class)] +#[Group('Routing')] class SecuredRedirectResponseTest extends TestCase { /** * Tests copying of redirect response. * - * @covers ::createFromRedirectResponse - * @covers ::fromResponse + * @legacy-covers ::createFromRedirectResponse + * @legacy-covers ::fromResponse */ public function testRedirectCopy(): void { $redirect = new RedirectResponse('/magic_redirect_url', 301, ['x-cache-foobar' => 123]); diff --git a/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageReadOnlyTest.php b/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageReadOnlyTest.php index 4d4c56ef1ebf..e1c05c7111cd 100644 --- a/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageReadOnlyTest.php +++ b/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageReadOnlyTest.php @@ -4,17 +4,19 @@ declare(strict_types=1); namespace Drupal\Tests\Component\PhpStorage; -use Drupal\Component\PhpStorage\FileStorage; use Drupal\Component\PhpStorage\FileReadOnlyStorage; +use Drupal\Component\PhpStorage\FileStorage; use Drupal\Component\Utility\Random; use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; /** - * @coversDefaultClass \Drupal\Component\PhpStorage\FileReadOnlyStorage - * - * @group Drupal - * @group PhpStorage + * Tests Drupal\Component\PhpStorage\FileReadOnlyStorage. */ +#[CoversClass(FileReadOnlyStorage::class)] +#[Group('Drupal')] +#[Group('PhpStorage')] class FileStorageReadOnlyTest extends PhpStorageTestBase { use ExpectDeprecationTrait; @@ -83,7 +85,7 @@ class FileStorageReadOnlyTest extends PhpStorageTestBase { } /** - * @covers ::deleteAll + * @legacy-covers ::deleteAll */ public function testDeleteAll(): void { // Random generator. diff --git a/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php b/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php index bc0d8e5e908e..e24013cfcc79 100644 --- a/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php +++ b/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php @@ -8,12 +8,15 @@ use Drupal\Component\PhpStorage\FileStorage; use Drupal\Component\Utility\Random; use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait; use org\bovigo\vfs\vfsStreamDirectory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; /** - * @coversDefaultClass \Drupal\Component\PhpStorage\FileStorage - * @group Drupal - * @group PhpStorage + * Tests Drupal\Component\PhpStorage\FileStorage. */ +#[CoversClass(FileStorage::class)] +#[Group('Drupal')] +#[Group('PhpStorage')] class FileStorageTest extends PhpStorageTestBase { use ExpectDeprecationTrait; @@ -40,10 +43,10 @@ class FileStorageTest extends PhpStorageTestBase { /** * Tests basic load/save/delete operations. * - * @covers ::load - * @covers ::save - * @covers ::exists - * @covers ::delete + * @legacy-covers ::load + * @legacy-covers ::save + * @legacy-covers ::exists + * @legacy-covers ::delete */ public function testCRUD(): void { $php = new FileStorage($this->standardSettings); @@ -51,7 +54,7 @@ class FileStorageTest extends PhpStorageTestBase { } /** - * @covers ::deleteAll + * @legacy-covers ::deleteAll */ public function testDeleteAll(): void { // Random generator. @@ -86,7 +89,7 @@ class FileStorageTest extends PhpStorageTestBase { } /** - * @covers ::createDirectory + * @legacy-covers ::createDirectory */ public function testCreateDirectoryFailWarning(): void { $directory = new vfsStreamDirectory('permissionDenied', 0200); diff --git a/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFastFileStorageTest.php b/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFastFileStorageTest.php index 6e65e0ca8dde..90896b95a173 100644 --- a/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFastFileStorageTest.php +++ b/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFastFileStorageTest.php @@ -4,14 +4,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\PhpStorage; +use Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; + /** * Tests the MTimeProtectedFastFileStorage implementation. - * - * @coversDefaultClass \Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage - * - * @group Drupal - * @group PhpStorage */ +#[CoversClass(MTimeProtectedFastFileStorage::class)] +#[Group('Drupal')] +#[Group('PhpStorage')] class MTimeProtectedFastFileStorageTest extends MTimeProtectedFileStorageBase { /** diff --git a/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageBase.php b/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageBase.php index e2fea54aeb13..20adb60eb9e8 100644 --- a/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageBase.php +++ b/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageBase.php @@ -7,10 +7,12 @@ namespace Drupal\Tests\Component\PhpStorage; use Drupal\Component\FileSecurity\FileSecurity; use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Random; +use PHPUnit\Framework\Attributes\Medium; /** * Base test class for MTime protected storage. */ +#[Medium] abstract class MTimeProtectedFileStorageBase extends PhpStorageTestBase { /** @@ -74,8 +76,6 @@ abstract class MTimeProtectedFileStorageBase extends PhpStorageTestBase { * mtime too. * * We need to delay over 1 second for mtime test. - * - * @medium */ public function testSecurity(): void { $php = new $this->storageClass($this->settings); diff --git a/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageTest.php b/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageTest.php index 0abed0e1233d..b58c6508af6b 100644 --- a/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageTest.php +++ b/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageTest.php @@ -4,14 +4,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\PhpStorage; +use Drupal\Component\PhpStorage\MTimeProtectedFileStorage; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; + /** * Tests the MTimeProtectedFileStorage implementation. - * - * @coversDefaultClass \Drupal\Component\PhpStorage\MTimeProtectedFileStorage - * - * @group Drupal - * @group PhpStorage */ +#[CoversClass(MTimeProtectedFileStorage::class)] +#[Group('Drupal')] +#[Group('PhpStorage')] class MTimeProtectedFileStorageTest extends MTimeProtectedFileStorageBase { /** diff --git a/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeBaseTest.php b/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeBaseTest.php index 1dfa467e5998..e43a2c3ce367 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeBaseTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeBaseTest.php @@ -5,17 +5,20 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Plugin\Attribute; use Drupal\Component\Plugin\Attribute\AttributeBase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Plugin\Attribute\AttributeBase - * @group Attribute + * Tests Drupal\Component\Plugin\Attribute\AttributeBase. */ +#[CoversClass(AttributeBase::class)] +#[Group('Attribute')] class AttributeBaseTest extends TestCase { /** - * @covers ::getProvider - * @covers ::setProvider + * @legacy-covers ::getProvider + * @legacy-covers ::setProvider */ public function testSetProvider(): void { $plugin = new AttributeBaseStub(id: '1'); @@ -24,7 +27,7 @@ class AttributeBaseTest extends TestCase { } /** - * @covers ::getId + * @legacy-covers ::getId */ public function testGetId(): void { $plugin = new AttributeBaseStub(id: 'example'); @@ -32,8 +35,8 @@ class AttributeBaseTest extends TestCase { } /** - * @covers ::getClass - * @covers ::setClass + * @legacy-covers ::getClass + * @legacy-covers ::setClass */ public function testSetClass(): void { $plugin = new AttributeBaseStub(id: '1'); diff --git a/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeClassDiscoveryCachedTest.php b/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeClassDiscoveryCachedTest.php index 92cd1ff1ef81..9fcb6a8f59f5 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeClassDiscoveryCachedTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeClassDiscoveryCachedTest.php @@ -5,16 +5,19 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Plugin\Attribute; use Composer\Autoload\ClassLoader; -use Drupal\Component\Plugin\Discovery\AttributeClassDiscovery; use Drupal\Component\FileCache\FileCacheFactory; +use Drupal\Component\Plugin\Discovery\AttributeClassDiscovery; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Plugin\Discovery\AttributeClassDiscovery - * @covers \Drupal\Component\Discovery\MissingClassDetectionClassLoader - * @group Attribute - * @runTestsInSeparateProcesses + * @legacy-covers \Drupal\Component\Discovery\MissingClassDetectionClassLoader */ +#[CoversClass(AttributeClassDiscovery::class)] +#[Group('Attribute')] +#[RunTestsInSeparateProcesses] class AttributeClassDiscoveryCachedTest extends TestCase { /** @@ -40,7 +43,7 @@ class AttributeClassDiscoveryCachedTest extends TestCase { /** * Tests that getDefinitions() retrieves the file cache correctly. * - * @covers ::getDefinitions + * @legacy-covers ::getDefinitions */ public function testGetDefinitions(): void { // Path to the classes which we'll discover and parse annotation. @@ -100,7 +103,7 @@ class AttributeClassDiscoveryCachedTest extends TestCase { /** * Tests discovery with missing traits. * - * @covers ::getDefinitions + * @legacy-covers ::getDefinitions */ public function testGetDefinitionsMissingTrait(): void { // Path to the classes which we'll discover and parse annotation. diff --git a/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeClassDiscoveryTest.php b/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeClassDiscoveryTest.php index aa20f8146f8e..be0664d1f198 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeClassDiscoveryTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Attribute/AttributeClassDiscoveryTest.php @@ -5,15 +5,19 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Plugin\Attribute; use Composer\Autoload\ClassLoader; -use Drupal\Component\Plugin\Discovery\AttributeClassDiscovery; use Drupal\Component\FileCache\FileCacheFactory; +use Drupal\Component\Plugin\Discovery\AttributeClassDiscovery; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Plugin\Discovery\AttributeClassDiscovery - * @group Attribute - * @runTestsInSeparateProcesses + * Tests Drupal\Component\Plugin\Discovery\AttributeClassDiscovery. */ +#[CoversClass(AttributeClassDiscovery::class)] +#[Group('Attribute')] +#[RunTestsInSeparateProcesses] class AttributeClassDiscoveryTest extends TestCase { /** @@ -36,8 +40,8 @@ class AttributeClassDiscoveryTest extends TestCase { } /** - * @covers ::__construct - * @covers ::getPluginNamespaces + * @legacy-covers ::__construct + * @legacy-covers ::getPluginNamespaces */ public function testGetPluginNamespaces(): void { // Path to the classes which we'll discover and parse annotation. @@ -51,8 +55,8 @@ class AttributeClassDiscoveryTest extends TestCase { } /** - * @covers ::getDefinitions - * @covers ::prepareAttributeDefinition + * @legacy-covers ::getDefinitions + * @legacy-covers ::prepareAttributeDefinition */ public function testGetDefinitions(): void { $discovery = new AttributeClassDiscovery(['com\example' => [__DIR__ . "/../../../../../fixtures/plugins/Plugin"]]); diff --git a/core/tests/Drupal/Tests/Component/Plugin/Attribute/PluginIdTest.php b/core/tests/Drupal/Tests/Component/Plugin/Attribute/PluginIdTest.php index 2791af3d3f7b..0d1fbb93e6e5 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Attribute/PluginIdTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Attribute/PluginIdTest.php @@ -5,16 +5,19 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Plugin\Attribute; use Drupal\Component\Plugin\Attribute\PluginID; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Plugin\Attribute\PluginID - * @group Attribute + * Tests Drupal\Component\Plugin\Attribute\PluginID. */ +#[CoversClass(PluginID::class)] +#[Group('Attribute')] class PluginIdTest extends TestCase { /** - * @covers ::get + * @legacy-covers ::get */ public function testGet(): void { // Assert plugin starts with only an ID. diff --git a/core/tests/Drupal/Tests/Component/Plugin/Attribute/PluginTest.php b/core/tests/Drupal/Tests/Component/Plugin/Attribute/PluginTest.php index 5cad2a940673..d241d5509fb3 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Attribute/PluginTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Attribute/PluginTest.php @@ -5,17 +5,20 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Plugin\Attribute; use Drupal\Component\Plugin\Attribute\Plugin; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Plugin\Attribute\Plugin - * @group Attribute + * Tests Drupal\Component\Plugin\Attribute\Plugin. */ +#[CoversClass(Plugin::class)] +#[Group('Attribute')] class PluginTest extends TestCase { /** - * @covers ::__construct - * @covers ::get + * @legacy-covers ::__construct + * @legacy-covers ::get */ public function testGet(): void { $plugin = new PluginStub(id: 'example', deriver: 'test'); @@ -28,8 +31,8 @@ class PluginTest extends TestCase { } /** - * @covers ::setProvider - * @covers ::getProvider + * @legacy-covers ::setProvider + * @legacy-covers ::getProvider */ public function testSetProvider(): void { $plugin = new Plugin(id: 'example'); @@ -38,7 +41,7 @@ class PluginTest extends TestCase { } /** - * @covers ::getId + * @legacy-covers ::getId */ public function testGetId(): void { $plugin = new Plugin(id: 'example'); @@ -46,8 +49,8 @@ class PluginTest extends TestCase { } /** - * @covers ::setClass - * @covers ::getClass + * @legacy-covers ::setClass + * @legacy-covers ::getClass */ public function testSetClass(): void { $plugin = new Plugin(id: 'test'); diff --git a/core/tests/Drupal/Tests/Component/Plugin/Context/ContextTest.php b/core/tests/Drupal/Tests/Component/Plugin/Context/ContextTest.php index 6004e705acf8..fe5413c505f5 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Context/ContextTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Context/ContextTest.php @@ -5,12 +5,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Plugin\Context; use Drupal\Component\Plugin\Context\Context; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Plugin\Context\Context - * @group Plugin + * Tests Drupal\Component\Plugin\Context\Context. */ +#[CoversClass(Context::class)] +#[Group('Plugin')] class ContextTest extends TestCase { /** @@ -25,9 +29,9 @@ class ContextTest extends TestCase { } /** - * @covers ::getContextValue - * @dataProvider providerGetContextValue + * @legacy-covers ::getContextValue */ + #[DataProvider('providerGetContextValue')] public function testGetContextValue($expected, $context_value, $is_required, $data_type): void { // Mock a Context object. $mock_context = $this->getMockBuilder('Drupal\Component\Plugin\Context\Context') @@ -97,9 +101,9 @@ class ContextTest extends TestCase { } /** - * @covers ::hasContextValue - * @dataProvider providerHasContextValue + * @legacy-covers ::hasContextValue */ + #[DataProvider('providerHasContextValue')] public function testHasContextValue($has_context_value, $default_value): void { $mock_definition = $this->createMock('Drupal\Component\Plugin\Context\ContextDefinitionInterface'); @@ -114,7 +118,7 @@ class ContextTest extends TestCase { } /** - * @covers ::getContextValue + * @legacy-covers ::getContextValue */ public function testDefaultValue(): void { $mock_definition = $this->createMock('Drupal\Component\Plugin\Context\ContextDefinitionInterface'); diff --git a/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php b/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php index f92d274cd858..16fa9ad86c4b 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php @@ -10,18 +10,21 @@ use Drupal\Component\Plugin\Factory\DefaultFactory; use Drupal\Tests\Component\Plugin\Fixtures\vegetable\Broccoli; use Drupal\Tests\Component\Plugin\Fixtures\vegetable\Corn; use Drupal\Tests\Component\Plugin\Fixtures\vegetable\VegetableInterface; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Plugin\Factory\DefaultFactory - * @group Plugin + * Tests Drupal\Component\Plugin\Factory\DefaultFactory. */ +#[CoversClass(DefaultFactory::class)] +#[Group('Plugin')] class DefaultFactoryTest extends TestCase { /** * Tests getPluginClass() with a valid array plugin definition. * - * @covers ::getPluginClass + * @legacy-covers ::getPluginClass */ public function testGetPluginClassWithValidArrayPluginDefinition(): void { $plugin_class = Corn::class; @@ -33,7 +36,7 @@ class DefaultFactoryTest extends TestCase { /** * Tests getPluginClass() with a valid object plugin definition. * - * @covers ::getPluginClass + * @legacy-covers ::getPluginClass */ public function testGetPluginClassWithValidObjectPluginDefinition(): void { $plugin_class = Corn::class; @@ -49,7 +52,7 @@ class DefaultFactoryTest extends TestCase { /** * Tests getPluginClass() with a missing class definition. * - * @covers ::getPluginClass + * @legacy-covers ::getPluginClass */ public function testGetPluginClassWithMissingClassWithArrayPluginDefinition(): void { $this->expectException(PluginException::class); @@ -60,7 +63,7 @@ class DefaultFactoryTest extends TestCase { /** * Tests getPluginClass() with a missing class definition. * - * @covers ::getPluginClass + * @legacy-covers ::getPluginClass */ public function testGetPluginClassWithMissingClassWithObjectPluginDefinition(): void { $plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class) @@ -73,7 +76,7 @@ class DefaultFactoryTest extends TestCase { /** * Tests getPluginClass() with a non-existent class definition. * - * @covers ::getPluginClass + * @legacy-covers ::getPluginClass */ public function testGetPluginClassWithNotExistingClassWithArrayPluginDefinition(): void { $this->expectException(PluginException::class); @@ -84,7 +87,7 @@ class DefaultFactoryTest extends TestCase { /** * Tests getPluginClass() with a non-existent class definition. * - * @covers ::getPluginClass + * @legacy-covers ::getPluginClass */ public function testGetPluginClassWithNotExistingClassWithObjectPluginDefinition(): void { $plugin_class = 'Drupal\Tests\Component\Plugin\Fixtures\vegetable\Carrot'; @@ -99,7 +102,7 @@ class DefaultFactoryTest extends TestCase { /** * Tests getPluginClass() with a required interface. * - * @covers ::getPluginClass + * @legacy-covers ::getPluginClass */ public function testGetPluginClassWithInterfaceWithArrayPluginDefinition(): void { $plugin_class = Corn::class; @@ -111,7 +114,7 @@ class DefaultFactoryTest extends TestCase { /** * Tests getPluginClass() with a required interface. * - * @covers ::getPluginClass + * @legacy-covers ::getPluginClass */ public function testGetPluginClassWithInterfaceWithObjectPluginDefinition(): void { $plugin_class = Corn::class; @@ -127,7 +130,7 @@ class DefaultFactoryTest extends TestCase { /** * Tests getPluginClass() with a required interface but no implementation. * - * @covers ::getPluginClass + * @legacy-covers ::getPluginClass */ public function testGetPluginClassWithInterfaceAndInvalidClassWithArrayPluginDefinition(): void { $this->expectException(PluginException::class); @@ -138,7 +141,7 @@ class DefaultFactoryTest extends TestCase { /** * Tests getPluginClass() with a required interface but no implementation. * - * @covers ::getPluginClass + * @legacy-covers ::getPluginClass */ public function testGetPluginClassWithInterfaceAndInvalidClassWithObjectPluginDefinition(): void { $plugin_class = Broccoli::class; diff --git a/core/tests/Drupal/Tests/Component/Plugin/Discovery/AnnotatedClassDiscoveryTest.php b/core/tests/Drupal/Tests/Component/Plugin/Discovery/AnnotatedClassDiscoveryTest.php index 483764b3c0ea..8b632acae002 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Discovery/AnnotatedClassDiscoveryTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Discovery/AnnotatedClassDiscoveryTest.php @@ -9,14 +9,18 @@ use Drupal\Component\FileCache\FileCacheFactory; use org\bovigo\vfs\vfsStream; use org\bovigo\vfs\vfsStreamDirectory; use org\bovigo\vfs\vfsStreamWrapper; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery - * - * @group Annotation - * @group Plugin + * Tests Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery. */ +#[CoversClass(AnnotatedClassDiscovery::class)] +#[Group('Annotation')] +#[Group('Plugin')] class AnnotatedClassDiscoveryTest extends TestCase { /** @@ -53,11 +57,9 @@ class AnnotatedClassDiscoveryTest extends TestCase { /** * Make sure AnnotatedClassDiscovery never tries to autoload bad annotations. - * - * @dataProvider provideBadAnnotations - * - * @coversNothing */ + #[CoversNothing] + #[DataProvider('provideBadAnnotations')] public function testAutoloadBadAnnotations($annotation): void { // Set up a class file in vfsStream. vfsStreamWrapper::register(); diff --git a/core/tests/Drupal/Tests/Component/Plugin/Discovery/AttributeBridgeDecoratorTest.php b/core/tests/Drupal/Tests/Component/Plugin/Discovery/AttributeBridgeDecoratorTest.php index cc8d2aabcee9..a85c01c7ee63 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Discovery/AttributeBridgeDecoratorTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Discovery/AttributeBridgeDecoratorTest.php @@ -4,20 +4,24 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Plugin\Discovery; +use Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator; use Drupal\Component\Plugin\Attribute\Plugin; use Drupal\Component\Plugin\Definition\PluginDefinition; use Drupal\Component\Plugin\Discovery\AttributeBridgeDecorator; use Drupal\Component\Plugin\Discovery\DiscoveryInterface; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator - * @group Plugin + * Tests Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator. */ +#[CoversClass(AnnotationBridgeDecorator::class)] +#[Group('Plugin')] class AttributeBridgeDecoratorTest extends TestCase { /** - * @covers ::getDefinitions + * @legacy-covers ::getDefinitions */ public function testGetDefinitions(): void { // Normally the attribute classes would be autoloaded. @@ -47,7 +51,7 @@ class AttributeBridgeDecoratorTest extends TestCase { /** * Tests that the decorator of other methods works. * - * @covers ::__call + * @legacy-covers ::__call */ public function testOtherMethod(): void { // Normally the attribute classes would be autoloaded. diff --git a/core/tests/Drupal/Tests/Component/Plugin/Discovery/DiscoveryCachedTraitTest.php b/core/tests/Drupal/Tests/Component/Plugin/Discovery/DiscoveryCachedTraitTest.php index 9db89444c171..3368c9a14ab6 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Discovery/DiscoveryCachedTraitTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Discovery/DiscoveryCachedTraitTest.php @@ -5,13 +5,19 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Plugin\Discovery; use Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait; +use Drupal\Component\Plugin\Discovery\DiscoveryTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait - * @uses \Drupal\Component\Plugin\Discovery\DiscoveryTrait - * @group Plugin + * Tests Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait. */ +#[CoversClass(DiscoveryCachedTrait::class)] +#[Group('Plugin')] +#[UsesClass(DiscoveryTrait::class)] class DiscoveryCachedTraitTest extends TestCase { /** @@ -32,9 +38,9 @@ class DiscoveryCachedTraitTest extends TestCase { } /** - * @covers ::getDefinition - * @dataProvider providerGetDefinition + * @legacy-covers ::getDefinition */ + #[DataProvider('providerGetDefinition')] public function testGetDefinition($expected, $cached_definitions, $get_definitions, $plugin_id): void { $trait = $this->getMockBuilder(DiscoveryCachedTraitMockableClass::class) ->onlyMethods(['getDefinitions']) diff --git a/core/tests/Drupal/Tests/Component/Plugin/Discovery/DiscoveryTraitTest.php b/core/tests/Drupal/Tests/Component/Plugin/Discovery/DiscoveryTraitTest.php index d26a538aad62..f85eeeb193cc 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Discovery/DiscoveryTraitTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Discovery/DiscoveryTraitTest.php @@ -6,12 +6,19 @@ namespace Drupal\Tests\Component\Plugin\Discovery; use Drupal\Component\Plugin\Discovery\DiscoveryTrait; use Drupal\Component\Plugin\Exception\PluginNotFoundException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; /** - * @group Plugin - * @coversDefaultClass \Drupal\Component\Plugin\Discovery\DiscoveryTrait + * Tests Drupal\Component\Plugin\Discovery\DiscoveryTrait. */ +#[CoversClass(DiscoveryTrait::class)] +#[Group('Plugin')] +#[UsesClass(PluginNotFoundException::class)] +#[UsesClass(PluginNotFoundException::class)] class DiscoveryTraitTest extends TestCase { /** @@ -30,9 +37,9 @@ class DiscoveryTraitTest extends TestCase { } /** - * @covers ::doGetDefinition - * @dataProvider providerDoGetDefinition + * @legacy-covers ::doGetDefinition */ + #[DataProvider('providerDoGetDefinition')] public function testDoGetDefinition($expected, $definitions, $plugin_id): void { $trait = new DiscoveryTraitMockableClass(); // Un-protect the method using reflection. @@ -59,10 +66,9 @@ class DiscoveryTraitTest extends TestCase { } /** - * @covers ::doGetDefinition - * @dataProvider providerDoGetDefinitionException - * @uses \Drupal\Component\Plugin\Exception\PluginNotFoundException + * @legacy-covers ::doGetDefinition */ + #[DataProvider('providerDoGetDefinitionException')] public function testDoGetDefinitionException($expected, $definitions, $plugin_id): void { $trait = new DiscoveryTraitMockableClass(); // Un-protect the method using reflection. @@ -73,9 +79,9 @@ class DiscoveryTraitTest extends TestCase { } /** - * @covers ::getDefinition - * @dataProvider providerDoGetDefinition + * @legacy-covers ::getDefinition */ + #[DataProvider('providerDoGetDefinition')] public function testGetDefinition($expected, $definitions, $plugin_id): void { // Since getDefinition is a wrapper around doGetDefinition(), we can re-use // its data provider. We just have to tell abstract method getDefinitions() @@ -94,10 +100,9 @@ class DiscoveryTraitTest extends TestCase { } /** - * @covers ::getDefinition - * @dataProvider providerDoGetDefinitionException - * @uses \Drupal\Component\Plugin\Exception\PluginNotFoundException + * @legacy-covers ::getDefinition */ + #[DataProvider('providerDoGetDefinitionException')] public function testGetDefinitionException($expected, $definitions, $plugin_id): void { // Since getDefinition is a wrapper around doGetDefinition(), we can re-use // its data provider. We just have to tell abstract method getDefinitions() @@ -128,9 +133,9 @@ class DiscoveryTraitTest extends TestCase { } /** - * @covers ::hasDefinition - * @dataProvider providerHasDefinition + * @legacy-covers ::hasDefinition */ + #[DataProvider('providerHasDefinition')] public function testHasDefinition($expected, $plugin_id): void { $trait = $this->getMockBuilder(DiscoveryTraitMockableClass::class) ->onlyMethods(['getDefinition']) diff --git a/core/tests/Drupal/Tests/Component/Plugin/Discovery/StaticDiscoveryDecoratorTest.php b/core/tests/Drupal/Tests/Component/Plugin/Discovery/StaticDiscoveryDecoratorTest.php index b2f430416a77..2de43953ea0a 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Discovery/StaticDiscoveryDecoratorTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Discovery/StaticDiscoveryDecoratorTest.php @@ -7,12 +7,16 @@ namespace Drupal\Tests\Component\Plugin\Discovery; use Drupal\Component\Plugin\Discovery\DiscoveryInterface; use Drupal\Component\Plugin\Discovery\StaticDiscovery; use Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @group Plugin - * @coversDefaultClass \Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator + * Tests Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator. */ +#[CoversClass(StaticDiscoveryDecorator::class)] +#[Group('Plugin')] class StaticDiscoveryDecoratorTest extends TestCase { /** @@ -58,9 +62,9 @@ class StaticDiscoveryDecoratorTest extends TestCase { } /** - * @covers ::getDefinition - * @dataProvider providerGetDefinition + * @legacy-covers ::getDefinition */ + #[DataProvider('providerGetDefinition')] public function testGetDefinition($expected, $has_register_definitions, $exception_on_invalid, $definitions, $base_plugin_id): void { // Mock our StaticDiscoveryDecorator. $mock_decorator = $this->getMockBuilder(StaticDiscoveryDecorator::class) @@ -123,9 +127,9 @@ class StaticDiscoveryDecoratorTest extends TestCase { } /** - * @covers ::getDefinitions - * @dataProvider providerGetDefinitions + * @legacy-covers ::getDefinitions */ + #[DataProvider('providerGetDefinitions')] public function testGetDefinitions($has_register_definitions, $definitions): void { // Mock our StaticDiscoveryDecorator. $mock_decorator = $this->getMockBuilder(StaticDiscoveryDecorator::class) @@ -186,9 +190,9 @@ class StaticDiscoveryDecoratorTest extends TestCase { } /** - * @covers ::__call - * @dataProvider providerCall + * @legacy-covers ::__call */ + #[DataProvider('providerCall')] public function testCall($method, $args): void { // Mock a decorated object. $mock_decorated = $this->getMockBuilder(StaticDiscoveryTestDecoratedClass::class) diff --git a/core/tests/Drupal/Tests/Component/Plugin/Factory/ReflectionFactoryTest.php b/core/tests/Drupal/Tests/Component/Plugin/Factory/ReflectionFactoryTest.php index 860c96c6e22a..282b2f4edf37 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/Factory/ReflectionFactoryTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/Factory/ReflectionFactoryTest.php @@ -5,12 +5,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Plugin\Factory; use Drupal\Component\Plugin\Factory\ReflectionFactory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @group Plugin - * @coversDefaultClass \Drupal\Component\Plugin\Factory\ReflectionFactory + * Tests Drupal\Component\Plugin\Factory\ReflectionFactory. */ +#[CoversClass(ReflectionFactory::class)] +#[Group('Plugin')] class ReflectionFactoryTest extends TestCase { /** @@ -77,9 +81,9 @@ class ReflectionFactoryTest extends TestCase { } /** - * @covers ::createInstance - * @dataProvider providerGetInstanceArguments + * @legacy-covers ::createInstance */ + #[DataProvider('providerGetInstanceArguments')] public function testCreateInstance($expected, $reflector_name, $plugin_id, $plugin_definition, $configuration): void { // Create a mock DiscoveryInterface which can return our plugin definition. $mock_discovery = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface') @@ -102,9 +106,9 @@ class ReflectionFactoryTest extends TestCase { } /** - * @covers ::getInstanceArguments - * @dataProvider providerGetInstanceArguments + * @legacy-covers ::getInstanceArguments */ + #[DataProvider('providerGetInstanceArguments')] public function testGetInstanceArguments($expected, $reflector_name, $plugin_id, $plugin_definition, $configuration): void { $reflection_factory = $this->getMockBuilder('Drupal\Component\Plugin\Factory\ReflectionFactory') ->disableOriginalConstructor() diff --git a/core/tests/Drupal/Tests/Component/Plugin/PluginManagerBaseTest.php b/core/tests/Drupal/Tests/Component/Plugin/PluginManagerBaseTest.php index 9842f252a839..d4bd2ff757de 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/PluginManagerBaseTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/PluginManagerBaseTest.php @@ -6,13 +6,17 @@ namespace Drupal\Tests\Component\Plugin; use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Component\Plugin\Mapper\MapperInterface; +use Drupal\Component\Plugin\PluginManagerBase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; /** - * @coversDefaultClass \Drupal\Component\Plugin\PluginManagerBase - * @group Plugin + * Tests Drupal\Component\Plugin\PluginManagerBase. */ +#[CoversClass(PluginManagerBase::class)] +#[Group('Plugin')] class PluginManagerBaseTest extends TestCase { use ProphecyTrait; @@ -47,7 +51,7 @@ class PluginManagerBaseTest extends TestCase { /** * Tests createInstance() with no fallback methods. * - * @covers ::createInstance + * @legacy-covers ::createInstance */ public function testCreateInstance(): void { $manager = new StubPluginManagerBase(); @@ -66,7 +70,7 @@ class PluginManagerBaseTest extends TestCase { /** * Tests createInstance() with a fallback method. * - * @covers ::createInstance + * @legacy-covers ::createInstance */ public function testCreateInstanceFallback(): void { // We use our special stub class which extends PluginManagerBase and also @@ -92,7 +96,7 @@ class PluginManagerBaseTest extends TestCase { } /** - * @covers ::getInstance + * @legacy-covers ::getInstance */ public function testGetInstance(): void { $options = [ @@ -109,7 +113,7 @@ class PluginManagerBaseTest extends TestCase { } /** - * @covers ::getInstance + * @legacy-covers ::getInstance */ public function testGetInstanceWithoutMapperShouldThrowException(): void { $options = [ diff --git a/core/tests/Drupal/Tests/Component/ProxyBuilder/ProxyBuilderTest.php b/core/tests/Drupal/Tests/Component/ProxyBuilder/ProxyBuilderTest.php index 5545b2e01409..26ab691bf1af 100644 --- a/core/tests/Drupal/Tests/Component/ProxyBuilder/ProxyBuilderTest.php +++ b/core/tests/Drupal/Tests/Component/ProxyBuilder/ProxyBuilderTest.php @@ -5,12 +5,15 @@ declare(strict_types=1); namespace Drupal\Tests\Component\ProxyBuilder; use Drupal\Component\ProxyBuilder\ProxyBuilder; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\ProxyBuilder\ProxyBuilder - * @group proxy_builder + * Tests Drupal\Component\ProxyBuilder\ProxyBuilder. */ +#[CoversClass(ProxyBuilder::class)] +#[Group('proxy_builder')] class ProxyBuilderTest extends TestCase { /** @@ -30,7 +33,7 @@ class ProxyBuilderTest extends TestCase { } /** - * @covers ::buildProxyClassName + * @legacy-covers ::buildProxyClassName */ public function testBuildProxyClassName(): void { $class_name = $this->proxyBuilder->buildProxyClassName('Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod'); @@ -38,7 +41,7 @@ class ProxyBuilderTest extends TestCase { } /** - * @covers ::buildProxyClassName + * @legacy-covers ::buildProxyClassName */ public function testBuildProxyClassNameForModule(): void { $class_name = $this->proxyBuilder->buildProxyClassName('Drupal\views_ui\ParamConverter\ViewUIConverter'); @@ -46,7 +49,7 @@ class ProxyBuilderTest extends TestCase { } /** - * @covers ::buildProxyNamespace + * @legacy-covers ::buildProxyNamespace */ public function testBuildProxyNamespace(): void { $class_name = $this->proxyBuilder->buildProxyNamespace('Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod'); @@ -56,9 +59,9 @@ class ProxyBuilderTest extends TestCase { /** * Tests the basic methods like the constructor and the lazyLoadItself method. * - * @covers ::build - * @covers ::buildConstructorMethod - * @covers ::buildLazyLoadItselfMethod + * @legacy-covers ::build + * @legacy-covers ::buildConstructorMethod + * @legacy-covers ::buildLazyLoadItselfMethod */ public function testBuildNoMethod(): void { $class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod'; @@ -68,8 +71,8 @@ class ProxyBuilderTest extends TestCase { } /** - * @covers ::buildMethod - * @covers ::buildMethodBody + * @legacy-covers ::buildMethod + * @legacy-covers ::buildMethodBody */ public function testBuildSimpleMethod(): void { $class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceSimpleMethod'; @@ -91,9 +94,9 @@ EOS; } /** - * @covers ::buildMethod - * @covers ::buildParameter - * @covers ::buildMethodBody + * @legacy-covers ::buildMethod + * @legacy-covers ::buildParameter + * @legacy-covers ::buildMethodBody */ public function testBuildMethodWithParameter(): void { $class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceMethodWithParameter'; @@ -115,9 +118,9 @@ EOS; } /** - * @covers ::buildMethod - * @covers ::buildParameter - * @covers ::buildMethodBody + * @legacy-covers ::buildMethod + * @legacy-covers ::buildParameter + * @legacy-covers ::buildMethodBody */ public function testBuildComplexMethod(): void { $class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceComplexMethod'; @@ -142,7 +145,7 @@ EOS; } /** - * @covers ::buildMethodBody + * @legacy-covers ::buildMethodBody */ public function testBuildServiceMethodReturnsVoid(): void { $class = TestServiceMethodReturnsVoid::class; @@ -166,8 +169,8 @@ EOS; } /** - * @covers ::buildMethod - * @covers ::buildMethodBody + * @legacy-covers ::buildMethod + * @legacy-covers ::buildMethodBody */ public function testBuildReturnReference(): void { $class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceReturnReference'; @@ -191,9 +194,9 @@ EOS; } /** - * @covers ::buildMethod - * @covers ::buildParameter - * @covers ::buildMethodBody + * @legacy-covers ::buildMethod + * @legacy-covers ::buildParameter + * @legacy-covers ::buildMethodBody */ public function testBuildWithInterface(): void { $class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithInterface'; @@ -217,7 +220,7 @@ EOS; } /** - * @covers ::build + * @legacy-covers ::build */ public function testBuildWithNestedInterface(): void { $class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithChildInterfaces'; @@ -230,9 +233,9 @@ EOS; } /** - * @covers ::buildMethod - * @covers ::buildParameter - * @covers ::buildMethodBody + * @legacy-covers ::buildMethod + * @legacy-covers ::buildParameter + * @legacy-covers ::buildMethodBody */ public function testBuildWithProtectedAndPrivateMethod(): void { $class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithProtectedMethods'; @@ -255,9 +258,9 @@ EOS; } /** - * @covers ::buildMethod - * @covers ::buildParameter - * @covers ::buildMethodBody + * @legacy-covers ::buildMethod + * @legacy-covers ::buildParameter + * @legacy-covers ::buildMethodBody */ public function testBuildWithPublicStaticMethod(): void { $class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithPublicStaticMethod'; @@ -281,9 +284,9 @@ EOS; } /** - * @covers ::buildMethod - * @covers ::buildParameter - * @covers ::buildMethodBody + * @legacy-covers ::buildMethod + * @legacy-covers ::buildParameter + * @legacy-covers ::buildMethodBody */ public function testBuildWithNullableSelfTypeHint(): void { $class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceNullableTypeHintSelf'; diff --git a/core/tests/Drupal/Tests/Component/Render/FormattableMarkupTest.php b/core/tests/Drupal/Tests/Component/Render/FormattableMarkupTest.php index 5e82b2daaef1..4547de6b9d39 100644 --- a/core/tests/Drupal/Tests/Component/Render/FormattableMarkupTest.php +++ b/core/tests/Drupal/Tests/Component/Render/FormattableMarkupTest.php @@ -6,14 +6,16 @@ namespace Drupal\Tests\Component\Render; use Drupal\Component\Render\FormattableMarkup; use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests the TranslatableMarkup class. - * - * @coversDefaultClass \Drupal\Component\Render\FormattableMarkup - * @group utility */ +#[CoversClass(FormattableMarkup::class)] +#[Group('utility')] class FormattableMarkupTest extends TestCase { use ExpectDeprecationTrait; @@ -33,8 +35,8 @@ class FormattableMarkupTest extends TestCase { protected $lastErrorNumber; /** - * @covers ::__toString - * @covers ::jsonSerialize + * @legacy-covers ::__toString + * @legacy-covers ::jsonSerialize */ public function testToString(): void { $string = 'Can I have a @replacement'; @@ -46,7 +48,7 @@ class FormattableMarkupTest extends TestCase { } /** - * @covers ::count + * @legacy-covers ::count */ public function testCount(): void { $string = 'Can I have a @replacement'; @@ -72,9 +74,9 @@ class FormattableMarkupTest extends TestCase { } /** - * @covers ::__toString - * @dataProvider providerTestUnexpectedPlaceholder + * @legacy-covers ::__toString */ + #[DataProvider('providerTestUnexpectedPlaceholder')] public function testUnexpectedPlaceholder($string, $arguments, $error_number, $error_message): void { // We set a custom error handler because of // https://github.com/sebastianbergmann/phpunit/issues/487 diff --git a/core/tests/Drupal/Tests/Component/Render/HtmlEscapedTextTest.php b/core/tests/Drupal/Tests/Component/Render/HtmlEscapedTextTest.php index 2b10bbfbd79f..ff5e407d81b0 100644 --- a/core/tests/Drupal/Tests/Component/Render/HtmlEscapedTextTest.php +++ b/core/tests/Drupal/Tests/Component/Render/HtmlEscapedTextTest.php @@ -6,23 +6,24 @@ namespace Drupal\Tests\Component\Render; use Drupal\Component\Render\HtmlEscapedText; use Drupal\Component\Render\MarkupInterface; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Prophecy\Prophet; /** * Tests the HtmlEscapedText class. - * - * @coversDefaultClass \Drupal\Component\Render\HtmlEscapedText - * @group utility */ +#[CoversClass(HtmlEscapedText::class)] +#[Group('utility')] class HtmlEscapedTextTest extends TestCase { /** - * @covers ::__toString - * @covers ::jsonSerialize - * - * @dataProvider providerToString + * @legacy-covers ::__toString + * @legacy-covers ::jsonSerialize */ + #[DataProvider('providerToString')] public function testToString($text, $expected, $message): void { $escapable_string = new HtmlEscapedText($text); $this->assertEquals($expected, (string) $escapable_string, $message); @@ -58,7 +59,7 @@ class HtmlEscapedTextTest extends TestCase { } /** - * @covers ::count + * @legacy-covers ::count */ public function testCount(): void { $string = 'Can I have a <em>kitten</em>'; diff --git a/core/tests/Drupal/Tests/Component/Render/PlainTextOutputTest.php b/core/tests/Drupal/Tests/Component/Render/PlainTextOutputTest.php index 21e8830fa89c..57d09fddc83d 100644 --- a/core/tests/Drupal/Tests/Component/Render/PlainTextOutputTest.php +++ b/core/tests/Drupal/Tests/Component/Render/PlainTextOutputTest.php @@ -4,16 +4,20 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Render; -use Drupal\Component\Render\PlainTextOutput; use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Render\MarkupInterface; +use Drupal\Component\Render\PlainTextOutput; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Prophecy\Prophet; /** - * @coversDefaultClass \Drupal\Component\Render\PlainTextOutput - * @group Utility + * Tests Drupal\Component\Render\PlainTextOutput. */ +#[CoversClass(PlainTextOutput::class)] +#[Group('Utility')] class PlainTextOutputTest extends TestCase { /** @@ -27,9 +31,9 @@ class PlainTextOutputTest extends TestCase { * (optional) An associative array of replacements to make. Defaults to * none. * - * @covers ::renderFromHtml - * @dataProvider providerRenderFromHtml + * @legacy-covers ::renderFromHtml */ + #[DataProvider('providerRenderFromHtml')] public function testRenderFromHtml($expected, $string, $args = []): void { $markup = new FormattableMarkup($string, $args); $output = PlainTextOutput::renderFromHtml($markup); diff --git a/core/tests/Drupal/Tests/Component/Serialization/JsonTest.php b/core/tests/Drupal/Tests/Component/Serialization/JsonTest.php index 9f24283f962f..3e9169c5969b 100644 --- a/core/tests/Drupal/Tests/Component/Serialization/JsonTest.php +++ b/core/tests/Drupal/Tests/Component/Serialization/JsonTest.php @@ -5,12 +5,15 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Serialization; use Drupal\Component\Serialization\Json; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Serialization\Json - * @group Serialization + * Tests Drupal\Component\Serialization\Json. */ +#[CoversClass(Json::class)] +#[Group('Serialization')] class JsonTest extends TestCase { /** diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php index 8f124a85ba95..35f45159a77f 100644 --- a/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php @@ -6,24 +6,27 @@ namespace Drupal\Tests\Component\Serialization; use Drupal\Component\Serialization\Exception\InvalidDataTypeException; use Drupal\Component\Serialization\YamlPecl; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; /** * Tests the YamlPecl serialization implementation. - * - * @group Drupal - * @group Serialization - * @coversDefaultClass \Drupal\Component\Serialization\YamlPecl - * @requires extension yaml */ +#[CoversClass(YamlPecl::class)] +#[Group('Drupal')] +#[Group('Serialization')] +#[RequiresPhpExtension('yaml')] class YamlPeclTest extends YamlTestBase { /** * Tests encoding and decoding basic data structures. * - * @covers ::encode - * @covers ::decode - * @dataProvider providerEncodeDecodeTests + * @legacy-covers ::encode + * @legacy-covers ::decode */ + #[DataProvider('providerEncodeDecodeTests')] public function testEncodeDecode(array $data): void { $this->assertEquals($data, YamlPecl::decode(YamlPecl::encode($data))); } @@ -41,9 +44,9 @@ class YamlPeclTest extends YamlTestBase { /** * Tests decoding YAML node anchors. * - * @covers ::decode - * @dataProvider providerDecodeTests + * @legacy-covers ::decode */ + #[DataProvider('providerDecodeTests')] public function testDecode($string, $data): void { $this->assertEquals($data, YamlPecl::decode($string)); } @@ -51,7 +54,7 @@ class YamlPeclTest extends YamlTestBase { /** * Tests our encode settings. * - * @covers ::encode + * @legacy-covers ::encode */ public function testEncode(): void { // cSpell:disable @@ -71,15 +74,15 @@ foo: * @param string|bool $expected * The expected return value. * - * @covers ::applyBooleanCallbacks - * @dataProvider providerBoolTest + * @legacy-covers ::applyBooleanCallbacks */ + #[DataProvider('providerBoolTest')] public function testApplyBooleanCallbacks($string, $expected): void { $this->assertEquals($expected, YamlPecl::applyBooleanCallbacks($string, 'bool', NULL)); } /** - * @covers ::getFileExtension + * @legacy-covers ::getFileExtension */ public function testGetFileExtension(): void { $this->assertEquals('yml', YamlPecl::getFileExtension()); @@ -88,7 +91,7 @@ foo: /** * Tests that invalid YAML throws an exception. * - * @covers ::errorHandler + * @legacy-covers ::errorHandler */ public function testError(): void { $this->expectException(InvalidDataTypeException::class); diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php index 7980f5845357..ef7e51a5101f 100644 --- a/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php @@ -6,23 +6,25 @@ namespace Drupal\Tests\Component\Serialization; use Drupal\Component\Serialization\Exception\InvalidDataTypeException; use Drupal\Component\Serialization\Yaml; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; /** * Tests the Yaml serialization implementation. - * - * @group Drupal - * @group Serialization - * @coversDefaultClass \Drupal\Component\Serialization\Yaml */ +#[CoversClass(Yaml::class)] +#[Group('Drupal')] +#[Group('Serialization')] class YamlTest extends YamlTestBase { /** * Tests encoding and decoding basic data structures. * - * @covers ::encode - * @covers ::decode - * @dataProvider providerEncodeDecodeTests + * @legacy-covers ::encode + * @legacy-covers ::decode */ + #[DataProvider('providerEncodeDecodeTests')] public function testEncodeDecode(array $data): void { $this->assertSame($data, Yaml::decode(Yaml::encode($data))); } @@ -30,9 +32,9 @@ class YamlTest extends YamlTestBase { /** * Tests decoding YAML node anchors. * - * @covers ::decode - * @dataProvider providerDecodeTests + * @legacy-covers ::decode */ + #[DataProvider('providerDecodeTests')] public function testDecode($string, $data): void { $this->assertSame($data, Yaml::decode($string)); } @@ -40,7 +42,7 @@ class YamlTest extends YamlTestBase { /** * Tests our encode settings. * - * @covers ::encode + * @legacy-covers ::encode */ public function testEncode(): void { // cSpell:disable @@ -51,7 +53,7 @@ class YamlTest extends YamlTestBase { } /** - * @covers ::getFileExtension + * @legacy-covers ::getFileExtension */ public function testGetFileExtension(): void { $this->assertSame('yml', Yaml::getFileExtension()); @@ -60,7 +62,7 @@ class YamlTest extends YamlTestBase { /** * Tests that invalid YAML throws an exception. * - * @covers ::decode + * @legacy-covers ::decode */ public function testError(): void { $this->expectException(InvalidDataTypeException::class); @@ -70,7 +72,7 @@ class YamlTest extends YamlTestBase { /** * Ensures that php object support is disabled. * - * @covers ::encode + * @legacy-covers ::encode */ public function testEncodeObjectSupportDisabled(): void { $this->expectException(InvalidDataTypeException::class); @@ -83,7 +85,7 @@ class YamlTest extends YamlTestBase { /** * Ensures that decoding PHP objects does not work in Symfony. * - * @covers ::decode + * @legacy-covers ::decode */ public function testDecodeObjectSupportDisabled(): void { $this->expectException(InvalidDataTypeException::class); diff --git a/core/tests/Drupal/Tests/Component/Transliteration/PhpTransliterationTest.php b/core/tests/Drupal/Tests/Component/Transliteration/PhpTransliterationTest.php index 3badd32fab84..aa26390ae785 100644 --- a/core/tests/Drupal/Tests/Component/Transliteration/PhpTransliterationTest.php +++ b/core/tests/Drupal/Tests/Component/Transliteration/PhpTransliterationTest.php @@ -7,15 +7,16 @@ namespace Drupal\Tests\Component\Transliteration; use Drupal\Component\Transliteration\PhpTransliteration; use Drupal\Component\Utility\Random; use org\bovigo\vfs\vfsStream; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests Transliteration component functionality. - * - * @group Transliteration - * - * @coversDefaultClass \Drupal\Component\Transliteration\PhpTransliteration */ +#[CoversClass(PhpTransliteration::class)] +#[Group('Transliteration')] class PhpTransliterationTest extends TestCase { /** @@ -25,9 +26,8 @@ class PhpTransliterationTest extends TestCase { * The language code to test. * @param string $expected * The expected return from PhpTransliteration::removeDiacritics(). - * - * @dataProvider providerTestPhpTransliterationRemoveDiacritics */ + #[DataProvider('providerTestPhpTransliterationRemoveDiacritics')] public function testRemoveDiacritics($original, $expected): void { $transliterator_class = new PhpTransliteration(); $result = $transliterator_class->removeDiacritics($original); @@ -88,9 +88,8 @@ class PhpTransliterationTest extends TestCase { * (optional) If provided, return at most this many characters, ensuring * that the transliteration does not split in the middle of an input * character's transliteration. - * - * @dataProvider providerTestPhpTransliteration */ + #[DataProvider('providerTestPhpTransliteration')] public function testPhpTransliteration(string $langcode, string $original, string $expected, string $unknown_character = '?', ?int $max_length = NULL): void { $transliterator_class = new PhpTransliteration(); $actual = $transliterator_class->transliterate($original, $langcode, $unknown_character, $max_length); @@ -223,7 +222,7 @@ class PhpTransliterationTest extends TestCase { /** * Tests inclusion is safe. * - * @covers ::readLanguageOverrides + * @legacy-covers ::readLanguageOverrides */ public function testSafeInclude(): void { // The overrides in the transliteration data directory transliterates 0x82 diff --git a/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php index d6fadcc9fbeb..438c7e70ae72 100644 --- a/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php @@ -5,19 +5,22 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\ArgumentsResolver; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Utility\ArgumentsResolver - * @group Access + * Tests Drupal\Component\Utility\ArgumentsResolver. */ +#[CoversClass(ArgumentsResolver::class)] +#[Group('Access')] class ArgumentsResolverTest extends TestCase { /** * Tests the getArgument() method. - * - * @dataProvider providerTestGetArgument */ + #[DataProvider('providerTestGetArgument')] public function testGetArgument($callable, $scalars, $objects, $wildcards, $expected): void { $arguments = (new ArgumentsResolver($scalars, $objects, $wildcards))->getArguments($callable); $this->assertSame($expected, $arguments); @@ -164,9 +167,8 @@ class ArgumentsResolverTest extends TestCase { /** * Tests handleUnresolvedArgument() for missing arguments. - * - * @dataProvider providerTestHandleUnresolvedArgument */ + #[DataProvider('providerTestHandleUnresolvedArgument')] public function testHandleUnresolvedArgument($callable): void { $resolver = new ArgumentsResolver([], [], []); $this->expectException(\RuntimeException::class); diff --git a/core/tests/Drupal/Tests/Component/Utility/BytesTest.php b/core/tests/Drupal/Tests/Component/Utility/BytesTest.php index 04c77eb0a71b..3a7d57caa16e 100644 --- a/core/tests/Drupal/Tests/Component/Utility/BytesTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/BytesTest.php @@ -6,6 +6,9 @@ namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Bytes; use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -13,11 +16,9 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * Tests bytes size parsing helper methods. - * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\Bytes */ +#[CoversClass(Bytes::class)] +#[Group('Utility')] class BytesTest extends TestCase { use ExpectDeprecationTrait; @@ -33,9 +34,9 @@ class BytesTest extends TestCase { * The expected return value from * \Drupal\Component\Utility\Bytes::toNumber(). * - * @dataProvider providerTestToNumber - * @covers ::toNumber + * @legacy-covers ::toNumber */ + #[DataProvider('providerTestToNumber')] public function testToNumber($size, float $expected_number): void { $this->assertSame($expected_number, Bytes::toNumber($size)); } @@ -87,10 +88,10 @@ class BytesTest extends TestCase { * The expected return value from * \Drupal\Component\Utility\Bytes::validate(). * - * @dataProvider providerTestValidate - * @covers ::validate - * @covers ::validateConstraint + * @legacy-covers ::validate + * @legacy-covers ::validateConstraint */ + #[DataProvider('providerTestValidate')] public function testValidate($string, bool $expected_result): void { $this->assertSame($expected_result, Bytes::validate($string)); diff --git a/core/tests/Drupal/Tests/Component/Utility/ColorTest.php b/core/tests/Drupal/Tests/Component/Utility/ColorTest.php index d78db87c02d1..3fc7ae24fdb3 100644 --- a/core/tests/Drupal/Tests/Component/Utility/ColorTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/ColorTest.php @@ -5,25 +5,25 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Color; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests Color utility class conversions. - * - * @group Utility */ +#[Group('Utility')] class ColorTest extends TestCase { /** - * @covers \Drupal\Component\Utility\Color::validateHex * * @param bool $expected * The expected result of validation. * @param string $value * The hex color value. - * - * @dataProvider providerTestValidateHex + * @legacy-covers \Drupal\Component\Utility\Color::validateHex */ + #[DataProvider('providerTestValidateHex')] public function testValidateHex($expected, $value): void { $this->assertSame($expected, Color::validateHex($value)); } @@ -77,9 +77,8 @@ class ColorTest extends TestCase { * The expected rgb color value. * @param bool $invalid * Whether this value is invalid and exception should be expected. - * - * @dataProvider providerTestHexToRgb */ + #[DataProvider('providerTestHexToRgb')] public function testHexToRgb($value, $expected, $invalid = FALSE): void { if ($invalid) { $this->expectException('InvalidArgumentException'); @@ -138,9 +137,8 @@ class ColorTest extends TestCase { * The rgb color value. * @param string $expected * The expected hex color value. - * - * @dataProvider providerTestRbgToHex */ + #[DataProvider('providerTestRbgToHex')] public function testRgbToHex($value, $expected): void { $this->assertSame($expected, Color::rgbToHex($value)); } @@ -205,9 +203,8 @@ class ColorTest extends TestCase { * The input hex color value. * @param string $expected * The expected normalized hex color value. - * - * @dataProvider providerTestNormalizeHexLength */ + #[DataProvider('providerTestNormalizeHexLength')] public function testNormalizeHexLength($value, $expected): void { $this->assertSame($expected, Color::normalizeHexLength($value)); } diff --git a/core/tests/Drupal/Tests/Component/Utility/CryptTest.php b/core/tests/Drupal/Tests/Component/Utility/CryptTest.php index 7aff98aab423..b19f815f67ab 100644 --- a/core/tests/Drupal/Tests/Component/Utility/CryptTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/CryptTest.php @@ -5,15 +5,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Crypt; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests random byte generation. - * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\Crypt */ +#[CoversClass(Crypt::class)] +#[Group('Utility')] class CryptTest extends TestCase { /** @@ -24,9 +25,9 @@ class CryptTest extends TestCase { * @param string $expected_hash * Expected result from hashing $data. * - * @dataProvider providerTestHashBase64 - * @covers ::hashBase64 + * @legacy-covers ::hashBase64 */ + #[DataProvider('providerTestHashBase64')] public function testHashBase64($data, $expected_hash): void { $hash = Crypt::hashBase64($data); $this->assertEquals($expected_hash, $hash, 'The correct hash was not calculated.'); @@ -42,9 +43,9 @@ class CryptTest extends TestCase { * @param string $expected_hmac * Expected result from hashing $data using $key. * - * @dataProvider providerTestHmacBase64 - * @covers ::hmacBase64 + * @legacy-covers ::hmacBase64 */ + #[DataProvider('providerTestHmacBase64')] public function testHmacBase64($data, $key, $expected_hmac): void { $hmac = Crypt::hmacBase64($data, $key); $this->assertEquals($expected_hmac, $hmac, 'The correct hmac was not calculated.'); @@ -58,9 +59,9 @@ class CryptTest extends TestCase { * @param string $key * Key to use in hashing process. * - * @dataProvider providerTestHmacBase64Invalid - * @covers ::hmacBase64 + * @legacy-covers ::hmacBase64 */ + #[DataProvider('providerTestHmacBase64Invalid')] public function testHmacBase64Invalid($data, $key): void { $this->expectException('InvalidArgumentException'); Crypt::hmacBase64($data, $key); diff --git a/core/tests/Drupal/Tests/Component/Utility/DeprecationHelperTest.php b/core/tests/Drupal/Tests/Component/Utility/DeprecationHelperTest.php index 2383752bffd6..b720bad1f6d2 100644 --- a/core/tests/Drupal/Tests/Component/Utility/DeprecationHelperTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/DeprecationHelperTest.php @@ -5,12 +5,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\DeprecationHelper; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Utility\DeprecationHelper - * @group Utility + * Tests Drupal\Component\Utility\DeprecationHelper. */ +#[CoversClass(DeprecationHelper::class)] +#[Group('Utility')] class DeprecationHelperTest extends TestCase { /** @@ -18,9 +22,8 @@ class DeprecationHelperTest extends TestCase { * The core version to test against. * @param array $tests * Array of versions and their expected result. - * - * @dataProvider deprecatedHelperTestCases */ + #[DataProvider('deprecatedHelperTestCases')] public function testDeprecationHelper(string $currentVersion, array $tests): void { foreach ($tests as $deprecatedVersion => $expectedCallable) { $result = DeprecationHelper::backwardsCompatibleCall( diff --git a/core/tests/Drupal/Tests/Component/Utility/EmailValidatorTest.php b/core/tests/Drupal/Tests/Component/Utility/EmailValidatorTest.php index b57f51c8b6a1..e353785fd7d4 100644 --- a/core/tests/Drupal/Tests/Component/Utility/EmailValidatorTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/EmailValidatorTest.php @@ -6,18 +6,19 @@ namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\EmailValidator; use Egulias\EmailValidator\Validation\RFCValidation; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests the EmailValidator utility class. - * - * @coversDefaultClass \Drupal\Component\Utility\EmailValidator - * @group Utility */ +#[CoversClass(EmailValidator::class)] +#[Group('Utility')] class EmailValidatorTest extends TestCase { /** - * @covers ::isValid + * @legacy-covers ::isValid */ public function testIsValid(): void { // Note that \Drupal\Component\Utility\EmailValidator wraps @@ -31,7 +32,7 @@ class EmailValidatorTest extends TestCase { } /** - * @covers ::isValid + * @legacy-covers ::isValid */ public function testIsValidException(): void { $validator = new EmailValidator(); diff --git a/core/tests/Drupal/Tests/Component/Utility/EnvironmentTest.php b/core/tests/Drupal/Tests/Component/Utility/EnvironmentTest.php index 6a70b028d34e..fa8cf4912958 100644 --- a/core/tests/Drupal/Tests/Component/Utility/EnvironmentTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/EnvironmentTest.php @@ -5,15 +5,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Environment; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Test PHP Environment helper methods. - * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\Environment */ +#[CoversClass(Environment::class)] +#[Group('Utility')] class EnvironmentTest extends TestCase { /** @@ -29,9 +30,9 @@ class EnvironmentTest extends TestCase { * The expected return value from * \Drupal\Component\Utility\Environment::checkMemoryLimit(). * - * @dataProvider providerTestCheckMemoryLimit - * @covers ::checkMemoryLimit + * @legacy-covers ::checkMemoryLimit */ + #[DataProvider('providerTestCheckMemoryLimit')] public function testCheckMemoryLimit($required, $custom_memory_limit, $expected): void { $actual = Environment::checkMemoryLimit($required, $custom_memory_limit); $this->assertEquals($expected, $actual); diff --git a/core/tests/Drupal/Tests/Component/Utility/FilterArrayTest.php b/core/tests/Drupal/Tests/Component/Utility/FilterArrayTest.php index 289b19bfe824..e766d8ecf61c 100644 --- a/core/tests/Drupal/Tests/Component/Utility/FilterArrayTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/FilterArrayTest.php @@ -5,23 +5,24 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\FilterArray; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Test filter array functions. - * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\FilterArray */ +#[CoversClass(FilterArray::class)] +#[Group('Utility')] class FilterArrayTest extends TestCase { /** * Tests removing empty strings. * - * @dataProvider providerRemoveEmptyStrings - * @covers ::removeEmptyStrings + * @legacy-covers ::removeEmptyStrings */ + #[DataProvider('providerRemoveEmptyStrings')] public function testRemoveEmptyStrings(array $values, array $expected): void { $this->assertEquals($expected, array_values(FilterArray::removeEmptyStrings($values))); } diff --git a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php index 800483fab501..a01684d768d3 100644 --- a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php @@ -8,17 +8,17 @@ use Drupal\Component\Render\MarkupInterface; use Drupal\Component\Render\MarkupTrait; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Random; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; // cspell:ignore répét répété - /** * Tests \Drupal\Component\Utility\Html. - * - * @group Common - * - * @coversDefaultClass \Drupal\Component\Utility\Html */ +#[CoversClass(Html::class)] +#[Group('Common')] class HtmlTest extends TestCase { /** @@ -42,10 +42,9 @@ class HtmlTest extends TestCase { * (optional) An array of string replacements to use on the identifier. If * NULL, no filter will be passed and a default will be used. * - * @dataProvider providerTestCleanCssIdentifier - * - * @covers ::cleanCssIdentifier + * @legacy-covers ::cleanCssIdentifier */ + #[DataProvider('providerTestCleanCssIdentifier')] public function testCleanCssIdentifier($expected, $source, $filter = NULL): void { if ($filter !== NULL) { $this->assertSame($expected, Html::cleanCssIdentifier($source, $filter)); @@ -94,7 +93,7 @@ class HtmlTest extends TestCase { /** * Tests that Html::getClass() cleans the class name properly. * - * @covers ::getClass + * @legacy-covers ::getClass */ public function testHtmlClass(): void { // Verify Drupal coding standards are enforced. @@ -116,10 +115,9 @@ class HtmlTest extends TestCase { * @param bool $reset * (optional) If TRUE, reset the list of seen IDs. Defaults to FALSE. * - * @dataProvider providerTestHtmlGetUniqueId - * - * @covers ::getUniqueId + * @legacy-covers ::getUniqueId */ + #[DataProvider('providerTestHtmlGetUniqueId')] public function testHtmlGetUniqueId($expected, $source, $reset = FALSE): void { if ($reset) { Html::resetSeenIds(); @@ -159,10 +157,9 @@ class HtmlTest extends TestCase { * @param string $source * The string being transformed to an ID. * - * @dataProvider providerTestHtmlGetUniqueIdWithAjaxIds - * - * @covers ::getUniqueId + * @legacy-covers ::getUniqueId */ + #[DataProvider('providerTestHtmlGetUniqueIdWithAjaxIds')] public function testHtmlGetUniqueIdWithAjaxIds($expected, $source): void { Html::setIsAjax(TRUE); $id = Html::getUniqueId($source); @@ -203,10 +200,9 @@ class HtmlTest extends TestCase { * @param string $source * The string being transformed to an ID. * - * @dataProvider providerTestHtmlGetId - * - * @covers ::getId + * @legacy-covers ::getId */ + #[DataProvider('providerTestHtmlGetId')] public function testHtmlGetId($expected, $source): void { Html::setIsAjax(FALSE); $this->assertSame($expected, Html::getId($source)); @@ -238,9 +234,9 @@ class HtmlTest extends TestCase { /** * Tests Html::decodeEntities(). * - * @dataProvider providerDecodeEntities - * @covers ::decodeEntities + * @legacy-covers ::decodeEntities */ + #[DataProvider('providerDecodeEntities')] public function testDecodeEntities($text, $expected): void { $this->assertEquals($expected, Html::decodeEntities($text)); } @@ -279,9 +275,9 @@ class HtmlTest extends TestCase { /** * Tests Html::escape(). * - * @dataProvider providerEscape - * @covers ::escape + * @legacy-covers ::escape */ + #[DataProvider('providerEscape')] public function testEscape($expected, $text): void { $this->assertEquals($expected, Html::escape($text)); } @@ -313,8 +309,8 @@ class HtmlTest extends TestCase { /** * Tests relationship between escaping and decoding HTML entities. * - * @covers ::decodeEntities - * @covers ::escape + * @legacy-covers ::decodeEntities + * @legacy-covers ::escape */ public function testDecodeEntitiesAndEscape(): void { $string = "<em>répété</em>"; @@ -335,7 +331,7 @@ class HtmlTest extends TestCase { * serialization would cause errors in getElementsByTagName() in the * serialization function. * - * @covers ::serialize + * @legacy-covers ::serialize */ public function testSerialize(): void { $document = new \DOMDocument(); @@ -344,17 +340,17 @@ class HtmlTest extends TestCase { } /** - * @covers ::transformRootRelativeUrlsToAbsolute - * @dataProvider providerTestTransformRootRelativeUrlsToAbsolute + * @legacy-covers ::transformRootRelativeUrlsToAbsolute */ + #[DataProvider('providerTestTransformRootRelativeUrlsToAbsolute')] public function testTransformRootRelativeUrlsToAbsolute($html, $scheme_and_host, $expected_html): void { $this->assertSame($expected_html ?: $html, Html::transformRootRelativeUrlsToAbsolute($html, $scheme_and_host)); } /** - * @covers ::transformRootRelativeUrlsToAbsolute - * @dataProvider providerTestTransformRootRelativeUrlsToAbsoluteAssertion + * @legacy-covers ::transformRootRelativeUrlsToAbsolute */ + #[DataProvider('providerTestTransformRootRelativeUrlsToAbsoluteAssertion')] public function testTransformRootRelativeUrlsToAbsoluteAssertion($scheme_and_host): void { $this->expectException(\AssertionError::class); Html::transformRootRelativeUrlsToAbsolute('', $scheme_and_host); diff --git a/core/tests/Drupal/Tests/Component/Utility/ImageTest.php b/core/tests/Drupal/Tests/Component/Utility/ImageTest.php index e67eb9a4d163..2b0bfaab2a8e 100644 --- a/core/tests/Drupal/Tests/Component/Utility/ImageTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/ImageTest.php @@ -5,19 +5,22 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Image; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Utility\Image - * @group Image + * Tests Drupal\Component\Utility\Image. */ +#[CoversClass(Image::class)] +#[Group('Image')] class ImageTest extends TestCase { /** * Tests all control flow branches in image_dimensions_scale(). - * - * @dataProvider providerTestScaleDimensions */ + #[DataProvider('providerTestScaleDimensions')] public function testScaleDimensions($input, $output): void { // Process the test dataset. $return_value = Image::scaleDimensions($input['dimensions'], $input['width'], $input['height'], $input['upscale']); @@ -158,7 +161,7 @@ class ImageTest extends TestCase { } /** - * @covers ::getKeywordOffset + * @legacy-covers ::getKeywordOffset */ public function testInvalidGetKeywordOffset(): void { $this->expectException(\InvalidArgumentException::class); @@ -167,10 +170,9 @@ class ImageTest extends TestCase { } /** - * @covers ::getKeywordOffset - * - * @dataProvider providerTestGetKeywordOffset + * @legacy-covers ::getKeywordOffset */ + #[DataProvider('providerTestGetKeywordOffset')] public function testGetKeywordOffset(array $input, int $expected): void { $this->assertSame($expected, Image::getKeywordOffset($input['anchor'], $input['current'], $input['new'])); } diff --git a/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php b/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php index 7a43bda8882d..97f42571ca17 100644 --- a/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php @@ -5,12 +5,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\NestedArray; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Utility\NestedArray - * @group Utility + * Tests Drupal\Component\Utility\NestedArray. */ +#[CoversClass(NestedArray::class)] +#[Group('Utility')] class NestedArrayTest extends TestCase { /** @@ -45,7 +49,7 @@ class NestedArrayTest extends TestCase { /** * Tests getting nested array values. * - * @covers ::getValue + * @legacy-covers ::getValue */ public function testGetValue(): void { // Verify getting a value of a nested element. @@ -75,7 +79,7 @@ class NestedArrayTest extends TestCase { /** * Tests setting nested array values. * - * @covers ::setValue + * @legacy-covers ::setValue */ public function testSetValue(): void { $new_value = [ @@ -96,7 +100,7 @@ class NestedArrayTest extends TestCase { /** * Tests force-setting values. * - * @covers ::setValue + * @legacy-covers ::setValue */ public function testSetValueForce(): void { $new_value = [ @@ -111,7 +115,7 @@ class NestedArrayTest extends TestCase { /** * Tests unsetting nested array values. * - * @covers ::unsetValue + * @legacy-covers ::unsetValue */ public function testUnsetValue(): void { // Verify unsetting a non-existing nested element throws no errors and the @@ -146,8 +150,8 @@ class NestedArrayTest extends TestCase { /** * Tests NestedArray::mergeDeepArray(). * - * @covers ::mergeDeep - * @covers ::mergeDeepArray + * @legacy-covers ::mergeDeep + * @legacy-covers ::mergeDeepArray */ public function testMergeDeepArray(): void { $link_options_1 = [ @@ -174,7 +178,7 @@ class NestedArrayTest extends TestCase { /** * Tests that arrays with implicit keys are appended, not merged. * - * @covers ::mergeDeepArray + * @legacy-covers ::mergeDeepArray */ public function testMergeImplicitKeys(): void { $a = [ @@ -195,7 +199,7 @@ class NestedArrayTest extends TestCase { /** * Tests that even with explicit keys, values are appended, not merged. * - * @covers ::mergeDeepArray + * @legacy-covers ::mergeDeepArray */ public function testMergeExplicitKeys(): void { $a = [ @@ -231,7 +235,7 @@ class NestedArrayTest extends TestCase { * before those in the first one, they are still appended, and the keys on * the first array are deleted and regenerated. * - * @covers ::mergeDeepArray + * @legacy-covers ::mergeDeepArray */ public function testMergeOutOfSequenceKeys(): void { $a = [ @@ -261,9 +265,9 @@ class NestedArrayTest extends TestCase { } /** - * @covers ::filter - * @dataProvider providerTestFilter + * @legacy-covers ::filter */ + #[DataProvider('providerTestFilter')] public function testFilter($array, $callable, $expected): void { $this->assertEquals($expected, NestedArray::filter($array, $callable)); } diff --git a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php index 2055eecde647..fdac9baf7965 100644 --- a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php @@ -6,17 +6,19 @@ namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Number; use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; /** * Tests number manipulation utilities. * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\Number - * * @see \Drupal\Component\Utility\Number */ +#[CoversClass(Number::class)] +#[Group('Utility')] class NumberTest extends TestCase { use ExpectDeprecationTrait; @@ -31,9 +33,9 @@ class NumberTest extends TestCase { * @param bool $expected * Expected return value from Number::validStep(). * - * @dataProvider providerTestValidStep - * @covers ::validStep + * @legacy-covers ::validStep */ + #[DataProvider('providerTestValidStep')] public function testValidStep($value, $step, $expected): void { $return = Number::validStep($value, $step); $this->assertEquals($expected, $return); @@ -51,9 +53,9 @@ class NumberTest extends TestCase { * @param bool $expected * Expected return value from Number::validStep(). * - * @dataProvider providerTestValidStepOffset - * @covers ::validStep + * @legacy-covers ::validStep */ + #[DataProvider('providerTestValidStepOffset')] public function testValidStepOffset($value, $step, $offset, $expected): void { $return = Number::validStep($value, $step, $offset); $this->assertEquals($expected, $return); @@ -129,10 +131,10 @@ class NumberTest extends TestCase { * @param string $expected * The expected alphadecimal value. * - * @dataProvider providerTestConversions - * @covers ::intToAlphadecimal - * @covers ::alphadecimalToInt + * @legacy-covers ::intToAlphadecimal + * @legacy-covers ::alphadecimalToInt */ + #[DataProvider('providerTestConversions')] public function testConversions($value, $expected): void { $this->assertSame(Number::intToAlphadecimal($value), $expected); $this->assertSame($value, Number::alphadecimalToInt($expected)); @@ -166,7 +168,7 @@ class NumberTest extends TestCase { * Number::alphadecimalToInt() must throw an exception * when non-alphanumeric characters are passed as input. * - * @covers ::alphadecimalToInt + * @legacy-covers ::alphadecimalToInt */ public function testAlphadecimalToIntThrowsExceptionWithMalformedStrings(): void { $this->expectException(\InvalidArgumentException::class); @@ -180,9 +182,9 @@ class NumberTest extends TestCase { * Many tests and code rely on Number::alphadecimalToInt() returning 0 * for degenerate values '' and NULL. We must ensure they are accepted. * - * @group legacy - * @covers ::alphadecimalToInt + * @legacy-covers ::alphadecimalToInt */ + #[IgnoreDeprecations] public function testAlphadecimalToIntReturnsZeroWithNullAndEmptyString(): void { $deprecationMessage = 'Passing NULL or an empty string to Drupal\Component\Utility\Number::alphadecimalToInt() is deprecated in drupal:11.2.0 and will be removed in drupal:12.0.0. See https://www.drupal.org/node/3494472'; $this->expectDeprecation($deprecationMessage); diff --git a/core/tests/Drupal/Tests/Component/Utility/RandomTest.php b/core/tests/Drupal/Tests/Component/Utility/RandomTest.php index 64de3b2061a4..f46b0cd9b557 100644 --- a/core/tests/Drupal/Tests/Component/Utility/RandomTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/RandomTest.php @@ -5,15 +5,15 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Random; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests random data generation. - * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\Random */ +#[CoversClass(Random::class)] +#[Group('Utility')] class RandomTest extends TestCase { /** @@ -28,7 +28,7 @@ class RandomTest extends TestCase { /** * Tests unique random string generation. * - * @covers ::string + * @legacy-covers ::string */ public function testRandomStringUniqueness(): void { $strings = []; @@ -43,7 +43,7 @@ class RandomTest extends TestCase { /** * Tests unique random name generation. * - * @covers ::name + * @legacy-covers ::name */ public function testRandomNamesUniqueness(): void { $names = []; @@ -58,7 +58,7 @@ class RandomTest extends TestCase { /** * Tests infinite loop prevention whilst generating random names. * - * @covers ::name + * @legacy-covers ::name */ public function testRandomNameException(): void { // There are fewer than 100 possibilities so an exception should occur to @@ -74,7 +74,7 @@ class RandomTest extends TestCase { /** * Tests infinite loop prevention whilst generating random strings. * - * @covers ::string + * @legacy-covers ::string */ public function testRandomStringException(): void { // There are fewer than 100 possibilities so an exception should occur to @@ -90,7 +90,7 @@ class RandomTest extends TestCase { /** * Tests random name generation if uniqueness is not enforced. * - * @covers ::name + * @legacy-covers ::name */ public function testRandomNameNonUnique(): void { // There are fewer than 100 possibilities if we were forcing uniqueness so @@ -105,7 +105,7 @@ class RandomTest extends TestCase { /** * Tests random string if uniqueness is not enforced. * - * @covers ::string + * @legacy-covers ::string */ public function testRandomStringNonUnique(): void { // There are fewer than 100 possibilities if we were forcing uniqueness so @@ -120,7 +120,7 @@ class RandomTest extends TestCase { /** * Tests unique random name generation. * - * @covers ::machineName + * @legacy-covers ::machineName */ public function testRandomMachineNamesUniqueness(): void { $names = []; @@ -135,7 +135,7 @@ class RandomTest extends TestCase { /** * Tests infinite loop prevention whilst generating random names. * - * @covers ::machineName + * @legacy-covers ::machineName */ public function testRandomMachineNameException(): void { // There are fewer than 100 possibilities so an exception should occur to @@ -150,7 +150,7 @@ class RandomTest extends TestCase { /** * Tests random name generation if uniqueness is not enforced. * - * @covers ::machineName + * @legacy-covers ::machineName */ public function testRandomMachineNameNonUnique(): void { // There are fewer than 100 possibilities meaning if uniqueness was @@ -165,7 +165,7 @@ class RandomTest extends TestCase { /** * Tests random object generation to ensure the expected number of properties. * - * @covers ::object + * @legacy-covers ::object */ public function testRandomObject(): void { // For values of 0 and 1 \Drupal\Component\Utility\Random::object() will @@ -180,7 +180,7 @@ class RandomTest extends TestCase { /** * Tests random string validation callbacks. * - * @covers ::string + * @legacy-covers ::string */ public function testRandomStringValidator(): void { $random = new Random(); @@ -192,7 +192,7 @@ class RandomTest extends TestCase { /** * Tests random word. * - * @covers ::word + * @legacy-covers ::word */ public function testRandomWordValidator(): void { $random = new Random(); diff --git a/core/tests/Drupal/Tests/Component/Utility/RectangleTest.php b/core/tests/Drupal/Tests/Component/Utility/RectangleTest.php index 5635173a6de6..45942f226fa3 100644 --- a/core/tests/Drupal/Tests/Component/Utility/RectangleTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/RectangleTest.php @@ -5,18 +5,22 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Rectangle; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Utility\Rectangle - * @group Image + * Tests Drupal\Component\Utility\Rectangle. */ +#[CoversClass(Rectangle::class)] +#[Group('Image')] class RectangleTest extends TestCase { /** * Tests wrong rectangle width. * - * @covers ::rotate + * @legacy-covers ::rotate */ public function testWrongWidth(): void { $this->expectException(\InvalidArgumentException::class); @@ -26,7 +30,7 @@ class RectangleTest extends TestCase { /** * Tests wrong rectangle height. * - * @covers ::rotate + * @legacy-covers ::rotate */ public function testWrongHeight(): void { $this->expectException(\InvalidArgumentException::class); @@ -47,12 +51,11 @@ class RectangleTest extends TestCase { * @param int $exp_height * The expected height of the rotated rectangle. * - * @covers ::rotate - * @covers ::getBoundingWidth - * @covers ::getBoundingHeight - * - * @dataProvider providerPhp55RotateDimensions + * @legacy-covers ::rotate + * @legacy-covers ::getBoundingWidth + * @legacy-covers ::getBoundingHeight */ + #[DataProvider('providerPhp55RotateDimensions')] public function testRotateDimensions($width, $height, $angle, $exp_width, $exp_height): void { $rect = new Rectangle($width, $height); $rect->rotate($angle); diff --git a/core/tests/Drupal/Tests/Component/Utility/ReflectionTest.php b/core/tests/Drupal/Tests/Component/Utility/ReflectionTest.php index 4a0a6fb4b854..f498157f08b8 100644 --- a/core/tests/Drupal/Tests/Component/Utility/ReflectionTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/ReflectionTest.php @@ -5,12 +5,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Reflection; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Utility\Reflection - * @group Utility + * Tests Drupal\Component\Utility\Reflection. */ +#[CoversClass(Reflection::class)] +#[Group('Utility')] class ReflectionTest extends TestCase { /** @@ -19,9 +23,9 @@ class ReflectionTest extends TestCase { * @param \ReflectionParameter $parameter * The reflection parameter. * - * @covers ::getParameterClassName - * @dataProvider providerGetParameterClassName + * @legacy-covers ::getParameterClassName */ + #[DataProvider('providerGetParameterClassName')] public function testGetParameterClassName(?string $expected, \ReflectionParameter $parameter): void { $this->assertEquals($expected, Reflection::getParameterClassName($parameter)); } diff --git a/core/tests/Drupal/Tests/Component/Utility/SortArrayTest.php b/core/tests/Drupal/Tests/Component/Utility/SortArrayTest.php index 9cc47d17d965..37a431411b34 100644 --- a/core/tests/Drupal/Tests/Component/Utility/SortArrayTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/SortArrayTest.php @@ -5,15 +5,16 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\SortArray; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests the SortArray component. - * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\SortArray */ +#[CoversClass(SortArray::class)] +#[Group('Utility')] class SortArrayTest extends TestCase { /** @@ -26,11 +27,10 @@ class SortArrayTest extends TestCase { * @param int $expected * The expected output from calling the method. * - * @covers ::sortByWeightElement - * @covers ::sortByKeyInt - * - * @dataProvider providerSortByWeightElement + * @legacy-covers ::sortByWeightElement + * @legacy-covers ::sortByKeyInt */ + #[DataProvider('providerSortByWeightElement')] public function testSortByWeightElement($a, $b, $expected): void { $result = SortArray::sortByWeightElement($a, $b); $this->assertBothNegativePositiveOrZero($expected, $result); @@ -103,10 +103,10 @@ class SortArrayTest extends TestCase { * @param int $expected * The expected output from calling the method. * - * @dataProvider providerSortByWeightProperty - * @covers ::sortByWeightProperty - * @covers ::sortByKeyInt + * @legacy-covers ::sortByWeightProperty + * @legacy-covers ::sortByKeyInt */ + #[DataProvider('providerSortByWeightProperty')] public function testSortByWeightProperty($a, $b, $expected): void { $result = SortArray::sortByWeightProperty($a, $b); $this->assertBothNegativePositiveOrZero($expected, $result); @@ -179,10 +179,10 @@ class SortArrayTest extends TestCase { * @param int $expected * The expected output from calling the method. * - * @dataProvider providerSortByTitleElement - * @covers ::sortByTitleElement - * @covers ::sortByKeyString + * @legacy-covers ::sortByTitleElement + * @legacy-covers ::sortByKeyString */ + #[DataProvider('providerSortByTitleElement')] public function testSortByTitleElement($a, $b, $expected): void { $result = SortArray::sortByTitleElement($a, $b); $this->assertBothNegativePositiveOrZero($expected, $result); @@ -248,10 +248,10 @@ class SortArrayTest extends TestCase { * @param int $expected * The expected output from calling the method. * - * @dataProvider providerSortByTitleProperty - * @covers ::sortByTitleProperty - * @covers ::sortByKeyString + * @legacy-covers ::sortByTitleProperty + * @legacy-covers ::sortByKeyString */ + #[DataProvider('providerSortByTitleProperty')] public function testSortByTitleProperty($a, $b, $expected): void { $result = SortArray::sortByTitleProperty($a, $b); $this->assertBothNegativePositiveOrZero($expected, $result); diff --git a/core/tests/Drupal/Tests/Component/Utility/TimerTest.php b/core/tests/Drupal/Tests/Component/Utility/TimerTest.php index 470ff1de13fe..12996f564f4b 100644 --- a/core/tests/Drupal/Tests/Component/Utility/TimerTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/TimerTest.php @@ -5,23 +5,23 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Timer; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests the Timer system. - * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\Timer */ +#[CoversClass(Timer::class)] +#[Group('Utility')] class TimerTest extends TestCase { /** * Tests Timer::read() time accumulation accuracy across multiple restarts. * - * @covers ::start - * @covers ::stop - * @covers ::read + * @legacy-covers ::start + * @legacy-covers ::stop + * @legacy-covers ::read */ public function testTimer(): void { Timer::start('test'); diff --git a/core/tests/Drupal/Tests/Component/Utility/UnicodeTest.php b/core/tests/Drupal/Tests/Component/Utility/UnicodeTest.php index c0dbfa92d757..06e6e1ef7d4c 100644 --- a/core/tests/Drupal/Tests/Component/Utility/UnicodeTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/UnicodeTest.php @@ -6,24 +6,25 @@ namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Unicode; use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Test unicode handling features implemented in Unicode component. - * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\Unicode */ +#[CoversClass(Unicode::class)] +#[Group('Utility')] class UnicodeTest extends TestCase { use ExpectDeprecationTrait; /** * Tests multibyte ucfirst. * - * @dataProvider providerUcfirst - * @covers ::ucfirst + * @legacy-covers ::ucfirst */ + #[DataProvider('providerUcfirst')] public function testUcfirst($text, $expected): void { $this->assertEquals($expected, Unicode::ucfirst($text)); } @@ -52,9 +53,9 @@ class UnicodeTest extends TestCase { /** * Tests multibyte lcfirst. * - * @dataProvider providerLcfirst - * @covers ::lcfirst + * @legacy-covers ::lcfirst */ + #[DataProvider('providerLcfirst')] public function testLcfirst($text, $expected): void { $this->assertEquals($expected, Unicode::lcfirst($text)); } @@ -83,9 +84,9 @@ class UnicodeTest extends TestCase { /** * Tests multibyte ucwords. * - * @dataProvider providerUcwords - * @covers ::ucwords + * @legacy-covers ::ucwords */ + #[DataProvider('providerUcwords')] public function testUcwords($text, $expected): void { $this->assertEquals($expected, Unicode::ucwords($text)); } @@ -116,9 +117,9 @@ class UnicodeTest extends TestCase { /** * Tests multibyte truncate. * - * @dataProvider providerTruncate - * @covers ::truncate + * @legacy-covers ::truncate */ + #[DataProvider('providerTruncate')] public function testTruncate($text, $max_length, $expected, $wordsafe = FALSE, $add_ellipsis = FALSE): void { $this->assertEquals($expected, Unicode::truncate($text, $max_length, $wordsafe, $add_ellipsis)); } @@ -215,9 +216,9 @@ EOF; * @param string $expected * The expected return from Unicode::truncateBytes(). * - * @dataProvider providerTestTruncateBytes - * @covers ::truncateBytes + * @legacy-covers ::truncateBytes */ + #[DataProvider('providerTestTruncateBytes')] public function testTruncateBytes($text, $max_length, $expected): void { $this->assertEquals($expected, Unicode::truncateBytes($text, $max_length), 'The string was not correctly truncated.'); } @@ -250,9 +251,9 @@ EOF; * @param string $message * The message to display on failure. * - * @dataProvider providerTestValidateUtf8 - * @covers ::validateUtf8 + * @legacy-covers ::validateUtf8 */ + #[DataProvider('providerTestValidateUtf8')] public function testValidateUtf8($text, $expected, $message): void { $this->assertEquals($expected, Unicode::validateUtf8($text), $message); } @@ -290,9 +291,9 @@ EOF; * @param string|bool $expected * The expected result. * - * @dataProvider providerTestConvertToUtf8 - * @covers ::convertToUtf8 + * @legacy-covers ::convertToUtf8 */ + #[DataProvider('providerTestConvertToUtf8')] public function testConvertToUtf8($data, $encoding, $expected): void { $this->assertEquals($expected, Unicode::convertToUtf8($data, $encoding)); } diff --git a/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php index 0d5eb029d07b..571009cf539a 100644 --- a/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php @@ -5,13 +5,17 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\UrlHelper; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; use PHPUnit\Framework\TestCase; /** - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\UrlHelper + * Tests Drupal\Component\Utility\UrlHelper. */ +#[CoversClass(UrlHelper::class)] +#[Group('Utility')] class UrlHelperTest extends TestCase { /** @@ -43,9 +47,9 @@ class UrlHelperTest extends TestCase { * @param string $message * The assertion message. * - * @dataProvider providerTestBuildQuery - * @covers ::buildQuery + * @legacy-covers ::buildQuery */ + #[DataProvider('providerTestBuildQuery')] public function testBuildQuery($query, $expected, $message): void { $this->assertEquals(UrlHelper::buildQuery($query), $expected, $message); } @@ -91,9 +95,9 @@ class UrlHelperTest extends TestCase { * @param string $scheme * The scheme to test. * - * @dataProvider providerTestValidAbsoluteData - * @covers ::isValid + * @legacy-covers ::isValid */ + #[DataProvider('providerTestValidAbsoluteData')] public function testValidAbsolute(string $url, string $scheme): void { $test_url = $scheme . '://' . $url; $valid_url = UrlHelper::isValid($test_url, TRUE); @@ -147,9 +151,9 @@ class UrlHelperTest extends TestCase { * @param string $scheme * The scheme to test. * - * @dataProvider providerTestInvalidAbsolute - * @covers ::isValid + * @legacy-covers ::isValid */ + #[DataProvider('providerTestInvalidAbsolute')] public function testInvalidAbsolute(string $url, string $scheme): void { $test_url = $scheme . '://' . $url; $valid_url = UrlHelper::isValid($test_url, TRUE); @@ -182,9 +186,9 @@ class UrlHelperTest extends TestCase { * @param string $prefix * The prefix to test. * - * @dataProvider providerTestValidRelativeData - * @covers ::isValid + * @legacy-covers ::isValid */ + #[DataProvider('providerTestValidRelativeData')] public function testValidRelative(string $url, string $prefix): void { $test_url = $prefix . $url; $valid_url = UrlHelper::isValid($test_url); @@ -215,9 +219,9 @@ class UrlHelperTest extends TestCase { * @param string $prefix * The prefix to test. * - * @dataProvider providerTestInvalidRelativeData - * @covers ::isValid + * @legacy-covers ::isValid */ + #[DataProvider('providerTestInvalidRelativeData')] public function testInvalidRelative(string $url, string $prefix): void { $test_url = $prefix . $url; $valid_url = UrlHelper::isValid($test_url); @@ -235,9 +239,9 @@ class UrlHelperTest extends TestCase { * @param array $expected * An array containing query parameters. * - * @dataProvider providerTestFilterQueryParameters - * @covers ::filterQueryParameters + * @legacy-covers ::filterQueryParameters */ + #[DataProvider('providerTestFilterQueryParameters')] public function testFilterQueryParameters($query, $exclude, $expected): void { $filtered = UrlHelper::filterQueryParameters($query, $exclude); $this->assertEquals($expected, $filtered, 'The query was not properly filtered.'); @@ -274,9 +278,9 @@ class UrlHelperTest extends TestCase { * @param array $expected * Associative array with expected parameters. * - * @dataProvider providerTestParse - * @covers ::parse + * @legacy-covers ::parse */ + #[DataProvider('providerTestParse')] public function testParse($url, $expected): void { $parsed = UrlHelper::parse($url); $this->assertEquals($expected, $parsed, 'The URL was not properly parsed.'); @@ -405,9 +409,9 @@ class UrlHelperTest extends TestCase { * @param string $expected * The expected encoded path. * - * @dataProvider providerTestEncodePath - * @covers ::encodePath + * @legacy-covers ::encodePath */ + #[DataProvider('providerTestEncodePath')] public function testEncodePath($path, $expected): void { $encoded = UrlHelper::encodePath($path); $this->assertEquals($expected, $encoded); @@ -434,9 +438,9 @@ class UrlHelperTest extends TestCase { * @param bool $expected * Expected result. * - * @dataProvider providerTestIsExternal - * @covers ::isExternal + * @legacy-covers ::isExternal */ + #[DataProvider('providerTestIsExternal')] public function testIsExternal($path, $expected): void { $isExternal = UrlHelper::isExternal($path); $this->assertEquals($expected, $isExternal); @@ -494,11 +498,11 @@ class UrlHelperTest extends TestCase { * @param array $protocols * Protocols to allow. * - * @dataProvider providerTestFilterBadProtocol - * @covers ::setAllowedProtocols - * @covers ::filterBadProtocol - * @runInSeparateProcess + * @legacy-covers ::setAllowedProtocols + * @legacy-covers ::filterBadProtocol */ + #[DataProvider('providerTestFilterBadProtocol')] + #[RunInSeparateProcess] public function testFilterBadProtocol($uri, $expected, $protocols): void { UrlHelper::setAllowedProtocols($protocols); $this->assertEquals($expected, UrlHelper::filterBadProtocol($uri)); @@ -535,11 +539,11 @@ class UrlHelperTest extends TestCase { * @param array $protocols * Protocols to allow. * - * @dataProvider providerTestStripDangerousProtocols - * @covers ::setAllowedProtocols - * @covers ::stripDangerousProtocols - * @runInSeparateProcess + * @legacy-covers ::setAllowedProtocols + * @legacy-covers ::stripDangerousProtocols */ + #[DataProvider('providerTestStripDangerousProtocols')] + #[RunInSeparateProcess] public function testStripDangerousProtocols($uri, $expected, $protocols): void { UrlHelper::setAllowedProtocols($protocols); $stripped = UrlHelper::stripDangerousProtocols($uri); @@ -615,9 +619,9 @@ class UrlHelperTest extends TestCase { * TRUE if an external URL points to this installation as determined by the * base URL. * - * @covers ::externalIsLocal - * @dataProvider providerTestExternalIsLocal + * @legacy-covers ::externalIsLocal */ + #[DataProvider('providerTestExternalIsLocal')] public function testExternalIsLocal($url, $base_url, $expected): void { $this->assertSame($expected, UrlHelper::externalIsLocal($url, $base_url)); } @@ -667,9 +671,9 @@ class UrlHelperTest extends TestCase { * @param string $base_url * The base URL. * - * @covers ::externalIsLocal - * @dataProvider providerTestExternalIsLocalInvalid + * @legacy-covers ::externalIsLocal */ + #[DataProvider('providerTestExternalIsLocalInvalid')] public function testExternalIsLocalInvalid($url, $base_url): void { $this->expectException(\InvalidArgumentException::class); UrlHelper::externalIsLocal($url, $base_url); diff --git a/core/tests/Drupal/Tests/Component/Utility/UserAgentTest.php b/core/tests/Drupal/Tests/Component/Utility/UserAgentTest.php index 601b31956968..467d65d4d36a 100644 --- a/core/tests/Drupal/Tests/Component/Utility/UserAgentTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/UserAgentTest.php @@ -6,17 +6,17 @@ namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Random; use Drupal\Component\Utility\UserAgent; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; // cspell:ignore Teletubbies - /** * Tests bytes size parsing helper methods. - * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\UserAgent */ +#[CoversClass(UserAgent::class)] +#[Group('Utility')] class UserAgentTest extends TestCase { /** @@ -71,9 +71,9 @@ class UserAgentTest extends TestCase { /** * Tests matching language from user agent. * - * @dataProvider providerTestGetBestMatchingLangcode - * @covers ::getBestMatchingLangcode + * @legacy-covers ::getBestMatchingLangcode */ + #[DataProvider('providerTestGetBestMatchingLangcode')] public function testGetBestMatchingLangcode($accept_language, $expected): void { $result = UserAgent::getBestMatchingLangcode($accept_language, $this->getLanguages(), $this->getMappings()); $this->assertSame($expected, $result); diff --git a/core/tests/Drupal/Tests/Component/Utility/VariableTest.php b/core/tests/Drupal/Tests/Component/Utility/VariableTest.php index 172a9b038781..d00203bfe9cf 100644 --- a/core/tests/Drupal/Tests/Component/Utility/VariableTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/VariableTest.php @@ -5,16 +5,17 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Variable; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Test variable export functionality in Variable component. - * - * @group Variable - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\Variable */ +#[CoversClass(Variable::class)] +#[Group('Variable')] +#[Group('Utility')] class VariableTest extends TestCase { /** @@ -75,10 +76,9 @@ class VariableTest extends TestCase { * @param string $expected_name * The expected human-readable name of the callable. * - * @dataProvider providerCallableToString - * - * @covers ::callableToString + * @legacy-covers ::callableToString */ + #[DataProvider('providerCallableToString')] public function testCallableToString($callable, string $expected_name): void { $this->assertSame($expected_name, Variable::callableToString($callable)); } @@ -171,9 +171,9 @@ class VariableTest extends TestCase { * @param mixed $variable * The variable to be exported. * - * @covers ::export - * @dataProvider providerTestExport + * @legacy-covers ::export */ + #[DataProvider('providerTestExport')] public function testExport($expected, $variable): void { $this->assertEquals($expected, Variable::export($variable)); } diff --git a/core/tests/Drupal/Tests/Component/Utility/XssTest.php b/core/tests/Drupal/Tests/Component/Utility/XssTest.php index 32f345ac0160..693a75d6f928 100644 --- a/core/tests/Drupal/Tests/Component/Utility/XssTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/XssTest.php @@ -7,12 +7,15 @@ namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\UrlHelper; use Drupal\Component\Utility\Xss; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; // cspell:ignore ascript barbaz ckers cript CVEs dynsrc fooÿñ msgbox ncript // cspell:ignore nfocus nmedi nosuchscheme nosuchtag onmediaerror scrscriptipt // cspell:ignore tascript vbscript - /** * XSS Filtering tests. * @@ -21,11 +24,10 @@ use PHPUnit\Framework\TestCase; * Relevant CVEs: * - CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973, * CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740. - * - * @group Utility - * @coversDefaultClass \Drupal\Component\Utility\Xss - * @runTestsInSeparateProcesses */ +#[CoversClass(Xss::class)] +#[Group('Utility')] +#[RunTestsInSeparateProcesses] class XssTest extends TestCase { /** @@ -66,9 +68,8 @@ class XssTest extends TestCase { * @param array $allowed_tags * (optional) The allowed HTML tags to be passed to * \Drupal\Component\Utility\Xss::filter(). - * - * @dataProvider providerTestFilterXssNormalized */ + #[DataProvider('providerTestFilterXssNormalized')] public function testFilterXssNormalized($value, $expected, $message, ?array $allowed_tags = NULL): void { if ($allowed_tags === NULL) { $value = Xss::filter($value); @@ -134,9 +135,8 @@ class XssTest extends TestCase { * @param array $allowed_tags * (optional) The allowed HTML tags to be passed to * \Drupal\Component\Utility\Xss::filter(). - * - * @dataProvider providerTestFilterXssNotNormalized */ + #[DataProvider('providerTestFilterXssNotNormalized')] public function testFilterXssNotNormalized($value, $expected, $message, ?array $allowed_tags = NULL): void { if ($allowed_tags === NULL) { $value = Xss::filter($value); @@ -453,9 +453,8 @@ class XssTest extends TestCase { * The expected result. * @param string $message * The assertion message to display upon failure. - * - * @dataProvider providerTestInvalidMultiByte */ + #[DataProvider('providerTestInvalidMultiByte')] public function testInvalidMultiByte($value, $expected, $message): void { $this->assertEquals(Xss::filter($value), $expected, $message); } @@ -490,9 +489,9 @@ class XssTest extends TestCase { /** * Check that strings in HTML attributes are correctly processed. * - * @covers ::attributes - * @dataProvider providerTestAttributes + * @legacy-covers ::attributes */ + #[DataProvider('providerTestAttributes')] public function testAttribute($value, $expected, $message, $allowed_tags = NULL): void { $value = Xss::filter($value, $allowed_tags); $this->assertEquals($expected, $value, $message); @@ -589,9 +588,8 @@ class XssTest extends TestCase { * The expected result. * @param string $message * The assertion message to display upon failure. - * - * @dataProvider providerTestFilterXssAdminNotNormalized */ + #[DataProvider('providerTestFilterXssAdminNotNormalized')] public function testFilterXssAdminNotNormalized($value, $expected, $message): void { $this->assertNotNormalized(Xss::filterAdmin($value), $expected, $message); } diff --git a/core/tests/Drupal/Tests/Component/Uuid/UuidTest.php b/core/tests/Drupal/Tests/Component/Uuid/UuidTest.php index 1d1f0b402322..26eeed78544d 100644 --- a/core/tests/Drupal/Tests/Component/Uuid/UuidTest.php +++ b/core/tests/Drupal/Tests/Component/Uuid/UuidTest.php @@ -4,34 +4,33 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Uuid; -use Drupal\Component\Uuid\Uuid; -use Drupal\Component\Uuid\UuidInterface; use Drupal\Component\Uuid\Com; use Drupal\Component\Uuid\Pecl; use Drupal\Component\Uuid\Php; +use Drupal\Component\Uuid\Uuid; +use Drupal\Component\Uuid\UuidInterface; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests the handling of Universally Unique Identifiers (UUIDs). - * - * @group Uuid */ +#[Group('Uuid')] class UuidTest extends TestCase { /** * Tests generating valid UUIDs. - * - * @dataProvider providerUuidInstances */ + #[DataProvider('providerUuidInstances')] public function testGenerateUuid(UuidInterface $instance): void { $this->assertTrue(Uuid::isValid($instance->generate()), sprintf('UUID generation for %s works.', get_class($instance))); } /** * Tests that generated UUIDs are unique. - * - * @dataProvider providerUuidInstances */ + #[DataProvider('providerUuidInstances')] public function testUuidIsUnique(UuidInterface $instance): void { $this->assertNotEquals($instance->generate(), $instance->generate(), sprintf('Same UUID was not generated twice with %s.', get_class($instance))); } @@ -69,9 +68,8 @@ class UuidTest extends TestCase { * Whether the uuid is valid or not. * @param string $message * The message to display on failure. - * - * @dataProvider providerTestValidation */ + #[DataProvider('providerTestValidation')] public function testValidation($uuid, $is_valid, $message): void { $this->assertSame($is_valid, Uuid::isValid($uuid), $message); } diff --git a/core/tests/Drupal/Tests/Component/Version/ConstraintTest.php b/core/tests/Drupal/Tests/Component/Version/ConstraintTest.php index 78109e573f0a..566a2d207a1e 100644 --- a/core/tests/Drupal/Tests/Component/Version/ConstraintTest.php +++ b/core/tests/Drupal/Tests/Component/Version/ConstraintTest.php @@ -5,18 +5,22 @@ declare(strict_types=1); namespace Drupal\Tests\Component\Version; use Drupal\Component\Version\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \Drupal\Component\Version\Constraint - * @group Version + * Tests Drupal\Component\Version\Constraint. */ +#[CoversClass(Constraint::class)] +#[Group('Version')] class ConstraintTest extends TestCase { /** - * @covers ::isCompatible - * @dataProvider providerIsCompatible + * @legacy-covers ::isCompatible */ + #[DataProvider('providerIsCompatible')] public function testIsCompatible(Constraint $version_info, string $current_version, bool $result): void { $this->assertSame($result, $version_info->isCompatible($current_version)); } diff --git a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryTest.php b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryTest.php index 6b8df5044f27..9db6abea52c1 100644 --- a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryTest.php @@ -87,7 +87,8 @@ class LibraryDiscoveryTest extends UnitTestCase { * Tests getting a deprecated library. */ public function testAssetLibraryDeprecation(): void { - $previous_error_handler = set_error_handler(function ($severity, $message, $file, $line) use (&$previous_error_handler) { + $previous_error_handler = get_error_handler(); + set_error_handler(function ($severity, $message, $file, $line) use (&$previous_error_handler) { // Convert deprecation error into a catchable exception. if ($severity === E_USER_DEPRECATED) { throw new \ErrorException($message, 0, $severity, $file, $line); diff --git a/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php b/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php index ad6f98c3cf78..bd64798d2406 100644 --- a/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php +++ b/core/tests/Drupal/Tests/Core/DefaultContent/FinderTest.php @@ -19,18 +19,55 @@ class FinderTest extends UnitTestCase { */ public function testFoundDataIsInDependencyOrder(): void { $finder = new Finder(__DIR__ . '/../../../../fixtures/default_content'); + $actual_order = array_keys($finder->data); - $expected_order = [ - // First is the author of the node. - '94503467-be7f-406c-9795-fc25baa22203', - // Next, the taxonomy term referenced by the node. - '550f86ad-aa11-4047-953f-636d42889f85', - // Then we have the node itself, since it has no other dependencies. - 'e1714f23-70c0-4493-8e92-af1901771921', - // Finally, the menu link to the node. - '3434bd5a-d2cd-4f26-bf79-a7f6b951a21b', - ]; - $this->assertSame($expected_order, array_slice(array_keys($finder->data), 0, 4)); + $node_uuid = 'e1714f23-70c0-4493-8e92-af1901771921'; + // The author of the node should come before the node itself. We're using + // named arguments here purely for clarity. + $this->assertRelativeOrder( + $actual_order, + earlier: '94503467-be7f-406c-9795-fc25baa22203', + later: $node_uuid, + ); + // Same with the taxonomy term referenced by the node. + $this->assertRelativeOrder( + $actual_order, + earlier: '550f86ad-aa11-4047-953f-636d42889f85', + later: $node_uuid, + ); + // The menu link to the node should come after the node. + $this->assertRelativeOrder( + $actual_order, + earlier: $node_uuid, + later: '3434bd5a-d2cd-4f26-bf79-a7f6b951a21b', + ); + + // A node that is in a workspace should come after the workspace itself. + $this->assertRelativeOrder( + $actual_order, + earlier: '384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d', + later: '48475954-e878-439c-9d3d-226724a44269', + ); + } + + /** + * Asserts that an item in an array comes before another item in that array. + * + * @param array $haystack + * The array to examine. + * @param mixed $earlier + * The item which should come first. + * @param mixed $later + * The item which should come after. + */ + private function assertRelativeOrder(array $haystack, mixed $earlier, mixed $later): void { + $haystack = array_values($haystack); + $earlier_index = array_search($earlier, $haystack, TRUE); + $later_index = array_search($later, $haystack, TRUE); + $this->assertIsInt($earlier_index); + $this->assertIsInt($later_index); + // "Later" should be greater than "earlier". + $this->assertGreaterThan($earlier_index, $later_index); } /** diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php index df504f4b7269..cdfb897046c6 100644 --- a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php @@ -6,6 +6,7 @@ namespace Drupal\Tests\Core\DependencyInjection\Compiler; use Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass; use Drupal\Tests\UnitTestCase; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Reference; @@ -307,6 +308,31 @@ class TaggedHandlersPassTest extends UnitTestCase { } /** + * Tests child handler with parent service. + * + * @covers ::process + */ + public function testProcessChildDefinition(): void { + $container = $this->buildContainer(); + + $container + ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer') + ->addTag('service_collector'); + $container + ->register('root_handler', __NAMESPACE__ . '\ValidHandler'); + $container->addDefinitions([ + 'parent_handler' => new ChildDefinition('root_handler'), + 'child_handler' => (new ChildDefinition('parent_handler'))->addTag('consumer_id'), + ]); + + $handler_pass = new TaggedHandlersPass(); + $handler_pass->process($container); + + $method_calls = $container->getDefinition('consumer_id')->getMethodCalls(); + $this->assertCount(1, $method_calls); + } + + /** * Tests consumer method with extra parameters. * * @covers ::process diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index d7a364170f04..f2afab3f8a9c 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -1002,6 +1002,57 @@ class FormBuilderTest extends FormTestBase { ]; } + /** + * Tests the detection of the triggering element. + */ + public function testTriggeringElement(): void { + $form_arg = 'Drupal\Tests\Core\Form\TestForm'; + + // No triggering element. + $form_state = new FormState(); + $this->formBuilder->buildForm($form_arg, $form_state); + $this->assertNull($form_state->getTriggeringElement()); + + // When no op is provided, default to the first button element. + $form_state = new FormState(); + $form_state->setMethod('GET'); + $form_state->setUserInput(['form_id' => 'test_form']); + $this->formBuilder->buildForm($form_arg, $form_state); + $triggeringElement = $form_state->getTriggeringElement(); + $this->assertIsArray($triggeringElement); + $this->assertSame('op', $triggeringElement['#name']); + $this->assertSame('Submit', $triggeringElement['#value']); + + // A single triggering element. + $form_state = new FormState(); + $form_state->setMethod('GET'); + $form_state->setUserInput(['form_id' => 'test_form', 'op' => 'Submit']); + $this->formBuilder->buildForm($form_arg, $form_state); + $triggeringElement = $form_state->getTriggeringElement(); + $this->assertIsArray($triggeringElement); + $this->assertSame('op', $triggeringElement['#name']); + + // A different triggering element. + $form_state = new FormState(); + $form_state->setMethod('GET'); + $form_state->setUserInput(['form_id' => 'test_form', 'other_action' => 'Other action']); + $this->formBuilder->buildForm($form_arg, $form_state); + $triggeringElement = $form_state->getTriggeringElement(); + $this->assertIsArray($triggeringElement); + $this->assertSame('other_action', $triggeringElement['#name']); + + // Two triggering elements. + $form_state = new FormState(); + $form_state->setMethod('GET'); + $form_state->setUserInput(['form_id' => 'test_form', 'op' => 'Submit', 'other_action' => 'Other action']); + $this->formBuilder->buildForm($form_arg, $form_state); + + // Verify that only the first triggering element is respected. + $triggeringElement = $form_state->getTriggeringElement(); + $this->assertIsArray($triggeringElement); + $this->assertSame('op', $triggeringElement['#name']); + } + } /** diff --git a/core/tests/Drupal/Tests/Core/Form/fixtures/form_base_test.inc b/core/tests/Drupal/Tests/Core/Form/fixtures/form_base_test.inc index 0e34dd9110d5..0e633183d331 100644 --- a/core/tests/Drupal/Tests/Core/Form/fixtures/form_base_test.inc +++ b/core/tests/Drupal/Tests/Core/Form/fixtures/form_base_test.inc @@ -36,5 +36,10 @@ function test_form_id() { '#type' => 'submit', '#value' => 'Submit', ]; + $form['actions']['other_action'] = [ + '#type' => 'submit', + '#name' => 'other_action', + '#value' => 'Other action', + ]; return $form; } diff --git a/core/tests/Drupal/Tests/Core/Render/Element/HtmlTagTest.php b/core/tests/Drupal/Tests/Core/Render/Element/HtmlTagTest.php index d0c09e97fc53..8490a5c0876e 100644 --- a/core/tests/Drupal/Tests/Core/Render/Element/HtmlTagTest.php +++ b/core/tests/Drupal/Tests/Core/Render/Element/HtmlTagTest.php @@ -6,6 +6,7 @@ namespace Drupal\Tests\Core\Render\Element; use Drupal\Core\Render\Markup; use Drupal\Tests\Core\Render\RendererTestBase; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Render\Element\HtmlTag; /** @@ -18,7 +19,7 @@ class HtmlTagTest extends RendererTestBase { * @covers ::getInfo */ public function testGetInfo(): void { - $htmlTag = new HtmlTag([], 'test', 'test'); + $htmlTag = new HtmlTag([], 'test', 'test', elementInfoManager: $this->createStub(ElementInfoManagerInterface::class)); $info = $htmlTag->getInfo(); $this->assertArrayHasKey('#pre_render', $info); $this->assertArrayHasKey('#attributes', $info); diff --git a/core/tests/Drupal/Tests/Core/Render/Element/ModernRenderElementTest.php b/core/tests/Drupal/Tests/Core/Render/Element/ModernRenderElementTest.php new file mode 100644 index 000000000000..c91b74f8c0a4 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Render/Element/ModernRenderElementTest.php @@ -0,0 +1,60 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\Core\Render\Element; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\Core\Render\Element; +use Drupal\Core\Render\Element\Textfield; +use Drupal\Core\Render\ElementInfoManager; +use Drupal\Tests\UnitTestCase; + +/** + * @coversDefaultClass \Drupal\Core\Render\Element\RenderElementBase + * @group Render + */ +class ModernRenderElementTest extends UnitTestCase { + + public function testChildren(): void { + $factory = $this->createMock(FactoryInterface::class); + $elementInfoManager = new class ($factory) extends ElementInfoManager { + + public function __construct(protected $factory) {} + + }; + $factory->expects($this->any()) + ->method('createInstance') + ->willReturnCallback(fn () => new Textfield([], '', NULL, $elementInfoManager)); + // If the type is not given ::fromRenderable presumes "form" and uses the + // plugin discovery to find which class provides the form element. This + // test does not set up discovery so some type must be provided. + $element = ['#type' => 'ignored by the mock factory']; + $elementObject = $elementInfoManager->fromRenderable($element); + for ($i = 0; $i <= 2; $i++) { + $child = [ + '#type' => 'ignored by the mock factory', + '#test' => $i, + ]; + $elementObject->addChild("test$i", $child); + // addChild() takes the $child render array by reference and stores a + // reference to it in the render object. To avoid modifying the + // previously created render object when reusing the $child variable, + // unset() it to break the reference before reassigning. + unset($child); + } + foreach ([1 => ['test0', 'test1', 'test2'], 2 => ['test0', 'test2']] as $delta => $expectedChildrenKeys) { + $i = 0; + foreach ($elementObject->getChildren() as $name => $child) { + $this->assertSame($name, "test$i"); + $this->assertSame($i, $child->test); + $i += $delta; + } + $this->assertSame(Element::children($elementObject->toRenderable()), $expectedChildrenKeys); + // The first iteration tests removing an existing child. The second + // iteration tests removing a nonexistent child. + $elementObject->removeChild('test1'); + } + } + +} diff --git a/core/tests/Drupal/Tests/Core/Render/Element/TableSelectTest.php b/core/tests/Drupal/Tests/Core/Render/Element/TableSelectTest.php index fc58c1db4efe..7acfef4ca509 100644 --- a/core/tests/Drupal/Tests/Core/Render/Element/TableSelectTest.php +++ b/core/tests/Drupal/Tests/Core/Render/Element/TableSelectTest.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\Core\Render\Element; use Drupal\Core\Form\FormState; use Drupal\Core\Link; use Drupal\Core\Render\Element\Tableselect; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Url; use Drupal\Tests\UnitTestCase; @@ -25,7 +26,7 @@ class TableSelectTest extends UnitTestCase { $form_state = new FormState(); $complete_form = []; - $element_object = new Tableselect([], 'table_select', []); + $element_object = new Tableselect([], 'table_select', [], elementInfoManager: $this->createStub(ElementInfoManagerInterface::class)); $info = $element_object->getInfo(); $element += $info; @@ -50,7 +51,7 @@ class TableSelectTest extends UnitTestCase { $form_state = new FormState(); $complete_form = []; - $element_object = new Tableselect([], 'table_select', []); + $element_object = new Tableselect([], 'table_select', [], elementInfoManager: $this->createStub(ElementInfoManagerInterface::class)); $info = $element_object->getInfo(); $element += $info; diff --git a/core/tests/Drupal/Tests/Core/Test/BrowserTestBaseTest.php b/core/tests/Drupal/Tests/Core/Test/BrowserTestBaseTest.php index 43ff47406ecc..d1a043446f8a 100644 --- a/core/tests/Drupal/Tests/Core/Test/BrowserTestBaseTest.php +++ b/core/tests/Drupal/Tests/Core/Test/BrowserTestBaseTest.php @@ -25,7 +25,7 @@ class BrowserTestBaseTest extends UnitTestCase { ->method('getDriver') ->willReturn($driver); - $btb = $this->getMockBuilder(BrowserTestBaseMockableClass::class) + $btb = $this->getMockBuilder(BrowserTestBaseMockableClassTest::class) ->disableOriginalConstructor() ->onlyMethods(['getSession']) ->getMock(); @@ -82,7 +82,7 @@ class BrowserTestBaseTest extends UnitTestCase { public function testTearDownWithoutSetUp(): void { $method = 'cleanupEnvironment'; $this->assertTrue(method_exists(BrowserTestBase::class, $method)); - $btb = $this->getMockBuilder(BrowserTestBaseMockableClass::class) + $btb = $this->getMockBuilder(BrowserTestBaseMockableClassTest::class) ->disableOriginalConstructor() ->onlyMethods([$method]) ->getMock(); @@ -96,6 +96,6 @@ class BrowserTestBaseTest extends UnitTestCase { /** * A class extending BrowserTestBase for testing purposes. */ -class BrowserTestBaseMockableClass extends BrowserTestBase { +class BrowserTestBaseMockableClassTest extends BrowserTestBase { } diff --git a/core/tests/Drupal/Tests/Core/Theme/Icon/IconTest.php b/core/tests/Drupal/Tests/Core/Theme/Icon/IconTest.php index 2c67cf9193fb..d1a6e6433407 100644 --- a/core/tests/Drupal/Tests/Core/Theme/Icon/IconTest.php +++ b/core/tests/Drupal/Tests/Core/Theme/Icon/IconTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Drupal\Tests\Core\Theme\Icon; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Render\Element\Icon; use Drupal\Core\Template\Attribute; use Drupal\Core\Theme\Icon\IconDefinition; @@ -41,7 +42,7 @@ class IconTest extends UnitTestCase { * Test the Icon::getInfo method. */ public function testGetInfo(): void { - $icon = new Icon([], 'test', 'test'); + $icon = new Icon([], 'test', 'test', elementInfoManager: $this->createStub(ElementInfoManagerInterface::class)); $info = $icon->getInfo(); $this->assertArrayHasKey('#pre_render', $info); diff --git a/core/tests/fixtures/default_content/node/48475954-e878-439c-9d3d-226724a44269.yml b/core/tests/fixtures/default_content/node/48475954-e878-439c-9d3d-226724a44269.yml new file mode 100644 index 000000000000..bae16bcd3f9e --- /dev/null +++ b/core/tests/fixtures/default_content/node/48475954-e878-439c-9d3d-226724a44269.yml @@ -0,0 +1,47 @@ +_meta: + version: '1.0' + entity_type: node + uuid: 48475954-e878-439c-9d3d-226724a44269 + bundle: page + default_langcode: en + depends: + 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d: workspace +default: + revision_uid: + - + target_id: 1 + status: + - + value: false + uid: + - + target_id: 1 + title: + - + value: 'A happy little workspace' + created: + - + value: 1751155670 + promote: + - + value: false + sticky: + - + value: false + revision_translation_affected: + - + value: true + path: + - + alias: '' + langcode: en + # TRICKY! Default Content does not export the `workspace` field because it skips internal + # properties, but core's exporter should be sure to include it. + workspace: + - + target_id: test_workspace + body: + - + value: 'This page lives in a workspace! How neat!' + format: plain_text + summary: '' diff --git a/core/tests/fixtures/default_content/workspace/inner_test.yml b/core/tests/fixtures/default_content/workspace/inner_test.yml new file mode 100644 index 000000000000..45a1bcc22dcb --- /dev/null +++ b/core/tests/fixtures/default_content/workspace/inner_test.yml @@ -0,0 +1,24 @@ +_meta: + version: '1.0' + entity_type: workspace + uuid: 93f5b0b4-ada9-4bcd-a11d-f7329e9afe21 + depends: + 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d: workspace +default: + # TRICKY! Default Content does not export the `id` field, but core's exporter should. + # Without it, the import will fail. + id: + - + value: inner_test + uid: + - + target_id: 1 + label: + - + value: 'Inner Test' + parent: + - + entity: 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d + created: + - + value: 1751154834 diff --git a/core/tests/fixtures/default_content/workspace/test_workspace.yml b/core/tests/fixtures/default_content/workspace/test_workspace.yml new file mode 100644 index 000000000000..c22379406797 --- /dev/null +++ b/core/tests/fixtures/default_content/workspace/test_workspace.yml @@ -0,0 +1,19 @@ +_meta: + version: '1.0' + entity_type: workspace + uuid: 384c4c10-cc41-4d7e-a1cc-85d1cdc9e87d +default: + # TRICKY! Default Content does not export the `id` field, but core's exporter should. + # Without it, the import will fail. + id: + - + value: test_workspace + uid: + - + target_id: 1 + label: + - + value: 'Test Workspace' + created: + - + value: 1751154825 diff --git a/core/themes/claro/claro.theme b/core/themes/claro/claro.theme index 81255d798a3e..e6b5cc443163 100644 --- a/core/themes/claro/claro.theme +++ b/core/themes/claro/claro.theme @@ -1181,7 +1181,7 @@ function claro_preprocess_table(&$variables): void { $first_cell_key = array_key_first($row['cells']); // The 'attributes' key is always here and it is an // \Drupal\Core\Template\Attribute. - // @see template_preprocess_table(); + // @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable(); $row['cells'][$first_cell_key]['attributes']->addClass('tabledrag-cell'); // Check that the first cell is empty or not. diff --git a/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig b/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig index e9928fd77660..4940f047477e 100644 --- a/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig +++ b/core/themes/claro/templates/classy/dataset/item-list--search-results.html.twig @@ -17,7 +17,7 @@ * results, the following data is set: * - plugin: The search plugin ID, for example "node_search". * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% diff --git a/core/themes/claro/templates/classy/dataset/item-list.html.twig b/core/themes/claro/templates/classy/dataset/item-list.html.twig index 20541b0b7e66..6e7b8e317b1d 100644 --- a/core/themes/claro/templates/classy/dataset/item-list.html.twig +++ b/core/themes/claro/templates/classy/dataset/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if context.list_style %} diff --git a/core/themes/claro/templates/classy/dataset/table.html.twig b/core/themes/claro/templates/classy/dataset/table.html.twig index cdfe0bff7e73..d7e6459bd4a4 100644 --- a/core/themes/claro/templates/classy/dataset/table.html.twig +++ b/core/themes/claro/templates/classy/dataset/table.html.twig @@ -38,7 +38,7 @@ * - no_striping: A boolean indicating that the row should receive no striping. * - header_columns: The number of columns in the header. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() */ #} <table{{ attributes }}> diff --git a/core/themes/claro/templates/classy/field/image.html.twig b/core/themes/claro/templates/classy/field/image.html.twig index 31f782bb60a8..90d955c180a6 100644 --- a/core/themes/claro/templates/classy/field/image.html.twig +++ b/core/themes/claro/templates/classy/field/image.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the img tag. * - style_name: (optional) The name of the image style applied. * - * @see template_preprocess_image() + * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage() */ #} {% diff --git a/core/themes/claro/templates/classy/layout/region.html.twig b/core/themes/claro/templates/classy/layout/region.html.twig index 95e71cec37e4..a4e8cc0af72a 100644 --- a/core/themes/claro/templates/classy/layout/region.html.twig +++ b/core/themes/claro/templates/classy/layout/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/claro/templates/details.html.twig b/core/themes/claro/templates/details.html.twig index 196f6f21e03f..bf5037ead199 100644 --- a/core/themes/claro/templates/details.html.twig +++ b/core/themes/claro/templates/details.html.twig @@ -75,7 +75,12 @@ </div> {% endif %} {%- if description -%} - <div class="claro-details__description{{ disabled ? ' is-disabled' }}">{{ description }}</div> + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + {% set description_classes = [ + 'claro-details__description', + disabled ? 'is-disabled', + ] %} + <div{{ description_attributes.addClass(description_classes) }}>{{ description }}</div> {%- endif -%} {%- if children -%} {{ children }} diff --git a/core/themes/claro/templates/install-page.html.twig b/core/themes/claro/templates/install-page.html.twig index bd22c5a94598..f7456333c8d1 100644 --- a/core/themes/claro/templates/install-page.html.twig +++ b/core/themes/claro/templates/install-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_install_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessInstallPage() */ #} <div class="layout-container"> diff --git a/core/themes/claro/templates/maintenance-page--front.html.twig b/core/themes/claro/templates/maintenance-page--front.html.twig index 12a364ee6a00..daeaf2d7ca23 100644 --- a/core/themes/claro/templates/maintenance-page--front.html.twig +++ b/core/themes/claro/templates/maintenance-page--front.html.twig @@ -9,7 +9,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() */ #} <div class="layout-container"> diff --git a/core/themes/claro/templates/maintenance-page.html.twig b/core/themes/claro/templates/maintenance-page.html.twig index 9bbb9a0e0ce4..ef40bbcbec0e 100644 --- a/core/themes/claro/templates/maintenance-page.html.twig +++ b/core/themes/claro/templates/maintenance-page.html.twig @@ -9,7 +9,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() * @see maintenance-page--front.html.twig */ #} diff --git a/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig b/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig index d7931e16b1d5..302e437c8a1f 100644 --- a/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig +++ b/core/themes/claro/templates/media-library/item-list--media-library-add-form-media-list.html.twig @@ -20,7 +20,7 @@ * - list_style: The custom list style. * * @see claro_preprocess_item_list__media_library_add_form_media_list() - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if items -%} diff --git a/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig b/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig index be73cf7bb89c..7c8a59ebd35a 100644 --- a/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig +++ b/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig @@ -11,7 +11,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() * * @todo remove this after https://www.drupal.org/node/3051605 has been solved. */ diff --git a/core/themes/claro/templates/navigation/menu-local-task.html.twig b/core/themes/claro/templates/navigation/menu-local-task.html.twig index b135a5ddcf3b..b0b6ce08db94 100644 --- a/core/themes/claro/templates/navigation/menu-local-task.html.twig +++ b/core/themes/claro/templates/navigation/menu-local-task.html.twig @@ -12,7 +12,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() */ #} {% diff --git a/core/themes/claro/templates/pager.html.twig b/core/themes/claro/templates/pager.html.twig index 3b47d51b2079..9c2086a8eb36 100644 --- a/core/themes/claro/templates/pager.html.twig +++ b/core/themes/claro/templates/pager.html.twig @@ -28,7 +28,7 @@ * at the first page. * - next: Present if the visible list of pages ends before the last page. * - * @see template_preprocess_pager() + * @see \Drupal\Core\Pager\PagerPreprocess::preprocessPager() * * @todo review all uses of the replace() filter below in * https://www.drupal.org/node/3053707 as the behavior it addresses will diff --git a/core/themes/claro/templates/region--breadcrumb.html.twig b/core/themes/claro/templates/region--breadcrumb.html.twig index a66f43131d60..e58c6a01a2d5 100644 --- a/core/themes/claro/templates/region--breadcrumb.html.twig +++ b/core/themes/claro/templates/region--breadcrumb.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/olivero/olivero.theme b/core/themes/olivero/olivero.theme index 88aa7531379c..8ddfb49b87ff 100644 --- a/core/themes/olivero/olivero.theme +++ b/core/themes/olivero/olivero.theme @@ -83,7 +83,7 @@ function olivero_preprocess_maintenance_page(&$variables): void { // By default, site_name is set to Drupal if no db connection is available // or during site installation. Setting site_name to an empty string makes // the site and update pages look cleaner. - // @see template_preprocess_maintenance_page + // @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() if (!$variables['db_is_active']) { $variables['site_name'] = ''; } diff --git a/core/themes/olivero/templates/dataset/item-list--search-results.html.twig b/core/themes/olivero/templates/dataset/item-list--search-results.html.twig index 6bd6441738d6..665634e86885 100644 --- a/core/themes/olivero/templates/dataset/item-list--search-results.html.twig +++ b/core/themes/olivero/templates/dataset/item-list--search-results.html.twig @@ -17,7 +17,7 @@ * results, the following data is set: * - plugin: The search plugin ID, for example "node_search". * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% diff --git a/core/themes/olivero/templates/dataset/item-list.html.twig b/core/themes/olivero/templates/dataset/item-list.html.twig index 7c7a6e365bfe..06b1e2ebe4fe 100644 --- a/core/themes/olivero/templates/dataset/item-list.html.twig +++ b/core/themes/olivero/templates/dataset/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if context.list_style %} diff --git a/core/themes/olivero/templates/form/details.html.twig b/core/themes/olivero/templates/form/details.html.twig index c7a7f9c416a2..e4de3d168152 100644 --- a/core/themes/olivero/templates/form/details.html.twig +++ b/core/themes/olivero/templates/form/details.html.twig @@ -50,7 +50,8 @@ </div> {% endif %} {%- if description -%} - <div class="olivero-details__description">{{ description }}</div> + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + <div{{ description_attributes.addClass(['olivero-details__description']) }}>{{ description }}</div> {%- endif -%} {%- if children -%} {{ children }} diff --git a/core/themes/olivero/templates/layout/region--breadcrumb.html.twig b/core/themes/olivero/templates/layout/region--breadcrumb.html.twig index 5dbd5d45ffd7..a76183c62c4d 100644 --- a/core/themes/olivero/templates/layout/region--breadcrumb.html.twig +++ b/core/themes/olivero/templates/layout/region--breadcrumb.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--content-above.html.twig b/core/themes/olivero/templates/layout/region--content-above.html.twig index e50082ea3952..1a86195d928e 100644 --- a/core/themes/olivero/templates/layout/region--content-above.html.twig +++ b/core/themes/olivero/templates/layout/region--content-above.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--content-below.html.twig b/core/themes/olivero/templates/layout/region--content-below.html.twig index c0f9ed29e5e2..5cae5f8b825d 100644 --- a/core/themes/olivero/templates/layout/region--content-below.html.twig +++ b/core/themes/olivero/templates/layout/region--content-below.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/olivero/templates/layout/region--content.html.twig b/core/themes/olivero/templates/layout/region--content.html.twig index 7f86b5810362..60e53660b1f8 100644 --- a/core/themes/olivero/templates/layout/region--content.html.twig +++ b/core/themes/olivero/templates/layout/region--content.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--footer-bottom.html.twig b/core/themes/olivero/templates/layout/region--footer-bottom.html.twig index 36ffd885f95a..9677f84970ec 100644 --- a/core/themes/olivero/templates/layout/region--footer-bottom.html.twig +++ b/core/themes/olivero/templates/layout/region--footer-bottom.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--footer-top.html.twig b/core/themes/olivero/templates/layout/region--footer-top.html.twig index 8baaf314056c..c657df25ba3b 100644 --- a/core/themes/olivero/templates/layout/region--footer-top.html.twig +++ b/core/themes/olivero/templates/layout/region--footer-top.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--header.html.twig b/core/themes/olivero/templates/layout/region--header.html.twig index 8ecee6633649..47912006894d 100644 --- a/core/themes/olivero/templates/layout/region--header.html.twig +++ b/core/themes/olivero/templates/layout/region--header.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--highlighted.html.twig b/core/themes/olivero/templates/layout/region--highlighted.html.twig index 6c4293680741..b3dd5f2570c1 100644 --- a/core/themes/olivero/templates/layout/region--highlighted.html.twig +++ b/core/themes/olivero/templates/layout/region--highlighted.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--primary-menu.html.twig b/core/themes/olivero/templates/layout/region--primary-menu.html.twig index 32ade151fe6e..f1fa999007e3 100644 --- a/core/themes/olivero/templates/layout/region--primary-menu.html.twig +++ b/core/themes/olivero/templates/layout/region--primary-menu.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--secondary-menu.html.twig b/core/themes/olivero/templates/layout/region--secondary-menu.html.twig index ce7312194fe3..e64c0743b98b 100644 --- a/core/themes/olivero/templates/layout/region--secondary-menu.html.twig +++ b/core/themes/olivero/templates/layout/region--secondary-menu.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region--sidebar.html.twig b/core/themes/olivero/templates/layout/region--sidebar.html.twig index a3f88864f958..8b44262fee3a 100644 --- a/core/themes/olivero/templates/layout/region--sidebar.html.twig +++ b/core/themes/olivero/templates/layout/region--sidebar.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/olivero/templates/layout/region--social.html.twig b/core/themes/olivero/templates/layout/region--social.html.twig index 6f3cf697408d..dff6e3bbda7f 100644 --- a/core/themes/olivero/templates/layout/region--social.html.twig +++ b/core/themes/olivero/templates/layout/region--social.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} diff --git a/core/themes/olivero/templates/layout/region.html.twig b/core/themes/olivero/templates/layout/region.html.twig index 651a0112a56c..df39e7d8b451 100644 --- a/core/themes/olivero/templates/layout/region.html.twig +++ b/core/themes/olivero/templates/layout/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/olivero/templates/maintenance-page.html.twig b/core/themes/olivero/templates/maintenance-page.html.twig index f1c97e5c6e7f..cce49aea822a 100644 --- a/core/themes/olivero/templates/maintenance-page.html.twig +++ b/core/themes/olivero/templates/maintenance-page.html.twig @@ -5,7 +5,7 @@ * * All available variables are mirrored in page.html.twig. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() */ #} diff --git a/core/themes/olivero/templates/menu-local-action.html.twig b/core/themes/olivero/templates/menu-local-action.html.twig index f78e6f538f9c..12bf7d27ec53 100644 --- a/core/themes/olivero/templates/menu-local-action.html.twig +++ b/core/themes/olivero/templates/menu-local-action.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the wrapper element. * - link: A rendered link element. * - * @see template_preprocess_menu_local_action() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalAction() * * @ingroup themeable */ diff --git a/core/themes/olivero/templates/navigation/menu-local-task.html.twig b/core/themes/olivero/templates/navigation/menu-local-task.html.twig index 34bbb1c44d4c..50b71dabee56 100644 --- a/core/themes/olivero/templates/navigation/menu-local-task.html.twig +++ b/core/themes/olivero/templates/navigation/menu-local-task.html.twig @@ -12,7 +12,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() */ #} <li{{ attributes.addClass('tabs__tab', is_active ? 'is-active') }}> diff --git a/core/themes/olivero/templates/navigation/pager.html.twig b/core/themes/olivero/templates/navigation/pager.html.twig index a038ef0abc5b..e26ba9764345 100644 --- a/core/themes/olivero/templates/navigation/pager.html.twig +++ b/core/themes/olivero/templates/navigation/pager.html.twig @@ -28,7 +28,7 @@ * at the first page. * - next: Present if the visible list of pages ends before the last page. * - * @see template_preprocess_pager() + * @see \Drupal\Core\Pager\PagerPreprocess::preprocessPager() */ #} {% if items %} diff --git a/core/themes/stable9/templates/dataset/item-list.html.twig b/core/themes/stable9/templates/dataset/item-list.html.twig index 86cc63670c92..4fceba2d0702 100644 --- a/core/themes/stable9/templates/dataset/item-list.html.twig +++ b/core/themes/stable9/templates/dataset/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if context.list_style %} diff --git a/core/themes/stable9/templates/dataset/table.html.twig b/core/themes/stable9/templates/dataset/table.html.twig index d9e12ff284f8..89ab32c486e7 100644 --- a/core/themes/stable9/templates/dataset/table.html.twig +++ b/core/themes/stable9/templates/dataset/table.html.twig @@ -38,7 +38,7 @@ * - no_striping: A boolean indicating that the row should receive no striping. * - header_columns: The number of columns in the header. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() */ #} <table{{ attributes }}> diff --git a/core/themes/stable9/templates/field/image.html.twig b/core/themes/stable9/templates/field/image.html.twig index b342eee6dada..bb3d34e23210 100644 --- a/core/themes/stable9/templates/field/image.html.twig +++ b/core/themes/stable9/templates/field/image.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the img tag. * - style_name: (optional) The name of the image style applied. * - * @see template_preprocess_image() + * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage() */ #} <img{{ attributes }} /> diff --git a/core/themes/stable9/templates/form/details.html.twig b/core/themes/stable9/templates/form/details.html.twig index 19879959273d..5a6538a21cce 100644 --- a/core/themes/stable9/templates/form/details.html.twig +++ b/core/themes/stable9/templates/form/details.html.twig @@ -32,7 +32,11 @@ </div> {% endif %} - {{ description }} + {%- if description -%} + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + <div{{ description_attributes }}>{{ description }}</div> + {%- endif -%} + {{ children }} {{ value }} </details> diff --git a/core/themes/stable9/templates/layout/install-page.html.twig b/core/themes/stable9/templates/layout/install-page.html.twig index e2d3381e4858..3a7a346edfe6 100644 --- a/core/themes/stable9/templates/layout/install-page.html.twig +++ b/core/themes/stable9/templates/layout/install-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_install_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessInstallPage() */ #} <div class="layout-container"> diff --git a/core/themes/stable9/templates/layout/maintenance-page.html.twig b/core/themes/stable9/templates/layout/maintenance-page.html.twig index de0acaabbbb4..cdbb3c0e11fe 100644 --- a/core/themes/stable9/templates/layout/maintenance-page.html.twig +++ b/core/themes/stable9/templates/layout/maintenance-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() */ #} <header role="banner"> diff --git a/core/themes/stable9/templates/layout/region.html.twig b/core/themes/stable9/templates/layout/region.html.twig index e5e36d07410b..400b985d07f6 100644 --- a/core/themes/stable9/templates/layout/region.html.twig +++ b/core/themes/stable9/templates/layout/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% if content %} diff --git a/core/themes/stable9/templates/navigation/menu-local-action.html.twig b/core/themes/stable9/templates/navigation/menu-local-action.html.twig index 27872837abdc..138d29a00997 100644 --- a/core/themes/stable9/templates/navigation/menu-local-action.html.twig +++ b/core/themes/stable9/templates/navigation/menu-local-action.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the wrapper element. * - link: A rendered link element. * - * @see template_preprocess_menu_local_action() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalAction() */ #} <li{{ attributes }}>{{ link }}</li> diff --git a/core/themes/stable9/templates/navigation/menu-local-task.html.twig b/core/themes/stable9/templates/navigation/menu-local-task.html.twig index b6c3ca241913..2bd91d6bf64f 100644 --- a/core/themes/stable9/templates/navigation/menu-local-task.html.twig +++ b/core/themes/stable9/templates/navigation/menu-local-task.html.twig @@ -11,7 +11,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() */ #} <li{{ attributes }}>{{ link }}</li> diff --git a/core/themes/stable9/templates/navigation/pager.html.twig b/core/themes/stable9/templates/navigation/pager.html.twig index cb96612a40e1..0292022f5373 100644 --- a/core/themes/stable9/templates/navigation/pager.html.twig +++ b/core/themes/stable9/templates/navigation/pager.html.twig @@ -28,7 +28,7 @@ * at the first page. * - next: Present if the visible list of pages ends before the last page. * - * @see template_preprocess_pager() + * @see \Drupal\Core\Pager\PagerPreprocess::preprocessPager() */ #} {% if items %} diff --git a/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig b/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig index e9928fd77660..4940f047477e 100644 --- a/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig +++ b/core/themes/starterkit_theme/templates/dataset/item-list--search-results.html.twig @@ -17,7 +17,7 @@ * results, the following data is set: * - plugin: The search plugin ID, for example "node_search". * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% diff --git a/core/themes/starterkit_theme/templates/dataset/item-list.html.twig b/core/themes/starterkit_theme/templates/dataset/item-list.html.twig index 20541b0b7e66..6e7b8e317b1d 100644 --- a/core/themes/starterkit_theme/templates/dataset/item-list.html.twig +++ b/core/themes/starterkit_theme/templates/dataset/item-list.html.twig @@ -16,7 +16,7 @@ * - context: A list of contextual data associated with the list. May contain: * - list_style: The custom list style. * - * @see template_preprocess_item_list() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessItemList() */ #} {% if context.list_style %} diff --git a/core/themes/starterkit_theme/templates/dataset/table.html.twig b/core/themes/starterkit_theme/templates/dataset/table.html.twig index cdfe0bff7e73..d7e6459bd4a4 100644 --- a/core/themes/starterkit_theme/templates/dataset/table.html.twig +++ b/core/themes/starterkit_theme/templates/dataset/table.html.twig @@ -38,7 +38,7 @@ * - no_striping: A boolean indicating that the row should receive no striping. * - header_columns: The number of columns in the header. * - * @see template_preprocess_table() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable() */ #} <table{{ attributes }}> diff --git a/core/themes/starterkit_theme/templates/field/image.html.twig b/core/themes/starterkit_theme/templates/field/image.html.twig index 31f782bb60a8..90d955c180a6 100644 --- a/core/themes/starterkit_theme/templates/field/image.html.twig +++ b/core/themes/starterkit_theme/templates/field/image.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the img tag. * - style_name: (optional) The name of the image style applied. * - * @see template_preprocess_image() + * @see \Drupal\Core\Theme\ImagePreprocess::preprocessImage() */ #} {% diff --git a/core/themes/starterkit_theme/templates/form/details.html.twig b/core/themes/starterkit_theme/templates/form/details.html.twig index c554096da9d7..5dea0b485f1a 100644 --- a/core/themes/starterkit_theme/templates/form/details.html.twig +++ b/core/themes/starterkit_theme/templates/form/details.html.twig @@ -32,7 +32,8 @@ </div> {% endif %} {%- if description -%} - <div class="details-description">{{ description }}</div> + {% set description_attributes = create_attribute({id: attributes['aria-describedby']}) %} + <div{{ description_attributes.addClass(['details-description']) }}>{{ description }}</div> {%- endif -%} {%- if children -%} {{ children }} diff --git a/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig b/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig index 7463b0238ca3..edd2783619b8 100644 --- a/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig +++ b/core/themes/starterkit_theme/templates/layout/maintenance-page.html.twig @@ -6,7 +6,7 @@ * All available variables are mirrored in page.html.twig. * Some may be blank but they are provided for consistency. * - * @see template_preprocess_maintenance_page() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage() */ #} <div class="layout-container"> diff --git a/core/themes/starterkit_theme/templates/layout/region.html.twig b/core/themes/starterkit_theme/templates/layout/region.html.twig index 95e71cec37e4..a4e8cc0af72a 100644 --- a/core/themes/starterkit_theme/templates/layout/region.html.twig +++ b/core/themes/starterkit_theme/templates/layout/region.html.twig @@ -9,7 +9,7 @@ * - region: The name of the region variable as defined in the theme's * .info.yml file. * - * @see template_preprocess_region() + * @see \Drupal\Core\Theme\ThemePreprocess::preprocessRegion() */ #} {% diff --git a/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig b/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig index 27872837abdc..138d29a00997 100644 --- a/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig +++ b/core/themes/starterkit_theme/templates/navigation/menu-local-action.html.twig @@ -7,7 +7,7 @@ * - attributes: HTML attributes for the wrapper element. * - link: A rendered link element. * - * @see template_preprocess_menu_local_action() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalAction() */ #} <li{{ attributes }}>{{ link }}</li> diff --git a/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig b/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig index b8559815b9e9..ce62d46c9f84 100644 --- a/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig +++ b/core/themes/starterkit_theme/templates/navigation/menu-local-task.html.twig @@ -11,7 +11,7 @@ * Note: This template renders the content for each task item in * menu-local-tasks.html.twig. * - * @see template_preprocess_menu_local_task() + * @see \Drupal\Core\Menu\MenuPreprocess::preprocessMenuLocalTask() */ #} <li{{ attributes.addClass(is_active ? 'is-active') }}>{{ link }}</li> diff --git a/core/themes/starterkit_theme/templates/navigation/pager.html.twig b/core/themes/starterkit_theme/templates/navigation/pager.html.twig index cb96612a40e1..0292022f5373 100644 --- a/core/themes/starterkit_theme/templates/navigation/pager.html.twig +++ b/core/themes/starterkit_theme/templates/navigation/pager.html.twig @@ -28,7 +28,7 @@ * at the first page. * - next: Present if the visible list of pages ends before the last page. * - * @see template_preprocess_pager() + * @see \Drupal\Core\Pager\PagerPreprocess::preprocessPager() */ #} {% if items %} diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index d4ba8a91829a..a1b3eba99ca5 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -602,6 +602,18 @@ $settings['update_free_access'] = FALSE; # $settings['file_temp_path'] = '/tmp'; /** + * Automatically create an Apache HTTP .htaccess file in writable directories. + * + * This setting can be disabled if you are not using Apache HTTP server, or if + * you have a web server configuration that protects the various writable file + * directories. + * + * @see \Drupal\Component\FileSecurity\FileSecurity::writeHtaccess() + * @see https://www.drupal.org/docs/administering-a-drupal-site/security-in-drupal/securing-file-permissions-and-ownership + */ +# $settings['auto_create_htaccess'] = FALSE; + +/** * Session write interval: * * Set the minimum interval between each session write to database. |