Verified Commit 8e7d3e0f authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3431203 by kim.pepper, alexpott, smustgrave, longwave: Deprecate...

Issue #3431203 by kim.pepper, alexpott, smustgrave, longwave: Deprecate user_validate_name() and replace with service
parent 644d8f2a
Loading
Loading
Loading
Loading
Loading
+21 −5
Original line number Diff line number Diff line
@@ -8,8 +8,9 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Locale\CountryManagerInterface;
use Drupal\Core\Site\Settings;
use Drupal\user\UserStorageInterface;
use Drupal\user\UserInterface;
use Drupal\user\UserStorageInterface;
use Drupal\user\UserNameValidator;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
@@ -67,13 +68,26 @@ class SiteConfigureForm extends ConfigFormBase {
   *   The module installer.
   * @param \Drupal\Core\Locale\CountryManagerInterface $country_manager
   *   The country manager.
   * @param \Drupal\user\UserNameValidator|null $userNameValidator
   *   The user validator.
   */
  public function __construct($root, $site_path, UserStorageInterface $user_storage, ModuleInstallerInterface $module_installer, CountryManagerInterface $country_manager) {
  public function __construct(
    $root,
    $site_path,
    UserStorageInterface $user_storage,
    ModuleInstallerInterface $module_installer,
    CountryManagerInterface $country_manager,
    protected ?UserNameValidator $userNameValidator = NULL,
  ) {
    $this->root = $root;
    $this->sitePath = $site_path;
    $this->userStorage = $user_storage;
    $this->moduleInstaller = $module_installer;
    $this->countryManager = $country_manager;
    if (!$userNameValidator) {
      @\trigger_error('Calling ' . __METHOD__ . ' without the $userNameValidator argument is deprecated in drupal:10.3.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3431205', E_USER_DEPRECATED);
      $this->userNameValidator = \Drupal::service('user.name_validator');
    }
  }

  /**
@@ -85,7 +99,8 @@ public static function create(ContainerInterface $container) {
      $container->getParameter('site.path'),
      $container->get('entity_type.manager')->getStorage('user'),
      $container->get('module_installer'),
      $container->get('country_manager')
      $container->get('country_manager'),
      $container->get('user.name_validator'),
    );
  }

@@ -253,8 +268,9 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    if ($error = user_validate_name($form_state->getValue(['account', 'name']))) {
      $form_state->setErrorByName('account][name', $error);
    $violations = $this->userNameValidator->validateName($form_state->getValue(['account', 'name']));
    if ($violations->count() > 0) {
      $form_state->setErrorByName('account][name', $violations[0]->getMessage());
    }
  }

+28 −19
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@

use Drupal\Component\Utility\EmailValidatorInterface;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
use Drupal\Core\Flood\FloodInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
@@ -13,6 +13,7 @@
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\user\UserInterface;
use Drupal\user\UserStorageInterface;
use Drupal\user\UserNameValidator;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
@@ -24,6 +25,15 @@
 */
class UserPasswordForm extends FormBase {

  use DeprecatedServicePropertyTrait;

  /**
   * The deprecated properties.
   */
  protected array $deprecatedProperties = [
    'typedDataManager' => 'typed_data_manager',
  ];

  /**
   * The user storage.
   *
@@ -45,13 +55,6 @@ class UserPasswordForm extends FormBase {
   */
  protected $flood;

  /**
   * The typed data manager.
   *
   * @var \Drupal\Core\TypedData\TypedDataManagerInterface
   */
  protected $typedDataManager;

  /**
   * The email validator service.
   *
@@ -70,18 +73,28 @@ class UserPasswordForm extends FormBase {
   *   The config factory.
   * @param \Drupal\Core\Flood\FloodInterface $flood
   *   The flood service.
   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
   *   The typed data manager.
   * @param \Drupal\user\UserNameValidator|\Drupal\Core\TypedData\TypedDataManagerInterface $userNameValidator
   *   The user validator service.
   * @param \Drupal\Component\Utility\EmailValidatorInterface $email_validator
   *   The email validator service.
   */
  public function __construct(UserStorageInterface $user_storage, LanguageManagerInterface $language_manager, ConfigFactory $config_factory, FloodInterface $flood, TypedDataManagerInterface $typed_data_manager, EmailValidatorInterface $email_validator) {
  public function __construct(
    UserStorageInterface $user_storage,
    LanguageManagerInterface $language_manager,
    ConfigFactory $config_factory,
    FloodInterface $flood,
    protected UserNameValidator|TypedDataManagerInterface $userNameValidator,
    EmailValidatorInterface $email_validator,
  ) {
    $this->userStorage = $user_storage;
    $this->languageManager = $language_manager;
    $this->configFactory = $config_factory;
    $this->flood = $flood;
    $this->typedDataManager = $typed_data_manager;
    $this->emailValidator = $email_validator;
    if (!$userNameValidator instanceof UserNameValidator) {
      @\trigger_error('Passing $userNameValidator as \Drupal\Core\TypedData\TypedDataManagerInterface to ' . __METHOD__ . ' () is deprecated in drupal:10.3.0 and is removed in drupal:10.0.0. Pass a Drupal\user\UserValidator instead. See https://www.drupal.org/node/3431205', E_USER_DEPRECATED);
      $this->userNameValidator = \Drupal::service('user.name_validator');
    }
  }

  /**
@@ -93,8 +106,8 @@ public static function create(ContainerInterface $container) {
      $container->get('language_manager'),
      $container->get('config.factory'),
      $container->get('flood'),
      $container->get('typed_data_manager'),
      $container->get('email.validator')
      $container->get('user.name_validator'),
      $container->get('email.validator'),
    );
  }

@@ -161,11 +174,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    $this->flood->register('user.password_request_ip', $flood_config->get('ip_window'));
    // First, see if the input is possibly valid as a username.
    $name = trim($form_state->getValue('name'));
    $definition = BaseFieldDefinition::create('string')
      ->addConstraint('UserName', []);
    $data = $this->typedDataManager->create($definition);
    $data->setValue($name);
    $violations = $data->validate();
    $violations = $this->userNameValidator->validateName($name);
    // Usernames have a maximum length shorter than email addresses. Only print
    // this error if the input is not valid as a username or email address.
    if ($violations->count() > 0 && !$this->emailValidator->isValid($name)) {
+3 −2
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

namespace Drupal\user\Plugin\Validation\Constraint;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\user\UserInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
@@ -15,11 +16,11 @@ class UserNameConstraintValidator extends ConstraintValidator {
   * {@inheritdoc}
   */
  public function validate($items, Constraint $constraint): void {
    if (!isset($items) || !$items->value) {
    if (empty($items) || ($items instanceof FieldItemListInterface && $items->isEmpty())) {
      $this->context->addViolation($constraint->emptyMessage);
      return;
    }
    $name = $items->first()->value;
    $name = $items instanceof FieldItemListInterface ? $items->first()->value : $items;
    if (str_starts_with($name, ' ')) {
      $this->context->addViolation($constraint->spaceBeginMessage);
    }
+36 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\user;

use Drupal\Core\Validation\BasicRecursiveValidatorFactory;
use Drupal\Core\Validation\ConstraintManager;
use Symfony\Component\Validator\ConstraintViolationListInterface;

/**
 * Provides a username validator.
 *
 * This validator re-uses the UserName constraint plugin but does not require a
 * User entity.
 */
class UserNameValidator {

  public function __construct(
    protected readonly BasicRecursiveValidatorFactory $validatorFactory,
    protected readonly ConstraintManager $constraintManager,
  ) {}

  /**
   * Validates a user name.
   *
   * @return \Symfony\Component\Validator\ConstraintViolationListInterface
   *   The list of constraint violations.
   */
  public function validateName(string $name): ConstraintViolationListInterface {
    $validator = $this->validatorFactory->createValidator();
    $constraint = $this->constraintManager->create('UserName', []);
    return $validator->validate($name, $constraint);
  }

}
+96 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\user\Kernel;

use Drupal\KernelTests\KernelTestBase;
use Drupal\user\UserInterface;
use Drupal\user\UserNameValidator;

/**
 * Verify that user validity checks behave as designed.
 *
 * @group user
 */
class UserNameValidatorTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = ['user'];

  /**
   * The user validator under test.
   */
  protected UserNameValidator $userValidator;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->userValidator = $this->container->get('user.name_validator');
  }

  /**
   * Tests valid user name validation.
   *
   * @dataProvider validUsernameProvider
   */
  public function testValidUsernames($name): void {
    $violations = $this->userValidator->validateName($name);
    $this->assertEmpty($violations);
  }

  /**
   * Tests invalid user name validation.
   *
   * @dataProvider invalidUserNameProvider
   */
  public function testInvalidUsernames($name, $expectedMessage): void {
    $violations = $this->userValidator->validateName($name);
    $this->assertNotEmpty($violations);
    $this->assertEquals($expectedMessage, $violations[0]->getMessage());
  }

  /**
   * Provides valid user names.
   */
  public static function validUsernameProvider(): array {
    // cSpell:disable
    return [
      'lowercase' => ['foo'],
      'uppercase' => ['FOO'],
      'contains space' => ['Foo O\'Bar'],
      'contains @' => ['foo@bar'],
      'allow email' => ['foo@example.com'],
      'allow invalid domain' => ['foo@-example.com'],
      'allow special chars' => ['þòøÇߪř€'],
      'allow plus' => ['foo+bar'],
      'utf8 runes' => ['ᚠᛇᚻ᛫ᛒᛦᚦ'],
    ];
    // cSpell:enable
  }

  /**
   * Provides invalid user names.
   */
  public static function invalidUserNameProvider(): array {
    return [
      'starts with space' => [' foo', 'The username cannot begin with a space.'],
      'ends with space' => ['foo ', 'The username cannot end with a space.'],
      'contains 2 spaces' => ['foo  bar', 'The username cannot contain multiple spaces in a row.'],
      'empty string' => ['', 'You must enter a username.'],
      'invalid chars' => ['foo/', 'The username contains an illegal character.'],
      // NULL.
      'contains chr(0)' => ['foo' . chr(0) . 'bar', 'The username contains an illegal character.'],
      // CR.
      'contains chr(13)' => ['foo' . chr(13) . 'bar', 'The username contains an illegal character.'],
      'excessively long' => [str_repeat('x', UserInterface::USERNAME_MAX_LENGTH + 1),
        'The username xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx is too long: it must be 60 characters or less.',
      ],
    ];
  }

}
Loading