diff options
133 files changed, 3539 insertions, 3991 deletions
diff --git a/composer.lock b/composer.lock index 9845f90593e6..8601d07b621f 100644 --- a/composer.lock +++ b/composer.lock @@ -497,7 +497,7 @@ "dist": { "type": "path", "url": "core", - "reference": "6f52d7dd7a51d6925d01eb25052131d20ba3ee73" + "reference": "a2b58b83685c78305e1448a22fc4b6d0efa3e16d" }, "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", @@ -3636,6 +3637,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": { 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/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 4b4b01e4701b..de0cfedb16f1 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, @@ -11534,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, @@ -11582,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, @@ -11858,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, @@ -11906,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, @@ -11954,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, @@ -11996,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, @@ -13022,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, @@ -13076,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, @@ -13124,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, @@ -13166,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, @@ -13592,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, @@ -13640,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, @@ -14132,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, @@ -14180,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, @@ -14222,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, @@ -14270,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, @@ -15686,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, @@ -15836,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, @@ -15884,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, @@ -16184,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, @@ -16232,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, @@ -16274,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, @@ -16322,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, @@ -17678,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, @@ -17720,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, @@ -17774,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, @@ -18224,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, @@ -18272,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, @@ -18968,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, @@ -19016,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, @@ -20594,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, @@ -20642,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, @@ -20684,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, @@ -20732,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, @@ -21482,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, @@ -21524,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, @@ -23192,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, @@ -23246,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, @@ -23294,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, @@ -23336,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, @@ -23942,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, @@ -23990,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, @@ -26387,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[] = [ @@ -26487,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, @@ -27021,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, @@ -27069,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, @@ -27117,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, @@ -27159,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, @@ -27819,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, @@ -27867,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, @@ -28173,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, @@ -28221,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, @@ -28533,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, @@ -28587,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, @@ -28725,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, @@ -28773,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, @@ -29277,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, @@ -29325,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, @@ -29967,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, @@ -30015,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, @@ -30063,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, @@ -30105,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, @@ -30849,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, @@ -31017,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, @@ -31793,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, @@ -31841,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, @@ -31901,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, @@ -31955,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, @@ -32003,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, @@ -32093,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, @@ -34213,24 +33013,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, @@ -34261,24 +33043,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, @@ -34303,24 +33067,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, @@ -34351,24 +33097,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, @@ -35149,24 +33877,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, @@ -35203,24 +33913,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, @@ -35245,24 +33937,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, @@ -35293,24 +33967,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, @@ -36609,24 +35265,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, @@ -36657,24 +35295,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, @@ -36699,24 +35319,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, @@ -36753,24 +35355,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, @@ -36879,12 +35463,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, @@ -36897,18 +35475,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, @@ -44115,24 +42681,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, @@ -44163,24 +42711,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, @@ -45615,24 +44145,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, @@ -45663,24 +44175,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, @@ -45867,6 +44361,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, @@ -46131,24 +44637,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, @@ -46179,24 +44667,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, @@ -46599,6 +45069,31 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/modules/workspaces/tests/src/Kernel/EntityWorkspaceConflictConstraintValidatorTest.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\\\\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\\\\EntityWorkspaceConflictConstraintValidatorTest\\:\\:initializeWorkspacesModule\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -47415,24 +45910,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, @@ -47463,24 +45940,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, @@ -47505,24 +45964,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, @@ -47553,24 +45994,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, @@ -47595,24 +46018,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, @@ -47643,24 +46048,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, @@ -47685,24 +46072,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, @@ -47733,24 +46102,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, @@ -47775,24 +46126,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, @@ -47823,24 +46156,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, @@ -47865,24 +46180,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, @@ -47913,24 +46210,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 648959a56b32..a81dcc29d301 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -222,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 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..1ef99e7496b0 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", 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..33e9498bbc4b 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1797,7 +1797,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 +1902,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..6c5d563cb63a 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -10,9 +10,8 @@ use Drupal\Core\Datetime\DatePreprocess; use Drupal\Core\Field\FieldPreprocess; +use Drupal\Core\Pager\PagerPreprocess; 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; @@ -1212,137 +1211,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.2.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); } /** 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/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/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index b187560c10fb..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 = []; @@ -624,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/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/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/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/Render/Element/Pager.php b/core/lib/Drupal/Core/Render/Element/Pager.php index b5aa9bacf2c4..f6f4b05a3243 100644 --- a/core/lib/Drupal/Core/Render/Element/Pager.php +++ b/core/lib/Drupal/Core/Render/Element/Pager.php @@ -80,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/ElementInfoManager.php b/core/lib/Drupal/Core/Render/ElementInfoManager.php index b906c6204c26..a57aaf71b1fe 100644 --- a/core/lib/Drupal/Core/Render/ElementInfoManager.php +++ b/core/lib/Drupal/Core/Render/ElementInfoManager.php @@ -189,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. 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/ThemeCommonElements.php b/core/lib/Drupal/Core/Theme/ThemeCommonElements.php index 90994e47dca5..0d5e7c6638a5 100644 --- a/core/lib/Drupal/Core/Theme/ThemeCommonElements.php +++ b/core/lib/Drupal/Core/Theme/ThemeCommonElements.php @@ -6,6 +6,7 @@ namespace Drupal\Core\Theme; use Drupal\Core\Datetime\DatePreprocess; use Drupal\Core\Field\FieldPreprocess; +use Drupal\Core\Pager\PagerPreprocess; /** * Provide common theme render elements. @@ -181,6 +182,7 @@ class ThemeCommonElements { ], 'pager' => [ 'render element' => 'pager', + 'initial preprocess' => PagerPreprocess::class . ':preprocessPager', ], 'menu' => [ 'variables' => [ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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. |