summaryrefslogtreecommitdiffstatshomepage
path: root/core/lib
diff options
context:
space:
mode:
Diffstat (limited to 'core/lib')
-rw-r--r--core/lib/Drupal.php2
-rw-r--r--core/lib/Drupal/Component/Annotation/Doctrine/DocParser.php34
-rw-r--r--core/lib/Drupal/Component/Annotation/composer.json2
-rw-r--r--core/lib/Drupal/Component/DependencyInjection/composer.json4
-rw-r--r--core/lib/Drupal/Component/EventDispatcher/composer.json6
-rw-r--r--core/lib/Drupal/Component/HttpFoundation/composer.json2
-rw-r--r--core/lib/Drupal/Component/Plugin/LazyPluginCollection.php6
-rw-r--r--core/lib/Drupal/Component/Plugin/composer.json2
-rw-r--r--core/lib/Drupal/Component/Render/FormattableMarkup.php8
-rw-r--r--core/lib/Drupal/Component/Serialization/composer.json2
-rw-r--r--core/lib/Drupal/Core/Access/AccessGroupAnd.php55
-rw-r--r--core/lib/Drupal/Core/Access/DependentAccessInterface.php35
-rw-r--r--core/lib/Drupal/Core/Access/RefinableDependentAccessInterface.php46
-rw-r--r--core/lib/Drupal/Core/Access/RefinableDependentAccessTrait.php50
-rw-r--r--core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php13
-rw-r--r--core/lib/Drupal/Core/Cache/CacheTagsPurgeInterface.php24
-rw-r--r--core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php18
-rw-r--r--core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/ConfigExistsConstraintValidator.php3
-rw-r--r--core/lib/Drupal/Core/Database/Connection.php4
-rw-r--r--core/lib/Drupal/Core/Database/Statement/PdoTrait.php10
-rw-r--r--core/lib/Drupal/Core/Database/Statement/ResultBase.php8
-rw-r--r--core/lib/Drupal/Core/Database/Statement/StatementBase.php8
-rw-r--r--core/lib/Drupal/Core/Database/StatementInterface.php8
-rw-r--r--core/lib/Drupal/Core/Database/StatementPrefetchIterator.php2
-rw-r--r--core/lib/Drupal/Core/Database/StatementWrapperIterator.php2
-rw-r--r--core/lib/Drupal/Core/Database/Transaction.php30
-rw-r--r--core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php134
-rw-r--r--core/lib/Drupal/Core/Database/Transaction/TransactionManagerInterface.php4
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/Compiler/BackendCompilerPass.php5
-rw-r--r--core/lib/Drupal/Core/Extension/InstallRequirementsInterface.php13
-rw-r--r--core/lib/Drupal/Core/Extension/ModuleHandler.php6
-rw-r--r--core/lib/Drupal/Core/Extension/Requirement/RequirementSeverity.php120
-rw-r--r--core/lib/Drupal/Core/Extension/ThemeInstaller.php120
-rw-r--r--core/lib/Drupal/Core/Extension/module.api.php64
-rw-r--r--core/lib/Drupal/Core/Hook/Attribute/FormAlter.php54
-rw-r--r--core/lib/Drupal/Core/Hook/Attribute/Hook.php26
-rw-r--r--core/lib/Drupal/Core/Hook/Attribute/Preprocess.php23
-rw-r--r--core/lib/Drupal/Core/Mailer/Transport/SendmailCommandValidationTransportFactory.php52
-rw-r--r--core/lib/Drupal/Core/Mailer/TransportServiceFactory.php44
-rw-r--r--core/lib/Drupal/Core/Mailer/TransportServiceFactoryInterface.php28
-rw-r--r--core/lib/Drupal/Core/Mailer/TransportServiceFactoryTrait.php42
-rw-r--r--core/lib/Drupal/Core/Menu/MenuActiveTrail.php34
-rw-r--r--core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php8
-rw-r--r--core/lib/Drupal/Core/ProxyClass/Cron.php80
-rw-r--r--core/lib/Drupal/Core/Recipe/ConsoleInputCollector.php13
-rw-r--r--core/lib/Drupal/Core/Render/Element/ComponentElement.php8
-rw-r--r--core/lib/Drupal/Core/Render/Element/RenderElementBase.php5
-rw-r--r--core/lib/Drupal/Core/Render/Element/StatusReport.php31
-rw-r--r--core/lib/Drupal/Core/Render/Renderer.php145
-rw-r--r--core/lib/Drupal/Core/Render/RendererInterface.php2
-rw-r--r--core/lib/Drupal/Core/Session/SessionManager.php11
-rw-r--r--core/lib/Drupal/Core/Template/Loader/ComponentLoader.php7
-rw-r--r--core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php25
-rw-r--r--core/lib/Drupal/Core/Test/SimpletestTestRunResultsStorage.php6
-rw-r--r--core/lib/Drupal/Core/Theme/Component/ComponentMetadata.php57
-rw-r--r--core/lib/Drupal/Core/Theme/Component/ComponentValidator.php4
-rw-r--r--core/lib/Drupal/Core/Theme/ComponentPluginManager.php4
57 files changed, 1010 insertions, 549 deletions
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 63514b60eff8..9b6dc8765017 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -76,7 +76,7 @@ class Drupal {
/**
* The current system version.
*/
- const VERSION = '11.2-dev';
+ const VERSION = '11.3-dev';
/**
* Core API compatibility.
diff --git a/core/lib/Drupal/Component/Annotation/Doctrine/DocParser.php b/core/lib/Drupal/Component/Annotation/Doctrine/DocParser.php
index 052542ecfca7..5fb3e2e3a75e 100644
--- a/core/lib/Drupal/Component/Annotation/Doctrine/DocParser.php
+++ b/core/lib/Drupal/Component/Annotation/Doctrine/DocParser.php
@@ -417,7 +417,7 @@ final class DocParser
$message = sprintf('Expected %s, got ', $expected);
$message .= ($this->lexer->lookahead === null)
? 'end of string'
- : sprintf("'%s' at position %s", $token['value'], $token['position']);
+ : sprintf("'%s' at position %s", $token->value, $token->position);
if (strlen($this->context)) {
$message .= ' in ' . $this->context;
@@ -616,13 +616,13 @@ final class DocParser
$annotations = array();
while (null !== $this->lexer->lookahead) {
- if (DocLexer::T_AT !== $this->lexer->lookahead['type']) {
+ if (DocLexer::T_AT !== $this->lexer->lookahead->type) {
$this->lexer->moveNext();
continue;
}
// make sure the @ is preceded by non-catchable pattern
- if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) {
+ if (null !== $this->lexer->token && $this->lexer->lookahead->position === $this->lexer->token->position + strlen($this->lexer->token->value)) {
$this->lexer->moveNext();
continue;
}
@@ -630,8 +630,8 @@ final class DocParser
// make sure the @ is followed by either a namespace separator, or
// an identifier token
if ((null === $peek = $this->lexer->glimpse())
- || (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true))
- || $peek['position'] !== $this->lexer->lookahead['position'] + 1) {
+ || (DocLexer::T_NAMESPACE_SEPARATOR !== $peek->type && !in_array($peek->type, self::$classIdentifiers, true))
+ || $peek->position !== $this->lexer->lookahead->position + 1) {
$this->lexer->moveNext();
continue;
}
@@ -988,17 +988,17 @@ final class DocParser
$this->lexer->moveNext();
- $className = $this->lexer->token['value'];
+ $className = $this->lexer->token->value;
while (
null !== $this->lexer->lookahead &&
- $this->lexer->lookahead['position'] === ($this->lexer->token['position'] + strlen($this->lexer->token['value'])) &&
+ $this->lexer->lookahead->position === ($this->lexer->token->position + strlen($this->lexer->token->value)) &&
$this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)
) {
$this->match(DocLexer::T_NAMESPACE_SEPARATOR);
$this->matchAny(self::$classIdentifiers);
- $className .= '\\' . $this->lexer->token['value'];
+ $className .= '\\' . $this->lexer->token->value;
}
return $className;
@@ -1013,7 +1013,7 @@ final class DocParser
{
$peek = $this->lexer->glimpse();
- if (DocLexer::T_EQUALS === $peek['type']) {
+ if (DocLexer::T_EQUALS === $peek->type) {
return $this->FieldAssignment();
}
@@ -1039,18 +1039,18 @@ final class DocParser
return $this->Constant();
}
- switch ($this->lexer->lookahead['type']) {
+ switch ($this->lexer->lookahead->type) {
case DocLexer::T_STRING:
$this->match(DocLexer::T_STRING);
- return $this->lexer->token['value'];
+ return $this->lexer->token->value;
case DocLexer::T_INTEGER:
$this->match(DocLexer::T_INTEGER);
- return (int)$this->lexer->token['value'];
+ return (int)$this->lexer->token->value;
case DocLexer::T_FLOAT:
$this->match(DocLexer::T_FLOAT);
- return (float)$this->lexer->token['value'];
+ return (float)$this->lexer->token->value;
case DocLexer::T_TRUE:
$this->match(DocLexer::T_TRUE);
@@ -1078,7 +1078,7 @@ final class DocParser
private function FieldAssignment()
{
$this->match(DocLexer::T_IDENTIFIER);
- $fieldName = $this->lexer->token['value'];
+ $fieldName = $this->lexer->token->value;
$this->match(DocLexer::T_EQUALS);
@@ -1146,14 +1146,14 @@ final class DocParser
{
$peek = $this->lexer->glimpse();
- if (DocLexer::T_EQUALS === $peek['type']
- || DocLexer::T_COLON === $peek['type']) {
+ if (DocLexer::T_EQUALS === $peek->type
+ || DocLexer::T_COLON === $peek->type) {
if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
$key = $this->Constant();
} else {
$this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING));
- $key = $this->lexer->token['value'];
+ $key = $this->lexer->token->value;
}
$this->matchAny(array(DocLexer::T_EQUALS, DocLexer::T_COLON));
diff --git a/core/lib/Drupal/Component/Annotation/composer.json b/core/lib/Drupal/Component/Annotation/composer.json
index 3575798a7c15..24977f8da9f7 100644
--- a/core/lib/Drupal/Component/Annotation/composer.json
+++ b/core/lib/Drupal/Component/Annotation/composer.json
@@ -9,7 +9,7 @@
"require": {
"php": ">=8.3.0",
"doctrine/annotations": "^2.0",
- "doctrine/lexer": "^2.0",
+ "doctrine/lexer": "^2 || ^3",
"drupal/core-class-finder": "11.x-dev",
"drupal/core-file-cache": "11.x-dev",
"drupal/core-plugin": "11.x-dev",
diff --git a/core/lib/Drupal/Component/DependencyInjection/composer.json b/core/lib/Drupal/Component/DependencyInjection/composer.json
index ccf16002da9c..df9e64651815 100644
--- a/core/lib/Drupal/Component/DependencyInjection/composer.json
+++ b/core/lib/Drupal/Component/DependencyInjection/composer.json
@@ -14,8 +14,8 @@
},
"require": {
"php": ">=8.3.0",
- "symfony/dependency-injection": "^7.3@beta",
- "symfony/service-contracts": "v3.5.1"
+ "symfony/dependency-injection": "^7.3",
+ "symfony/service-contracts": "v3.6.0"
},
"suggest": {
"symfony/expression-language": "For using expressions in service container configuration"
diff --git a/core/lib/Drupal/Component/EventDispatcher/composer.json b/core/lib/Drupal/Component/EventDispatcher/composer.json
index b832a6c45b1b..d78d1b980c0c 100644
--- a/core/lib/Drupal/Component/EventDispatcher/composer.json
+++ b/core/lib/Drupal/Component/EventDispatcher/composer.json
@@ -8,9 +8,9 @@
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.3.0",
- "symfony/dependency-injection": "^7.3@beta",
- "symfony/event-dispatcher": "^7.3@beta",
- "symfony/event-dispatcher-contracts": "v3.5.1"
+ "symfony/dependency-injection": "^7.3",
+ "symfony/event-dispatcher": "^7.3",
+ "symfony/event-dispatcher-contracts": "v3.6.0"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Component/HttpFoundation/composer.json b/core/lib/Drupal/Component/HttpFoundation/composer.json
index 283e0713ca5c..85e62e4ef9f5 100644
--- a/core/lib/Drupal/Component/HttpFoundation/composer.json
+++ b/core/lib/Drupal/Component/HttpFoundation/composer.json
@@ -8,7 +8,7 @@
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.3.0",
- "symfony/http-foundation": "^7.3@beta"
+ "symfony/http-foundation": "^7.3"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Component/Plugin/LazyPluginCollection.php b/core/lib/Drupal/Component/Plugin/LazyPluginCollection.php
index e11003efcb2d..86408c233900 100644
--- a/core/lib/Drupal/Component/Plugin/LazyPluginCollection.php
+++ b/core/lib/Drupal/Component/Plugin/LazyPluginCollection.php
@@ -142,7 +142,11 @@ abstract class LazyPluginCollection implements \IteratorAggregate, \Countable {
$this->remove($instance_id);
}
- public function getIterator(): \ArrayIterator {
+ /**
+ * @return \Traversable<string, mixed>
+ * A traversable generator.
+ */
+ public function getIterator(): \Traversable {
$instances = [];
foreach ($this->getInstanceIds() as $instance_id) {
$instances[$instance_id] = $this->get($instance_id);
diff --git a/core/lib/Drupal/Component/Plugin/composer.json b/core/lib/Drupal/Component/Plugin/composer.json
index 6a4f1fc7e2cd..2c5a4864291a 100644
--- a/core/lib/Drupal/Component/Plugin/composer.json
+++ b/core/lib/Drupal/Component/Plugin/composer.json
@@ -10,7 +10,7 @@
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.3.0",
- "symfony/validator": "^7.3@beta"
+ "symfony/validator": "^7.3"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Component/Render/FormattableMarkup.php b/core/lib/Drupal/Component/Render/FormattableMarkup.php
index 6db6288d47d5..c6e5ebcb9ddf 100644
--- a/core/lib/Drupal/Component/Render/FormattableMarkup.php
+++ b/core/lib/Drupal/Component/Render/FormattableMarkup.php
@@ -124,10 +124,10 @@ class FormattableMarkup implements MarkupInterface, \Countable {
* Insecure examples.
* @code
* // The following are using the @ placeholder inside an HTML tag.
- * $this->placeholderFormat('<@foo>text</@foo>, ['@foo' => $some_variable]);
- * $this->placeholderFormat('<a @foo>link text</a>, ['@foo' => $some_variable]);
- * $this->placeholderFormat('<a href="@foo">link text</a>, ['@foo' => $some_variable]);
- * $this->placeholderFormat('<a title="@foo">link text</a>, ['@foo' => $some_variable]);
+ * $this->placeholderFormat('<@foo>text</@foo>', ['@foo' => $some_variable]);
+ * $this->placeholderFormat('<a @foo>link text</a>', ['@foo' => $some_variable]);
+ * $this->placeholderFormat('<a href="@foo">link text</a>', ['@foo' => $some_variable]);
+ * $this->placeholderFormat('<a title="@foo">link text</a>', ['@foo' => $some_variable]);
* // Implicitly convert an object to a string, which is not sanitized.
* $this->placeholderFormat('Non-sanitized replacement value: @foo', ['@foo' => $safe_string_interface_object]);
* @endcode
diff --git a/core/lib/Drupal/Component/Serialization/composer.json b/core/lib/Drupal/Component/Serialization/composer.json
index 10068bba3ced..22325e148742 100644
--- a/core/lib/Drupal/Component/Serialization/composer.json
+++ b/core/lib/Drupal/Component/Serialization/composer.json
@@ -8,7 +8,7 @@
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.3.0",
- "symfony/yaml": "^7.3@beta"
+ "symfony/yaml": "^7.3"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Core/Access/AccessGroupAnd.php b/core/lib/Drupal/Core/Access/AccessGroupAnd.php
new file mode 100644
index 000000000000..d4535ccac523
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/AccessGroupAnd.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\Core\Access;
+
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * An access group where all the dependencies must be allowed.
+ *
+ * @internal
+ */
+class AccessGroupAnd implements AccessibleInterface {
+
+ /**
+ * The access dependencies.
+ *
+ * @var \Drupal\Core\Access\AccessibleInterface[]
+ */
+ protected $dependencies = [];
+
+ /**
+ * Adds an access dependency.
+ *
+ * @param \Drupal\Core\Access\AccessibleInterface $dependency
+ * The access dependency to be added.
+ *
+ * @return $this
+ */
+ public function addDependency(AccessibleInterface $dependency) {
+ $this->dependencies[] = $dependency;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
+ $access_result = AccessResult::neutral();
+ foreach (array_slice($this->dependencies, 1) as $dependency) {
+ $access_result = $access_result->andIf($dependency->access($operation, $account, TRUE));
+ }
+ return $return_as_object ? $access_result : $access_result->isAllowed();
+ }
+
+ /**
+ * Gets all the access dependencies.
+ *
+ * @return list<\Drupal\Core\Access\AccessibleInterface>
+ * The list of access dependencies.
+ */
+ public function getDependencies() {
+ return $this->dependencies;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Access/DependentAccessInterface.php b/core/lib/Drupal/Core/Access/DependentAccessInterface.php
new file mode 100644
index 000000000000..eee44102c953
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/DependentAccessInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\Core\Access;
+
+/**
+ * Interface for AccessibleInterface objects that have an access dependency.
+ *
+ * Objects should implement this interface when their access depends on access
+ * to another object that implements \Drupal\Core\Access\AccessibleInterface.
+ * This interface simply provides the getter method for the access
+ * dependency object. Objects that implement this interface are responsible for
+ * checking access of the access dependency because the dependency may not take
+ * effect in all cases. For instance an entity may only need the access
+ * dependency set when it is embedded within another entity and its access
+ * should be dependent on access to the entity in which it is embedded.
+ *
+ * To check the access to the dependency the object implementing this interface
+ * can use code like this:
+ * @code
+ * $accessible->getAccessDependency()->access($op, $account, TRUE);
+ * @endcode
+ *
+ * @internal
+ */
+interface DependentAccessInterface {
+
+ /**
+ * Gets the access dependency.
+ *
+ * @return \Drupal\Core\Access\AccessibleInterface|null
+ * The access dependency or NULL if none has been set.
+ */
+ public function getAccessDependency();
+
+}
diff --git a/core/lib/Drupal/Core/Access/RefinableDependentAccessInterface.php b/core/lib/Drupal/Core/Access/RefinableDependentAccessInterface.php
new file mode 100644
index 000000000000..a6e5e671aaae
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/RefinableDependentAccessInterface.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\Core\Access;
+
+/**
+ * An interface to allow adding an access dependency.
+ *
+ * @internal
+ */
+interface RefinableDependentAccessInterface extends DependentAccessInterface {
+
+ /**
+ * Sets the access dependency.
+ *
+ * If an access dependency is already set this will replace the existing
+ * dependency.
+ *
+ * @param \Drupal\Core\Access\AccessibleInterface $access_dependency
+ * The object upon which access depends.
+ *
+ * @return $this
+ */
+ public function setAccessDependency(AccessibleInterface $access_dependency);
+
+ /**
+ * Adds an access dependency into the existing access dependency.
+ *
+ * If no existing dependency is currently set this will set the dependency
+ * will be set to the new value.
+ *
+ * If there is an existing dependency and it is not an instance of
+ * AccessGroupAnd the dependency will be set as a new AccessGroupAnd
+ * instance with the existing and new dependencies as the members of the
+ * group.
+ *
+ * If there is an existing dependency and it is an instance of AccessGroupAnd
+ * the dependency will be added to the existing access group.
+ *
+ * @param \Drupal\Core\Access\AccessibleInterface $access_dependency
+ * The access dependency to merge.
+ *
+ * @return $this
+ */
+ public function addAccessDependency(AccessibleInterface $access_dependency);
+
+}
diff --git a/core/lib/Drupal/Core/Access/RefinableDependentAccessTrait.php b/core/lib/Drupal/Core/Access/RefinableDependentAccessTrait.php
new file mode 100644
index 000000000000..96b966e8d873
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/RefinableDependentAccessTrait.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\Core\Access;
+
+/**
+ * Trait for \Drupal\Core\Access\RefinableDependentAccessInterface.
+ *
+ * @internal
+ */
+trait RefinableDependentAccessTrait {
+
+ /**
+ * The access dependency.
+ *
+ * @var \Drupal\Core\Access\AccessibleInterface
+ */
+ protected $accessDependency;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAccessDependency(AccessibleInterface $access_dependency) {
+ $this->accessDependency = $access_dependency;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAccessDependency() {
+ return $this->accessDependency;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addAccessDependency(AccessibleInterface $access_dependency) {
+ if (empty($this->accessDependency)) {
+ $this->accessDependency = $access_dependency;
+ return $this;
+ }
+ if (!$this->accessDependency instanceof AccessGroupAnd) {
+ $accessGroup = new AccessGroupAnd();
+ $this->accessDependency = $accessGroup->addDependency($this->accessDependency);
+ }
+ $this->accessDependency->addDependency($access_dependency);
+ return $this;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php b/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php
index 6c02649d2705..dad8bc10a21e 100644
--- a/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php
+++ b/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php
@@ -7,7 +7,7 @@ use Drupal\Component\Assertion\Inspector;
/**
* Passes cache tag events to classes that wish to respond to them.
*/
-class CacheTagsInvalidator implements CacheTagsInvalidatorInterface {
+class CacheTagsInvalidator implements CacheTagsInvalidatorInterface, CacheTagsPurgeInterface {
/**
* Holds an array of cache tags invalidators.
@@ -54,6 +54,17 @@ class CacheTagsInvalidator implements CacheTagsInvalidatorInterface {
}
/**
+ * {@inheritdoc}
+ */
+ public function purge(): void {
+ foreach ($this->invalidators as $invalidator) {
+ if ($invalidator instanceof CacheTagsPurgeInterface) {
+ $invalidator->purge();
+ }
+ }
+ }
+
+ /**
* Adds a cache tags invalidator.
*
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $invalidator
diff --git a/core/lib/Drupal/Core/Cache/CacheTagsPurgeInterface.php b/core/lib/Drupal/Core/Cache/CacheTagsPurgeInterface.php
new file mode 100644
index 000000000000..24c110372d12
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CacheTagsPurgeInterface.php
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Provides purging of cache tag invalidations.
+ *
+ * Backends that persistently store cache tag invalidations can use this
+ * interface to implement purging of cache tag invalidations. By default, cache
+ * tag purging will only be called during drupal_flush_all_caches(), after all
+ * other caches have been cleared.
+ *
+ * @ingroup cache
+ */
+interface CacheTagsPurgeInterface {
+
+ /**
+ * Purge cache tag invalidations.
+ */
+ public function purge(): void;
+
+}
diff --git a/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php b/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php
index cb88c69495a7..9602bc8ba5d5 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php
@@ -8,7 +8,7 @@ use Drupal\Core\Database\DatabaseException;
/**
* Cache tags invalidations checksum implementation that uses the database.
*/
-class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface, CacheTagsChecksumPreloadInterface {
+class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface, CacheTagsChecksumPreloadInterface, CacheTagsPurgeInterface {
use CacheTagsChecksumTrait;
@@ -70,6 +70,22 @@ class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTags
}
/**
+ * {@inheritdoc}
+ */
+ public function purge(): void {
+ try {
+ $this->connection->truncate('cachetags')->execute();
+ }
+ catch (\Throwable $e) {
+ // If the table does not exist yet, there is nothing to purge.
+ if (!$this->ensureTableExists()) {
+ throw $e;
+ }
+ }
+ $this->reset();
+ }
+
+ /**
* Check if the cache tags table exists and create it if not.
*/
protected function ensureTableExists() {
diff --git a/core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/ConfigExistsConstraintValidator.php b/core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/ConfigExistsConstraintValidator.php
index fbc7542a7de9..9f6d1188dc88 100644
--- a/core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/ConfigExistsConstraintValidator.php
+++ b/core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/ConfigExistsConstraintValidator.php
@@ -5,6 +5,7 @@ declare(strict_types = 1);
namespace Drupal\Core\Config\Plugin\Validation\Constraint;
use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\Schema\TypeResolver;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
@@ -50,6 +51,8 @@ class ConfigExistsConstraintValidator extends ConstraintValidator implements Con
return;
}
+ $constraint->prefix = TypeResolver::resolveDynamicTypeName($constraint->prefix, $this->context->getObject());
+
if (!in_array($constraint->prefix . $name, $this->configFactory->listAll($constraint->prefix), TRUE)) {
$this->context->addViolation($constraint->message, ['@name' => $constraint->prefix . $name]);
}
diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php
index 489b2f5f94d1..133dc99f182e 100644
--- a/core/lib/Drupal/Core/Database/Connection.php
+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -433,7 +433,7 @@ abstract class Connection {
public function prepareStatement(string $query, array $options, bool $allow_row_count = FALSE): StatementInterface {
assert(!isset($options['return']), 'Passing "return" option to prepareStatement() has no effect. See https://www.drupal.org/node/3185520');
if (isset($options['fetch']) && is_int($options['fetch'])) {
- @trigger_error("Passing the 'fetch' key as an integer to \$options in prepareStatement() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
+ @trigger_error("Passing the 'fetch' key as an integer to \$options in prepareStatement() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\Statement\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
}
try {
@@ -654,7 +654,7 @@ abstract class Connection {
assert(!isset($options['return']), 'Passing "return" option to query() has no effect. See https://www.drupal.org/node/3185520');
assert(!isset($options['target']), 'Passing "target" option to query() has no effect. See https://www.drupal.org/node/2993033');
if (isset($options['fetch']) && is_int($options['fetch'])) {
- @trigger_error("Passing the 'fetch' key as an integer to \$options in query() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
+ @trigger_error("Passing the 'fetch' key as an integer to \$options in query() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\Statement\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
}
// Use default values if not already set.
diff --git a/core/lib/Drupal/Core/Database/Statement/PdoTrait.php b/core/lib/Drupal/Core/Database/Statement/PdoTrait.php
index 3e1f104c9f4f..f477b466a639 100644
--- a/core/lib/Drupal/Core/Database/Statement/PdoTrait.php
+++ b/core/lib/Drupal/Core/Database/Statement/PdoTrait.php
@@ -12,7 +12,7 @@ trait PdoTrait {
/**
* Converts a FetchAs mode to a \PDO::FETCH_* constant value.
*
- * @param \Drupal\Core\Database\FetchAs $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs $mode
* The FetchAs mode.
*
* @return int
@@ -34,7 +34,7 @@ trait PdoTrait {
* @param int $mode
* The \PDO::FETCH_* constant value.
*
- * @return \Drupal\Core\Database\FetchAs
+ * @return \Drupal\Core\Database\Statement\FetchAs
* A FetchAs mode.
*/
protected function pdoToFetchAs(int $mode): FetchAs {
@@ -70,7 +70,7 @@ trait PdoTrait {
/**
* Sets the default fetch mode for the PDO statement.
*
- * @param \Drupal\Core\Database\FetchAs $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs $mode
* One of the cases of the FetchAs enum.
* @param int|class-string|null $columnOrClass
* If $mode is FetchAs::Column, the index of the column to fetch.
@@ -118,7 +118,7 @@ trait PdoTrait {
/**
* Fetches the next row from the PDO statement.
*
- * @param \Drupal\Core\Database\FetchAs|null $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs|null $mode
* (Optional) one of the cases of the FetchAs enum. If not specified,
* defaults to what is specified by setFetchMode().
* @param int|null $cursorOrientation
@@ -175,7 +175,7 @@ trait PdoTrait {
/**
* Returns an array containing all of the result set rows.
*
- * @param \Drupal\Core\Database\FetchAs|null $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs|null $mode
* (Optional) one of the cases of the FetchAs enum. If not specified,
* defaults to what is specified by setFetchMode().
* @param int|class-string|null $columnOrClass
diff --git a/core/lib/Drupal/Core/Database/Statement/ResultBase.php b/core/lib/Drupal/Core/Database/Statement/ResultBase.php
index 6232581f906f..af1b12a56534 100644
--- a/core/lib/Drupal/Core/Database/Statement/ResultBase.php
+++ b/core/lib/Drupal/Core/Database/Statement/ResultBase.php
@@ -42,7 +42,7 @@ abstract class ResultBase {
/**
* Sets the default fetch mode for this result set.
*
- * @param \Drupal\Core\Database\FetchAs $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs $mode
* One of the cases of the FetchAs enum.
* @param array{class: class-string, constructor_args: list<mixed>, column: int, cursor_orientation?: int, cursor_offset?: int} $fetchOptions
* An array of fetch options.
@@ -55,7 +55,7 @@ abstract class ResultBase {
/**
* Fetches the next row.
*
- * @param \Drupal\Core\Database\FetchAs $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs $mode
* One of the cases of the FetchAs enum.
* @param array{class?: class-string, constructor_args?: list<mixed>, column?: int, cursor_orientation?: int, cursor_offset?: int} $fetchOptions
* An array of fetch options.
@@ -68,7 +68,7 @@ abstract class ResultBase {
/**
* Returns an array containing all of the result set rows.
*
- * @param \Drupal\Core\Database\FetchAs $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs $mode
* One of the cases of the FetchAs enum.
* @param array{class?: class-string, constructor_args?: list<mixed>, column?: int, cursor_orientation?: int, cursor_offset?: int} $fetchOptions
* An array of fetch options.
@@ -120,7 +120,7 @@ abstract class ResultBase {
*
* @param string $column
* The name of the field on which to index the array.
- * @param \Drupal\Core\Database\FetchAs $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs $mode
* One of the cases of the FetchAs enum. If set to FetchAs::Associative
* or FetchAs::List the returned value with be an array of arrays. For any
* other value it will be an array of objects. If not specified, defaults to
diff --git a/core/lib/Drupal/Core/Database/Statement/StatementBase.php b/core/lib/Drupal/Core/Database/Statement/StatementBase.php
index c193c5d35020..98fa378d58f4 100644
--- a/core/lib/Drupal/Core/Database/Statement/StatementBase.php
+++ b/core/lib/Drupal/Core/Database/Statement/StatementBase.php
@@ -180,7 +180,7 @@ abstract class StatementBase implements \Iterator, StatementInterface {
*/
public function setFetchMode($mode, $a1 = NULL, $a2 = []) {
if (is_int($mode)) {
- @trigger_error("Passing the \$mode argument as an integer to setFetchMode() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
+ @trigger_error("Passing the \$mode argument as an integer to setFetchMode() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\Statement\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
$mode = $this->pdoToFetchAs($mode);
}
assert($mode instanceof FetchAs);
@@ -217,7 +217,7 @@ abstract class StatementBase implements \Iterator, StatementInterface {
*/
public function fetch($mode = NULL, $cursorOrientation = NULL, $cursorOffset = NULL) {
if (is_int($mode)) {
- @trigger_error("Passing the \$mode argument as an integer to fetch() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
+ @trigger_error("Passing the \$mode argument as an integer to fetch() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\Statement\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
$mode = $this->pdoToFetchAs($mode);
}
assert($mode === NULL || $mode instanceof FetchAs);
@@ -292,7 +292,7 @@ abstract class StatementBase implements \Iterator, StatementInterface {
*/
public function fetchAll($mode = NULL, $columnIndex = NULL, $constructorArguments = NULL) {
if (is_int($mode)) {
- @trigger_error("Passing the \$mode argument as an integer to fetchAll() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
+ @trigger_error("Passing the \$mode argument as an integer to fetchAll() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\Statement\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
$mode = $this->pdoToFetchAs($mode);
}
@@ -325,7 +325,7 @@ abstract class StatementBase implements \Iterator, StatementInterface {
*/
public function fetchAllAssoc($key, $fetch = NULL) {
if (is_int($fetch)) {
- @trigger_error("Passing the \$fetch argument as an integer to fetchAllAssoc() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
+ @trigger_error("Passing the \$fetch argument as an integer to fetchAllAssoc() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\Statement\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
$fetch = $this->pdoToFetchAs($fetch);
}
assert($fetch === NULL || $fetch instanceof FetchAs);
diff --git a/core/lib/Drupal/Core/Database/StatementInterface.php b/core/lib/Drupal/Core/Database/StatementInterface.php
index c4cafb9d2892..7f906620674d 100644
--- a/core/lib/Drupal/Core/Database/StatementInterface.php
+++ b/core/lib/Drupal/Core/Database/StatementInterface.php
@@ -65,7 +65,7 @@ interface StatementInterface extends \Traversable {
/**
* Sets the default fetch mode for this statement.
*
- * @param \Drupal\Core\Database\FetchAs|int $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs|int $mode
* One of the cases of the FetchAs enum, or (deprecated) a \PDO::FETCH_*
* constant.
* @param string|int|null $a1
@@ -87,7 +87,7 @@ interface StatementInterface extends \Traversable {
/**
* Fetches the next row from a result set.
*
- * @param \Drupal\Core\Database\FetchAs|int|null $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs|int|null $mode
* (Optional) one of the cases of the FetchAs enum, or (deprecated) a
* \PDO::FETCH_* constant. If not specified, defaults to what is specified
* by setFetchMode().
@@ -147,7 +147,7 @@ interface StatementInterface extends \Traversable {
/**
* Returns an array containing all of the result set rows.
*
- * @param \Drupal\Core\Database\FetchAs|int|null $mode
+ * @param \Drupal\Core\Database\Statement\FetchAs|int|null $mode
* (Optional) one of the cases of the FetchAs enum, or (deprecated) a
* \PDO::FETCH_* constant. If not specified, defaults to what is specified
* by setFetchMode().
@@ -206,7 +206,7 @@ interface StatementInterface extends \Traversable {
*
* @param string $key
* The name of the field on which to index the array.
- * @param \Drupal\Core\Database\FetchAs|int|string|null $fetch
+ * @param \Drupal\Core\Database\Statement\FetchAs|int|string|null $fetch
* (Optional) the fetch mode to use. One of the cases of the FetchAs enum,
* or (deprecated) a \PDO::FETCH_* constant. If set to FetchAs::Associative
* or FetchAs::List the returned value with be an array of arrays. For any
diff --git a/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php b/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php
index 96bc07e7f89e..8a2a73f1bf7c 100644
--- a/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php
+++ b/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php
@@ -101,7 +101,7 @@ class StatementPrefetchIterator extends StatementBase {
*/
public function execute($args = [], $options = []) {
if (isset($options['fetch']) && is_int($options['fetch'])) {
- @trigger_error("Passing the 'fetch' key as an integer to \$options in execute() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
+ @trigger_error("Passing the 'fetch' key as an integer to \$options in execute() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\Statement\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
}
$startEvent = $this->dispatchStatementExecutionStartEvent($args ?? []);
diff --git a/core/lib/Drupal/Core/Database/StatementWrapperIterator.php b/core/lib/Drupal/Core/Database/StatementWrapperIterator.php
index 88dc007f5403..f580d645cad4 100644
--- a/core/lib/Drupal/Core/Database/StatementWrapperIterator.php
+++ b/core/lib/Drupal/Core/Database/StatementWrapperIterator.php
@@ -52,7 +52,7 @@ class StatementWrapperIterator extends StatementBase {
*/
public function execute($args = [], $options = []) {
if (isset($options['fetch']) && is_int($options['fetch'])) {
- @trigger_error("Passing the 'fetch' key as an integer to \$options in execute() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
+ @trigger_error("Passing the 'fetch' key as an integer to \$options in execute() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\Statement\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
}
if (isset($options['fetch'])) {
diff --git a/core/lib/Drupal/Core/Database/Transaction.php b/core/lib/Drupal/Core/Database/Transaction.php
index dcecc44e17c6..b8693e4bb761 100644
--- a/core/lib/Drupal/Core/Database/Transaction.php
+++ b/core/lib/Drupal/Core/Database/Transaction.php
@@ -30,12 +30,12 @@ class Transaction {
/**
* Destructs the object.
*
- * Depending on the nesting level of the object, this leads to a COMMIT (for
- * a root item) or to a RELEASE SAVEPOINT (for a savepoint item) executed on
- * the database.
+ * If the transaction is still active at this stage, and depending on the
+ * state of the transaction stack, this leads to a COMMIT (for a root item)
+ * or to a RELEASE SAVEPOINT (for a savepoint item) executed on the database.
*/
public function __destruct() {
- $this->connection->transactionManager()->unpile($this->name, $this->id);
+ $this->connection->transactionManager()->purge($this->name, $this->id);
}
/**
@@ -46,16 +46,22 @@ class Transaction {
}
/**
- * Rolls back the current transaction.
+ * Returns the transaction to the parent nesting level.
*
- * This is just a wrapper method to rollback whatever transaction stack we are
- * currently in, which is managed by the TransactionManager. Note that logging
- * needs to happen after a transaction has been rolled back or the log
- * messages will be rolled back too.
+ * Depending on the state of the transaction stack, this leads to a COMMIT
+ * operation (for a root item), or to a RELEASE SAVEPOINT operation (for a
+ * savepoint item) executed on the database.
+ */
+ public function commitOrRelease(): void {
+ $this->connection->transactionManager()->unpile($this->name, $this->id);
+ }
+
+ /**
+ * Rolls back the transaction.
*
- * Depending on the nesting level of the object, this leads to a ROLLBACK (for
- * a root item) or to a ROLLBACK TO SAVEPOINT (for a savepoint item) executed
- * on the database.
+ * Depending on the state of the transaction stack, this leads to a ROLLBACK
+ * operation (for a root item), or to a ROLLBACK TO SAVEPOINT + a RELEASE
+ * SAVEPOINT operations (for a savepoint item) executed on the database.
*/
public function rollBack() {
$this->connection->transactionManager()->rollback($this->name, $this->id);
diff --git a/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php b/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php
index aa663d942265..fa1a309a7678 100644
--- a/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php
+++ b/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php
@@ -102,6 +102,16 @@ abstract class TransactionManagerBase implements TransactionManagerInterface {
private ClientConnectionTransactionState $connectionTransactionState;
/**
+ * Whether to trigger warnings when unpiling a void transaction.
+ *
+ * Normally FALSE, is set to TRUE by specific tests checking the internal
+ * state of the transaction stack.
+ *
+ * @internal
+ */
+ public bool $triggerWarningWhenUnpilingOnVoidTransaction = FALSE;
+
+ /**
* Constructor.
*
* @param \Drupal\Core\Database\Connection $connection
@@ -202,7 +212,9 @@ abstract class TransactionManagerBase implements TransactionManagerInterface {
protected function voidStackItem(string $id): void {
// The item should be removed from $stack and added to $voidedItems for
// later processing.
- $this->voidedItems[$id] = $this->stack[$id];
+ if (isset($this->stack[$id])) {
+ $this->voidedItems[$id] = $this->stack[$id];
+ }
$this->removeStackItem($id);
}
@@ -285,14 +297,29 @@ abstract class TransactionManagerBase implements TransactionManagerInterface {
}
/**
- * {@inheritdoc}
+ * Purges a Drupal transaction from the manager.
+ *
+ * This is only called by a Transaction object's ::__destruct() method and
+ * should only be called internally by a database driver.
+ *
+ * @param string $name
+ * The name of the transaction.
+ * @param string $id
+ * The id of the transaction.
+ *
+ * @throws \Drupal\Core\Database\TransactionOutOfOrderException
+ * If a Drupal Transaction with the specified name does not exist.
+ * @throws \Drupal\Core\Database\TransactionCommitFailedException
+ * If the commit of the root transaction failed.
+ *
+ * @internal
*/
- public function unpile(string $name, string $id): void {
+ public function purge(string $name, string $id): void {
// If this is a 'root' transaction, and it is voided (that is, no longer in
// the stack), then the transaction on the database is no longer active. An
- // action such as a rollback, or a DDL statement, was executed that
- // terminated the database transaction. So, we can process the post
- // transaction callbacks.
+ // action such as a commit, a release savepoint, a rollback, or a DDL
+ // statement, was executed that terminated the database transaction. So, we
+ // can process the post transaction callbacks.
if (!isset($this->stack()[$id]) && isset($this->voidedItems[$id]) && $this->rootId === $id) {
$this->processPostTransactionCallbacks();
$this->rootId = NULL;
@@ -309,6 +336,62 @@ abstract class TransactionManagerBase implements TransactionManagerInterface {
return;
}
+ // When we get here, the transaction (or savepoint) is still active on the
+ // database. We can unpile it, and if we are left with no more items in the
+ // stack, we can also process the post transaction callbacks.
+ $this->commit($name, $id);
+ $this->removeStackItem($id);
+ if ($this->rootId === $id) {
+ $this->processPostTransactionCallbacks();
+ $this->rootId = NULL;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unpile(string $name, string $id): void {
+ // If the transaction was voided, we cannot unpile. Skip but trigger a user
+ // warning if requested.
+ if ($this->getConnectionTransactionState() === ClientConnectionTransactionState::Voided) {
+ if ($this->triggerWarningWhenUnpilingOnVoidTransaction) {
+ trigger_error('Transaction::commitOrRelease() was not processed because a prior execution of a DDL statement already committed the transaction.', E_USER_WARNING);
+ }
+ return;
+ }
+
+ // If there is no $id to commit, or if $id does not correspond to the one
+ // in the stack for that $name, the commit is out of order.
+ if (!isset($this->stack()[$id]) || $this->stack()[$id]->name !== $name) {
+ throw new TransactionOutOfOrderException("Error attempting commit of {$id}\\{$name}. Active stack: " . $this->dumpStackItemsAsString());
+ }
+
+ // Commit the transaction.
+ $this->commit($name, $id);
+
+ // Void the transaction stack item.
+ $this->voidStackItem($id);
+ }
+
+ /**
+ * Commits a Drupal transaction.
+ *
+ * @param string $name
+ * The name of the transaction.
+ * @param string $id
+ * The id of the transaction.
+ *
+ * @throws \Drupal\Core\Database\TransactionOutOfOrderException
+ * If a Drupal Transaction with the specified name does not exist.
+ * @throws \Drupal\Core\Database\TransactionCommitFailedException
+ * If the commit of the root transaction failed.
+ */
+ protected function commit(string $name, string $id): void {
+ if ($this->getConnectionTransactionState() !== ClientConnectionTransactionState::Active) {
+ // The stack got corrupted.
+ throw new TransactionOutOfOrderException("Transaction {$id}\\{$name} is out of order. Active stack: " . $this->dumpStackItemsAsString());
+ }
+
// If we are not releasing the last savepoint but an earlier one, or
// committing a root transaction while savepoints are active, all
// subsequent savepoints will be released as well. The stack must be
@@ -317,33 +400,20 @@ abstract class TransactionManagerBase implements TransactionManagerInterface {
$this->voidStackItem((string) $i);
}
- if ($this->getConnectionTransactionState() === ClientConnectionTransactionState::Active) {
- if ($this->stackDepth() > 1 && $this->stack()[$id]->type === StackItemType::Savepoint) {
- // Release the client transaction savepoint in case the Drupal
- // transaction is not a root one.
- $this->releaseClientSavepoint($name);
- }
- elseif ($this->stackDepth() === 1 && $this->stack()[$id]->type === StackItemType::Root) {
- // If this was the root Drupal transaction, we can commit the client
- // transaction.
- $this->processRootCommit();
- if ($this->rootId === $id) {
- $this->processPostTransactionCallbacks();
- $this->rootId = NULL;
- }
- }
- else {
- // The stack got corrupted.
- throw new TransactionOutOfOrderException("Transaction {$id}/{$name} is out of order. Active stack: " . $this->dumpStackItemsAsString());
- }
-
- // Remove the transaction from the stack.
- $this->removeStackItem($id);
- return;
+ if ($this->stackDepth() > 1 && $this->stack()[$id]->type === StackItemType::Savepoint) {
+ // Release the client transaction savepoint in case the Drupal
+ // transaction is not a root one.
+ $this->releaseClientSavepoint($name);
+ }
+ elseif ($this->stackDepth() === 1 && $this->stack()[$id]->type === StackItemType::Root) {
+ // If this was the root Drupal transaction, we can commit the client
+ // transaction.
+ $this->processRootCommit();
+ }
+ else {
+ // The stack got corrupted.
+ throw new TransactionOutOfOrderException("Transaction {$id}/{$name} is out of order. Active stack: " . $this->dumpStackItemsAsString());
}
-
- // The stack got corrupted.
- throw new TransactionOutOfOrderException("Transaction {$id}/{$name} is out of order. Active stack: " . $this->dumpStackItemsAsString());
}
/**
diff --git a/core/lib/Drupal/Core/Database/Transaction/TransactionManagerInterface.php b/core/lib/Drupal/Core/Database/Transaction/TransactionManagerInterface.php
index 11af511f14bc..a9aa2c770525 100644
--- a/core/lib/Drupal/Core/Database/Transaction/TransactionManagerInterface.php
+++ b/core/lib/Drupal/Core/Database/Transaction/TransactionManagerInterface.php
@@ -53,8 +53,8 @@ interface TransactionManagerInterface {
* Removes a Drupal transaction from the stack.
*
* The unpiled item does not necessarily need to be the last on the stack.
- * This method should only be called by a Transaction object going out of
- * scope.
+ * This method should only be called by a Transaction object's
+ * ::commitOrRelease() method.
*
* This method should only be called internally by a database driver.
*
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/BackendCompilerPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/BackendCompilerPass.php
index 1cdeb7263cee..127318c1cd0c 100644
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/BackendCompilerPass.php
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/BackendCompilerPass.php
@@ -37,7 +37,6 @@ class BackendCompilerPass implements CompilerPassInterface {
* {@inheritdoc}
*/
public function process(ContainerBuilder $container): void {
- $driver_backend = NULL;
if ($container->hasParameter('default_backend')) {
$default_backend = $container->getParameter('default_backend');
// Opt out from the default backend.
@@ -64,10 +63,10 @@ class BackendCompilerPass implements CompilerPassInterface {
if ($container->hasAlias($id)) {
continue;
}
- if ($container->hasDefinition("$driver_backend.$id") || $container->hasAlias("$driver_backend.$id")) {
+ if (isset($driver_backend) && ($container->hasDefinition("$driver_backend.$id") || $container->hasAlias("$driver_backend.$id"))) {
$container->setAlias($id, new Alias("$driver_backend.$id"));
}
- elseif ($container->hasDefinition("$default_backend.$id") || $container->hasAlias("$default_backend.$id")) {
+ elseif (!empty($default_backend) && ($container->hasDefinition("$default_backend.$id") || $container->hasAlias("$default_backend.$id"))) {
$container->setAlias($id, new Alias("$default_backend.$id"));
}
}
diff --git a/core/lib/Drupal/Core/Extension/InstallRequirementsInterface.php b/core/lib/Drupal/Core/Extension/InstallRequirementsInterface.php
index f2e8d7137c82..c40c9b830faa 100644
--- a/core/lib/Drupal/Core/Extension/InstallRequirementsInterface.php
+++ b/core/lib/Drupal/Core/Extension/InstallRequirementsInterface.php
@@ -20,8 +20,8 @@ interface InstallRequirementsInterface {
* hand. As a consequence, install-time requirements must be checked without
* access to the full Drupal API, because it is not available during
* install.php.
- * If a requirement has a severity of REQUIREMENT_ERROR, install.php will
- * abort or at least the module will not install.
+ * If a requirement has a severity of RequirementSeverity::Error, install.php
+ * will abort or at least the module will not install.
* Other severity levels have no effect on the installation.
* Module dependencies do not belong to these installation requirements,
* but should be defined in the module's .info.yml file.
@@ -37,12 +37,9 @@ interface InstallRequirementsInterface {
* - value: This should only be used for version numbers, do not set it if
* not applicable.
* - description: The description of the requirement/status.
- * - severity: (optional) The requirement's result/severity level, one of:
- * - REQUIREMENT_INFO: For info only.
- * - REQUIREMENT_OK: The requirement is satisfied.
- * - REQUIREMENT_WARNING: The requirement failed with a warning.
- * - REQUIREMENT_ERROR: The requirement failed with an error.
- * Defaults to REQUIREMENT_OK when installing.
+ * - severity: (optional) An instance of
+ * \Drupal\Core\Extension\Requirement\RequirementSeverity enum. Defaults
+ * to RequirementSeverity::OK when installing.
*/
public static function getRequirements(): array;
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 53cf3c95aa5f..ad361d3fe669 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -333,6 +333,9 @@ class ModuleHandler implements ModuleHandlerInterface {
*/
public function resetImplementations() {
$this->alterEventListeners = [];
+ $this->invokeMap = [];
+ $this->listenersByHook = [];
+ $this->modulesByHook = [];
}
/**
@@ -730,6 +733,7 @@ class ModuleHandler implements ModuleHandlerInterface {
*/
protected function getFlatHookListeners(string $hook): array {
if (!isset($this->listenersByHook[$hook])) {
+ $this->listenersByHook[$hook] = [];
foreach ($this->eventDispatcher->getListeners("drupal_hook.$hook") as $listener) {
if (is_array($listener) && is_object($listener[0])) {
$module = $this->hookImplementationsMap[$hook][get_class($listener[0])][$listener[1]];
@@ -755,7 +759,7 @@ class ModuleHandler implements ModuleHandlerInterface {
}
}
- return $this->listenersByHook[$hook] ?? [];
+ return $this->listenersByHook[$hook];
}
}
diff --git a/core/lib/Drupal/Core/Extension/Requirement/RequirementSeverity.php b/core/lib/Drupal/Core/Extension/Requirement/RequirementSeverity.php
new file mode 100644
index 000000000000..ec085c0cb5b4
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/Requirement/RequirementSeverity.php
@@ -0,0 +1,120 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Extension\Requirement;
+
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+
+/**
+ * The requirements severity enum.
+ */
+enum RequirementSeverity: int {
+
+ /*
+ * Informational message only.
+ */
+ case Info = -1;
+
+ /*
+ * Requirement successfully met.
+ */
+ case OK = 0;
+
+ /*
+ * Warning condition; proceed but flag warning.
+ */
+ case Warning = 1;
+
+ /*
+ * Error condition; abort installation.
+ */
+ case Error = 2;
+
+ /**
+ * Returns the translated title of the severity.
+ */
+ public function title(): TranslatableMarkup {
+ return match ($this) {
+ self::Info => new TranslatableMarkup('Checked'),
+ self::OK => new TranslatableMarkup('OK'),
+ self::Warning => new TranslatableMarkup('Warnings found'),
+ self::Error => new TranslatableMarkup('Errors found'),
+ };
+ }
+
+ /**
+ * Returns the status of the severity.
+ *
+ * This string representation can be used as an array key when grouping
+ * requirements checks by severity, or in other places where the int-backed
+ * value is not appropriate.
+ */
+ public function status(): string {
+ return match ($this) {
+ self::Info => 'checked',
+ self::OK => 'ok',
+ self::Warning => 'warning',
+ self::Error => 'error',
+ };
+
+ }
+
+ /**
+ * Determines the most severe requirement in a list of requirements.
+ *
+ * @param array<string, array{'title': \Drupal\Core\StringTranslation\TranslatableMarkup, 'value': mixed, description: \Drupal\Core\StringTranslation\TranslatableMarkup, 'severity': \Drupal\Core\Extension\Requirement\RequirementSeverity}> $requirements
+ * An array of requirements, in the same format as is returned by
+ * hook_requirements(), hook_runtime_requirements(),
+ * hook_update_requirements(), and
+ * \Drupal\Core\Extension\InstallRequirementsInterface.
+ *
+ * @return \Drupal\Core\Extension\Requirement\RequirementSeverity
+ * The most severe requirement.
+ *
+ * @see \Drupal\Core\Extension\InstallRequirementsInterface::getRequirements()
+ * @see \hook_requirements()
+ * @see \hook_runtime_requirements()
+ * @see \hook_update_requirements()
+ */
+ public static function maxSeverityFromRequirements(array $requirements): RequirementSeverity {
+ RequirementSeverity::convertLegacyIntSeveritiesToEnums($requirements, __METHOD__);
+ return array_reduce(
+ $requirements,
+ function (RequirementSeverity $severity, $requirement) {
+ $requirementSeverity = $requirement['severity'] ?? RequirementSeverity::OK;
+ return RequirementSeverity::from(max($severity->value, $requirementSeverity->value));
+ },
+ RequirementSeverity::OK
+ );
+ }
+
+ /**
+ * Converts legacy int value severities to enums.
+ *
+ * @param array<string, array{'title': \Drupal\Core\StringTranslation\TranslatableMarkup, 'value': mixed, description: \Drupal\Core\StringTranslation\TranslatableMarkup, 'severity': \Drupal\Core\Extension\Requirement\RequirementSeverity}> $requirements
+ * An array of requirements, in the same format as is returned by
+ * hook_requirements(), hook_runtime_requirements(),
+ * hook_update_requirements(), and
+ * \Drupal\Core\Extension\InstallRequirementsInterface.
+ * @param string $deprecationMethod
+ * The method name to pass to the deprecation message.
+ *
+ * @see \Drupal\Core\Extension\InstallRequirementsInterface::getRequirements()
+ * @see \hook_requirements()
+ * @see \hook_runtime_requirements()
+ * @see \hook_update_requirements()
+ */
+ public static function convertLegacyIntSeveritiesToEnums(array &$requirements, string $deprecationMethod): void {
+ foreach ($requirements as &$requirement) {
+ if (isset($requirement['severity'])) {
+ $severity = $requirement['severity'];
+ if (!$severity instanceof RequirementSeverity) {
+ @trigger_error("Calling {$deprecationMethod}() with an array of \$requirements with 'severity' with values not of type " . RequirementSeverity::class . " enums is deprecated in drupal:11.2.0 and is required in drupal:12.0.0. See https://www.drupal.org/node/3410939", \E_USER_DEPRECATED);
+ $requirement['severity'] = RequirementSeverity::from($requirement['severity']);
+ }
+ }
+ }
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ThemeInstaller.php b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
index 364d672c12fc..172193ca8554 100644
--- a/core/lib/Drupal/Core/Extension/ThemeInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
@@ -2,6 +2,7 @@
namespace Drupal\Core\Extension;
+use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
@@ -22,100 +23,25 @@ class ThemeInstaller implements ThemeInstallerInterface {
use ModuleDependencyMessageTrait;
use StringTranslationTrait;
- /**
- * @var \Drupal\Core\Extension\ThemeHandlerInterface
- */
- protected $themeHandler;
-
- /**
- * @var \Drupal\Core\Config\ConfigFactoryInterface
- */
- protected $configFactory;
-
- /**
- * @var \Drupal\Core\Config\ConfigInstallerInterface
- */
- protected $configInstaller;
-
- /**
- * @var \Drupal\Core\Extension\ModuleHandlerInterface
- */
- protected $moduleHandler;
-
- /**
- * @var \Drupal\Core\State\StateInterface
- */
- protected $state;
-
- /**
- * @var \Drupal\Core\Config\ConfigManagerInterface
- */
- protected $configManager;
-
- /**
- * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
- */
- protected $cssCollectionOptimizer;
-
- /**
- * @var \Drupal\Core\Routing\RouteBuilderInterface
- */
- protected $routeBuilder;
-
- /**
- * @var \Psr\Log\LoggerInterface
- */
- protected $logger;
-
- /**
- * The module extension list.
- *
- * @var \Drupal\Core\Extension\ModuleExtensionList
- */
- protected $moduleExtensionList;
-
- /**
- * Constructs a new ThemeInstaller.
- *
- * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
- * The theme handler.
- * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
- * The config factory to get the installed themes.
- * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer
- * (optional) The config installer to install configuration. This optional
- * to allow the theme handler to work before Drupal is installed and has a
- * database.
- * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
- * The module handler to fire themes_installed/themes_uninstalled hooks.
- * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
- * The config manager used to uninstall a theme.
- * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer
- * The CSS asset collection optimizer service.
- * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
- * (optional) The route builder service to rebuild the routes if a theme is
- * installed.
- * @param \Psr\Log\LoggerInterface $logger
- * A logger instance.
- * @param \Drupal\Core\State\StateInterface $state
- * The state store.
- * @param \Drupal\Core\Extension\ModuleExtensionList $module_extension_list
- * The module extension list.
- * @param \Drupal\Core\Theme\Registry|null $themeRegistry
- * The theme registry.
- * @param \Drupal\Core\Extension\ThemeExtensionList|null $themeExtensionList
- * The theme extension list.
- */
- public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, AssetCollectionOptimizerInterface $css_collection_optimizer, RouteBuilderInterface $route_builder, LoggerInterface $logger, StateInterface $state, ModuleExtensionList $module_extension_list, protected Registry $themeRegistry, protected ThemeExtensionList $themeExtensionList) {
- $this->themeHandler = $theme_handler;
- $this->configFactory = $config_factory;
- $this->configInstaller = $config_installer;
- $this->moduleHandler = $module_handler;
- $this->configManager = $config_manager;
- $this->cssCollectionOptimizer = $css_collection_optimizer;
- $this->routeBuilder = $route_builder;
- $this->logger = $logger;
- $this->state = $state;
- $this->moduleExtensionList = $module_extension_list;
+ public function __construct(
+ protected readonly ThemeHandlerInterface $themeHandler,
+ protected readonly ConfigFactoryInterface $configFactory,
+ protected readonly ConfigInstallerInterface $configInstaller,
+ protected readonly ModuleHandlerInterface $moduleHandler,
+ protected readonly ConfigManagerInterface $configManager,
+ protected readonly AssetCollectionOptimizerInterface $cssCollectionOptimizer,
+ protected readonly RouteBuilderInterface $routeBuilder,
+ protected readonly LoggerInterface $logger,
+ protected readonly StateInterface $state,
+ protected readonly ModuleExtensionList $moduleExtensionList,
+ protected readonly Registry $themeRegistry,
+ protected readonly ThemeExtensionList $themeExtensionList,
+ protected ?CachedDiscoveryInterface $componentPluginManager = NULL,
+ ) {
+ if ($this->componentPluginManager === NULL) {
+ @trigger_error('Calling ' . __METHOD__ . ' without the $componentPluginManager argument is deprecated in drupal:11.2.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/node/3525649', E_USER_DEPRECATED);
+ $this->componentPluginManager = \Drupal::service('plugin.manager.sdc');
+ }
}
/**
@@ -311,11 +237,9 @@ class ThemeInstaller implements ThemeInstallerInterface {
* Resets some other systems like rebuilding the route information or caches.
*/
protected function resetSystem() {
- if ($this->routeBuilder) {
- $this->routeBuilder->setRebuildNeeded();
- }
-
+ $this->routeBuilder->setRebuildNeeded();
$this->themeRegistry->reset();
+ $this->componentPluginManager->clearCachedDefinitions();
}
}
diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php
index 40cd1824106d..4d8d0a863a34 100644
--- a/core/lib/Drupal/Core/Extension/module.api.php
+++ b/core/lib/Drupal/Core/Extension/module.api.php
@@ -6,6 +6,7 @@
*/
use Drupal\Core\Database\Database;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\Core\Utility\UpdateException;
@@ -1097,8 +1098,9 @@ function hook_updater_info_alter(&$updaters) {
* Drupal itself (by install.php) with an installation profile or later by hand.
* As a consequence, install-time requirements must be checked without access
* to the full Drupal API, because it is not available during install.php.
- * If a requirement has a severity of REQUIREMENT_ERROR, install.php will abort
- * or at least the module will not install.
+ * If a requirement has a severity of
+ * \Drupal\Core\Extension\Requirement\RequirementSeverity::Error, install.php
+ * will abort or at least the module will not install.
* Other severity levels have no effect on the installation.
* Module dependencies do not belong to these installation requirements,
* but should be defined in the module's .info.yml file.
@@ -1111,8 +1113,9 @@ function hook_updater_info_alter(&$updaters) {
* tasks and security issues.
* The returned 'requirements' will be listed on the status report in the
* administration section, with indication of the severity level.
- * Moreover, any requirement with a severity of REQUIREMENT_ERROR severity will
- * result in a notice on the administration configuration page.
+ * Moreover, any requirement with a severity of
+ * \Drupal\Core\Extension\Requirement\RequirementSeverity::Error will result in
+ * a notice on the administration configuration page.
*
* @param string $phase
* The phase in which requirements are checked:
@@ -1121,7 +1124,7 @@ function hook_updater_info_alter(&$updaters) {
* - runtime: The runtime requirements are being checked and shown on the
* status report page.
*
- * @return array
+ * @return array<string, array{'title': \Drupal\Core\StringTranslation\TranslatableMarkup, 'value': mixed, description: \Drupal\Core\StringTranslation\TranslatableMarkup, 'severity': \Drupal\Core\Extension\Requirement\RequirementSeverity}>
* An associative array where the keys are arbitrary but must be unique (it
* is suggested to use the module short name as a prefix) and the values are
* themselves associative arrays with the following elements:
@@ -1130,12 +1133,9 @@ function hook_updater_info_alter(&$updaters) {
* install phase, this should only be used for version numbers, do not set
* it if not applicable.
* - description: The description of the requirement/status.
- * - severity: (optional) The requirement's result/severity level, one of:
- * - REQUIREMENT_INFO: For info only.
- * - REQUIREMENT_OK: The requirement is satisfied.
- * - REQUIREMENT_WARNING: The requirement failed with a warning.
* - REQUIREMENT_ERROR: The requirement failed with an error.
- * Defaults to REQUIREMENT_OK when installing, REQUIREMENT_INFO otherwise.
+ * - severity: The requirement's severity. Defaults to RequirementSeverity::OK
+ * when installing, or RequirementSeverity::Info otherwise.
*/
function hook_requirements($phase): array {
$requirements = [];
@@ -1145,7 +1145,7 @@ function hook_requirements($phase): array {
$requirements['drupal'] = [
'title' => t('Drupal'),
'value' => \Drupal::VERSION,
- 'severity' => REQUIREMENT_INFO,
+ 'severity' => RequirementSeverity::Info,
];
}
@@ -1156,7 +1156,7 @@ function hook_requirements($phase): array {
];
if (version_compare(phpversion(), \Drupal::MINIMUM_PHP) < 0) {
$requirements['php']['description'] = t('Your PHP installation is too old. Drupal requires at least PHP %version.', ['%version' => \Drupal::MINIMUM_PHP]);
- $requirements['php']['severity'] = REQUIREMENT_ERROR;
+ $requirements['php']['severity'] = RequirementSeverity::Error;
}
// Report cron status
@@ -1169,7 +1169,7 @@ function hook_requirements($phase): array {
else {
$requirements['cron'] = [
'description' => t('Cron has not run. It appears cron jobs have not been setup on your system. Check the help pages for <a href=":url">configuring cron jobs</a>.', [':url' => 'https://www.drupal.org/docs/administering-a-drupal-site/cron-automated-tasks/cron-automated-tasks-overview']),
- 'severity' => REQUIREMENT_ERROR,
+ 'severity' => RequirementSeverity::Error,
'value' => t('Never run'),
];
}
@@ -1199,7 +1199,7 @@ function hook_requirements_alter(array &$requirements): void {
$requirements['php']['title'] = t('PHP version');
// Decrease the 'update status' requirement severity from warning to info.
- $requirements['update status']['severity'] = REQUIREMENT_INFO;
+ $requirements['update status']['severity'] = RequirementSeverity::Info;
// Remove a requirements entry.
unset($requirements['foo']);
@@ -1216,8 +1216,9 @@ function hook_requirements_alter(array &$requirements): void {
* general status information like maintenance tasks and security issues.
* The returned requirements will be listed on the status report in the
* administration section, with an indication of the severity level.
- * Moreover, any requirement with a severity of REQUIREMENT_ERROR will result in
- * a notice on the 'Configuration' administration page (/admin/config).
+ * Moreover, any requirement with severity of RequirementSeverity::Error will
+ * result in a notice on the 'Configuration' administration page
+ * (/admin/config).
*
* @return array
* An associative array where the keys are arbitrary but must be unique (it
@@ -1226,12 +1227,9 @@ function hook_requirements_alter(array &$requirements): void {
* - title: The name of the requirement.
* - value: The current value (e.g., version, time, level, etc).
* - description: The description of the requirement/status.
- * - severity: (optional) The requirement's severity level, one of:
- * - REQUIREMENT_INFO: For info only.
- * - REQUIREMENT_OK: The requirement is satisfied.
- * - REQUIREMENT_WARNING: The requirement failed with a warning.
- * - REQUIREMENT_ERROR: The requirement failed with an error.
- * Defaults to REQUIREMENT_OK.
+ * - severity: (optional) An instance of
+ * \Drupal\Core\Extension\Requirement\RequirementSeverity enum. Defaults to
+ * RequirementSeverity::OK.
*/
function hook_runtime_requirements(): array {
$requirements = [];
@@ -1240,7 +1238,7 @@ function hook_runtime_requirements(): array {
$requirements['drupal'] = [
'title' => t('Drupal'),
'value' => \Drupal::VERSION,
- 'severity' => REQUIREMENT_INFO,
+ 'severity' => RequirementSeverity::Info,
];
// Test PHP version
@@ -1250,7 +1248,7 @@ function hook_runtime_requirements(): array {
];
if (version_compare(phpversion(), \Drupal::MINIMUM_PHP) < 0) {
$requirements['php']['description'] = t('Your PHP installation is too old. Drupal requires at least PHP %version.', ['%version' => \Drupal::MINIMUM_PHP]);
- $requirements['php']['severity'] = REQUIREMENT_ERROR;
+ $requirements['php']['severity'] = RequirementSeverity::Error;
}
// Report cron status
@@ -1263,7 +1261,7 @@ function hook_runtime_requirements(): array {
else {
$requirements['cron']['description'] = t('Cron has not run. It appears cron jobs have not been setup on your system. Check the help pages for <a href=":url">configuring cron jobs</a>.', [':url' => 'https://www.drupal.org/docs/administering-a-drupal-site/cron-automated-tasks/cron-automated-tasks-overview']);
$requirements['cron']['value'] = t('Never run');
- $requirements['cron']['severity'] = REQUIREMENT_ERROR;
+ $requirements['cron']['severity'] = RequirementSeverity::Error;
}
$requirements['cron']['description'] .= ' ' . t('You can <a href=":cron">run cron manually</a>.', [':cron' => Url::fromRoute('system.run_cron')->toString()]);
@@ -1287,7 +1285,7 @@ function hook_runtime_requirements_alter(array &$requirements): void {
$requirements['php']['title'] = t('PHP version');
// Decrease the 'update status' requirement severity from warning to info.
- $requirements['update status']['severity'] = REQUIREMENT_INFO;
+ $requirements['update status']['severity'] = RequirementSeverity::Info;
// Remove a requirements entry.
unset($requirements['foo']);
@@ -1306,13 +1304,9 @@ function hook_runtime_requirements_alter(array &$requirements): void {
* - title: The name of the requirement.
* - value: The current value (e.g., version, time, level, etc).
* - description: The description of the requirement/status.
- * - severity: (optional) The requirement's result/severity level, one of:
- * - REQUIREMENT_INFO: Has no effect during updates.
- * - REQUIREMENT_OK: Has no effect during updates.
- * - REQUIREMENT_WARNING: Displays a warning, user can choose to continue.
- * - REQUIREMENT_ERROR: Displays an error message, user cannot continue
- * until the problem is resolved.
- * Defaults to REQUIREMENT_OK.
+ * - severity: (optional) An instance of
+ * \Drupal\Core\Extension\Requirement\RequirementSeverity enum. Defaults to
+ * RequirementSeverity::OK.
*/
function hook_update_requirements() {
$requirements = [];
@@ -1324,7 +1318,7 @@ function hook_update_requirements() {
];
if (version_compare(phpversion(), \Drupal::MINIMUM_PHP) < 0) {
$requirements['php']['description'] = t('Your PHP installation is too old. Drupal requires at least PHP %version.', ['%version' => \Drupal::MINIMUM_PHP]);
- $requirements['php']['severity'] = REQUIREMENT_ERROR;
+ $requirements['php']['severity'] = RequirementSeverity::Error;
}
return $requirements;
@@ -1347,7 +1341,7 @@ function hook_update_requirements_alter(array &$requirements): void {
$requirements['php']['title'] = t('PHP version');
// Decrease the 'update status' requirement severity from warning to info.
- $requirements['update status']['severity'] = REQUIREMENT_INFO;
+ $requirements['update status']['severity'] = RequirementSeverity::Info;
// Remove a requirements entry.
unset($requirements['foo']);
diff --git a/core/lib/Drupal/Core/Hook/Attribute/FormAlter.php b/core/lib/Drupal/Core/Hook/Attribute/FormAlter.php
deleted file mode 100644
index 158010463d2a..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/FormAlter.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-use Drupal\Core\Hook\Order\OrderInterface;
-
-/**
- * Hook attribute for FormAlter.
- *
- * @see hook_form_alter().
- */
-#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
-class FormAlter extends Hook {
-
- /**
- * {@inheritdoc}
- */
- public const string PREFIX = 'form';
-
- /**
- * {@inheritdoc}
- */
- public const string SUFFIX = 'alter';
-
- /**
- * Constructs a FormAlter attribute object.
- *
- * @param string $form_id
- * (optional) The ID of the form that this implementation alters.
- * If this is left blank then `form_alter` is the hook that is registered.
- * @param string $method
- * (optional) The method name. If this attribute is on a method, this
- * parameter is not required. If this attribute is on a class and this
- * parameter is omitted, the class must have an __invoke() method, which is
- * taken as the hook implementation.
- * @param string|null $module
- * (optional) The module this implementation is for. This allows one module
- * to implement a hook on behalf of another module. Defaults to the module
- * the implementation is in.
- * @param \Drupal\Core\Hook\Order\OrderInterface|null $order
- * (optional) Set the order of the implementation.
- */
- public function __construct(
- string $form_id = '',
- public string $method = '',
- public ?string $module = NULL,
- public ?OrderInterface $order = NULL,
- ) {
- parent::__construct($form_id, $method, $module, $order);
- }
-
-}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 34dbc8ebf916..0084e651180d 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -98,27 +98,10 @@ use Drupal\Core\Hook\Order\OrderInterface;
class Hook implements HookAttributeInterface {
/**
- * The hook prefix such as `form`.
- *
- * @var string
- */
- public const string PREFIX = '';
-
- /**
- * The hook suffix such as `alter`.
- *
- * @var string
- */
- public const string SUFFIX = '';
-
- /**
* Constructs a Hook attribute object.
*
* @param string $hook
* The short hook name, without the 'hook_' prefix.
- * $hook is only optional when Hook is extended and a PREFIX or SUFFIX is
- * defined. When using the [#Hook] attribute directly $hook is required.
- * See Drupal\Core\Hook\Attribute\Preprocess.
* @param string $method
* (optional) The method name. If this attribute is on a method, this
* parameter is not required. If this attribute is on a class and this
@@ -132,15 +115,10 @@ class Hook implements HookAttributeInterface {
* (optional) Set the order of the implementation.
*/
public function __construct(
- public string $hook = '',
+ public string $hook,
public string $method = '',
public ?string $module = NULL,
public ?OrderInterface $order = NULL,
- ) {
- $this->hook = implode('_', array_filter([static::PREFIX, $hook, static::SUFFIX]));
- if ($this->hook === '') {
- throw new \LogicException('The Hook attribute or an attribute extending the Hook attribute must provide the $hook parameter, a PREFIX or a SUFFIX.');
- }
- }
+ ) {}
}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/Preprocess.php b/core/lib/Drupal/Core/Hook/Attribute/Preprocess.php
deleted file mode 100644
index 47642859a20b..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/Preprocess.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-/**
- * Attribute for defining a class method as a preprocess function.
- *
- * Pass no arguments for hook_preprocess `#[Preprocess]`.
- * For `hook_preprocess_HOOK` pass the `HOOK` without the `hook_preprocess`
- * portion `#[Preprocess('HOOK')]`.
- *
- * See \Drupal\Core\Hook\Attribute\Hook for additional information.
- */
-#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
-class Preprocess extends Hook {
- /**
- * {@inheritdoc}
- */
- public const string PREFIX = 'preprocess';
-
-}
diff --git a/core/lib/Drupal/Core/Mailer/Transport/SendmailCommandValidationTransportFactory.php b/core/lib/Drupal/Core/Mailer/Transport/SendmailCommandValidationTransportFactory.php
new file mode 100644
index 000000000000..84dfb64c7b28
--- /dev/null
+++ b/core/lib/Drupal/Core/Mailer/Transport/SendmailCommandValidationTransportFactory.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Mailer\Transport;
+
+use Drupal\Core\Site\Settings;
+use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
+use Symfony\Component\Mailer\Transport\Dsn;
+use Symfony\Component\Mailer\Transport\TransportFactoryInterface;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+
+/**
+ * Command validation decorator for sendmail transport factory.
+ */
+class SendmailCommandValidationTransportFactory implements TransportFactoryInterface {
+
+ /**
+ * Construct command validation decorator for sendmail transport factory.
+ *
+ * @param \Symfony\Component\Mailer\Transport\TransportFactoryInterface $inner
+ * The decorated sendmail transport factory.
+ */
+ public function __construct(
+ #[AutowireDecorated]
+ protected TransportFactoryInterface $inner,
+ ) {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function create(Dsn $dsn): TransportInterface {
+ $command = $dsn->getOption('command');
+ if (!empty($command)) {
+ $commands = Settings::get('mailer_sendmail_commands', []);
+ if (!in_array($command, $commands, TRUE)) {
+ throw new \RuntimeException("Unsafe sendmail command {$command}");
+ }
+ }
+
+ return $this->inner->create($dsn);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports(Dsn $dsn): bool {
+ return $this->inner->supports($dsn);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Mailer/TransportServiceFactory.php b/core/lib/Drupal/Core/Mailer/TransportServiceFactory.php
new file mode 100644
index 000000000000..8950d44e3648
--- /dev/null
+++ b/core/lib/Drupal/Core/Mailer/TransportServiceFactory.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Mailer;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
+use Symfony\Component\Mailer\Transport\Dsn;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+
+/**
+ * The default mailer transport service factory.
+ */
+class TransportServiceFactory implements TransportServiceFactoryInterface {
+
+ use TransportServiceFactoryTrait;
+
+ /**
+ * Constructs a new transport service factory.
+ *
+ * @param Iterable<TransportFactoryInterface> $factories
+ * A list of transport factories.
+ * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+ * The config factory service.
+ */
+ public function __construct(
+ #[AutowireIterator(tag: 'mailer.transport_factory')]
+ iterable $factories,
+ protected ConfigFactoryInterface $configFactory,
+ ) {
+ $this->factories = $factories;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createTransport(): TransportInterface {
+ $dsn = $this->configFactory->get('system.mail')->get('mailer_dsn');
+ $dsnObject = new Dsn(...$dsn);
+ return $this->fromDsnObject($dsnObject);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Mailer/TransportServiceFactoryInterface.php b/core/lib/Drupal/Core/Mailer/TransportServiceFactoryInterface.php
new file mode 100644
index 000000000000..8a2b5368db05
--- /dev/null
+++ b/core/lib/Drupal/Core/Mailer/TransportServiceFactoryInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Mailer;
+
+use Symfony\Component\Mailer\Transport\TransportInterface;
+
+/**
+ * An interface defining mailer transport service factory implementations.
+ *
+ * The transport service factory is responsible to create a transport instance
+ * according to the site configuration. The default service factory looks up the
+ * `mailer_dsn` key from the `system.mail` config and returns an appropriate
+ * transport implementation.
+ *
+ * Contrib and custom code may choose to replace or decorate the transport
+ * service factory in order to provide a mailer transport instance which
+ * requires more complex setup.
+ */
+interface TransportServiceFactoryInterface {
+
+ /**
+ * Creates and returns a configured mailer transport class.
+ */
+ public function createTransport(): TransportInterface;
+
+}
diff --git a/core/lib/Drupal/Core/Mailer/TransportServiceFactoryTrait.php b/core/lib/Drupal/Core/Mailer/TransportServiceFactoryTrait.php
new file mode 100644
index 000000000000..c4aa2c736a4e
--- /dev/null
+++ b/core/lib/Drupal/Core/Mailer/TransportServiceFactoryTrait.php
@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Mailer;
+
+use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
+use Symfony\Component\Mailer\Transport\Dsn;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+
+/**
+ * A trait containing helper methods for transport service construction.
+ */
+trait TransportServiceFactoryTrait {
+
+ /**
+ * A list of transport factories.
+ *
+ * @var Iterable<TransportFactoryInterface>
+ */
+ protected iterable $factories;
+
+ /**
+ * Constructs a transport instance given a DSN object.
+ *
+ * @param \Symfony\Component\Mailer\Transport\Dsn $dsn
+ * The mailer DSN object.
+ *
+ * @throws \Symfony\Component\Mailer\Exception\IncompleteDsnException
+ * @throws \Symfony\Component\Mailer\Exception\UnsupportedSchemeException
+ */
+ protected function fromDsnObject(Dsn $dsn): TransportInterface {
+ foreach ($this->factories as $factory) {
+ if ($factory->supports($dsn)) {
+ return $factory->create($dsn);
+ }
+ }
+
+ throw new UnsupportedSchemeException($dsn);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Menu/MenuActiveTrail.php b/core/lib/Drupal/Core/Menu/MenuActiveTrail.php
index 31de8a4485cf..0cbfa25c850c 100644
--- a/core/lib/Drupal/Core/Menu/MenuActiveTrail.php
+++ b/core/lib/Drupal/Core/Menu/MenuActiveTrail.php
@@ -5,6 +5,7 @@ namespace Drupal\Core\Menu;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheCollector;
use Drupal\Core\Lock\LockBackendInterface;
+use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Routing\RouteMatchInterface;
/**
@@ -40,11 +41,23 @@ class MenuActiveTrail extends CacheCollector implements MenuActiveTrailInterface
* The cache backend.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend.
+ * @param \Drupal\Core\Path\PathMatcherInterface|null $pathMatcher
+ * The path.matcher service.
*/
- public function __construct(MenuLinkManagerInterface $menu_link_manager, RouteMatchInterface $route_match, CacheBackendInterface $cache, LockBackendInterface $lock) {
+ public function __construct(
+ MenuLinkManagerInterface $menu_link_manager,
+ RouteMatchInterface $route_match,
+ CacheBackendInterface $cache,
+ LockBackendInterface $lock,
+ protected ?PathMatcherInterface $pathMatcher = NULL,
+ ) {
parent::__construct(NULL, $cache, $lock);
$this->menuLinkManager = $menu_link_manager;
$this->routeMatch = $route_match;
+ if ($this->pathMatcher === NULL) {
+ @trigger_error('Calling ' . __METHOD__ . ' without the $pathMatcher argument is deprecated in drupal:11.2.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/node/3523495', E_USER_DEPRECATED);
+ $this->pathMatcher = \Drupal::service('path.matcher');
+ }
}
/**
@@ -129,6 +142,7 @@ class MenuActiveTrail extends CacheCollector implements MenuActiveTrailInterface
// The menu links coming from the storage are already sorted by depth,
// weight and ID.
$found = NULL;
+ $links = [];
$route_name = $this->routeMatch->getRouteName();
// On a default (not custom) 403 page the route name is NULL. On a custom
@@ -139,10 +153,20 @@ class MenuActiveTrail extends CacheCollector implements MenuActiveTrailInterface
// Load links matching this route.
$links = $this->menuLinkManager->loadLinksByRoute($route_name, $route_parameters, $menu_name);
- // Select the first matching link.
- if ($links) {
- $found = reset($links);
- }
+ }
+
+ // If the request is for the site's front page, then menu links containing
+ // <front> must also be loaded since it's a special route that's an alias
+ // for the page.
+ // @todo Combine the two calls to loadLinksByRoute() in
+ // https://www.drupal.org/project/drupal/issues/3523497
+ if ($this->pathMatcher->isFrontPage()) {
+ $links = array_merge($links, $this->menuLinkManager->loadLinksByRoute('<front>', [], $menu_name));
+ }
+
+ // Select the first matching link.
+ if ($links) {
+ $found = reset($links);
}
return $found;
}
diff --git a/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php b/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php
index 8410e5af3f7d..9322c3628c61 100644
--- a/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php
+++ b/core/lib/Drupal/Core/Menu/MenuActiveTrailInterface.php
@@ -23,16 +23,16 @@ interface MenuActiveTrailInterface {
public function getActiveTrailIds($menu_name);
/**
- * Fetches a menu link which matches the route name, parameters and menu name.
+ * Fetches a menu link that matches the currently active route.
*
* @param string|null $menu_name
* (optional) The menu within which to find the active link. If omitted, all
* menus will be searched.
*
* @return \Drupal\Core\Menu\MenuLinkInterface|null
- * The menu link for the given route name, parameters and menu, or NULL if
- * there is no matching menu link or the current user cannot access the
- * current page (i.e. we have a 403 response).
+ * The menu link for the currently active route, or NULL if there is no
+ * matching menu link or the current user cannot access the current page
+ * (i.e. we have a 403 response).
*/
public function getActiveLink($menu_name = NULL);
diff --git a/core/lib/Drupal/Core/ProxyClass/Cron.php b/core/lib/Drupal/Core/ProxyClass/Cron.php
deleted file mode 100644
index 640b9d030c53..000000000000
--- a/core/lib/Drupal/Core/ProxyClass/Cron.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-// phpcs:ignoreFile
-
-/**
- * This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\Core\Cron' "core/lib/Drupal/Core".
- */
-
-namespace Drupal\Core\ProxyClass {
-
- /**
- * Provides a proxy class for \Drupal\Core\Cron.
- *
- * @see \Drupal\Component\ProxyBuilder
- */
- class Cron implements \Drupal\Core\CronInterface
- {
-
- use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
-
- /**
- * The id of the original proxied service.
- *
- * @var string
- */
- protected $drupalProxyOriginalServiceId;
-
- /**
- * The real proxied service, after it was lazy loaded.
- *
- * @var \Drupal\Core\Cron
- */
- protected $service;
-
- /**
- * The service container.
- *
- * @var \Symfony\Component\DependencyInjection\ContainerInterface
- */
- protected $container;
-
- /**
- * Constructs a ProxyClass Drupal proxy object.
- *
- * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
- * The container.
- * @param string $drupal_proxy_original_service_id
- * The service ID of the original service.
- */
- public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
- {
- $this->container = $container;
- $this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
- }
-
- /**
- * Lazy loads the real service from the container.
- *
- * @return object
- * Returns the constructed real service.
- */
- protected function lazyLoadItself()
- {
- if (!isset($this->service)) {
- $this->service = $this->container->get($this->drupalProxyOriginalServiceId);
- }
-
- return $this->service;
- }
-
- /**
- * {@inheritdoc}
- */
- public function run()
- {
- return $this->lazyLoadItself()->run();
- }
-
- }
-
-}
diff --git a/core/lib/Drupal/Core/Recipe/ConsoleInputCollector.php b/core/lib/Drupal/Core/Recipe/ConsoleInputCollector.php
index f1db3a342af1..9feb9bed8da7 100644
--- a/core/lib/Drupal/Core/Recipe/ConsoleInputCollector.php
+++ b/core/lib/Drupal/Core/Recipe/ConsoleInputCollector.php
@@ -93,11 +93,14 @@ final class ConsoleInputCollector implements InputCollectorInterface {
$method = $settings['method'];
$arguments = $settings['arguments'] ?? [];
- // Most of the input-collecting methods of StyleInterface have a `default`
- // parameter.
- $arguments += [
- 'default' => $default_value,
- ];
+ if ($method !== 'askHidden') {
+ // Most of the input-collecting methods of StyleInterface have a `default`
+ // parameter.
+ $arguments += [
+ 'default' => $default_value,
+ ];
+ }
+
// We don't support using Symfony Console's inline validation; instead,
// input definitions should define constraints.
unset($arguments['validator']);
diff --git a/core/lib/Drupal/Core/Render/Element/ComponentElement.php b/core/lib/Drupal/Core/Render/Element/ComponentElement.php
index 70e42154cbb9..62db902c0681 100644
--- a/core/lib/Drupal/Core/Render/Element/ComponentElement.php
+++ b/core/lib/Drupal/Core/Render/Element/ComponentElement.php
@@ -67,6 +67,14 @@ class ComponentElement extends RenderElementBase {
),
$props
);
+
+ // Handle children as slots.
+ $children = Element::children($element, TRUE);
+ foreach ($children as $key) {
+ $element['#slots'][$key] = $element[$key];
+ unset($element[$key]);
+ }
+
$inline_template = $this->generateComponentTemplate(
$element['#component'],
$element['#slots'],
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElementBase.php b/core/lib/Drupal/Core/Render/Element/RenderElementBase.php
index 451df8a0e369..5ff3a5a0f98e 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElementBase.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElementBase.php
@@ -38,7 +38,10 @@ use Drupal\Core\Url;
*
* Here is the list of the properties used during the rendering of all render
* elements:
- * - #access: (bool) Whether the element is accessible or not. When FALSE,
+ * - #access: (bool or AccessResultInterface)
+ * Whether the element is accessible or not.
+ * When the value is FALSE (if boolean)
+ * or the isAllowed() method returns FALSE (if AccessResultInterface),
* the element is not rendered and user-submitted values are not taken
* into consideration.
* - #access_callback: A callable or function name to call to check access.
diff --git a/core/lib/Drupal/Core/Render/Element/StatusReport.php b/core/lib/Drupal/Core/Render/Element/StatusReport.php
index 41fc7d7fda11..057e314d24e0 100644
--- a/core/lib/Drupal/Core/Render/Element/StatusReport.php
+++ b/core/lib/Drupal/Core/Render/Element/StatusReport.php
@@ -2,6 +2,7 @@
namespace Drupal\Core\Render\Element;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Render\Attribute\RenderElement;
/**
@@ -34,21 +35,21 @@ class StatusReport extends RenderElementBase {
* This function is assigned as a #pre_render callback.
*/
public static function preRenderGroupRequirements($element) {
- $severities = static::getSeverities();
$grouped_requirements = [];
+ RequirementSeverity::convertLegacyIntSeveritiesToEnums($element['#requirements'], __METHOD__);
+ /** @var array{title: \Drupal\Core\StringTranslation\TranslatableMarkup, value: mixed, description: \Drupal\Core\StringTranslation\TranslatableMarkup, severity: \Drupal\Core\Extension\Requirement\RequirementSeverity} $requirement */
foreach ($element['#requirements'] as $key => $requirement) {
- $severity = $severities[REQUIREMENT_INFO];
+ $severity = RequirementSeverity::Info;
if (isset($requirement['severity'])) {
- $requirement_severity = (int) $requirement['severity'] === REQUIREMENT_OK ? REQUIREMENT_INFO : (int) $requirement['severity'];
- $severity = $severities[$requirement_severity];
+ $severity = $requirement['severity'] === RequirementSeverity::OK ? RequirementSeverity::Info : $requirement['severity'];
}
elseif (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install') {
- $severity = $severities[REQUIREMENT_OK];
+ $severity = RequirementSeverity::OK;
}
- $grouped_requirements[$severity['status']]['title'] = $severity['title'];
- $grouped_requirements[$severity['status']]['type'] = $severity['status'];
- $grouped_requirements[$severity['status']]['items'][$key] = $requirement;
+ $grouped_requirements[$severity->status()]['title'] = $severity->title();
+ $grouped_requirements[$severity->status()]['type'] = $severity->status();
+ $grouped_requirements[$severity->status()]['items'][$key] = $requirement;
}
// Order the grouped requirements by a set order.
@@ -68,22 +69,28 @@ class StatusReport extends RenderElementBase {
* @return array
* An associative array of the requirements severities. The keys are the
* requirement constants defined in install.inc.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement.
+ *
+ * @see https://www.drupal.org/node/3410939
*/
public static function getSeverities() {
+ @trigger_error('Calling ' . __METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from in drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3410939', \E_USER_DEPRECATED);
return [
- REQUIREMENT_INFO => [
+ RequirementSeverity::Info->value => [
'title' => t('Checked', [], ['context' => 'Examined']),
'status' => 'checked',
],
- REQUIREMENT_OK => [
+ RequirementSeverity::OK->value => [
'title' => t('OK'),
'status' => 'ok',
],
- REQUIREMENT_WARNING => [
+ RequirementSeverity::Warning->value => [
'title' => t('Warnings found'),
'status' => 'warning',
],
- REQUIREMENT_ERROR => [
+ RequirementSeverity::Error->value => [
'title' => t('Errors found'),
'status' => 'error',
],
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 8fb473aa8df2..1f26381bb181 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -8,6 +8,7 @@ use Drupal\Component\Utility\Variable;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Form\FormHelper;
use Drupal\Core\Render\Element\RenderCallbackInterface;
@@ -96,6 +97,9 @@ class Renderer implements RendererInterface {
* {@inheritdoc}
*/
public function renderRoot(&$elements) {
+ if (!$elements) {
+ return '';
+ }
// Disallow calling ::renderRoot() from within another ::renderRoot() call.
if ($this->isRenderingRoot) {
$this->isRenderingRoot = FALSE;
@@ -104,21 +108,39 @@ class Renderer implements RendererInterface {
// Render in its own render context.
$this->isRenderingRoot = TRUE;
- $output = $this->executeInRenderContext(new RenderContext(), function () use (&$elements) {
- return $this->render($elements, TRUE);
- });
- $this->isRenderingRoot = FALSE;
+ try {
+ $output = $this->renderInIsolation($elements);
+ }
+ // Since #pre_render, #post_render, #lazy_builder callbacks and theme
+ // functions or templates may be used for generating a render array's
+ // content, and we might be rendering the main content for the page, it is
+ // possible that any of them throw an exception that will cause a different
+ // page to be rendered (e.g. throwing
+ // \Symfony\Component\HttpKernel\Exception\NotFoundHttpException will cause
+ // the 404 page to be rendered). That page might also use
+ // Renderer::renderRoot() but if exceptions aren't caught here, it will be
+ // impossible to call Renderer::renderRoot() again.
+ // Hence, catch all exceptions, reset the isRenderingRoot property and
+ // re-throw exceptions.
+ finally {
+ $this->isRenderingRoot = FALSE;
+ }
+
+ if ((string) $output === '') {
+ return '';
+ }
- return $output;
+ return $elements['#markup'];
}
/**
* {@inheritdoc}
*/
public function renderInIsolation(&$elements) {
- return $this->executeInRenderContext(new RenderContext(), function () use (&$elements) {
- return $this->render($elements, TRUE);
- });
+ $context = new RenderContext();
+ return Markup::create($this->executeInRenderContext($context, function () use (&$elements, $context) {
+ return $this->doRenderRoot($elements, $context);
+ }));
}
/**
@@ -188,31 +210,60 @@ class Renderer implements RendererInterface {
* {@inheritdoc}
*/
public function render(&$elements, $is_root_call = FALSE) {
- // Since #pre_render, #post_render, #lazy_builder callbacks and theme
- // functions or templates may be used for generating a render array's
- // content, and we might be rendering the main content for the page, it is
- // possible that any of them throw an exception that will cause a different
- // page to be rendered (e.g. throwing
- // \Symfony\Component\HttpKernel\Exception\NotFoundHttpException will cause
- // the 404 page to be rendered). That page might also use
- // Renderer::renderRoot() but if exceptions aren't caught here, it will be
- // impossible to call Renderer::renderRoot() again.
- // Hence, catch all exceptions, reset the isRenderingRoot property and
- // re-throw exceptions.
- try {
- return $this->doRender($elements, $is_root_call);
+ $context = $this->getCurrentRenderContext();
+ if (!isset($context)) {
+ throw new \LogicException("Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead.");
}
- catch (\Exception $e) {
- // Mark the ::rootRender() call finished due to this exception & re-throw.
- $this->isRenderingRoot = FALSE;
- throw $e;
+
+ if ($is_root_call) {
+ trigger_error(__METHOD__ . ' with $is_root_call is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . __CLASS__ . '::renderRoot() instead. See https://www.drupal.org/node/3497318.');
+ return $this->doRenderRoot($elements, $context);
+ }
+
+ return $this->doRender($elements, $context);
+ }
+
+ /**
+ * See the docs for ::render().
+ */
+ protected function doRenderRoot(array &$elements, RenderContext $context): string|MarkupInterface {
+ if (!$elements) {
+ return '';
+ }
+ // Set the bubbleable rendering metadata that has configurable defaults
+ // to ensure that the final render array definitely has these configurable
+ // defaults, even when no subtree is render cached.
+ $required_cache_contexts = $this->rendererConfig['required_cache_contexts'];
+
+ if (isset($elements['#cache']['contexts'])) {
+ $elements['#cache']['contexts'] = Cache::mergeContexts($elements['#cache']['contexts'], $required_cache_contexts);
}
+ else {
+ $elements['#cache']['contexts'] = $required_cache_contexts;
+ }
+
+ // Render the elements normally.
+ $return = $this->doRender($elements, $context);
+
+ // If there is no output, return early as placeholders can't make a
+ // difference.
+ if ((string) $return === '') {
+ return $return;
+ }
+
+ // Only when rendering the root do placeholders have to be processed. If we
+ // were to replace them while rendering cacheable nested elements, their
+ // cacheable metadata would still bubble all the way up the render tree,
+ // effectively making the use of placeholders pointless.
+ $this->replacePlaceholders($elements);
+
+ return $elements['#markup'];
}
/**
* See the docs for ::render().
*/
- protected function doRender(&$elements, $is_root_call = FALSE) {
+ protected function doRender(array &$elements, RenderContext $context): string|MarkupInterface {
if (empty($elements)) {
return '';
}
@@ -233,10 +284,6 @@ class Renderer implements RendererInterface {
$this->addCacheableDependency($elements, $elements['#access']);
if (!$elements['#access']->isAllowed()) {
// Abort, but bubble new cache metadata from the access result.
- $context = $this->getCurrentRenderContext();
- if (!isset($context)) {
- throw new \LogicException("Render context is empty, because render() was called outside of a renderRoot() or renderInIsolation() call. Use renderInIsolation()/renderRoot() or #lazy_builder/#pre_render instead.");
- }
$context->push(new BubbleableMetadata());
$context->update($elements);
$context->bubble();
@@ -259,12 +306,10 @@ class Renderer implements RendererInterface {
}
$context->push(new BubbleableMetadata());
- // Set the bubbleable rendering metadata that has configurable defaults, if:
- // - this is the root call, to ensure that the final render array definitely
- // has these configurable defaults, even when no subtree is render cached.
- // - this is a render cacheable subtree, to ensure that the cached data has
- // the configurable defaults (which may affect the ID and invalidation).
- if ($is_root_call || isset($elements['#cache']['keys'])) {
+ // Set the bubbleable rendering metadata that has configurable defaults if
+ // this is a render cacheable subtree, to ensure that the cached data has
+ // the configurable defaults (which may affect the ID and invalidation).
+ if (isset($elements['#cache']['keys'])) {
$required_cache_contexts = $this->rendererConfig['required_cache_contexts'];
if (isset($elements['#cache']['contexts'])) {
$elements['#cache']['contexts'] = Cache::mergeContexts($elements['#cache']['contexts'], $required_cache_contexts);
@@ -287,12 +332,6 @@ class Renderer implements RendererInterface {
$cached_element = $this->renderCache->get($elements);
if ($cached_element !== FALSE) {
$elements = $cached_element;
- // Only when we're in a root (non-recursive) Renderer::render() call,
- // placeholders must be processed, to prevent breaking the render cache
- // in case of nested elements with #cache set.
- if ($is_root_call) {
- $this->replacePlaceholders($elements);
- }
// Mark the element markup as safe if is it a string.
if (is_string($elements['#markup'])) {
$elements['#markup'] = Markup::create($elements['#markup']);
@@ -464,7 +503,7 @@ class Renderer implements RendererInterface {
// same process as Renderer::render() but is inlined for speed.
if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) {
foreach ($children as $key) {
- $elements['#children'] .= $this->doRender($elements[$key]);
+ $elements['#children'] .= $this->doRender($elements[$key], $context);
}
$elements['#children'] = Markup::create($elements['#children']);
}
@@ -558,23 +597,6 @@ class Renderer implements RendererInterface {
$context->update($elements);
}
- // Only when we're in a root (non-recursive) Renderer::render() call,
- // placeholders must be processed, to prevent breaking the render cache in
- // case of nested elements with #cache set.
- //
- // By running them here, we ensure that:
- // - they run when #cache is disabled,
- // - they run when #cache is enabled and there is a cache miss.
- // Only the case of a cache hit when #cache is enabled, is not handled here,
- // that is handled earlier in Renderer::render().
- if ($is_root_call) {
- $this->replacePlaceholders($elements);
- // @todo remove as part of https://www.drupal.org/node/2511330.
- if ($context->count() !== 1) {
- throw new \LogicException('A stray RendererInterface::render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.');
- }
- }
-
// Rendering is finished, all necessary info collected!
$context->bubble();
@@ -757,6 +779,9 @@ class Renderer implements RendererInterface {
* {@inheritdoc}
*/
public function addCacheableDependency(array &$elements, $dependency) {
+ if (!$dependency instanceof CacheableDependencyInterface) {
+ @trigger_error(sprintf("Calling %s() with an object that doesn't implement %s is deprecated in drupal:11.3.0 and will throw an error in drupal:13.0.0. See https://www.drupal.org/node/3525389", __METHOD__, CacheableDependencyInterface::class), E_USER_DEPRECATED);
+ }
$meta_a = CacheableMetadata::createFromRenderArray($elements);
$meta_b = CacheableMetadata::createFromObject($dependency);
$meta_a->merge($meta_b)->applyTo($elements);
diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php
index 92fa661f6b0f..020e594755fb 100644
--- a/core/lib/Drupal/Core/Render/RendererInterface.php
+++ b/core/lib/Drupal/Core/Render/RendererInterface.php
@@ -323,7 +323,7 @@ interface RendererInterface {
* (Internal use only.) Whether this is a recursive call or not. See
* ::renderRoot().
*
- * @return \Drupal\Component\Render\MarkupInterface
+ * @return \Drupal\Component\Render\MarkupInterface|string
* The rendered HTML.
*
* @throws \LogicException
diff --git a/core/lib/Drupal/Core/Session/SessionManager.php b/core/lib/Drupal/Core/Session/SessionManager.php
index 0170626181c0..6927ba2ebbec 100644
--- a/core/lib/Drupal/Core/Session/SessionManager.php
+++ b/core/lib/Drupal/Core/Session/SessionManager.php
@@ -6,6 +6,7 @@ use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
/**
@@ -162,6 +163,16 @@ class SessionManager extends NativeSessionStorage implements SessionManagerInter
parent::save();
}
+ $allowedKeys = array_map(
+ fn (SessionBagInterface $bag) => $bag->getStorageKey(),
+ $this->bags
+ );
+ $allowedKeys[] = $this->getMetadataBag()->getStorageKey();
+ $deprecatedKeys = array_diff(array_keys($_SESSION), $allowedKeys);
+ if (count($deprecatedKeys) > 0) {
+ @trigger_error(sprintf('Storing values directly in $_SESSION is deprecated in drupal:11.2.0 and will become unsupported in drupal:12.0.0. Use $request->getSession()->set() instead. Affected keys: %s. See https://www.drupal.org/node/3518527', implode(", ", $deprecatedKeys)), E_USER_DEPRECATED);
+ }
+
$this->startedLazy = FALSE;
}
diff --git a/core/lib/Drupal/Core/Template/Loader/ComponentLoader.php b/core/lib/Drupal/Core/Template/Loader/ComponentLoader.php
index d141d202ecb0..e3669f8f1450 100644
--- a/core/lib/Drupal/Core/Template/Loader/ComponentLoader.php
+++ b/core/lib/Drupal/Core/Template/Loader/ComponentLoader.php
@@ -122,13 +122,8 @@ class ComponentLoader implements LoaderInterface {
catch (ComponentNotFoundException) {
throw new LoaderError('Unable to find component');
}
- // If any of the templates, or the component definition, are fresh. Then the
- // component is fresh.
$metadata_path = $component->getPluginDefinition()[YamlDirectoryDiscovery::FILE_KEY];
- if ($file_is_fresh($metadata_path)) {
- return TRUE;
- }
- return $file_is_fresh($component->getTemplatePath());
+ return $file_is_fresh($component->getTemplatePath()) && $file_is_fresh($metadata_path);
}
}
diff --git a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php
index 6a4dcfcf8b02..00345c8b8461 100644
--- a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php
+++ b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php
@@ -599,7 +599,7 @@ trait FunctionalTestSetupTrait {
];
// If we only have one db driver available, we cannot set the driver.
- if (count($this->getDatabaseTypes()) == 1) {
+ if (count(Database::getDriverList()->getInstallableList()) == 1) {
unset($parameters['forms']['install_settings_form']['driver']);
}
return $parameters;
@@ -730,27 +730,4 @@ trait FunctionalTestSetupTrait {
$callbacks = [];
}
- /**
- * Returns all supported database driver installer objects.
- *
- * This wraps DatabaseDriverList::getInstallableList() for use without a
- * current container.
- *
- * @return \Drupal\Core\Database\Install\Tasks[]
- * An array of available database driver installer objects.
- */
- protected function getDatabaseTypes() {
- if (isset($this->originalContainer) && $this->originalContainer) {
- \Drupal::setContainer($this->originalContainer);
- }
- $database_types = [];
- foreach (Database::getDriverList()->getInstallableList() as $name => $driver) {
- $database_types[$name] = $driver->getInstallTasks();
- }
- if (isset($this->originalContainer) && $this->originalContainer) {
- \Drupal::unsetContainer();
- }
- return $database_types;
- }
-
}
diff --git a/core/lib/Drupal/Core/Test/SimpletestTestRunResultsStorage.php b/core/lib/Drupal/Core/Test/SimpletestTestRunResultsStorage.php
index cb754e1afaa4..0c000d675c3c 100644
--- a/core/lib/Drupal/Core/Test/SimpletestTestRunResultsStorage.php
+++ b/core/lib/Drupal/Core/Test/SimpletestTestRunResultsStorage.php
@@ -95,13 +95,14 @@ class SimpletestTestRunResultsStorage implements TestRunResultsStorageInterface
* {@inheritdoc}
*/
public function removeResults(TestRun $test_run): int {
- $this->connection->startTransaction('delete_test_run');
+ $transaction = $this->connection->startTransaction('delete_test_run');
$this->connection->delete('simpletest')
->condition('test_id', $test_run->id())
->execute();
$count = $this->connection->delete('simpletest_test_id')
->condition('test_id', $test_run->id())
->execute();
+ $transaction->commitOrRelease();
return $count;
}
@@ -169,9 +170,10 @@ class SimpletestTestRunResultsStorage implements TestRunResultsStorageInterface
*/
public function cleanUp(): int {
// Clear test results.
- $this->connection->startTransaction('delete_simpletest');
+ $transaction = $this->connection->startTransaction('delete_simpletest');
$this->connection->delete('simpletest')->execute();
$count = $this->connection->delete('simpletest_test_id')->execute();
+ $transaction->commitOrRelease();
return $count;
}
diff --git a/core/lib/Drupal/Core/Theme/Component/ComponentMetadata.php b/core/lib/Drupal/Core/Theme/Component/ComponentMetadata.php
index e19e759f173c..8cdb368e2e95 100644
--- a/core/lib/Drupal/Core/Theme/Component/ComponentMetadata.php
+++ b/core/lib/Drupal/Core/Theme/Component/ComponentMetadata.php
@@ -14,6 +14,11 @@ class ComponentMetadata {
use StringTranslationTrait;
/**
+ * The ID of the component, in the form of provider:machine_name.
+ */
+ public readonly string $id;
+
+ /**
* The absolute path to the component directory.
*
* @var string
@@ -115,6 +120,7 @@ class ComponentMetadata {
if (str_starts_with($path, $app_root)) {
$path = substr($path, strlen($app_root));
}
+ $this->id = $metadata_info['id'];
$this->mandatorySchemas = $enforce_schemas;
$this->path = $path;
@@ -149,7 +155,7 @@ class ComponentMetadata {
private function parseSchemaInfo(array $metadata_info): ?array {
if (empty($metadata_info['props'])) {
if ($this->mandatorySchemas) {
- throw new InvalidComponentException(sprintf('The component "%s" does not provide schema information. Schema definitions are mandatory for components declared in modules. For components declared in themes, schema definitions are only mandatory if the "enforce_prop_schemas" key is set to "true" in the theme info file.', $metadata_info['id']));
+ throw new InvalidComponentException(sprintf('The component "%s" does not provide schema information. Schema definitions are mandatory for components declared in modules. For components declared in themes, schema definitions are only mandatory if the "enforce_prop_schemas" key is set to "true" in the theme info file.', $this->id));
}
$schema = NULL;
}
@@ -167,6 +173,12 @@ class ComponentMetadata {
$schema_props = $metadata_info['props'];
foreach ($schema_props['properties'] ?? [] as $name => $prop_schema) {
$type = $prop_schema['type'] ?? '';
+ if (isset($prop_schema['enum'], $prop_schema['meta:enum'])) {
+ $enum_keys_diff = array_diff($prop_schema['enum'], array_keys($prop_schema['meta:enum']));
+ if (!empty($enum_keys_diff)) {
+ throw new InvalidComponentException(sprintf('The values for the %s prop enum in component %s must be defined in meta:enum.', $name, $this->id));
+ }
+ }
$schema['properties'][$name]['type'] = array_unique([
...(array) $type,
'object',
@@ -197,6 +209,14 @@ class ComponentMetadata {
* The normalized value object.
*/
public function normalize(): array {
+ $meta = [];
+ if (!empty($this->schema['properties'])) {
+ foreach ($this->schema['properties'] as $prop_name => $prop_definition) {
+ if (!empty($prop_definition['meta:enum'])) {
+ $meta['properties'][$prop_name] = $this->getEnumOptions($prop_name);
+ }
+ }
+ }
return [
'path' => $this->path,
'machineName' => $this->machineName,
@@ -204,7 +224,42 @@ class ComponentMetadata {
'name' => $this->name,
'group' => $this->group,
'variants' => $this->variants,
+ 'meta' => $meta,
];
}
+ /**
+ * Get translated options labels from enumeration.
+ *
+ * @param string $propertyName
+ * The enum property name.
+ *
+ * @return array<string, \Drupal\Core\StringTranslation\TranslatableMarkup>
+ * An array with enum options as keys and the (non-rendered)
+ * translated labels as values.
+ */
+ public function getEnumOptions(string $propertyName): array {
+ $options = [];
+ if (isset($this->schema['properties'][$propertyName])) {
+ $prop_definition = $this->schema['properties'][$propertyName];
+ if (!empty($prop_definition['enum'])) {
+ $translation_context = $prop_definition['x-translation-context'] ?? '';
+ // We convert ['a', 'b'], into ['a' => t('a'), 'b' => t('b')].
+ $options = array_combine(
+ $prop_definition['enum'],
+ // @phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
+ array_map(fn($value) => $this->t($value, [], ['context' => $translation_context]), $prop_definition['enum']),
+ );
+ if (!empty($prop_definition['meta:enum'])) {
+ foreach ($prop_definition['meta:enum'] as $enum_value => $enum_label) {
+ $options[$enum_value] =
+ // @phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
+ $this->t($enum_label, [], ['context' => $translation_context]);
+ }
+ }
+ }
+ }
+ return $options;
+ }
+
}
diff --git a/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php b/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php
index 246f143d4e25..ff102b5170a9 100644
--- a/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php
+++ b/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php
@@ -199,7 +199,9 @@ class ComponentValidator {
$errors = array_filter(
$this->validator->getErrors(),
function (array $error) use ($context): bool {
- if (($error['constraint'] ?? '') !== 'type') {
+ // Support 5.0 ($error['constraint']) and 6.0
+ // ($error['constraint']['name']) at the same time.
+ if (($error['constraint']['name'] ?? $error['constraint'] ?? '') !== 'type') {
return TRUE;
}
return !Element::isRenderArray($context[$error['property']] ?? NULL);
diff --git a/core/lib/Drupal/Core/Theme/ComponentPluginManager.php b/core/lib/Drupal/Core/Theme/ComponentPluginManager.php
index a0c93317699f..5a3b62773ea5 100644
--- a/core/lib/Drupal/Core/Theme/ComponentPluginManager.php
+++ b/core/lib/Drupal/Core/Theme/ComponentPluginManager.php
@@ -164,6 +164,10 @@ class ComponentPluginManager extends DefaultPluginManager implements Categorizin
public function clearCachedDefinitions(): void {
parent::clearCachedDefinitions();
$this->componentNegotiator->clearCache();
+ // When clearing cached definitions from theme install or uninstall, the
+ // container is not rebuilt. Unset discovery so it will be re-instantiated
+ // in getDiscovery() with the updated list of theme directories.
+ $this->discovery = NULL;
}
/**