diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldConstraint.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldConstraint.php
index dbb2ad7594f..23c7019bdcc 100644
--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldConstraint.php
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldConstraint.php
@@ -17,6 +17,16 @@ class UniqueFieldConstraint extends SymfonyConstraint {
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;
+
/**
* {@inheritdoc}
*/
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
index 7d387611742..14903051211 100644
--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
@@ -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)) {
diff --git a/core/modules/file/src/Plugin/Validation/Constraint/FileUriUnique.php b/core/modules/file/src/Plugin/Validation/Constraint/FileUriUnique.php
index 4ecb53b8516..fd2300a7ca1 100644
--- a/core/modules/file/src/Plugin/Validation/Constraint/FileUriUnique.php
+++ b/core/modules/file/src/Plugin/Validation/Constraint/FileUriUnique.php
@@ -4,7 +4,8 @@ namespace Drupal\file\Plugin\Validation\Constraint;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Validation\Attribute\Constraint;
-use Symfony\Component\Validator\Constraint as SymfonyConstraint;
+use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint;
+use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator;
/**
* Supports validating file URIs.
@@ -13,15 +14,25 @@ use Symfony\Component\Validator\Constraint as SymfonyConstraint;
id: 'FileUriUnique',
label: new TranslatableMarkup('File URI', [], ['context' => 'Validation'])
)]
-class FileUriUnique extends SymfonyConstraint {
+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(): string {
- return '\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator';
+ return UniqueFieldValueValidator::class;
}
}
diff --git a/core/modules/user/user.install b/core/modules/user/user.install
index 176d329511d..2e2f24def8f 100644
--- a/core/modules/user/user.install
+++ b/core/modules/user/user.install
@@ -98,6 +98,7 @@ function user_requirements($phase): array {
if ($phase !== 'runtime') {
return [];
}
+ $return = [];
$result = (bool) \Drupal::entityQuery('user')
->accessCheck(FALSE)
@@ -106,17 +107,32 @@ function user_requirements($phase): array {
->execute();
if ($result === FALSE) {
- return [
- 'anonymous user' => [
- 'title' => t('Anonymous user'),
- 'description' => t('The anonymous user does not exist. See the restore the anonymous (user ID 0) user record for more information', [
- ':url' => 'https://www.drupal.org/node/1029506',
- ]),
- 'severity' => REQUIREMENT_WARNING,
- ],
+ $return['anonymous user'] = [
+ 'title' => t('Anonymous user'),
+ 'description' => t('The anonymous user does not exist. See the restore the anonymous (user ID 0) user record for more information', [
+ ':url' => 'https://www.drupal.org/node/1029506',
+ ]),
+ 'severity' => REQUIREMENT_WARNING,
];
}
- return [];
+
+ $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 Conflicting User Emails for more information.', [
+ ':url' => 'https://www.drupal.org/node/3486109',
+ ]),
+ 'severity' => REQUIREMENT_WARNING,
+ ];
+ }
+
+ return $return;
}
/**