summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml7
-rw-r--r--core/.phpstan-baseline.php12
-rw-r--r--core/core.libraries.yml15
-rw-r--r--core/core.services.yml2
-rw-r--r--core/lib/Drupal/Component/Datetime/DateTimePlus.php56
-rw-r--r--core/misc/ajax.js14
-rw-r--r--core/misc/components/reset-appearance.module.css (renamed from core/modules/system/css/components/reset-appearance.module.css)0
-rw-r--r--core/misc/htmx/htmx-assets.js3
-rw-r--r--core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php11
-rw-r--r--core/modules/content_translation/src/ContentTranslationManager.php24
-rw-r--r--core/modules/content_translation/src/Hook/ContentTranslationHooks.php28
-rw-r--r--core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php2
-rw-r--r--core/modules/package_manager/package_manager.services.yml5
-rw-r--r--core/modules/package_manager/src/ComposerRunner.php54
-rw-r--r--core/modules/package_manager/src/ProcessFactory.php97
-rw-r--r--core/modules/package_manager/tests/src/Functional/ComposerRequirementTest.php5
-rw-r--r--core/modules/package_manager/tests/src/Kernel/ProcessFactoryTest.php36
-rw-r--r--core/modules/package_manager/tests/src/Kernel/ServicesTest.php6
-rw-r--r--core/modules/package_manager/tests/src/Unit/ComposerRunnerTest.php63
-rw-r--r--core/modules/path/src/Plugin/Field/FieldType/PathItem.php28
-rw-r--r--core/modules/path/tests/modules/path_test_misc/path_test_misc.info.yml5
-rw-r--r--core/modules/path/tests/modules/path_test_misc/src/Hook/PathTestMiscHooks.php40
-rw-r--r--core/modules/path/tests/src/Functional/PathNodeFormTest.php28
-rw-r--r--core/modules/system/system.libraries.yml1
-rw-r--r--core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml7
-rw-r--r--core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php31
-rw-r--r--core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php35
-rw-r--r--core/modules/system/tests/modules/test_htmx/src/Form/HtmxTestAjaxForm.php2
-rw-r--r--core/modules/system/tests/modules/test_htmx/test_htmx.routing.yml16
-rw-r--r--core/modules/views/src/Hook/ViewsHooks.php2
-rw-r--r--core/modules/views/src/ViewsConfigUpdater.php101
-rw-r--r--core/modules/views/tests/modules/views_test_config_updater/src/Hook/ViewsTestConfigUpdaterHooks.php34
-rw-r--r--core/modules/views/tests/modules/views_test_config_updater/views_test_config_updater.info.yml6
-rw-r--r--core/modules/views/tests/modules/views_test_config_updater/views_test_config_updater.post_update.php22
-rw-r--r--core/modules/views/tests/src/Functional/ViewsConfigUpdaterTest.php72
-rw-r--r--core/modules/views/views.api.php1
-rw-r--r--core/modules/views/views.post_update.php6
-rw-r--r--core/modules/views/views.services.yml2
-rw-r--r--core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php2
-rw-r--r--core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php4
-rw-r--r--core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php13
-rw-r--r--core/tests/Drupal/BuildTests/QuickStart/QuickStartTest.php (renamed from core/tests/Drupal/Tests/Core/Command/QuickStartTest.php)21
-rw-r--r--core/tests/Drupal/BuildTests/QuickStart/RecipeQuickStartTest.php (renamed from core/tests/Drupal/Tests/Core/Recipe/RecipeQuickStartTest.php)23
-rw-r--r--core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php6
-rw-r--r--core/tests/Drupal/Nightwatch/Tests/a11yTestAdmin.js31
-rw-r--r--core/tests/Drupal/Nightwatch/Tests/htmx/htmxTest.js54
-rw-r--r--core/themes/claro/css/components/breadcrumb.css3
-rw-r--r--core/themes/claro/css/components/breadcrumb.pcss.css3
-rw-r--r--core/themes/claro/css/components/tabs.css6
-rw-r--r--core/themes/claro/css/components/tabs.pcss.css6
-rw-r--r--core/themes/claro/templates/navigation/menu-local-task.html.twig1
-rw-r--r--core/themes/olivero/css/components/dropbutton.css10
-rw-r--r--core/themes/olivero/css/components/dropbutton.pcss.css12
-rw-r--r--core/themes/olivero/css/components/navigation/nav-primary-wide.css2
-rw-r--r--core/themes/olivero/css/components/navigation/nav-primary-wide.pcss.css2
-rw-r--r--core/themes/olivero/css/components/navigation/nav-secondary.css4
-rw-r--r--core/themes/olivero/css/components/navigation/nav-secondary.pcss.css4
-rw-r--r--core/themes/olivero/olivero.libraries.yml16
-rw-r--r--core/themes/olivero/olivero.theme10
-rw-r--r--core/themes/stable9/css/core/components/reset-appearance.module.css (renamed from core/themes/stable9/css/system/components/reset-appearance.module.css)0
-rw-r--r--core/themes/stable9/stable9.info.yml6
61 files changed, 714 insertions, 404 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 93640185196..c6cb7502b17 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -556,7 +556,7 @@ default:
<<: [ *default-job-settings-lint, *default-phpunit-job-settings ]
variables:
TESTSUITE: PHPUnit-Unit
- KUBERNETES_CPU_REQUEST: "8"
+ KUBERNETES_CPU_REQUEST: "4"
CONCURRENCY: 24
_TARGET_PHP: "8.3-ubuntu"
@@ -564,7 +564,7 @@ default:
<<: [ *default-job-settings-lint, *default-phpunit-job-settings ]
variables:
TESTSUITE: PHPUnit-Unit
- KUBERNETES_CPU_REQUEST: "8"
+ KUBERNETES_CPU_REQUEST: "4"
CONCURRENCY: 24
_TARGET_PHP: "8.4-ubuntu"
@@ -573,7 +573,7 @@ default:
allow_failure: true
variables:
TESTSUITE: PHPUnit-Unit
- KUBERNETES_CPU_REQUEST: "8"
+ KUBERNETES_CPU_REQUEST: "4"
CONCURRENCY: 24
_TARGET_PHP: "8.5-ubuntu"
@@ -636,6 +636,7 @@ default:
- "**/config/schema/*.schema.yml"
# Modules may alter config schema using hook_config_schema_info_alter().
- "**/*.module"
+ - "**/src/Hook/*.php"
- when: manual
allow_failure: true
artifacts:
diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php
index 21b7ee78a04..f413f2c8a31 100644
--- a/core/.phpstan-baseline.php
+++ b/core/.phpstan-baseline.php
@@ -41931,18 +41931,6 @@ $ignoreErrors[] = [
'path' => __DIR__ . '/modules/views/src/Views.php',
];
$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\views\\\\ViewsConfigUpdater\\:\\:create\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/modules/views/src/ViewsConfigUpdater.php',
-];
-$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\views\\\\ViewsConfigUpdater\\:\\:setDeprecationsEnabled\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/modules/views/src/ViewsConfigUpdater.php',
-];
-$ignoreErrors[] = [
'message' => '#^Method Drupal\\\\views\\\\ViewsData\\:\\:cacheSet\\(\\) has no return type specified\\.$#',
'identifier' => 'missingType.return',
'count' => 1,
diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 4803c6599b8..80851b7113f 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -695,6 +695,21 @@ drupal.textarea-resize:
css/components/resize.module.css:
component: misc/components/resize.module.css
+drupal.reset-appearance:
+ version: VERSION
+ css:
+ component:
+ misc/components/reset-appearance.module.css: { weight: -10 }
+ moved_files:
+ system/base:
+ deprecation_version: 11.3.0
+ removed_version: 12.0.0
+ deprecation_link: https://www.drupal.org/node/3432346
+ css:
+ component:
+ css/components/reset-appearance.module.css:
+ component: misc/components/reset-appearance.module.css
+
drupal.states:
version: VERSION
js:
diff --git a/core/core.services.yml b/core/core.services.yml
index 9f7c1a2d11c..2090ae1ceb5 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -515,12 +515,14 @@ services:
keyvalue:
class: Drupal\Core\KeyValueStore\KeyValueFactory
arguments: ['@service_container', '%factory.keyvalue%']
+ Drupal\Core\KeyValueStore\KeyValueFactoryInterface: '@keyvalue'
keyvalue.database:
class: Drupal\Core\KeyValueStore\KeyValueDatabaseFactory
arguments: ['@serialization.phpserialize', '@database']
keyvalue.expirable:
class: Drupal\Core\KeyValueStore\KeyValueExpirableFactory
arguments: ['@service_container', '%factory.keyvalue.expirable%']
+ Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface: '@keyvalue.expirable'
keyvalue.expirable.database:
class: Drupal\Core\KeyValueStore\KeyValueDatabaseExpirableFactory
arguments: ['@serialization.phpserialize', '@database', '@datetime.time']
diff --git a/core/lib/Drupal/Component/Datetime/DateTimePlus.php b/core/lib/Drupal/Component/Datetime/DateTimePlus.php
index 4f95d6d8b66..90d9c300854 100644
--- a/core/lib/Drupal/Component/Datetime/DateTimePlus.php
+++ b/core/lib/Drupal/Component/Datetime/DateTimePlus.php
@@ -53,62 +53,6 @@ class DateTimePlus {
const RFC7231 = 'D, d M Y H:i:s \G\M\T';
/**
- * An array of possible date parts.
- *
- * @var string[]
- */
- protected static $dateParts = [
- 'year',
- 'month',
- 'day',
- 'hour',
- 'minute',
- 'second',
- ];
-
- /**
- * The value of the time value passed to the constructor.
- *
- * @var string
- */
- protected $inputTimeRaw = '';
-
- /**
- * The prepared time, without timezone, for this date.
- *
- * @var string
- */
- protected $inputTimeAdjusted = '';
-
- /**
- * The value of the timezone passed to the constructor.
- *
- * @var string
- */
- protected $inputTimeZoneRaw = '';
-
- /**
- * The prepared timezone object used to construct this date.
- *
- * @var string
- */
- protected $inputTimeZoneAdjusted = '';
-
- /**
- * The value of the format passed to the constructor.
- *
- * @var string
- */
- protected $inputFormatRaw = '';
-
- /**
- * The prepared format, if provided.
- *
- * @var string
- */
- protected $inputFormatAdjusted = '';
-
- /**
* The value of the language code passed to the constructor.
*
* @var string|null
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index a9ce2cf3fcd..d7c776a7065 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -1337,14 +1337,16 @@
// Parse response.data into an element collection.
const parseHTML = (htmlString) => {
const fragment = document.createDocumentFragment();
- // Create a temporary div element
- const tempDiv = fragment.appendChild(document.createElement('div'));
+ // Create a temporary template element.
+ const template = fragment.appendChild(
+ document.createElement('template'),
+ );
- // Set the innerHTML of the div to the provided HTML string
- tempDiv.innerHTML = htmlString;
+ // Set the innerHTML of the template to the provided HTML string.
+ template.innerHTML = htmlString;
- // Return the contents of the temporary div
- return tempDiv.childNodes;
+ // Return the contents of the temporary template.
+ return template.content.childNodes;
};
let $newContent = $(parseHTML(response.data));
diff --git a/core/modules/system/css/components/reset-appearance.module.css b/core/misc/components/reset-appearance.module.css
index 59741a85ce8..59741a85ce8 100644
--- a/core/modules/system/css/components/reset-appearance.module.css
+++ b/core/misc/components/reset-appearance.module.css
diff --git a/core/misc/htmx/htmx-assets.js b/core/misc/htmx/htmx-assets.js
index bc5f00df432..9175405d047 100644
--- a/core/misc/htmx/htmx-assets.js
+++ b/core/misc/htmx/htmx-assets.js
@@ -166,7 +166,8 @@
// @see https://htmx.org/events/#htmx:afterSettle
htmx.on('htmx:afterSettle', ({ detail }) => {
requestAssetsLoaded.get(detail.xhr).then(() => {
- htmx.trigger(detail.elt, 'htmx:drupal:load');
+ // Some HTMX swaps put the incoming element before or after detail.elt.
+ htmx.trigger(detail.elt.parentNode, 'htmx:drupal:load');
// This should be automatic but don't wait for the garbage collector.
requestAssetsLoaded.delete(detail.xhr);
});
diff --git a/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
index 5f1933714bc..476eaaaf72f 100644
--- a/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
+++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
@@ -11,7 +11,6 @@ use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\language\Entity\ContentLanguageSettings;
-use Drupal\workflows\Entity\Workflow;
/**
* Access check for entity translation deletion.
@@ -83,13 +82,9 @@ class ContentTranslationDeleteAccess implements AccessInterface {
$entity_type_id = $entity->getEntityTypeId();
$result->addCacheableDependency($entity);
- // Add the cache dependencies used by
- // ContentTranslationManager::isPendingRevisionSupportEnabled().
- if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
- foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
- $result->addCacheableDependency($workflow);
- }
- }
+ // The information about workflows is stored in entity bundle info, depend
+ // on that cache tag.
+ $result->addCacheTags(['entity_bundles']);
if (!ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $entity->bundle())) {
return $result;
}
diff --git a/core/modules/content_translation/src/ContentTranslationManager.php b/core/modules/content_translation/src/ContentTranslationManager.php
index 56ac55cf7e7..0718a4cbf9d 100644
--- a/core/modules/content_translation/src/ContentTranslationManager.php
+++ b/core/modules/content_translation/src/ContentTranslationManager.php
@@ -3,7 +3,6 @@
namespace Drupal\content_translation;
use Drupal\Core\Entity\EntityInterface;
-use Drupal\workflows\Entity\Workflow;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -168,24 +167,13 @@ class ContentTranslationManager implements ContentTranslationManagerInterface, B
return FALSE;
}
- foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
- /** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
- $plugin = $workflow->getTypePlugin();
- $entity_type_ids = array_flip($plugin->getEntityTypes());
- if (isset($entity_type_ids[$entity_type_id])) {
- if (!isset($bundle_id)) {
- return TRUE;
- }
- else {
- $bundle_ids = array_flip($plugin->getBundlesForEntityType($entity_type_id));
- if (isset($bundle_ids[$bundle_id])) {
- return TRUE;
- }
- }
- }
+ $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+ if ($bundle_id) {
+ return \Drupal::service('content_moderation.moderation_information')->shouldModerateEntitiesOfBundle($entity_type, $bundle_id);
+ }
+ else {
+ return \Drupal::service('content_moderation.moderation_information')->canModerateEntitiesOfEntityType($entity_type);
}
-
- return FALSE;
}
}
diff --git a/core/modules/content_translation/src/Hook/ContentTranslationHooks.php b/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
index c681b07c059..9842c7085fd 100644
--- a/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
+++ b/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
@@ -10,7 +10,6 @@ use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\content_translation\ContentTranslationManager;
use Drupal\content_translation\BundleTranslationSettingsInterface;
use Drupal\language\ContentLanguageSettingsInterface;
use Drupal\Core\Language\LanguageInterface;
@@ -18,6 +17,7 @@ use Drupal\Core\Url;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Hook\Order\Order;
+use Drupal\workflows\Entity\Workflow;
/**
* Hook implementations for content_translation.
@@ -231,11 +231,27 @@ class ContentTranslationHooks {
$bundle_info['translatable'] = $content_translation_manager->isEnabled($entity_type_id, $bundle);
if ($bundle_info['translatable'] && $content_translation_manager instanceof BundleTranslationSettingsInterface) {
$settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
- // If pending revision support is enabled for this bundle, we need to
- // hide untranslatable field widgets, otherwise changes in pending
- // revisions might be overridden by changes in later default
- // revisions.
- $bundle_info['untranslatable_fields.default_translation_affected'] = !empty($settings['untranslatable_fields_hide']) || ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $bundle);
+ $bundle_info['untranslatable_fields.default_translation_affected'] = !empty($settings['untranslatable_fields_hide']);
+ }
+ }
+ }
+
+ // Always hide untranslatable field widgets if pending revision support is
+ // enabled otherwise changes in pending
+ // revisions might be overridden by changes in later default revisions.
+ // This can't use
+ // Drupal\content_translation\ContentTranslationManager::isPendingRevisionSupportEnabled()
+ // since that depends on entity bundle information to be completely built.
+ if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
+ foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
+ /** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
+ $plugin = $workflow->getTypePlugin();
+ foreach ($plugin->getEntityTypes() as $entity_type_id) {
+ foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle_id) {
+ if (isset($bundles[$entity_type_id][$bundle_id])) {
+ $bundles[$entity_type_id][$bundle_id]['untranslatable_fields.default_translation_affected'] = TRUE;
+ }
+ }
}
}
}
diff --git a/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php b/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php
index ad6c5712d71..528c874649f 100644
--- a/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php
+++ b/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php
@@ -94,7 +94,7 @@ class PerformanceTest extends PerformanceTestBase {
'ScriptCount' => 3,
'ScriptBytes' => 167569,
'StylesheetCount' => 2,
- 'StylesheetBytes' => 46000,
+ 'StylesheetBytes' => 45450,
];
$this->assertMetrics($expected, $performance_data);
diff --git a/core/modules/package_manager/package_manager.services.yml b/core/modules/package_manager/package_manager.services.yml
index 059be554270..ead88254823 100644
--- a/core/modules/package_manager/package_manager.services.yml
+++ b/core/modules/package_manager/package_manager.services.yml
@@ -14,9 +14,6 @@ services:
Drupal\package_manager\ExecutableFinder:
public: false
decorates: 'PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface'
- Drupal\package_manager\ProcessFactory:
- public: false
- decorates: 'PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface'
Drupal\package_manager\TranslatableStringFactory:
public: false
decorates: 'PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface'
@@ -175,7 +172,7 @@ services:
PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface:
class: PhpTuf\ComposerStager\Internal\Process\Factory\ProcessFactory
PhpTuf\ComposerStager\API\Process\Service\ComposerProcessRunnerInterface:
- class: PhpTuf\ComposerStager\Internal\Process\Service\ComposerProcessRunner
+ class: Drupal\package_manager\ComposerRunner
PhpTuf\ComposerStager\API\Process\Service\OutputCallbackInterface:
class: PhpTuf\ComposerStager\Internal\Process\Service\OutputCallback
PhpTuf\ComposerStager\API\Process\Service\ProcessInterface:
diff --git a/core/modules/package_manager/src/ComposerRunner.php b/core/modules/package_manager/src/ComposerRunner.php
new file mode 100644
index 00000000000..dbc029f6aa3
--- /dev/null
+++ b/core/modules/package_manager/src/ComposerRunner.php
@@ -0,0 +1,54 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\package_manager;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\File\FileSystemInterface;
+use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
+use PhpTuf\ComposerStager\API\Path\Value\PathInterface;
+use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
+use PhpTuf\ComposerStager\API\Process\Service\ComposerProcessRunnerInterface;
+use PhpTuf\ComposerStager\API\Process\Service\OutputCallbackInterface;
+use PhpTuf\ComposerStager\API\Process\Service\ProcessInterface;
+use Symfony\Component\Process\PhpExecutableFinder;
+
+// cspell:ignore BINDIR
+
+/**
+ * Runs Composer through the current PHP interpreter.
+ *
+ * @internal
+ * This is an internal part of Package Manager and may be changed or removed
+ * at any time without warning. External code should not interact with this
+ * class.
+ */
+final class ComposerRunner implements ComposerProcessRunnerInterface {
+
+ public function __construct(
+ private readonly ExecutableFinderInterface $executableFinder,
+ private readonly ProcessFactoryInterface $processFactory,
+ private readonly FileSystemInterface $fileSystem,
+ private readonly ConfigFactoryInterface $configFactory,
+ ) {}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run(array $command, ?PathInterface $cwd = NULL, array $env = [], ?OutputCallbackInterface $callback = NULL, int $timeout = ProcessInterface::DEFAULT_TIMEOUT): void {
+ // Run Composer through the PHP interpreter so we don't have to rely on
+ // PHP being in the PATH.
+ array_unshift($command, (new PhpExecutableFinder())->find(), $this->executableFinder->find('composer'));
+
+ $home = $this->fileSystem->getTempDirectory();
+ $home .= '/package_manager_composer_home-';
+ $home .= $this->configFactory->get('system.site')->get('uuid');
+ $this->fileSystem->prepareDirectory($home, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
+
+ $process = $this->processFactory->create($command, $cwd, $env + ['COMPOSER_HOME' => $home]);
+ $process->setTimeout($timeout);
+ $process->mustRun($callback);
+ }
+
+}
diff --git a/core/modules/package_manager/src/ProcessFactory.php b/core/modules/package_manager/src/ProcessFactory.php
deleted file mode 100644
index 7e92977e7ff..00000000000
--- a/core/modules/package_manager/src/ProcessFactory.php
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\package_manager;
-
-use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\File\FileSystemInterface;
-use PhpTuf\ComposerStager\API\Path\Value\PathInterface;
-use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
-use PhpTuf\ComposerStager\API\Process\Service\ProcessInterface;
-
-// cspell:ignore BINDIR
-
-/**
- * Defines a process factory which sets the COMPOSER_HOME environment variable.
- *
- * @internal
- * This is an internal part of Package Manager and may be changed or removed
- * at any time without warning. External code should not interact with this
- * class.
- */
-final class ProcessFactory implements ProcessFactoryInterface {
-
- public function __construct(
- private readonly FileSystemInterface $fileSystem,
- private readonly ConfigFactoryInterface $configFactory,
- private readonly ProcessFactoryInterface $decorated,
- ) {}
-
- /**
- * {@inheritdoc}
- */
- public function create(array $command, ?PathInterface $cwd = NULL, array $env = []): ProcessInterface {
- $process = $this->decorated->create($command, $cwd, $env);
-
- $env = $process->getEnv();
- if ($command && $this->isComposerCommand($command)) {
- $env['COMPOSER_HOME'] = $this->getComposerHomePath();
- }
- // Ensure that the current PHP installation is the first place that will be
- // searched when looking for the PHP interpreter.
- $env['PATH'] = static::getPhpDirectory() . ':' . getenv('PATH');
- $process->setEnv($env);
- return $process;
- }
-
- /**
- * Returns the directory which contains the PHP interpreter.
- *
- * @return string
- * The path of the directory containing the PHP interpreter. If the server
- * is running in a command-line interface, the directory portion of
- * PHP_BINARY is returned; otherwise, the compile-time PHP_BINDIR is.
- *
- * @see php_sapi_name()
- * @see https://www.php.net/manual/en/reserved.constants.php
- */
- private static function getPhpDirectory(): string {
- if (PHP_SAPI === 'cli' || PHP_SAPI === 'cli-server') {
- return dirname(PHP_BINARY);
- }
- return PHP_BINDIR;
- }
-
- /**
- * Returns the path to use as the COMPOSER_HOME environment variable.
- *
- * @return string
- * The path which should be used as COMPOSER_HOME.
- */
- private function getComposerHomePath(): string {
- $home_path = $this->fileSystem->getTempDirectory();
- $home_path .= '/package_manager_composer_home-';
- $home_path .= $this->configFactory->get('system.site')->get('uuid');
- $this->fileSystem->prepareDirectory($home_path, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
-
- return $home_path;
- }
-
- /**
- * Determines if a command is running Composer.
- *
- * @param string[] $command
- * The command parts.
- *
- * @return bool
- * TRUE if the command is running Composer, FALSE otherwise.
- */
- private function isComposerCommand(array $command): bool {
- $executable = $command[0];
- $executable_parts = explode('/', $executable);
- $file = array_pop($executable_parts);
- return str_starts_with($file, 'composer');
- }
-
-}
diff --git a/core/modules/package_manager/tests/src/Functional/ComposerRequirementTest.php b/core/modules/package_manager/tests/src/Functional/ComposerRequirementTest.php
index 45046a8e2ad..15a7a2aab6b 100644
--- a/core/modules/package_manager/tests/src/Functional/ComposerRequirementTest.php
+++ b/core/modules/package_manager/tests/src/Functional/ComposerRequirementTest.php
@@ -47,7 +47,10 @@ class ComposerRequirementTest extends PackageManagerTestBase {
$config->set('executables.composer', '/path/to/composer')->save();
$this->getSession()->reload();
$assert_session->statusCodeEquals(200);
- $assert_session->pageTextContains('Composer was not found. The error message was: Failed to run process: The command "\'/path/to/composer\' \'--format=json\'" failed.');
+ $assert_session->pageTextContains('Composer was not found. The error message was: ');
+ // Check for the part of the command string that is constant (the path to
+ // the PHP interpreter will vary).
+ $assert_session->pageTextContains("/php' '/path/to/composer' '--format=json'\" failed.");
}
}
diff --git a/core/modules/package_manager/tests/src/Kernel/ProcessFactoryTest.php b/core/modules/package_manager/tests/src/Kernel/ProcessFactoryTest.php
deleted file mode 100644
index db27b06efa8..00000000000
--- a/core/modules/package_manager/tests/src/Kernel/ProcessFactoryTest.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\package_manager\Kernel;
-
-use Drupal\package_manager\ProcessFactory;
-use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
-
-/**
- * @coversDefaultClass \Drupal\package_manager\ProcessFactory
- * @group auto_updates
- * @internal
- */
-class ProcessFactoryTest extends PackageManagerKernelTestBase {
-
- /**
- * Tests that the process factory prepends the PHP directory to PATH.
- */
- public function testPhpDirectoryPrependedToPath(): void {
- $factory = $this->container->get(ProcessFactoryInterface::class);
- $this->assertInstanceOf(ProcessFactory::class, $factory);
-
- // Ensure that the directory of the PHP interpreter can be found.
- $reflector = new \ReflectionObject($factory);
- $method = $reflector->getMethod('getPhpDirectory');
- $php_dir = $method->invoke(NULL);
- $this->assertNotEmpty($php_dir);
-
- // The process factory should always put the PHP interpreter's directory
- // at the beginning of the PATH environment variable.
- $env = $factory->create(['whoami'])->getEnv();
- $this->assertStringStartsWith("$php_dir:", $env['PATH']);
- }
-
-}
diff --git a/core/modules/package_manager/tests/src/Kernel/ServicesTest.php b/core/modules/package_manager/tests/src/Kernel/ServicesTest.php
index a1db5a517c1..9e52f9b4075 100644
--- a/core/modules/package_manager/tests/src/Kernel/ServicesTest.php
+++ b/core/modules/package_manager/tests/src/Kernel/ServicesTest.php
@@ -9,14 +9,12 @@ use Drupal\package_manager\ExecutableFinder;
use Drupal\package_manager\LoggingBeginner;
use Drupal\package_manager\LoggingCommitter;
use Drupal\package_manager\LoggingStager;
-use Drupal\package_manager\ProcessFactory;
use Drupal\package_manager\TranslatableStringFactory;
use Drupal\Tests\package_manager\Traits\AssertPreconditionsTrait;
use PhpTuf\ComposerStager\API\Core\BeginnerInterface;
use PhpTuf\ComposerStager\API\Core\CommitterInterface;
use PhpTuf\ComposerStager\API\Core\StagerInterface;
use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
-use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
use PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface;
/**
@@ -38,11 +36,9 @@ class ServicesTest extends KernelTestBase {
* Tests that Package Manager's public services can be instantiated.
*/
public function testPackageManagerServices(): void {
- // Ensure that any overridden Composer Stager services were overridden
- // correctly.
+ // Ensure that certain Composer Stager services are decorated correctly.
$overrides = [
ExecutableFinderInterface::class => ExecutableFinder::class,
- ProcessFactoryInterface::class => ProcessFactory::class,
TranslatableFactoryInterface::class => TranslatableStringFactory::class,
BeginnerInterface::class => LoggingBeginner::class,
StagerInterface::class => LoggingStager::class,
diff --git a/core/modules/package_manager/tests/src/Unit/ComposerRunnerTest.php b/core/modules/package_manager/tests/src/Unit/ComposerRunnerTest.php
new file mode 100644
index 00000000000..91253d377d2
--- /dev/null
+++ b/core/modules/package_manager/tests/src/Unit/ComposerRunnerTest.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\package_manager\Unit;
+
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\package_manager\ComposerRunner;
+use Drupal\Tests\UnitTestCase;
+use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
+use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Group;
+use Prophecy\Argument;
+
+// cspell:ignore BINDIR
+
+/**
+ * Tests Package Manager's Composer runner service.
+ *
+ * @internal
+ */
+#[CoversClass(ComposerRunner::class)]
+#[Group('package_manager')]
+class ComposerRunnerTest extends UnitTestCase {
+
+ /**
+ * Tests that the Composer runner runs Composer through the PHP interpreter.
+ */
+ public function testRunner(): void {
+ $executable_finder = $this->prophesize(ExecutableFinderInterface::class);
+ $executable_finder->find('composer')
+ ->willReturn('/mock/composer')
+ ->shouldBeCalled();
+
+ $process_factory = $this->prophesize(ProcessFactoryInterface::class);
+ $process_factory->create(
+ // Internally, ComposerRunner uses Symfony's PhpExecutableFinder to locate
+ // the PHP interpreter, which should resolve to PHP_BINARY a command-line
+ // test environment.
+ [PHP_BINARY, '/mock/composer', '--version'],
+ NULL,
+ Argument::withKey('COMPOSER_HOME'),
+ )->shouldBeCalled();
+
+ $file_system = $this->prophesize(FileSystemInterface::class);
+ $file_system->getTempDirectory()->shouldBeCalled();
+ $file_system->prepareDirectory(Argument::cetera())->shouldBeCalled();
+
+ $runner = new ComposerRunner(
+ $executable_finder->reveal(),
+ $process_factory->reveal(),
+ $file_system->reveal(),
+ $this->getConfigFactoryStub([
+ 'system.site' => [
+ 'uuid' => 'testing',
+ ],
+ ]),
+ );
+ $runner->run(['--version']);
+ }
+
+}
diff --git a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
index ed7adef588f..b393e8ac30c 100644
--- a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
+++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
@@ -82,12 +82,28 @@ class PathItem extends FieldItemBase {
// If we have an alias, we need to create or update a path alias entity.
if ($alias) {
- if (!$update || !$pid) {
- $path_alias = $path_alias_storage->create([
- 'path' => '/' . $entity->toUrl()->getInternalPath(),
- 'alias' => $alias,
- 'langcode' => $alias_langcode,
- ]);
+ $properties = [
+ 'path' => '/' . $entity->toUrl()->getInternalPath(),
+ 'alias' => $alias,
+ 'langcode' => $alias_langcode,
+ ];
+
+ if (!$pid) {
+ // Try to load it from storage before creating it. In some cases the
+ // path alias could be created before this function runs. For example,
+ // \Drupal\workspaces\EntityOperations::entityTranslationInsert will
+ // create a translation, and an associated path alias will be created
+ // with it.
+ $query = $path_alias_storage->getQuery()->accessCheck(FALSE);
+ foreach ($properties as $field => $value) {
+ $query->condition($field, $value);
+ }
+ $ids = $query->execute();
+ $pid = $ids ? reset($ids) : $pid;
+ }
+
+ if (!$pid) {
+ $path_alias = $path_alias_storage->create($properties);
$path_alias->save();
$this->set('pid', $path_alias->id());
}
diff --git a/core/modules/path/tests/modules/path_test_misc/path_test_misc.info.yml b/core/modules/path/tests/modules/path_test_misc/path_test_misc.info.yml
new file mode 100644
index 00000000000..701a1028914
--- /dev/null
+++ b/core/modules/path/tests/modules/path_test_misc/path_test_misc.info.yml
@@ -0,0 +1,5 @@
+name: 'Path test miscellaneous utilities'
+type: module
+description: 'Utilities for path testing'
+package: Testing
+version: VERSION
diff --git a/core/modules/path/tests/modules/path_test_misc/src/Hook/PathTestMiscHooks.php b/core/modules/path/tests/modules/path_test_misc/src/Hook/PathTestMiscHooks.php
new file mode 100644
index 00000000000..5fc6662a09d
--- /dev/null
+++ b/core/modules/path/tests/modules/path_test_misc/src/Hook/PathTestMiscHooks.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\path_test_misc\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\node\NodeInterface;
+
+/**
+ * Hook implementations for path_test_misc.
+ */
+class PathTestMiscHooks {
+
+ /**
+ * Implements hook_ENTITY_TYPE_presave() for node entities.
+ *
+ * This is invoked from testAliasDuplicationPrevention.
+ */
+ #[Hook('node_presave')]
+ public function nodePresave(NodeInterface $node): void {
+ if ($node->getTitle() !== 'path duplication test') {
+ return;
+ }
+
+ // Update the title to be able to check that this code ran.
+ $node->setTitle('path duplication test ran');
+
+ // Create a path alias that has the same values as the one in
+ // PathItem::postSave.
+ $path = \Drupal::entityTypeManager()->getStorage('path_alias')
+ ->create([
+ 'path' => '/node/1',
+ 'alias' => '/my-alias',
+ 'langcode' => 'en',
+ ]);
+ $path->save();
+ }
+
+}
diff --git a/core/modules/path/tests/src/Functional/PathNodeFormTest.php b/core/modules/path/tests/src/Functional/PathNodeFormTest.php
index 001ba2f09ba..9763a145dc9 100644
--- a/core/modules/path/tests/src/Functional/PathNodeFormTest.php
+++ b/core/modules/path/tests/src/Functional/PathNodeFormTest.php
@@ -4,6 +4,9 @@ declare(strict_types=1);
namespace Drupal\Tests\path\Functional;
+use Drupal\node\Entity\Node;
+use Drupal\node\NodeInterface;
+
/**
* Tests the Path Node form UI.
*
@@ -14,7 +17,7 @@ class PathNodeFormTest extends PathTestBase {
/**
* {@inheritdoc}
*/
- protected static $modules = ['node', 'path'];
+ protected static $modules = ['node', 'path', 'path_test_misc'];
/**
* {@inheritdoc}
@@ -59,4 +62,27 @@ class PathNodeFormTest extends PathTestBase {
$assert_session->fieldNotExists('path[0][alias]');
}
+ /**
+ * Tests that duplicate path aliases don't get created.
+ */
+ public function testAliasDuplicationPrevention(): void {
+ $this->drupalGet('node/add/page');
+ $edit['title[0][value]'] = 'path duplication test';
+ $edit['path[0][alias]'] = '/my-alias';
+ $this->submitForm($edit, 'Save');
+
+ // Test that PathItem::postSave detects if a path alias exists
+ // before creating one.
+ $aliases = \Drupal::entityTypeManager()
+ ->getStorage('path_alias')
+ ->loadMultiple();
+ static::assertCount(1, $aliases);
+ $node = Node::load(1);
+ static::assertInstanceOf(NodeInterface::class, $node);
+
+ // This updated title gets set in PathTestMiscHooks::nodePresave. This
+ // is a way of ensuring that bit of test code runs.
+ static::assertEquals('path duplication test ran', $node->getTitle());
+ }
+
}
diff --git a/core/modules/system/system.libraries.yml b/core/modules/system/system.libraries.yml
index acb02dd1f4b..cd7165bffbb 100644
--- a/core/modules/system/system.libraries.yml
+++ b/core/modules/system/system.libraries.yml
@@ -8,7 +8,6 @@ base:
css/components/clearfix.module.css: { weight: -10 }
css/components/hidden.module.css: { weight: -10 }
css/components/js.module.css: { weight: -10 }
- css/components/reset-appearance.module.css: { weight: -10 }
admin:
version: VERSION
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml b/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
index f9ca25544ab..95b7a1e4c0f 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
@@ -43,6 +43,13 @@ ajax_test.insert_links_inline_wrapper:
requirements:
_access: 'TRUE'
+ajax_test.insert_links_table_wrapper:
+ path: '/ajax-test/insert-table-wrapper'
+ defaults:
+ _controller: '\Drupal\ajax_test\Controller\AjaxTestController::insertLinksTableWrapper'
+ requirements:
+ _access: 'TRUE'
+
ajax_test.dialog_close:
path: '/ajax-test/dialog-close'
defaults:
diff --git a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
index aea5322ce2d..b0adea8e7c7 100644
--- a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
+++ b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
@@ -141,6 +141,36 @@ class AjaxTestController {
}
/**
+ * Returns a render array of links that directly Drupal.ajax().
+ *
+ * @return array
+ * Renderable array of AJAX response contents.
+ */
+ public function insertLinksTableWrapper(): array {
+ $build['links'] = [
+ 'ajax_target' => [
+ '#markup' => '<div class="ajax-target-wrapper"><table><tbody id="ajax-target"></tbody></table></div>',
+ ],
+ 'links' => [
+ '#theme' => 'links',
+ '#attached' => ['library' => ['ajax_test/ajax_insert']],
+ ],
+ ];
+
+ $build['links']['links']['#links']['table-row'] = [
+ 'title' => 'Link table-row',
+ 'url' => Url::fromRoute('ajax_test.ajax_render_types', ['type' => 'table-row']),
+ 'attributes' => [
+ 'class' => ['ajax-insert'],
+ 'data-method' => 'html',
+ 'data-effect' => 'none',
+ ],
+ ];
+
+ return $build;
+ }
+
+ /**
* Returns a render array that will be rendered by AjaxRenderer.
*
* Verifies that the response incorporates JavaScript settings generated
@@ -336,6 +366,7 @@ class AjaxTestController {
'comment-not-wrapped' => '<!-- COMMENT --><div class="comment-not-wrapped">comment-not-wrapped</div>',
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><rect x="0" y="0" height="10" width="10" fill="green"></rect></svg>',
'empty' => '',
+ 'table-row' => '<tr><td>table-row</td></tr>',
];
$render_multiple_root = [
'mixed' => ' foo <!-- COMMENT --> foo bar<div class="a class"><p>some string</p></div> additional not wrapped strings, <!-- ANOTHER COMMENT --> <p>final string</p>',
diff --git a/core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php b/core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php
index 9045a4c8b9c..9027509a19c 100644
--- a/core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php
+++ b/core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php
@@ -23,6 +23,26 @@ final class HtmxTestAttachmentsController extends ControllerBase {
}
/**
+ * Builds a response with a `beforebegin` swap.
+ *
+ * @return mixed[]
+ * A render array.
+ */
+ public function before(): array {
+ return self::generateHtmxButton('beforebegin');
+ }
+
+ /**
+ * Builds a response with an `afterend` swap..
+ *
+ * @return mixed[]
+ * A render array.
+ */
+ public function after(): array {
+ return self::generateHtmxButton('afterend');
+ }
+
+ /**
* Builds the HTMX response.
*
* @return mixed[]
@@ -44,12 +64,22 @@ final class HtmxTestAttachmentsController extends ControllerBase {
}
/**
+ * We need a static callback that ignores callback parameters.
+ *
+ * @return array
+ * The render array.
+ */
+ public static function replaceWithAjax(): array {
+ return static::generateHtmxButton();
+ }
+
+ /**
* Static helper to for reusable render array.
*
* @return array
* The render array.
*/
- public static function generateHtmxButton(): array {
+ public static function generateHtmxButton(string $swap = ''): array {
$url = Url::fromRoute('test_htmx.attachments.replace');
$build['replace'] = [
'#type' => 'html_tag',
@@ -68,6 +98,9 @@ final class HtmxTestAttachmentsController extends ControllerBase {
],
],
];
+ if ($swap !== '') {
+ $build['replace']['#attributes']['data-hx-swap'] = $swap;
+ }
$build['content'] = [
'#type' => 'container',
diff --git a/core/modules/system/tests/modules/test_htmx/src/Form/HtmxTestAjaxForm.php b/core/modules/system/tests/modules/test_htmx/src/Form/HtmxTestAjaxForm.php
index 8fffbbc5f40..f812a99582d 100644
--- a/core/modules/system/tests/modules/test_htmx/src/Form/HtmxTestAjaxForm.php
+++ b/core/modules/system/tests/modules/test_htmx/src/Form/HtmxTestAjaxForm.php
@@ -32,7 +32,7 @@ class HtmxTestAjaxForm extends FormBase {
'#ajax' => [
'callback' => [
HtmxTestAttachmentsController::class,
- 'generateHtmxButton',
+ 'replaceWithAjax',
],
'wrapper' => 'ajax-test-container',
],
diff --git a/core/modules/system/tests/modules/test_htmx/test_htmx.routing.yml b/core/modules/system/tests/modules/test_htmx/test_htmx.routing.yml
index 406c3027f3b..33dca377c71 100644
--- a/core/modules/system/tests/modules/test_htmx/test_htmx.routing.yml
+++ b/core/modules/system/tests/modules/test_htmx/test_htmx.routing.yml
@@ -6,6 +6,22 @@ test_htmx.attachments.page:
requirements:
_permission: 'access content'
+test_htmx.attachments.before:
+ path: '/htmx-test-attachments/before'
+ defaults:
+ _title: 'Page'
+ _controller: '\Drupal\test_htmx\Controller\HtmxTestAttachmentsController::before'
+ requirements:
+ _permission: 'access content'
+
+test_htmx.attachments.after:
+ path: '/htmx-test-attachments/after'
+ defaults:
+ _title: 'Page'
+ _controller: '\Drupal\test_htmx\Controller\HtmxTestAttachmentsController::after'
+ requirements:
+ _permission: 'access content'
+
test_htmx.attachments.replace:
path: '/htmx-test-attachments/replace'
defaults:
diff --git a/core/modules/views/src/Hook/ViewsHooks.php b/core/modules/views/src/Hook/ViewsHooks.php
index b309887723c..5a7b28735f5 100644
--- a/core/modules/views/src/Hook/ViewsHooks.php
+++ b/core/modules/views/src/Hook/ViewsHooks.php
@@ -375,7 +375,7 @@ class ViewsHooks {
#[Hook('view_presave')]
public function viewPresave(ViewEntityInterface $view): void {
/** @var \Drupal\views\ViewsConfigUpdater $config_updater */
- $config_updater = \Drupal::classResolver(ViewsConfigUpdater::class);
+ $config_updater = \Drupal::service(ViewsConfigUpdater::class);
$config_updater->updateAll($view);
}
diff --git a/core/modules/views/src/ViewsConfigUpdater.php b/core/modules/views/src/ViewsConfigUpdater.php
index 9c5d38e10ac..c6a4fbc62d0 100644
--- a/core/modules/views/src/ViewsConfigUpdater.php
+++ b/core/modules/views/src/ViewsConfigUpdater.php
@@ -4,106 +4,38 @@ namespace Drupal\views;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
-use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
/**
* Provides a BC layer for modules providing old configurations.
*
* @internal
*/
-class ViewsConfigUpdater implements ContainerInjectionInterface {
-
- /**
- * The entity type manager.
- *
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
- */
- protected $entityTypeManager;
-
- /**
- * The entity field manager.
- *
- * @var \Drupal\Core\Entity\EntityFieldManagerInterface
- */
- protected $entityFieldManager;
-
- /**
- * The typed config manager.
- *
- * @var \Drupal\Core\Config\TypedConfigManagerInterface
- */
- protected $typedConfigManager;
-
- /**
- * The views data service.
- *
- * @var \Drupal\views\ViewsData
- */
- protected $viewsData;
-
- /**
- * The formatter plugin manager service.
- *
- * @var \Drupal\Component\Plugin\PluginManagerInterface
- */
- protected $formatterPluginManager;
+class ViewsConfigUpdater {
/**
* Flag determining whether deprecations should be triggered.
- *
- * @var bool
*/
- protected $deprecationsEnabled = TRUE;
+ protected bool $deprecationsEnabled = TRUE;
/**
* Stores which deprecations were triggered.
- *
- * @var bool
*/
- protected $triggeredDeprecations = [];
+ protected array $triggeredDeprecations = [];
/**
* ViewsConfigUpdater constructor.
- *
- * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
- * The entity type manager.
- * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
- * The entity field manager.
- * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
- * The typed config manager.
- * @param \Drupal\views\ViewsData $views_data
- * The views data service.
- * @param \Drupal\Component\Plugin\PluginManagerInterface $formatter_plugin_manager
- * The formatter plugin manager service.
*/
public function __construct(
- EntityTypeManagerInterface $entity_type_manager,
- EntityFieldManagerInterface $entity_field_manager,
- TypedConfigManagerInterface $typed_config_manager,
- ViewsData $views_data,
- PluginManagerInterface $formatter_plugin_manager,
+ private readonly EntityTypeManagerInterface $entityTypeManager,
+ private readonly EntityFieldManagerInterface $entityFieldManager,
+ private readonly TypedConfigManagerInterface $typedConfigManager,
+ private readonly ViewsData $viewsData,
+ #[Autowire(service: 'plugin.manager.field.formatter')]
+ private readonly PluginManagerInterface $formatterPluginManager,
) {
- $this->entityTypeManager = $entity_type_manager;
- $this->entityFieldManager = $entity_field_manager;
- $this->typedConfigManager = $typed_config_manager;
- $this->viewsData = $views_data;
- $this->formatterPluginManager = $formatter_plugin_manager;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container) {
- return new static(
- $container->get('entity_type.manager'),
- $container->get('entity_field.manager'),
- $container->get('config.typed'),
- $container->get('views.views_data'),
- $container->get('plugin.manager.field.formatter')
- );
}
/**
@@ -112,11 +44,18 @@ class ViewsConfigUpdater implements ContainerInjectionInterface {
* @param bool $enabled
* Whether deprecations should be enabled.
*/
- public function setDeprecationsEnabled($enabled) {
+ public function setDeprecationsEnabled(bool $enabled): void {
$this->deprecationsEnabled = $enabled;
}
/**
+ * Whether deprecations are enabled.
+ */
+ public function areDeprecationsEnabled(): bool {
+ return $this->deprecationsEnabled;
+ }
+
+ /**
* Performs all required updates.
*
* @param \Drupal\views\ViewEntityInterface $view
@@ -259,7 +198,7 @@ class ViewsConfigUpdater implements ContainerInjectionInterface {
}
$deprecations_triggered = &$this->triggeredDeprecations['2640994'][$view->id()];
- if ($this->deprecationsEnabled && $changed && !$deprecations_triggered) {
+ if ($this->areDeprecationsEnabled() && $changed && !$deprecations_triggered) {
$deprecations_triggered = TRUE;
@trigger_error(sprintf('The update to convert "numeric" arguments to "entity_target_id" for entity reference fields for view "%s" is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Profile, module and theme provided configuration should be updated. See https://www.drupal.org/node/3441945', $view->id()), E_USER_DEPRECATED);
}
@@ -351,7 +290,7 @@ class ViewsConfigUpdater implements ContainerInjectionInterface {
}
$deprecations_triggered = &$this->triggeredDeprecations['table_css_class'][$view->id()];
- if ($this->deprecationsEnabled && $changed && !$deprecations_triggered) {
+ if ($this->areDeprecationsEnabled() && $changed && !$deprecations_triggered) {
$deprecations_triggered = TRUE;
@trigger_error(sprintf('The update to add a default table CSS class for view "%s" is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Profile, module and theme provided configuration should be updated. See https://www.drupal.org/node/3499943', $view->id()), E_USER_DEPRECATED);
}
diff --git a/core/modules/views/tests/modules/views_test_config_updater/src/Hook/ViewsTestConfigUpdaterHooks.php b/core/modules/views/tests/modules/views_test_config_updater/src/Hook/ViewsTestConfigUpdaterHooks.php
new file mode 100644
index 00000000000..77db1f9adc9
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config_updater/src/Hook/ViewsTestConfigUpdaterHooks.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\views_test_config_updater\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+use Drupal\views\ViewEntityInterface;
+use Drupal\views\ViewsConfigUpdater;
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
+
+/**
+ * Hooks for the views_test_config_updater module.
+ */
+class ViewsTestConfigUpdaterHooks {
+
+ public function __construct(
+ protected readonly ViewsConfigUpdater $viewsConfigUpdater,
+ #[Autowire(service: 'keyvalue')]
+ protected readonly KeyValueFactoryInterface $keyValueFactory,
+ ) {
+
+ }
+
+ /**
+ * Implements hook_ENTITY_TYPE_presave().
+ */
+ #[Hook('view_presave')]
+ public function viewPresave(ViewEntityInterface $view): void {
+ $this->keyValueFactory->get('views_test_config_updater')->set('deprecations_enabled', $this->viewsConfigUpdater->areDeprecationsEnabled());
+ }
+
+}
diff --git a/core/modules/views/tests/modules/views_test_config_updater/views_test_config_updater.info.yml b/core/modules/views/tests/modules/views_test_config_updater/views_test_config_updater.info.yml
new file mode 100644
index 00000000000..1efb67ee48f
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config_updater/views_test_config_updater.info.yml
@@ -0,0 +1,6 @@
+name: 'Views Test Config Updater'
+type: module
+package: Testing
+version: VERSION
+dependencies:
+ - drupal:views
diff --git a/core/modules/views/tests/modules/views_test_config_updater/views_test_config_updater.post_update.php b/core/modules/views/tests/modules/views_test_config_updater/views_test_config_updater.post_update.php
new file mode 100644
index 00000000000..db4c780aa8a
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config_updater/views_test_config_updater.post_update.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Post update functions for Views Test Config Updater.
+ */
+
+declare(strict_types=1);
+
+use Drupal\Core\Config\Entity\ConfigEntityUpdater;
+use Drupal\views\ViewEntityInterface;
+use Drupal\views\ViewsConfigUpdater;
+
+/**
+ * Test post update to set deprecations disabled.
+ */
+function views_test_config_updater_post_update_set_deprecations_disabled(?array &$sandbox = NULL): void {
+ /** @var \Drupal\views\ViewsConfigUpdater $viewsConfigUpdater */
+ $viewsConfigUpdater = \Drupal::service(ViewsConfigUpdater::class);
+ $viewsConfigUpdater->setDeprecationsEnabled(FALSE);
+ \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', static fn (ViewEntityInterface $view): bool => TRUE);
+}
diff --git a/core/modules/views/tests/src/Functional/ViewsConfigUpdaterTest.php b/core/modules/views/tests/src/Functional/ViewsConfigUpdaterTest.php
new file mode 100644
index 00000000000..dc5b47eece7
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/ViewsConfigUpdaterTest.php
@@ -0,0 +1,72 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\views\Functional;
+
+use Drupal\Core\Database\Database;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\UpdatePathTestTrait;
+
+/**
+ * Tests the views config updater service.
+ *
+ * @group views
+ */
+class ViewsConfigUpdaterTest extends BrowserTestBase {
+
+ use UpdatePathTestTrait;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $defaultTheme = 'stark';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $modules = [
+ 'views',
+ ];
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void {
+ parent::setUp();
+ $connection = Database::getConnection();
+
+ // Enable views_test_config_updater via the database so post_update hooks
+ // can run.
+ $extensions = $connection->select('config')
+ ->fields('config', ['data'])
+ ->condition('collection', '')
+ ->condition('name', 'core.extension')
+ ->execute()
+ ->fetchField();
+ $extensions = unserialize($extensions);
+ $extensions['module']['views_test_config_updater'] = 0;
+ $connection->update('config')
+ ->fields([
+ 'data' => serialize($extensions),
+ ])
+ ->condition('collection', '')
+ ->condition('name', 'core.extension')
+ ->execute();
+ }
+
+ /**
+ * Tests the deprecationsEnabled flag persists from post_update to presave.
+ *
+ * @see views_test_config_updater_post_update_set_deprecations_disabled
+ * @see \Drupal\views_test_config_updater\Hook\ViewsTestConfigUpdaterHooks::viewPresave()
+ */
+ public function testDeprecationsFlagPersists(): void {
+ $this->assertNull(\Drupal::keyValue('views_test_config_updater')->get('deprecations_enabled'));
+
+ $this->runUpdates();
+
+ $this->assertFalse(\Drupal::keyValue('views_test_config_updater')->get('deprecations_enabled'));
+ }
+
+}
diff --git a/core/modules/views/views.api.php b/core/modules/views/views.api.php
index 6579f93199b..9d2f23cf4c9 100644
--- a/core/modules/views/views.api.php
+++ b/core/modules/views/views.api.php
@@ -13,6 +13,7 @@ use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\LanguageInterface;
use Drupal\views\Plugin\views\cache\CachePluginBase;
use Drupal\views\Plugin\views\PluginBase;
+use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ViewExecutable;
/**
diff --git a/core/modules/views/views.post_update.php b/core/modules/views/views.post_update.php
index 48623fc593e..2dcc15e73e1 100644
--- a/core/modules/views/views.post_update.php
+++ b/core/modules/views/views.post_update.php
@@ -59,7 +59,7 @@ function views_removed_post_updates(): array {
*/
function views_post_update_views_data_argument_plugin_id(?array &$sandbox = NULL): void {
/** @var \Drupal\views\ViewsConfigUpdater $view_config_updater */
- $view_config_updater = \Drupal::classResolver(ViewsConfigUpdater::class);
+ $view_config_updater = \Drupal::service(ViewsConfigUpdater::class);
$view_config_updater->setDeprecationsEnabled(FALSE);
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function (ViewEntityInterface $view) use ($view_config_updater): bool {
return $view_config_updater->needsEntityArgumentUpdate($view);
@@ -71,7 +71,7 @@ function views_post_update_views_data_argument_plugin_id(?array &$sandbox = NULL
*/
function views_post_update_update_remember_role_empty(?array &$sandbox = NULL): void {
/** @var \Drupal\views\ViewsConfigUpdater $view_config_updater */
- $view_config_updater = \Drupal::classResolver(ViewsConfigUpdater::class);
+ $view_config_updater = \Drupal::service(ViewsConfigUpdater::class);
$view_config_updater->setDeprecationsEnabled(FALSE);
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function (ViewEntityInterface $view) use ($view_config_updater): bool {
return $view_config_updater->needsRememberRolesUpdate($view);
@@ -83,7 +83,7 @@ function views_post_update_update_remember_role_empty(?array &$sandbox = NULL):
*/
function views_post_update_table_css_class(?array &$sandbox = NULL): void {
/** @var \Drupal\views\ViewsConfigUpdater $view_config_updater */
- $view_config_updater = \Drupal::classResolver(ViewsConfigUpdater::class);
+ $view_config_updater = \Drupal::service(ViewsConfigUpdater::class);
$view_config_updater->setDeprecationsEnabled(FALSE);
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function (ViewEntityInterface $view) use ($view_config_updater): bool {
return $view_config_updater->needsTableCssClassUpdate($view);
diff --git a/core/modules/views/views.services.yml b/core/modules/views/views.services.yml
index 2a3aa1a16fa..19c5caf92b7 100644
--- a/core/modules/views/views.services.yml
+++ b/core/modules/views/views.services.yml
@@ -107,3 +107,5 @@ services:
tags:
- { name: backend_overridable }
Drupal\views\Plugin\views\query\CastSqlInterface: '@views.cast_sql'
+ Drupal\views\ViewsConfigUpdater:
+ autowire: true
diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php
index c7142f6b98c..80c5984cddd 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php
@@ -67,7 +67,7 @@ class OpenTelemetryAuthenticatedPerformanceTest extends PerformanceTestBase {
'ScriptCount' => 2,
'ScriptBytes' => 123850,
'StylesheetCount' => 2,
- 'StylesheetBytes' => 42000,
+ 'StylesheetBytes' => 41950,
];
$this->assertMetrics($expected, $performance_data);
}
diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
index 027d2933a99..e61eea0c30b 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
@@ -58,7 +58,7 @@ class OpenTelemetryFrontPagePerformanceTest extends PerformanceTestBase {
'ScriptCount' => 1,
'ScriptBytes' => 12000,
'StylesheetCount' => 2,
- 'StylesheetBytes' => 39750,
+ 'StylesheetBytes' => 39150,
];
$this->assertMetrics($expected, $performance_data);
}
@@ -128,7 +128,7 @@ class OpenTelemetryFrontPagePerformanceTest extends PerformanceTestBase {
'ScriptCount' => 1,
'ScriptBytes' => 12000,
'StylesheetCount' => 2,
- 'StylesheetBytes' => 39750,
+ 'StylesheetBytes' => 39150,
];
$this->assertMetrics($expected, $performance_data);
}
diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
index 3045b218bc4..2077da07d3a 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
@@ -59,7 +59,7 @@ class OpenTelemetryNodePagePerformanceTest extends PerformanceTestBase {
'ScriptCount' => 1,
'ScriptBytes' => 12000,
'StylesheetCount' => 2,
- 'StylesheetBytes' => 41350,
+ 'StylesheetBytes' => 40800,
];
$this->assertMetrics($expected, $performance_data);
}
@@ -90,7 +90,7 @@ class OpenTelemetryNodePagePerformanceTest extends PerformanceTestBase {
'ScriptCount' => 1,
'ScriptBytes' => 12000,
'StylesheetCount' => 2,
- 'StylesheetBytes' => 41350,
+ 'StylesheetBytes' => 40800,
];
$this->assertMetrics($expected, $performance_data);
}
@@ -113,7 +113,7 @@ class OpenTelemetryNodePagePerformanceTest extends PerformanceTestBase {
$this->assertSession()->pageTextContains('quiche');
$expected = [
- 'QueryCount' => 191,
+ 'QueryCount' => 190,
'CacheGetCount' => 210,
'CacheSetCount' => 65,
'CacheDeleteCount' => 0,
@@ -122,7 +122,7 @@ class OpenTelemetryNodePagePerformanceTest extends PerformanceTestBase {
'ScriptCount' => 1,
'ScriptBytes' => 12000,
'StylesheetCount' => 2,
- 'StylesheetBytes' => 41350,
+ 'StylesheetBytes' => 40800,
];
$this->assertMetrics($expected, $performance_data);
}
@@ -302,7 +302,6 @@ class OpenTelemetryNodePagePerformanceTest extends PerformanceTestBase {
'SELECT "t".* FROM "node_revision__field_summary" "t" WHERE ("revision_id" IN ("75")) AND ("deleted" = 0) AND ("langcode" IN ("en", "es", "und", "zxx")) ORDER BY "delta" ASC',
'SELECT "t".* FROM "node_revision__field_tags" "t" WHERE ("revision_id" IN ("75")) AND ("deleted" = 0) AND ("langcode" IN ("en", "es", "und", "zxx")) ORDER BY "delta" ASC',
'SELECT "t".* FROM "node_revision__layout_builder__layout" "t" WHERE ("revision_id" IN ("75")) AND ("deleted" = 0) AND ("langcode" IN ("en", "es", "und", "zxx")) ORDER BY "delta" ASC',
- 'SELECT "config"."name" AS "name" FROM "config" "config" WHERE ("collection" = "") AND ("name" LIKE "workflows.workflow.%" ESCAPE \'\\\\\') ORDER BY "collection" ASC, "name" ASC',
'SELECT "revision"."vid" AS "vid", "revision"."langcode" AS "langcode", "revision"."revision_uid" AS "revision_uid", "revision"."revision_timestamp" AS "revision_timestamp", "revision"."revision_log" AS "revision_log", "revision"."revision_default" AS "revision_default", "base"."nid" AS "nid", "base"."type" AS "type", "base"."uuid" AS "uuid", CASE "base"."vid" WHEN "revision"."vid" THEN 1 ELSE 0 END AS "isDefaultRevision" FROM "node" "base" INNER JOIN "node_revision" "revision" ON "revision"."nid" = "base"."nid" AND "revision"."vid" IN (75)',
'SELECT "revision".* FROM "node_field_revision" "revision" WHERE ("revision"."vid" IN (75)) AND ("revision"."vid" IN ("75")) ORDER BY "revision"."vid" ASC',
'SELECT "t".* FROM "node_revision__field_cooking_time" "t" WHERE ("revision_id" IN ("75")) AND ("deleted" = 0) AND ("langcode" IN ("en", "es", "und", "zxx")) ORDER BY "delta" ASC',
@@ -325,7 +324,7 @@ class OpenTelemetryNodePagePerformanceTest extends PerformanceTestBase {
$this->assertSame($expected_queries, $recorded_queries);
$expected = [
- 'QueryCount' => 171,
+ 'QueryCount' => 170,
'CacheGetCount' => 202,
'CacheGetCountByBin' => [
'page' => 1,
@@ -455,7 +454,7 @@ class OpenTelemetryNodePagePerformanceTest extends PerformanceTestBase {
'ScriptCount' => 1,
'ScriptBytes' => 12000,
'StylesheetCount' => 2,
- 'StylesheetBytes' => 41200,
+ 'StylesheetBytes' => 41000,
];
$this->assertMetrics($expected, $performance_data);
}
diff --git a/core/tests/Drupal/Tests/Core/Command/QuickStartTest.php b/core/tests/Drupal/BuildTests/QuickStart/QuickStartTest.php
index c6fc8f18aa5..fbd0473b562 100644
--- a/core/tests/Drupal/Tests/Core/Command/QuickStartTest.php
+++ b/core/tests/Drupal/BuildTests/QuickStart/QuickStartTest.php
@@ -2,14 +2,18 @@
declare(strict_types=1);
-namespace Drupal\Tests\Core\Command;
+namespace Drupal\BuildTests\QuickStart;
use Drupal\sqlite\Driver\Database\sqlite\Install\Tasks;
+use Drupal\BuildTests\Framework\BuildTestBase;
use Drupal\Core\Test\TestDatabase;
use Drupal\Tests\BrowserTestBase;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
-use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\PreserveGlobalState;
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;
@@ -18,15 +22,12 @@ use Symfony\Component\Process\Process;
*
* These tests are run in a separate process because they load Drupal code via
* an include.
- *
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- * @requires extension pdo_sqlite
- *
- * @group Command
- * @group #slow
*/
-class QuickStartTest extends TestCase {
+#[Group('Command')]
+#[PreserveGlobalState(FALSE)]
+#[RequiresPhpExtension('pdo_sqlite')]
+#[RunTestsInSeparateProcesses]
+class QuickStartTest extends BuildTestBase {
/**
* The PHP executable path.
diff --git a/core/tests/Drupal/Tests/Core/Recipe/RecipeQuickStartTest.php b/core/tests/Drupal/BuildTests/QuickStart/RecipeQuickStartTest.php
index 65bedfff3d0..07d907bb8d0 100644
--- a/core/tests/Drupal/Tests/Core/Recipe/RecipeQuickStartTest.php
+++ b/core/tests/Drupal/BuildTests/QuickStart/RecipeQuickStartTest.php
@@ -2,14 +2,18 @@
declare(strict_types=1);
-namespace Drupal\Tests\Core\Recipe;
+namespace Drupal\BuildTests\QuickStart;
use Drupal\sqlite\Driver\Database\sqlite\Install\Tasks;
+use Drupal\BuildTests\Framework\BuildTestBase;
use Drupal\Core\Test\TestDatabase;
use Drupal\Tests\BrowserTestBase;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
-use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\PreserveGlobalState;
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;
@@ -18,16 +22,13 @@ use Symfony\Component\Process\Process;
*
* These tests are run in a separate process because they load Drupal code via
* an include.
- *
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- * @requires extension pdo_sqlite
- *
- * @group Command
- * @group Recipe
- * @group #slow
*/
-class RecipeQuickStartTest extends TestCase {
+#[Group('Command')]
+#[Group('Recipe')]
+#[PreserveGlobalState(FALSE)]
+#[RequiresPhpExtension('pdo_sqlite')]
+#[RunTestsInSeparateProcesses]
+class RecipeQuickStartTest extends BuildTestBase {
/**
* The PHP executable path.
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php
index 284a3e35fa9..49c7d6987f7 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php
@@ -167,6 +167,12 @@ JS;
JS;
$expected = '<div class="div-wrapper-forever"></div>';
$this->assertInsert('empty', $expected, $custom_wrapper_new_content);
+
+ // Checking inserting table elements.
+ $expected = '<tr><td>table-row</td></tr>';
+ $this->drupalGet('ajax-test/insert-table-wrapper');
+ $this->clickLink('Link table-row');
+ $this->assertWaitPageContains('<div class="ajax-target-wrapper"><table><tbody id="ajax-target">' . $expected . '</tbody></table></div>');
}
/**
diff --git a/core/tests/Drupal/Nightwatch/Tests/a11yTestAdmin.js b/core/tests/Drupal/Nightwatch/Tests/a11yTestAdmin.js
index ea71f685206..3dc498246aa 100644
--- a/core/tests/Drupal/Nightwatch/Tests/a11yTestAdmin.js
+++ b/core/tests/Drupal/Nightwatch/Tests/a11yTestAdmin.js
@@ -25,6 +25,30 @@ const testCases = [
{ name: 'Structure Page', path: '/admin/structure' },
{ name: 'Add content type', path: '/admin/structure/types/add' },
{ name: 'Add vocabulary', path: '/admin/structure/taxonomy/add' },
+ {
+ // Tests long breadcrumb for https://drupal.org/i/3223147.
+ name: 'Manage text format, mobile',
+ path: '/admin/config/content/formats/manage/restricted_html',
+ windowSize: {
+ // Dimensions used by Lighthouse for mobile.
+ width: 415,
+ height: 823,
+ },
+ options: {
+ runOnly: {
+ type: 'tag',
+ values: [
+ 'wcag2a',
+ 'wcag2aa',
+ 'wcag21a',
+ 'wcag21aa',
+ 'best-practice',
+ 'wcag22a',
+ 'wcag22aa',
+ ],
+ },
+ },
+ },
// @todo remove the skipped rules below in https://drupal.org/i/3318394.
{
name: 'Structure | Block',
@@ -41,6 +65,13 @@ const testCases = [
testCases.forEach((testCase) => {
adminTest[`Accessibility - Admin Theme: ${testCase.name}`] = (browser) => {
+ if (testCase.windowSize) {
+ browser.setWindowSize(
+ testCase.windowSize.width,
+ testCase.windowSize.height,
+ );
+ }
+
browser.drupalLoginAsAdmin(() => {
browser
.drupalRelativeURL(testCase.path)
diff --git a/core/tests/Drupal/Nightwatch/Tests/htmx/htmxTest.js b/core/tests/Drupal/Nightwatch/Tests/htmx/htmxTest.js
index 98916702a88..4705455683d 100644
--- a/core/tests/Drupal/Nightwatch/Tests/htmx/htmxTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/htmx/htmxTest.js
@@ -41,8 +41,54 @@ module.exports = {
.assert.not.elementPresent(cssSelector)
.waitForElementVisible('[name="replace"]', 1000)
.click('[name="replace"]')
- .waitForElementVisible(elementSelector, 6000)
- .waitForElementVisible(elementInitSelector, 6000)
+ .waitForElementVisible(elementSelector, 1100)
+ .waitForElementVisible(elementInitSelector, 1100)
+ .assert.elementPresent(scriptSelector)
+ .assert.elementPresent(cssSelector);
+ },
+
+ 'Swap Before': (browser) => {
+ // Load the route htmx will use for the request on click and confirm the
+ // markup we will be looking for is present in the source markup.
+ browser
+ .drupalRelativeURL('/htmx-test-attachments/replace')
+ .waitForElementVisible('body', 1000)
+ .assert.elementPresent(elementInitSelector);
+ // Now load the page with the htmx enhanced button and verify the absence
+ // of the markup to be inserted. Click the button
+ // and check for inserted javascript and markup.
+ browser
+ .drupalRelativeURL('/htmx-test-attachments/before')
+ .waitForElementVisible('body', 1000)
+ .assert.not.elementPresent(scriptSelector)
+ .assert.not.elementPresent(cssSelector)
+ .waitForElementVisible('[name="replace"]', 1000)
+ .click('[name="replace"]')
+ .waitForElementVisible(elementSelector, 1100)
+ .waitForElementVisible(elementInitSelector, 1100)
+ .assert.elementPresent(scriptSelector)
+ .assert.elementPresent(cssSelector);
+ },
+
+ 'Swap After': (browser) => {
+ // Load the route htmx will use for the request on click and confirm the
+ // markup we will be looking for is present in the source markup.
+ browser
+ .drupalRelativeURL('/htmx-test-attachments/replace')
+ .waitForElementVisible('body', 1000)
+ .assert.elementPresent(elementInitSelector);
+ // Now load the page with the htmx enhanced button and verify the absence
+ // of the markup to be inserted. Click the button
+ // and check for inserted javascript and markup.
+ browser
+ .drupalRelativeURL('/htmx-test-attachments/after')
+ .waitForElementVisible('body', 1000)
+ .assert.not.elementPresent(scriptSelector)
+ .assert.not.elementPresent(cssSelector)
+ .waitForElementVisible('[name="replace"]', 1000)
+ .click('[name="replace"]')
+ .waitForElementVisible(elementSelector, 1100)
+ .waitForElementVisible(elementInitSelector, 1100)
.assert.elementPresent(scriptSelector)
.assert.elementPresent(cssSelector);
},
@@ -69,8 +115,8 @@ module.exports = {
.waitForElementVisible('[name="replace"]', 1000)
.pause(1000)
.click('[name="replace"]')
- .waitForElementVisible(elementSelector, 6000)
- .waitForElementVisible(elementInitSelector, 6000)
+ .waitForElementVisible(elementSelector, 1100)
+ .waitForElementVisible(elementInitSelector, 1100)
.assert.elementPresent(scriptSelector)
.assert.elementPresent(cssSelector);
},
diff --git a/core/themes/claro/css/components/breadcrumb.css b/core/themes/claro/css/components/breadcrumb.css
index 6bfb6fe74ab..b3a84f7200b 100644
--- a/core/themes/claro/css/components/breadcrumb.css
+++ b/core/themes/claro/css/components/breadcrumb.css
@@ -25,11 +25,12 @@
.breadcrumb__item,
.breadcrumb__link {
- display: inline;
+ display: inline-block;
-webkit-text-decoration: none;
text-decoration: none;
color: var(--color-text);
font-weight: bold;
+ line-height: 1.5rem;
}
.breadcrumb__item + .breadcrumb__item::before {
diff --git a/core/themes/claro/css/components/breadcrumb.pcss.css b/core/themes/claro/css/components/breadcrumb.pcss.css
index 320fc2c67d1..4c6893ae706 100644
--- a/core/themes/claro/css/components/breadcrumb.pcss.css
+++ b/core/themes/claro/css/components/breadcrumb.pcss.css
@@ -18,10 +18,11 @@
.breadcrumb__item,
.breadcrumb__link {
- display: inline;
+ display: inline-block;
text-decoration: none;
color: var(--color-text);
font-weight: bold;
+ line-height: 1.5rem;
}
.breadcrumb__item + .breadcrumb__item::before {
diff --git a/core/themes/claro/css/components/tabs.css b/core/themes/claro/css/components/tabs.css
index 8c575e2b2ba..16ab6620c4d 100644
--- a/core/themes/claro/css/components/tabs.css
+++ b/core/themes/claro/css/components/tabs.css
@@ -278,3 +278,9 @@
0 0 0 calc(var(--tabs--focus-height) + 2px) var(--color-focus);
}
}
+
+@media (forced-colors: active) {
+ .tabs__trigger svg path {
+ fill: currentColor;
+ }
+}
diff --git a/core/themes/claro/css/components/tabs.pcss.css b/core/themes/claro/css/components/tabs.pcss.css
index 81dcb7b65d2..51ed8a3f090 100644
--- a/core/themes/claro/css/components/tabs.pcss.css
+++ b/core/themes/claro/css/components/tabs.pcss.css
@@ -269,3 +269,9 @@
}
}
}
+
+@media (forced-colors: active) {
+ .tabs__trigger svg path {
+ fill: currentColor;
+ }
+}
diff --git a/core/themes/claro/templates/navigation/menu-local-task.html.twig b/core/themes/claro/templates/navigation/menu-local-task.html.twig
index b0b6ce08db9..41591f0f5a1 100644
--- a/core/themes/claro/templates/navigation/menu-local-task.html.twig
+++ b/core/themes/claro/templates/navigation/menu-local-task.html.twig
@@ -26,6 +26,7 @@
<li{{ attributes.addClass(classes) }}>
{{ link }}
{% if is_active and level == 'primary' %}
+ {{ attach_library('core/drupal.reset-appearance') }}
<button class="reset-appearance tabs__trigger" type="button" aria-label="{{ 'Tabs display toggle'|t }}" data-drupal-nav-tabs-trigger>
{% include "@claro/../images/src/hamburger-menu.svg" %}
</button>
diff --git a/core/themes/olivero/css/components/dropbutton.css b/core/themes/olivero/css/components/dropbutton.css
index 3ebcc55b782..fe80d2ff568 100644
--- a/core/themes/olivero/css/components/dropbutton.css
+++ b/core/themes/olivero/css/components/dropbutton.css
@@ -75,6 +75,10 @@
outline-offset: -2px;
}
+.dropbutton-toggle button:dir(rtl) {
+ border-radius: var(--dropbutton--border-radius) 0 0 var(--dropbutton--border-radius);
+}
+
.dropbutton-toggle button::before {
display: block;
width: var(--sp0-5);
@@ -85,14 +89,10 @@
border-bottom: solid 2px var(--dropbutton--outline-color);
}
-.dropbutton-wrapper.open :is(.dropbutton-toggle button::before) {
+.dropbutton-wrapper.open :is(.dropbutton-toggle button)::before {
transform: translateY(25%) rotate(225deg);
}
-.dropbutton-toggle button:dir(rtl) {
- border-radius: var(--dropbutton--border-radius) 0 0 var(--dropbutton--border-radius);
-}
-
/* This is the first <li> element in the list of actions. */
.dropbutton-action:first-child {
diff --git a/core/themes/olivero/css/components/dropbutton.pcss.css b/core/themes/olivero/css/components/dropbutton.pcss.css
index 8437d69a573..04bb51cc4c3 100644
--- a/core/themes/olivero/css/components/dropbutton.pcss.css
+++ b/core/themes/olivero/css/components/dropbutton.pcss.css
@@ -66,6 +66,10 @@
outline-offset: -2px;
}
+ &:dir(rtl) {
+ border-radius: var(--dropbutton--border-radius) 0 0 var(--dropbutton--border-radius);
+ }
+
&::before {
display: block;
width: var(--sp0-5);
@@ -74,15 +78,13 @@
transform: translateY(-25%) rotate(45deg);
border-right: solid 2px var(--dropbutton--outline-color);
border-bottom: solid 2px var(--dropbutton--outline-color);
+ }
- .dropbutton-wrapper.open & {
+ .dropbutton-wrapper.open & {
+ &::before {
transform: translateY(25%) rotate(225deg);
}
}
-
- &:dir(rtl) {
- border-radius: var(--dropbutton--border-radius) 0 0 var(--dropbutton--border-radius);
- }
}
/* This is the first <li> element in the list of actions. */
diff --git a/core/themes/olivero/css/components/navigation/nav-primary-wide.css b/core/themes/olivero/css/components/navigation/nav-primary-wide.css
index 50179032ffc..2192c44529b 100644
--- a/core/themes/olivero/css/components/navigation/nav-primary-wide.css
+++ b/core/themes/olivero/css/components/navigation/nav-primary-wide.css
@@ -40,7 +40,7 @@
top: 50%;
left: 50%;
width: calc(100% + var(--sp));
- height: var(--sp3);
+ height: calc(100% - var(--sp3));
content: "";
transform: translate(-50%, -50%);
border: solid 2px var(--color--primary-50);
diff --git a/core/themes/olivero/css/components/navigation/nav-primary-wide.pcss.css b/core/themes/olivero/css/components/navigation/nav-primary-wide.pcss.css
index db6b05465d5..a594e6e4215 100644
--- a/core/themes/olivero/css/components/navigation/nav-primary-wide.pcss.css
+++ b/core/themes/olivero/css/components/navigation/nav-primary-wide.pcss.css
@@ -37,7 +37,7 @@ body:not(.is-always-mobile-nav) {
top: 50%;
left: 50%;
width: calc(100% + var(--sp));
- height: var(--sp3);
+ height: calc(100% - var(--sp3));
content: "";
transform: translate(-50%, -50%);
border: solid 2px var(--color--primary-50);
diff --git a/core/themes/olivero/css/components/navigation/nav-secondary.css b/core/themes/olivero/css/components/navigation/nav-secondary.css
index 3caa30439b8..57bb75ef51e 100644
--- a/core/themes/olivero/css/components/navigation/nav-secondary.css
+++ b/core/themes/olivero/css/components/navigation/nav-secondary.css
@@ -47,7 +47,6 @@
position: relative;
display: inline-flex;
align-items: center;
- height: var(--sp2);
-webkit-text-decoration: none;
text-decoration: none;
color: inherit;
@@ -99,6 +98,7 @@
body:not(.is-always-mobile-nav) .secondary-nav__menu-link:focus {
position: relative;
outline: 0;
+ padding-block: var(--sp0-5);
}
body:not(.is-always-mobile-nav) .secondary-nav__menu-link:focus::before {
@@ -106,7 +106,7 @@
top: 50%;
left: 50%;
width: calc(100% + var(--sp));
- height: var(--sp3);
+ height: 100%;
content: "";
transform: translate(-50%, -50%);
border: solid 2px var(--color--primary-50);
diff --git a/core/themes/olivero/css/components/navigation/nav-secondary.pcss.css b/core/themes/olivero/css/components/navigation/nav-secondary.pcss.css
index eabeeff71a1..84c0bd86faf 100644
--- a/core/themes/olivero/css/components/navigation/nav-secondary.pcss.css
+++ b/core/themes/olivero/css/components/navigation/nav-secondary.pcss.css
@@ -42,7 +42,6 @@
position: relative;
display: inline-flex;
align-items: center;
- height: var(--sp2);
text-decoration: none;
color: inherit;
@@ -98,13 +97,14 @@ body:not(.is-always-mobile-nav) {
&:focus {
position: relative;
outline: 0;
+ padding-block: var(--sp0-5);
&::before {
position: absolute;
top: 50%;
left: 50%;
width: calc(100% + var(--sp));
- height: var(--sp3);
+ height: 100%;
content: "";
transform: translate(-50%, -50%);
border: solid 2px var(--color--primary-50);
diff --git a/core/themes/olivero/olivero.libraries.yml b/core/themes/olivero/olivero.libraries.yml
index c699ebc72c8..e6f0be35688 100644
--- a/core/themes/olivero/olivero.libraries.yml
+++ b/core/themes/olivero/olivero.libraries.yml
@@ -45,7 +45,6 @@ global-styling:
css/components/site-header.css: {}
css/components/skip-link.css: {}
css/components/pager.css: {}
- css/components/table.css: {}
css/components/text-content.css: {}
css/components/wide-content.css: {}
@@ -291,3 +290,18 @@ tags:
css:
theme:
css/components/tags.css: {}
+
+olivero.table:
+ version: VERSION
+ css:
+ component:
+ css/components/table.css: {}
+ moved_files:
+ olivero/global-styling:
+ deprecation_version: 11.3.0
+ removed_version: 12.0.0
+ deprecation_link: https://www.drupal.org/node/3517675
+ css:
+ component:
+ css/components/table.css:
+ base: css/components/table.css
diff --git a/core/themes/olivero/olivero.theme b/core/themes/olivero/olivero.theme
index 8ddfb49b87f..479a45aeec0 100644
--- a/core/themes/olivero/olivero.theme
+++ b/core/themes/olivero/olivero.theme
@@ -365,6 +365,7 @@ function olivero_preprocess_field(&$variables): void {
if (in_array($variables['field_type'], $rich_field_types, TRUE)) {
$variables['attributes']['class'][] = 'text-content';
+ $variables['#attached']['library'][] = 'olivero/olivero.table';
}
if ($variables['field_type'] == 'image' && $variables['element']['#view_mode'] == 'full' && !$variables["element"]["#is_multiple"] && $variables['field_name'] !== 'user_picture') {
@@ -616,6 +617,15 @@ function olivero_preprocess_table(&$variables): void {
}
}
}
+
+ $variables['#attached']['library'][] = 'olivero/olivero.table';
+}
+
+/**
+ * Implements hook_preprocess_HOOK() for views-view-table templates.
+ */
+function olivero_preprocess_views_view_table(&$variables): void {
+ $variables['#attached']['library'][] = 'olivero/olivero.table';
}
/**
diff --git a/core/themes/stable9/css/system/components/reset-appearance.module.css b/core/themes/stable9/css/core/components/reset-appearance.module.css
index 59741a85ce8..59741a85ce8 100644
--- a/core/themes/stable9/css/system/components/reset-appearance.module.css
+++ b/core/themes/stable9/css/core/components/reset-appearance.module.css
diff --git a/core/themes/stable9/stable9.info.yml b/core/themes/stable9/stable9.info.yml
index c079c2b9b47..ffe223b44f0 100644
--- a/core/themes/stable9/stable9.info.yml
+++ b/core/themes/stable9/stable9.info.yml
@@ -109,6 +109,11 @@ libraries-override:
component:
misc/vertical-tabs.css: css/core/vertical-tabs.css
+ core/drupal.reset-appearance:
+ css:
+ component:
+ misc/components/reset-appearance.module.css: css/core/components/reset-appearance.module.css
+
# Load version 3.0.3 of normalize.css for backwards compatibility.
core/normalize: stable9/normalize
@@ -244,7 +249,6 @@ libraries-override:
css/components/clearfix.module.css: css/system/components/clearfix.module.css
css/components/hidden.module.css: css/system/components/hidden.module.css
css/components/js.module.css: css/system/components/js.module.css
- css/components/reset-appearance.module.css: css/system/components/reset-appearance.module.css
system/admin:
css:
theme: