Merged 10.2.11.

10.2.x
Dave Long 2024-11-20 17:59:55 +00:00
commit 26d50b073f
No known key found for this signature in database
GPG Key ID: ED52AE211E142771
14 changed files with 123 additions and 16 deletions

4
composer.lock generated
View File

@ -9635,8 +9635,8 @@
},
"prefer-stable": true,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"platform": {},
"platform-dev": {},
"platform-overrides": {
"php": "8.1.0"
},

View File

@ -2,6 +2,8 @@
namespace Drupal\Core\Ajax;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Asset\AttachedAssets;
/**
@ -30,7 +32,7 @@ class MessageCommand implements CommandInterface, CommandWithAttachedAssetsInter
/**
* The message text.
*
* @var string
* @var string|\Drupal\Component\Render\MarkupInterface
*/
protected $message;
@ -58,7 +60,7 @@ class MessageCommand implements CommandInterface, CommandWithAttachedAssetsInter
/**
* Constructs a MessageCommand object.
*
* @param string $message
* @param string|\Drupal\Component\Render\MarkupInterface $message
* The text of the message.
* @param string|null $wrapper_query_selector
* The query selector of the element to display messages in when they
@ -82,7 +84,9 @@ class MessageCommand implements CommandInterface, CommandWithAttachedAssetsInter
public function render() {
return [
'command' => 'message',
'message' => $this->message,
'message' => $this->message instanceof MarkupInterface
? (string) $this->message
: Xss::filterAdmin($this->message),
'messageWrapperQuerySelector' => $this->wrapperQuerySelector,
'messageOptions' => $this->options,
'clearPrevious' => $this->clearPrevious,

View File

@ -2,6 +2,7 @@
namespace Drupal\Core\Config;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\MemoryBackend;
use Drupal\Core\Config\Entity\ConfigDependencyManager;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
@ -79,7 +80,7 @@ class StorageComparer implements StorageComparerInterface {
*
* @var \Drupal\Core\Cache\MemoryBackend
*/
protected $targetCacheStorage;
protected CacheBackendInterface $targetCacheStorage;
/**
* Constructs the Configuration storage comparer.

View File

@ -801,6 +801,15 @@ class Select extends Query implements SelectInterface {
* {@inheritdoc}
*/
public function __toString() {
if (!is_array($this->fields) ||
!is_array($this->expressions) ||
!is_array($this->tables) ||
!is_array($this->order) ||
!is_array($this->group) ||
!is_array($this->union)) {
throw new \UnexpectedValueException();
}
// For convenience, we compile the query ourselves if the caller forgot
// to do it. This allows constructs like "(string) $query" to work. When
// the query will be executed, it will be recompiled using the proper

View File

@ -164,6 +164,12 @@ class Update extends Query implements ConditionInterface {
* The prepared statement.
*/
public function __toString() {
if (!is_array($this->fields) ||
!is_array($this->arguments) ||
!is_array($this->expressionFields)) {
throw new \UnexpectedValueException();
}
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);

View File

@ -6,6 +6,7 @@ namespace Drupal\Core\Database;
use Drupal\Core\Database\Event\StatementExecutionEndEvent;
use Drupal\Core\Database\Event\StatementExecutionStartEvent;
use Drupal\Core\Site\Settings;
/**
* An implementation of StatementInterface that pre-fetches all data.
@ -344,6 +345,15 @@ class StatementPrefetch implements \Iterator, StatementInterface {
$class_name = $this->fetchOptions['class'];
}
if (count($this->fetchOptions['constructor_args'])) {
// Verify the current db connection to avoid this code being called
// in an inappropriate context.
$defaults = ['sqlite', 'oracle'];
$extras = Settings::get('database_statement_prefetch_valid_db_drivers', []);
$valid_db_drivers = array_merge($defaults, $extras);
$db_connection_options = Database::getConnection()->getConnectionOptions();
if (!in_array($db_connection_options['driver'], $valid_db_drivers)) {
throw new \BadMethodCallException();
}
$reflector = new \ReflectionClass($class_name);
$result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);
}

View File

@ -17,7 +17,7 @@ trait DependencySerializationTrait {
* @var array
*/
// phpcs:ignore Drupal.Classes.PropertyDeclaration
protected $_serviceIds = [];
protected array $_serviceIds = [];
/**
* An array of entity type IDs keyed by the property name of their storages.
@ -25,7 +25,7 @@ trait DependencySerializationTrait {
* @var array
*/
// phpcs:ignore Drupal.Classes.PropertyDeclaration
protected $_entityStorages = [];
protected array $_entityStorages = [];
/**
* {@inheritdoc}

View File

@ -73,7 +73,7 @@ class Attribute implements \ArrayAccess, \IteratorAggregate, MarkupInterface {
*
* @var \Drupal\Core\Template\AttributeValueBase[]
*/
protected $storage = [];
protected array $storage = [];
/**
* Constructs a \Drupal\Core\Template\Attribute object.

View File

@ -16,6 +16,16 @@ class UniqueFieldConstraint extends Constraint {
public $message = 'A @entity_type with @field_name %value already exists.';
/**
* This constraint is case-insensitive by default.
*
* For example "FOO" and "foo" would be considered as equivalent, and
* validation of the constraint would fail.
*
* @var bool
*/
public $caseSensitive = FALSE;
/**
* Returns the name of the class that validates this constraint.
*

View File

@ -64,12 +64,23 @@ class UniqueFieldValueValidator extends ConstraintValidator implements Container
->getStorage($entity_type_id)
->getAggregateQuery()
->accessCheck(FALSE)
->condition($field_name, $item_values, 'IN')
->groupBy("$field_name.$property_name");
if (!$is_new) {
$entity_id = $entity->id();
$query->condition($id_key, $entity_id, '<>');
}
if ($constraint->caseSensitive) {
$query->condition($field_name, $item_values, 'IN');
}
else {
$or_group = $query->orConditionGroup();
foreach ($item_values as $item_value) {
$or_group->condition($field_name, \Drupal::database()->escapeLike($item_value), 'LIKE');
}
$query->condition($or_group);
}
$results = $query->execute();
if (!empty($results)) {

View File

@ -2,7 +2,8 @@
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint;
use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator;
/**
* Supports validating file URIs.
@ -12,15 +13,25 @@ use Symfony\Component\Validator\Constraint;
* label = @Translation("File URI", context = "Validation")
* )
*/
class FileUriUnique extends Constraint {
class FileUriUnique extends UniqueFieldConstraint {
public $message = 'The file %value already exists. Enter a unique file URI.';
/**
* This constraint is case-sensitive.
*
* For example "public://foo.txt" and "public://FOO.txt" are treated as
* different values, and can co-exist.
*
* @var bool
*/
public $caseSensitive = TRUE;
/**
* {@inheritdoc}
*/
public function validatedBy() {
return '\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator';
public function validatedBy(): string {
return UniqueFieldValueValidator::class;
}
}

View File

@ -58,7 +58,7 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn
*
* @var array
*/
protected $attachedDatabases = [];
protected array $attachedDatabases = [];
/**
* Whether or not a table has been dropped this request.

View File

@ -93,6 +93,50 @@ function user_install() {
->save();
}
/**
* Implements hook_requirements().
*/
function user_requirements($phase): array {
if ($phase !== 'runtime') {
return [];
}
$return = [];
$result = (bool) \Drupal::entityQuery('user')
->accessCheck(FALSE)
->condition('uid', 0)
->range(0, 1)
->execute();
if ($result === FALSE) {
$return['anonymous user'] = [
'title' => t('Anonymous user'),
'description' => t('The anonymous user does not exist. See the <a href=":url">restore the anonymous (user ID 0) user record</a> for more information', [
':url' => 'https://www.drupal.org/node/1029506',
]),
'severity' => REQUIREMENT_WARNING,
];
}
$query = \Drupal::database()->select('users_field_data');
$query->addExpression('LOWER(mail)', 'lower_mail');
$query->groupBy('lower_mail');
$query->having('COUNT(uid) > :matches', [':matches' => 1]);
$conflicts = $query->countQuery()->execute()->fetchField();
if ($conflicts > 0) {
$return['conflicting emails'] = [
'title' => t('Conflicting user emails'),
'description' => t('Some user accounts have email addresses that differ only by case. For example, one account might have alice@example.com and another might have Alice@Example.com. See <a href=":url">Conflicting User Emails</a> for more information.', [
':url' => 'https://www.drupal.org/node/3486109',
]),
'severity' => REQUIREMENT_WARNING,
];
}
return $return;
}
/**
* Implements hook_update_last_removed().
*/

View File

@ -8,6 +8,7 @@ use Drupal\Component\Utility\Tags;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\views\display\DisplayRouterInterface;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
@ -199,7 +200,7 @@ class ViewExecutable {
*
* @var \Drupal\views\Plugin\views\query\QueryPluginBase
*/
public $query = NULL;
public ?QueryPluginBase $query = NULL;
/**
* The used pager plugin used by the current executed view.