username-1.0.x-dev/src/UsernameGenerator.php

src/UsernameGenerator.php
<?php

namespace Drupal\username;

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Utility\Token;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * The Username Generator service.
 */
final class UsernameGenerator {

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $config;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * The language manager.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The instantiated Cache backend.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cache;

  /**
   * The uuid generator object.
   *
   * @var \Drupal\Component\Uuid\UuidInterface
   */
  protected $uuid;

  /**
   * Token service.
   *
   * @var \Drupal\Core\Utility\Token
   */
  protected $token;

  /**
   * Constructs an UsernameGenerator object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
   *   The cache backend.
   * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
   *   The UUID service.
   * @param \Drupal\Core\Utility\Token $token
   *   The token service.
   */
  public function __construct(ConfigFactoryInterface $config_factory, EntityFieldManagerInterface $entity_field_manager, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, UuidInterface $uuid_service, Token $token) {
    $this->config = $config_factory->get('username.settings');
    $this->entityFieldManager = $entity_field_manager;
    $this->languageManager = $language_manager;
    $this->moduleHandler = $module_handler;
    $this->cache = $cache;
    $this->uuid = $uuid_service;
    $this->token = $token;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('config.factory'),
      $container->get('entity_type.manager'),
      $container->get('language_manager'),
      $container->get('module_handler'),
      $container->get('cache.default'),
      $container->get('uuid'),
      $container->get('token'),
    );
  }

  /**
   * Generates a random suffixed username.
   *
   * @return string
   *   The generated username.
   */
  public function generateRandomUsername(): string {
    return 'username_' . $this->uuid->generate();
  }

  /**
   * Generates a username value.
   *
   * Determines what the new username could be, calling API hooks where
   * applicable, and adding a number suffix if necessary.
   *
   * @param object $account
   *   The user account object.
   *
   * @return string
   *   The generated username.
   */
  public function generateUsername($account) {
    // Other modules may implement hook_username_auto_name($edit, $account) to
    // generate a username (return a string to be used as the username, NULL to
    // have username generate it).
    $names = $this->moduleHandler->invokeAll('username_auto_name', [$account]);

    // Remove any empty entries.
    $names = array_filter($names);

    if (empty($names)) {
      // Default implementation of name generation.
      $new_name = $this->patternProcessor($account);
    }
    else {
      // One would expect a single implementation of the hook, but if there
      // are multiples out there use the last one.
      $new_name = array_pop($names);
    }

    // If no new name was found, return the display name.
    if (empty($new_name)) {
      return $account->getDisplayName();
    }

    return $new_name;
  }

  /**
   * Processes account and assigns new username per current pattern.
   *
   * @param object $account
   *   The user object to process.
   *
   * @return string
   *   The new name for the user object.
   */
  public function patternProcessor($account) {
    $output = '';
    $pattern = $this->config->get('token.pattern');

    if (trim($pattern)) {
      // Replace any tokens in the pattern.
      $pattern_array = explode('\n', trim($pattern));
      // Uses callback option to clean replacements. No sanitization.
      $output = $this->token->replace($pattern, ['user' => $account], [
        'clear' => TRUE,
        'callback' => 'username_cleanup_token_values',
      ]);
      // Check if the token replacement has not actually replaced any values.
      // If that is the case, then stop because we should not generate a name.
      $pattern_tokens_removed = preg_replace('/\[[^\s\]:]*:[^\s\]]*\]/', '', implode('\n', $pattern_array));
      if ($output === $pattern_tokens_removed) {
        return '';
      }
    }

    return trim($output);
  }

  /**
   * Cleans up a string segment to be used in a username.
   *
   * Performs various alterations:
   * - Remove all HTML tags.
   * - Process the string through the transliteration module.
   * - Replace or remove punctuation with the separator character.
   * - Remove backslashes.
   * - Replace non-ascii and non-numeric characters with the separator.
   * - Remove common words.
   * - Replace whitespace with the separator character.
   * - Trim duplicate, leading, and trailing separators.
   * - Convert to lower-case.
   * - Shorten to a desired length and logical position based on word
   *   boundaries.
   *
   * @param string $string
   *   A string to clean.
   *
   * @return string
   *   The cleaned string.
   */
  public function cleanUsernameString(string $string): string {
    // Since this is often called, use the advanced drupal_static() pattern.
    static $drupal_static_fast;
    if (!isset($drupal_static_fast)) {
      $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
    }
    $cache = &$drupal_static_fast['cache'];

    // Generate and cache variables used in this function so that on the second
    // call to cleanUsernameString() we focus on processing.
    if (!isset($cache)) {
      $cache = [
        'lowercase' => (bool) $this->config->get('token.lowercase'),
        'whitespace' => (bool) $this->config->get('token.whitespace'),
        'punctuation' => [],
        'separator' => '_',
        'ascii' => FALSE,
        'maxlength' => min(60, self::getNameMaxLength()),
      ];

      // Generate and cache the punctuation replacements for strtr().
      $punctuation = $this->getPunctuationChars();
      foreach ($punctuation as $details) {
        $cache['punctuation'][$details['value']] = '';
      }
    }

    // Empty strings do not need any processing.
    if ($string === '' || $string === NULL) {
      return '';
    }

    // Remove all HTML tags from the string.
    $output = strip_tags(Html::decodeEntities($string));

    // Replace or drop punctuation based on user settings.
    $output = strtr($output, $cache['punctuation']);

    // Reduce strings to letters and numbers.
    if ($cache['ascii']) {
      $output = preg_replace('/[^a-zA-Z0-9\/]+/', $cache['separator'], $output);
    }

    // Replace whitespace with the separator.
    if ($cache['whitespace']) {
      $output = preg_replace('/\s+/', $cache['separator'], $output);
    }

    // Trim duplicates and remove trailing and leading separators.
    $output = $this->cleanUsernameSeparator($output, $cache['separator']);

    // Optionally convert to lower case.
    if ($cache['lowercase']) {
      $output = mb_strtolower($output);
    }

    // Shorten to a logical place based on word boundaries.
    return Unicode::truncate($output, $cache['maxlength'], TRUE);
  }

  /**
   * Trims duplicate, leading, and trailing separators from a string.
   *
   * @param string $string
   *   The string to clean separators from.
   * @param string $separator
   *   The separator to use when cleaning.
   *
   * @return string
   *   The cleaned version of the string.
   */
  public function cleanUsernameSeparator(string $string, string $separator = NULL): string {
    static $default_separator;

    if (!isset($separator)) {
      if (!isset($default_separator)) {
        $default_separator = '_';
      }
      $separator = $default_separator;
    }

    $output = $string;

    // Clean duplicate or trailing separators.
    if (strlen($separator)) {
      // Escape the separator.
      $seppattern = preg_quote($separator, '/');

      // Trim any leading or trailing separators.
      $output = preg_replace("/^$seppattern+|$seppattern+$/", '', $output);

      // Replace trailing separators around slashes.
      if ($separator !== '/') {
        $output = preg_replace("/$seppattern+\/|\/$seppattern+/", "/", $output);
      }

      // Replace multiple separators with a single one.
      $output = preg_replace("/$seppattern+/", $separator, $output);
    }

    return $output;
  }

  /**
   * Returns an array of arrays for punctuation values.
   *
   * Returns an array of arrays for punctuation values keyed by a name,
   * including the value and a textual description.
   *
   * @return array
   *   An array of arrays for punctuation values keyed by a name, including the
   *   value and a textual description.
   */
  public function getPunctuationChars(): array {
    $punctuation = &drupal_static(__FUNCTION__);
    $language = $this->languageManager->getCurrentLanguage()->getId();

    if (!isset($punctuation)) {
      $cid = 'username:punctuation:' . $language;
      if ($cache = $this->cache->get($cid)) {
        $punctuation = $cache->data;
      }
      else {
        $punctuations = [
          'double_quotes' => ['"', 'Double quotation marks'],
          'quotes' => ["'", "Single quotation marks (apostrophe)"],
          'backtick' => ['`', 'Back tick'],
          'comma' => [',', 'Comma'],
          'period' => ['.', 'Period'],
          'hyphen' => ['-', 'Hyphen'],
          'underscore' => ['_', 'Underscore'],
          'colon' => [':', 'Colon'],
          'semicolon' => [';', 'Semicolon'],
          'pipe' => ['|', 'Vertical bar (pipe)'],
          'left_curly' => ['{', 'Left curly bracket'],
          'left_square' => ['[', 'Left square bracket'],
          'right_curly' => ['}', 'Right curly bracket'],
          'right_square' => [']', 'Right square bracket'],
          'plus' => ['+', 'Plus sign'],
          'equal' => ['=', 'Equal sign'],
          'asterisk' => ['*', 'Asterisk'],
          'ampersand' => ['&', 'Ampersand'],
          'percent' => ['%', 'Percent sign'],
          'caret' => ['^', 'Caret'],
          'dollar' => ['$', 'Dollar sign'],
          'hash' => ['#', 'Number sign (pound sign, hash)'],
          'at' => ['@', 'At sign'],
          'exclamation' => ['!', 'Exclamation mark'],
          'tilde' => ['~', 'Tilde'],
          'left_parenthesis' => ['(', 'Left parenthesis'],
          'right_parenthesis' => [')', 'Right parenthesis'],
          'question_mark' => ['?', 'Question mark'],
          'less_than' => ['<', 'Less-than sign'],
          'greater_than' => ['>', 'Greater-than sign'],
          'slash' => ['/', 'Slash'],
          'back_slash' => ['\\', 'Backslash'],
        ];

        $punctuation = [];
        foreach ($punctuations as $name => $mark) {
          $punctuation[$name] = [
            'value' => $mark[0],
            'name' => $this->t('@punctuation_mark', ['@punctuation_mark' => $mark[1]]),
          ];
        }

        // Allow modules to alter the punctuation list and cache the result.
        $this->moduleHandler->alter('getPunctuationChars', $punctuation);
        $this->cache->set($cid, $punctuation);
      }
    }

    return $punctuation;
  }

  /**
   * Fetches the maximum length of the {users}.name field from the schema.
   *
   * @return int
   *   The maximum username length allowed by the database.
   */
  public function getNameMaxLength(): int {
    $maxlength = &drupal_static(__FUNCTION__);
    if (!isset($maxlength)) {
      $field_definitions = $this->entityFieldManager->getFieldDefinitions('user', 'user');
      $name_settings = $field_definitions['name']->getItemDefinition()->getSettings();
      $maxlength = $name_settings['max_length'] ?? 255;
    }
    return $maxlength;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc