username-1.0.x-dev/username.module

username.module
<?php

/**
 * @file
 * Exposes global functionality for username on user form.
 */

use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Element\Email;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;

/**
 * Implements hook_preprocess_HOOK().
 */
function username_preprocess_page_title(array &$variables) {
  // Get the current route name.
  $route_name = \Drupal::routeMatch()->getRouteName();
  $user_routes = [
    'user.login',
    'user.register',
    'user.pass',
  ];

  // Check if the current route is one of the user routes.
  if (in_array($route_name, $user_routes)) {
    // Load the username settings configuration.
    $config = \Drupal::config('username.settings');
    $override = $config->get('override');
    $title = $config->get('title');

    // Override the page title based on the route and configuration settings.
    if ($route_name == 'user.login' && $override['login']) {
      $variables['title'] = !empty($title['login']) ? $title['login'] : t('Log in');
    }
    if ($route_name == 'user.register' && $override['register']) {
      $variables['title'] = !empty($title['register']) ? $title['register'] : t('Create new account');
    }
    if ($route_name == 'user.pass' && $override['reset']) {
      $variables['title'] = !empty($title['reset']) ? $title['reset'] : t('Reset your password');
    }
  }
}

/**
 * Implements hook_menu_local_tasks_alter().
 *
 * Override tabs from username settings.
 */
function username_menu_local_tasks_alter(array &$data, $route_name) {
  // Load the username settings configuration.
  $config = \Drupal::config('username.settings');
  $override = $config->get('override');
  $title = $config->get('title');

  // Override the tabs titles based on the route and configuration settings.
  if ($route_name == 'user.login' || $route_name == 'user.register' || $route_name == 'user.pass') {
    if ($override['login'] && isset($data['tabs'][0]['user.login']['#link']["title"])) {
      $data['tabs'][0]['user.login']['#link']["title"] = !empty($title['login']) ? $title['login'] : t('Log in');
    }
    if ($override['register'] && isset($data['tabs'][0]['user.register']['#link']["title"])) {
      $data['tabs'][0]['user.register']['#link']["title"] = !empty($title['register']) ? $title['register'] : t('Create new account');
    }
    if ($override['reset'] && isset($data['tabs'][0]['user.pass']['#link']["title"])) {
      $data['tabs'][0]['user.pass']['#link']["title"] = !empty($title['reset']) ? $title['reset'] : t('Reset your password');
    }
  }
}

/**
 * Implements hook_js_alter().
 */
function username_js_alter(array &$javascript, AttachedAssetsInterface $assets) {
  $module_path = \Drupal::service('extension.list.module')->getPath('username');
  $username_js = $module_path . '/js/username.js';
  $usermask_js = $module_path . '/js/username.mask.js';

  // Move username.js to be loaded after user.js file.
  if (isset($javascript[$usermask_js])) {
    $javascript[$usermask_js]['group'] = -99;
  }
  if (isset($javascript[$username_js])) {
    $javascript[$username_js]['group'] = -90;
  }
}

/**
 * Implements hook_theme().
 */
function username_theme($existing, $type, $theme, $path) {
  return [
    'input__password' => [
      'render element' => 'element',
      'template' => 'input--password',
      'path' => $path . '/templates',
    ],
  ];
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function username_user_presave(UserInterface $account) {
  $config = \Drupal::config('username.settings');
  $username = array_filter($config->get('username'));

  // Generate a username if the name field is not enabled.
  if (!isset($username['name'])) {
    $auto = $config->get('auto');
    switch ($auto) {
      case 'mail':
        $new_name = username_strip_mail_and_cleanup($account->getEmail());
        break;

      case 'hash':
        $new_name = Crypt::hashBase64($account->getEmail());
        break;

      case 'token':
        $new_name = \Drupal::service('username.username_generator')->generateUsername($account);
        break;
    }

    // Allow other modules to alter the username.
    \Drupal::moduleHandler()->alter('username_name', $new_name, $account);
    // Ensure the username is unique.
    $new_name = username_unique_username($new_name, (int) $account->id());
    $account->setUsername($new_name);
  }
}

/**
 * Makes the username unique.
 *
 * Given a starting point for a Drupal username (e.g., the name portion of an
 * email address), return a legal, unique Drupal username. This function is
 * designed to work on the results of the /user/register or /admin/people/create
 * forms which have already called user_validate_name, valid_email_address,
 * or a similar function. If your custom code is creating users, you should
 * ensure that the email/name is already validated using something like that.
 *
 * @param string $name
 *   A name from which to base the final username. May contain illegal
 *   characters; these will be stripped.
 * @param int $uid
 *   (optional) Uid to ignore when searching for a unique username
 *   (e.g., if we update the username after the {users} row is inserted).
 *
 * @return string
 *   A unique username based on $name.
 *
 * @see user_validate_name()
 */
function username_unique_username($name, $uid = 0) {
  // Iterate until we find a unique name.
  $i = 0;
  $database = \Drupal::database();
  do {
    $new_name = empty($i) ? $name : $name . '_' . $i;
    $found = $database->queryRange("SELECT uid from {users_field_data} WHERE uid <> :uid AND name = :name", 0, 1, [
      ':uid' => $uid,
      ':name' => $new_name,
    ])->fetchAssoc();
    $i++;
  } while (!empty($found));

  return $new_name;
}

/**
 * Cleans up a username.
 *
 * Run username sanitation, e.g., replace two or more spaces
 * with a single underscore, and strip illegal characters.
 *
 * @param string $name
 *   The username to be cleaned up.
 *
 * @return string
 *   Cleaned up username.
 */
function username_cleanup_username($name) {
  // Strip illegal characters.
  $name = preg_replace('/[^\x{80}-\x{F7} a-zA-Z0-9@_.\'-]/u', '', $name);

  // Strip leading and trailing spaces.
  $name = trim($name);

  // Convert any other series of spaces to a single underscore.
  $name = preg_replace('/\s+/', '_', $name);

  // If there's nothing left, use a default.
  $name = ('' === $name) ? t('user') : $name;

  // Truncate to a reasonable size.
  $name = (mb_strlen($name) > (UserInterface::USERNAME_MAX_LENGTH - 10)) ? mb_substr($name, 0, UserInterface::USERNAME_MAX_LENGTH - 11) : $name;
  return $name;
}

/**
 * Strips down the email address to a username and cleans it up.
 *
 * Strips everything after and including the '@' and cleans the username (e.g.,
 * stripping illegal characters, spaces, etc.).
 *
 * @param string $mail
 *   The email address to be stripped and cleaned up.
 *
 * @return string
 *   A username converted from an email address.
 */
function username_strip_mail_and_cleanup($mail) {
  // Strip the email.
  $username = preg_replace('/@.*$/', '', $mail);
  // Clean up the resulting username.
  return username_cleanup_username($username);
}

/**
 * Clean token values.
 *
 * Callback from token replacement service.
 *
 * @param array $replacements
 *   An array of token replacements that need to be cleaned.
 */
function username_cleanup_token_values(array &$replacements) {
  foreach ($replacements as $token => $value) {
    $replacements[$token] = \Drupal::service('username.username_generator')->cleanUsernameString($value);
  }
}

/**
 * Detects the input type of the username.
 *
 * @param string $username
 *   The input username.
 *
 * @return string
 *   The detected type of the input username.
 */
function username_detect_input_type($username) {
  $email_pattern = '/^\w{2,}@\w{2,}\.\w{2,4}$/';
  $mobile_pattern = "/^[7-9][0-9]{9}$/";

  if (preg_match($email_pattern, $username)) {
    $detected_type = "email";
  }
  elseif (preg_match($mobile_pattern, $username)) {
    $detected_type = 'phone';
  }
  else {
    $detected_type = 'name';
  }

  return $detected_type;
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function username_form_user_form_alter(array &$form, FormStateInterface $form_state) {
  // Load configuration from username settings.
  $config = \Drupal::config('username.settings');
  $username = array_filter($config->get('username'));
  $usernames = username_options();

  // Load override settings for register button and messages.
  $override = $config->get('override');
  $message = $config->get('message');
  $button = $config->get('button');

  // Check if the name and mail fields exist in the form.
  if (isset($form['account']['name']) && isset($form['account']['mail'])) {
    // Check if 'name' is part of the enabled usernames.
    $name_enabled = in_array('name', $username);

    // Make the mail field always required with this module.
    $form['account']['mail']['#required'] = TRUE;
    $currentUser = \Drupal::currentUser();

    /** @var \Drupal\user\UserInterface $user */
    $user = $form_state->getFormObject()->getEntity();

    // Check if the current user has the permission to edit the username.
    $hasUsernameEditPermissions = ($currentUser->hasPermission('change own username') && $currentUser->id() === $user->id()) || $currentUser->hasPermission('administer users');

    // Disable the username field if it is not active
    // and the user doesn't have the right permissions.
    if (!$name_enabled && !$hasUsernameEditPermissions) {
      $form['account']['name']['#type'] = 'value';
    }

    // If the user is new and the username field is
    // disabled, ensure we are on the user creation page.
    if (!$name_enabled && $user->isNew()) {
      // Change the form validation to allow an empty username.
      array_unshift($form['#validate'], 'username_validate_user_form_name');
      $form['account']['name']['#required'] = FALSE;

      // Add a warning if the user has
      // permission to edit the username field.
      if ($hasUsernameEditPermissions) {
        $primary = $config->get('selective.primary');
        $usernames = array_splice($usernames, 1);
        if (isset($usernames[$primary])) {
          $primary_name = strtolower($usernames[$primary]);
          $form['account']['name']['#description'] = t('Leave empty to generate the username from the @primary_name.', ['@primary_name' => $primary_name]) . '<br>' . $form['account']['name']['#description'];
        }
        else {
          $form['account']['name']['#description'] = t('Leave empty to generate the username automatically by the system.') . '<br>' . $form['account']['name']['#description'];
        }
      }
    }
  }

  // Check if the current route is an admin route.
  $is_admin = \Drupal::service('router.admin_context')->isAdminRoute();
  if (!$is_admin && $override['register']) {
    // Add a custom message to the form if configured.
    if (!empty($message['register'])) {
      $form['#markup'] = '<p>' . $message['register'] . '</p>';
    }

    // Override the default register button label.
    $form['actions']['submit']['#value'] = !empty($button['register']) ? $button['register'] : t('Create new account');
  }

  // Attach username options and admin status to the form.
  $form['#attached']['drupalSettings']['username']['options'] = $config->get('options');
  $form['#attached']['drupalSettings']['username']['isAdmin'] = $is_admin;

  // Add username restriction mask and options if enabled.
  if ($config->get('restriction.enabled')) {
    // Change the username field restriction description.
    username_change_restricts_description($form, $config);

    // Attach the username mask library.
    $form['#attached']['library'][] = 'username/username.mask';
    $form['#attached']['drupalSettings']['username']['masks'] = $config->get('restriction.mask');
  }

  // Add init class for username JavaScript to the user form page.
  $form['#attributes']['class'][] = 'username-init';

  // Attach the username form library.
  $form['#attached']['library'][] = 'username/username';
}

/**
 * The username_form_user_form_alter validation callback.
 *
 * This is used to set a temporary username if the username is empty, since
 * core doesn't allow us to have an empty username.
 *
 * @param array $form
 *   The form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state object.
 */
function username_validate_user_form_name(array &$form, FormStateInterface $form_state) {
  $username = $form_state->getValue('name');
  if (empty($username)) {
    // Create a temporary username.
    $form_state->setValue('name', \Drupal::service('username.username_generator')->generateRandomUsername());
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function username_form_user_login_form_alter(array &$form, FormStateInterface $form_state) {
  // Load configuration from username settings.
  $config = \Drupal::config('username.settings');
  $username = array_filter($config->get('username'));

  // Load selective username settings.
  $selective = $config->get('selective.enabled');
  $primary = $config->get('selective.primary');
  $switch = $config->get('selective.switch');

  // Load override settings for login label and description.
  $override = $config->get('override');
  $message = $config->get('message');
  $button = $config->get('button');

  // Set custom username validation.
  $form['name']['#element_validate'][] = 'username_user_login_validate';

  // Remove username autofocus if disabled in the configuration.
  if (!$config->get('options.autofocus') && isset($form['name']['#attributes']['autofocus'])) {
    unset($form['name']['#attributes']['autofocus']);
  }

  // Check if selective username is configured and more
  // than one username is activated. Show options for
  // selecting which username to use for login.
  if ($selective && count($username) > 1) {
    $usernames = array_intersect_key(username_options(), $username);
    $form['username'] = [
      '#type' => 'radios',
      '#options' => $usernames,
      '#default_value' => $primary,
      '#weight' => -999,
    ];
  }

  // Only email as a username with override label and description.
  if (count($username) == 1 && isset($username['mail'])) {
    // Change name field parameters to email format.
    $form['name']['#type'] = 'email';
    $form['name']['#maxlength'] = Email::EMAIL_MAX_LENGTH;
  }

  // Both username and email as a username with override label and description.
  if (count($username) == 2 && isset($username['name']) && isset($username['mail'])) {
    // Create email input field separately if selective username is configured.
    if ($selective) {
      $form['mail'] = [
        '#type' => 'email',
        '#title' => t('Email address'),
        '#weight' => 0,
        '#states' => [
          'invisible' => [
            ':input[name="username"]' => ['value' => 'name'],
          ],
          'required' => [
            ':input[name="username"]' => ['value' => 'mail'],
          ],
        ],
      ];
      // Set name field to not required and handle it with states condition.
      $form['name']['#required'] = FALSE;
      $form['name']['#states'] = [
        'invisible' => [
          ':input[name="username"]' => ['value' => 'mail'],
        ],
        'required' => [
          ':input[name="username"]' => ['value' => 'name'],
        ],
      ];
    }
  }

  // Set username labels.
  username_set_label($form);

  // Set username placeholders.
  username_set_placeholder($form);

  // Set username descriptions.
  username_set_description($form);

  // Add message to login form.
  if ($override['login'] && !empty($message['login'])) {
    $form['#markup'] = '<p>' . $message['login'] . '</p>';
  }

  // Override login button text.
  $form['actions']['submit']['#value'] = $override['login'] && !empty($button['login']) ? $button['login'] : t('Log in');

  // Get username options and attach them to the form.
  $form['#attached']['drupalSettings']['username']['options'] = $config->get('options');

  // Add username restriction mask and options if enabled.
  if ($config->get('restriction.enabled')) {
    // Ensure username field is available for restriction.
    if ((count($username) == 1 && isset($username['name'])) || ($selective && isset($username['name']))) {
      $form['#attached']['library'][] = 'username/username.mask';
      $form['#attached']['drupalSettings']['username']['masks'] = $config->get('restriction.mask');
    }
  }

  // Add init class for username JavaScript to login form.
  $form['#attributes']['class'][] = 'username-init';

  // Attach username form library.
  $form['#attached']['library'][] = 'username/username';

  // Attach switch username library if selective
  // username is enabled and switch is configured.
  if ($selective && $switch != '') {
    $form['#attributes']['class'][] = 'username-' . strtr($switch, '_', '-');
    $form['#attached']['library'][] = 'username/username.style';
  }

  // Ensure the login form cache is invalidated when the setting changes.
  $form['#cache']['tags'][] = 'config:username.settings';
}

/**
 * Form element validation handler for the user login form.
 *
 * Allows users to authenticate by email, which is our preferred method.
 *
 * @param array $form
 *   The form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state object.
 */
function username_user_login_validate(array &$form, FormStateInterface $form_state) {
  // Get all form values.
  $values = $form_state->getValues();
  // Load the username settings configuration.
  $config = \Drupal::config('username.settings');
  // Filter the username settings.
  $usernames = array_filter($config->get('username'));

  // Check if email is enabled for login.
  if (isset($usernames['mail'])) {
    // Determine the field to use for
    // email (selective usernames) or default to 'name'.
    $field = isset($values['mail']) ? 'mail' : 'name';
    $email = $values['mail'] ?? $values['name'];

    if (!empty($email)) {
      // Attempt to load the user by email address.
      $user = user_load_by_mail($email);

      // If a user is found, the input is a valid email address.
      if ($user) {
        // Check if the account is active.
        if (!$user->isActive()) {
          // Set an error if the account is not activated or blocked.
          $form_state->setErrorByName(
            $field,
            t('The account associated with the email address %email is not activated or has been blocked.', ['%email' => $email])
          );
        }
        else {
          // Set the 'name' field with the account name obtained from the email.
          $form_state->setValue('name', $user->getAccountName());
        }
      }
      elseif (count($usernames) == 1 || isset($values['mail'])) {
        // If the user is not found and email login is enabled, set an error.
        $user_input = $form_state->getUserInput();
        $query = isset($user_input[$field]) ? ['name' => $user_input[$field]] : [];

        $form_state->setErrorByName(
          $field,
          t('Unrecognized email address or password. <a href=":password">Forgot your password?</a>', [
            ':password' => Url::fromRoute('user.pass', [], ['query' => $query])->toString(),
          ])
        );
      }
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alters the user password reset form.
 */
function username_form_user_pass_alter(array &$form, FormStateInterface $form_state) {
  // Load the username settings configuration.
  $config = \Drupal::config('username.settings');
  $usernames = array_filter($config->get('username'));

  // Load various configuration options.
  $override = $config->get('override');
  $label = $config->get('label');
  $button = $config->get('button');
  $message = $config->get('message');

  // Remove username autofocus if disabled in the configuration.
  if (!$config->get('options.autofocus') && isset($form['name']['#attributes']['autofocus'])) {
    unset($form['name']['#attributes']['autofocus']);
  }

  // Convert text fields to email fields if only email username is enabled.
  if (count($usernames) == 1 && isset($usernames['mail'])) {
    // Set the title for the email field based on the configuration.
    $form['name']['#title'] = $override['username'] && !empty($label['username']) ? $label['username'] : t('Email address');

    // Change the field type to email if it's a text field.
    if ($form['name']['#type'] === 'textfield') {
      $form['name']['#type'] = 'email';
      $form['name']['#maxlength'] = Email::EMAIL_MAX_LENGTH;
    }
  }

  // Change the title for the username or email field if both are enabled.
  if (count($usernames) == 2 && isset($usernames['name']) && isset($usernames['mail'])) {
    $form['name']['#title'] = $override['username'] && !empty($label['username']) ? $label['username'] : t('Username or email address');
  }

  // Add a message to the reset password form if configured.
  if ($override['reset'] && !empty($message['reset'])) {
    $form['#markup'] = '<p>' . $message['reset'] . '</p>';
  }

  // Set the submit button text based on the configuration.
  $form['actions']['submit']['#value'] = $override['reset'] && !empty($button['reset']) ? $button['reset'] : t('Submit');

  // Attach username options to the form.
  $form['#attached']['drupalSettings']['username']['options'] = $config->get('options');

  // Attach restriction masks if enabled in the configuration.
  if ($config->get('restriction.enabled')) {
    $form['#attached']['library'][] = 'username/username.mask';
    $form['#attached']['drupalSettings']['username']['masks'] = $config->get('restriction.mask');
  }

  // Attach the username form library.
  $form['#attached']['library'][] = 'username/username';

  // Check if the password reset prevention is enabled in the configuration.
  if ($config->get('prevention')) {
    // Add custom validation handler for password reset.
    $form['#validate'][] = 'username_user_pass_validate';

    // Override the core submit handler for password reset.
    $key_submit = array_search('::submitForm', $form['#submit']);
    if ($key_submit !== FALSE) {
      $form['#submit'][$key_submit] = 'username_user_pass_submit';
    }
  }
}

/**
 * Overrides user_pass_validate() found in user.pages.inc.
 *
 * Validates the username or email for password reset.
 *
 * @param array $form
 *   The form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state object.
 */
function username_user_pass_validate(array &$form, FormStateInterface $form_state) {
  // Get the trimmed value of the username or email.
  $name = trim($form_state->getValue('name'));

  // Try to load the user account by email.
  $account = user_load_by_mail($name);

  if (empty($account)) {
    // If not found by email, try to load by username.
    $account = user_load_by_name($name);
  }

  // Check if the account is active and set it in the form state.
  if ($account && $account->id() && $account->isActive()) {
    $form_state->setValueForElement(['#parents' => ['account']], $account);
  }
  else {
    // Log a notice if a blocked user is attempting to reset the password.
    \Drupal::logger('username')->notice('Blocked user attempting to reset password.');
  }

  // Set a flag in the form state to indicate if the username is blocked.
  $form_state->set('username_blocked', !empty($form_state->getErrors()));

  // Clear errors so they are not displayed to the end-user.
  $form_state->clearErrors();
}

/**
 * Overrides the user_pass_submit() found in user.pages.inc.
 *
 * Submits the password reset request.
 *
 * @param array $form
 *   The form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state object.
 */
function username_user_pass_submit(array &$form, FormStateInterface $form_state) {
  // Get the user account from the form state.
  $account = $form_state->getValue('account');

  // Check if the account is set and the username is not blocked.
  if (isset($account) && !$form_state->get('username_blocked')) {
    // Get the current language code.
    $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();

    // Mail one-time login URL and instructions using the current language.
    $mail = _user_mail_notify('password_reset', $account, $langcode);
    if (!empty($mail)) {
      // Log a notice when the password reset instructions are mailed.
      \Drupal::logger('username')->notice('Password reset instructions mailed to %name at %email.', [
        '%name' => $account->getAccountName(),
        '%email' => $account->getEmail(),
      ]);
    }
  }

  // Display a message to the user.
  \Drupal::messenger()->addMessage(t('If the username or email address exists and is active, further instructions have been sent to your email address.'));

  // Redirect to the user login page.
  $form_state->setRedirect('user.page');
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Ensures usernames are not exposed when spoofing a reset password URL.
 */
function username_form_user_pass_reset_alter(array &$form, FormStateInterface $form_state, $form_id) {
  // Alter the form to include a message indicating a one-time login.
  $form['message'] = [
    '#markup' => t('<p>This is a one-time login.</p><p>Click on this button to log in to the site and change your password.</p>'),
  ];
}

/**
 * @defgroup username Display name API
 */

/**
 * Implements hook_entity_extra_field_info().
 */
function username_entity_extra_field_info() {
  // Define an extra field for the 'user' entity type to display name.
  $fields['user']['user']['display']['displayname'] = [
    'label' => t('Display name'),
    'description' => t('Display name'),
    'weight' => -1,
    'visible' => FALSE,
  ];

  return $fields;
}

/**
 * Implements hook_user_format_name_alter().
 */
function username_user_format_name_alter(&$name, AccountInterface $account) {
  static $in_username_alter = FALSE;
  $uid = $account->id();

  // Don't alter anonymous users or objects that do not have any user ID.
  if (empty($uid)) {
    return;
  }

  // Use the already loaded/generated display name if available.
  if (isset($account->displayname)) {
    if (mb_strlen($account->displayname)) {
      // Only alter $name if displayname is a non-empty string.
      $name = $account->displayname;
    }
    return;
  }

  // If the display name wasn't available, generate it.
  // Prevent recursion caused by tokens calling format_username().
  if (!$in_username_alter) {
    $in_username_alter = TRUE;

    // Ensure the user account object is properly loaded.
    if ($displayname_account = User::load($uid)) {
      username_user_format_name_alter($name, $displayname_account);
    }

    $in_username_alter = FALSE;
  }
}

/**
 * Implements hook_ENTITY_TYPE_load() for user entities.
 */
function username_user_load(array $accounts) {
  // Load display names for the given user accounts.
  $displaynames = username_display_name_load_multiple($accounts);
  foreach ($displaynames as $uid => $displayname) {
    // Attach the display name to the user account object.
    $accounts[$uid]->displayname = $displayname;
  }
}

/**
 * Implements hook_ENTITY_TYPE_update() for user entities.
 */
function username_user_update(EntityInterface $account) {
  // Since user data may have changed, update the display name and its cache.
  $displaynames = &drupal_static('username_display_name_load_multiple', []);
  $displaynames[$account->id()] = username_display_name_update($account);
}

/**
 * Implements hook_ENTITY_TYPE_delete() for user entities.
 */
function username_user_delete(EntityInterface $account) {
  // Delete the display name for the given user ID.
  username_display_name_delete($account->id());
}

/**
 * Implements hook_ENTITY_TYPE_view() for user entities.
 */
function username_user_view(array &$build, EntityInterface $account, EntityViewDisplayInterface $display, $view_mode) {
  // Check if the 'displayname' component is enabled in the view display.
  if ($display->getComponent('displayname')) {
    // Check if the user has access to view the display name.
    if ($account->access('view')) {
      // Create a URL to the user's canonical page.
      $url = Url::fromRoute('entity.user.canonical', ['user' => $account->id()]);
      $markup = Link::fromTextAndUrl($account->displayname, $url)->toString();
    }
    else {
      // Escape the display name for safe output.
      $markup = Html::escape($account->displayname);
    }

    // Build the render array for the display name field.
    $build['displayname'] = [
      '#theme' => 'field',
      '#title' => t('Display name'),
      '#label_display' => 'inline',
      '#view_mode' => '_custom',
      '#field_name' => 'displayname',
      '#field_type' => 'text',
      '#field_translatable' => FALSE,
      '#entity_type' => 'custom',
      '#bundle' => 'custom',
      '#object' => $account,
      '#items' => [TRUE],
      '#is_multiple' => FALSE,
      0 => [
        '#markup' => $markup,
      ],
    ];
  }
}

/**
 * @addtogroup username Display name
 * @{
 */

/**
 * Loads a display name.
 *
 * @param \Drupal\user\Entity\User $account
 *   A user account object.
 *
 * @return mixed
 *   The user's generated display name.
 */
function username_display_name_load(User $account) {
  // Load multiple display names for the provided user account.
  $usernames = username_display_name_load_multiple([$account->id() => $account]);
  // Return the first display name found.
  return reset($usernames);
}

/**
 * Loads multiple display names.
 *
 * @param array $accounts
 *   An array of user account objects keyed by user ID.
 *
 * @return array
 *   An array of display names keyed by user ID.
 */
function username_display_name_load_multiple(array $accounts): array {
  // Use static caching for display names.
  $displaynames = &drupal_static(__FUNCTION__, []);

  // Find accounts that need to be loaded.
  if ($new_accounts = array_diff_key($accounts, $displaynames)) {
    // Fetch display names from the database for new accounts.
    $displaynames += \Drupal::database()->query(
      "SELECT uid, name FROM {user__names} WHERE uid IN (:uids[])",
      [':uids[]' => array_keys($new_accounts)]
    )->fetchAllKeyed();

    // Generate display names for accounts not found in the database.
    foreach ($new_accounts as $uid => $account) {
      if (!isset($displaynames[$uid])) {
        $displaynames[$uid] = username_display_name_update($account);
      }
    }
  }

  // Return the display names for the provided accounts.
  return array_intersect_key($displaynames, $accounts);
}

/**
 * Update the display name for a user account.
 *
 * @param \Drupal\user\Entity\User $account
 *   A user account object.
 *
 * @return string
 *   A string with the display name.
 *
 * @see hook_username_display_name_pattern_alter()
 * @see hook_username_display_name_alter()
 * @see hook_username_display_name_update()
 */
function username_display_name_update(User $account): string {
  // Load the username settings configuration.
  $config = \Drupal::config('username.settings');
  $displayname = '';

  // Check if display names are enabled in the configuration.
  if ($config->get('display')) {
    // Get the replacement method from the configuration.
    $replace = $config->get('replace');

    // Check if the user is not anonymous.
    if (!$account->isAnonymous()) {
      // Get the default pattern and allow other modules to alter it.
      if ($replace === 'mail') {
        // Use the email address to generate the display name.
        $displayname = username_strip_mail_and_cleanup($account->getEmail());
      }
      else {
        // Get the custom pattern from the configuration.
        $pattern = $config->get('custom');
        if ($replace !== 'custom') {
          // Allow other modules to alter the display name pattern.
          \Drupal::moduleHandler()->alter('username_display_name_pattern', $pattern, $account);
        }

        // Perform token replacement on the display name pattern.
        $displayname = \Drupal::token()->replace($pattern, ['user' => $account], [
          'clear' => TRUE,
          'sanitize' => FALSE,
        ], new BubbleableMetadata());

        // Process the pattern with TWIG.
        $twig_service = \Drupal::service('twig');
        $displayname = $twig_service->renderInline($displayname);

        // Remove any HTML tags.
        $displayname = strip_tags(Html::decodeEntities($displayname));

        // Remove double spaces (if a token had no value).
        $displayname = preg_replace('/ {2,}/', ' ', $displayname);

        // Allow other modules to alter the generated username display name.
        \Drupal::moduleHandler()->alter('username_display_name', $displayname, $account);

        // Trim the display name to 255 characters.
        $displayname = Unicode::truncate(trim($displayname), 255);
      }
    }
    else {
      // Display name cannot be generated with tokens for anonymous users.
      $displayname = $account->label();
    }

    // Save the display name to the database and the static cache.
    \Drupal::database()->merge('user__names')
      ->key(['uid' => $account->id()])
      ->fields([
        'name' => $displayname,
        'created' => \Drupal::time()->getRequestTime(),
      ])
      ->execute();

    // Allow modules to react to the username display name being updated.
    \Drupal::moduleHandler()->invokeAll('username_display_name_update', [
      $displayname,
      $account,
    ]);

    // Clear the entity cache for the user.
    \Drupal::entityTypeManager()->getStorage('user')->resetCache([$account->id()]);
  }

  // Return the generated display name.
  return $displayname;
}

/**
 * Delete a display name.
 *
 * @param int $uid
 *   A user ID.
 */
function username_display_name_delete(int $uid): void {
  // Call the function to delete multiple display names with a single user ID.
  username_display_name_delete_multiple([$uid]);
}

/**
 * Delete multiple display names.
 *
 * @param array $uids
 *   An array of user IDs.
 */
function username_display_name_delete_multiple(array $uids): void {
  // Delete the entries from the 'user__names' table for the given user IDs.
  \Drupal::database()->delete('user__names')
    ->condition('uid', $uids, 'IN')
    ->execute();

  // Reset the static cache.
  drupal_static_reset('username_display_name_load_multiple');

  // Reset the cache for the user entities for the given user IDs.
  \Drupal::entityTypeManager()->getStorage('user')->resetCache($uids);
}

/**
 * Delete all display names.
 */
function username_display_name_delete_all(): void {
  // Truncate the 'user__names' table to delete all entries.
  \Drupal::database()->truncate('user__names')->execute();

  // Reset the static cache.
  drupal_static_reset('username_display_name_load_multiple');

  // Reset the cache for all user entities.
  \Drupal::entityTypeManager()->getStorage('user')->resetCache();
}

/**
 * @} End of "addtogroup username Display name".
 */

/**
 * Set the username field labels.
 *
 * @param array $form
 *   An associative array containing the structure of the form.
 *
 * @return array
 *   The updated entity bundle form array.
 */
function username_set_label(array &$form): array {
  // Load the username settings configuration.
  $config = \Drupal::config('username.settings');
  // Get the username modes from the configuration.
  $username_modes = array_filter($config->get('username'));
  // Get the username options.
  $usernames = username_options();
  // Get the override settings from the configuration.
  $override = $config->get('override');
  // Get the label settings from the configuration.
  $label = $config->get('label');
  // Check if selective username label is enabled.
  $selective = $config->get('selective.enabled');

  // Set username labels if selective is enabled.
  if ($selective) {
    if ($override['username']) {
      $username_label = $label['username'];
      if (empty($username_label)) {
        // Set the title display to 'invisible' if the label is empty.
        foreach ($usernames as $field_name => $username) {
          if (isset($form[$field_name])) {
            $form[$field_name]['#title_display'] = 'invisible';
          }
        }
      }
      else {
        // Split the label if it contains '%%'.
        if (strpos($username_label, '%%') !== FALSE) {
          $username_label = explode('%%', $username_label);
        }
        // Set the username labels for each username field.
        $i = 0;
        foreach (array_keys($usernames) as $field_name) {
          if (isset($form[$field_name])) {
            if (is_array($username_label)) {
              if (isset($username_label[$i])) {
                $form[$field_name]['#title'] = t('@$username_label', ['@$username_label' => trim($username_label[$i])]);
                $i++;
              }
            }
            else {
              $form[$field_name]['#title'] = t('@$username_label', ['@$username_label' => $username_label]);
            }
          }
        }
      }
    }
    else {
      // Set default username labels if not overridden.
      foreach ($usernames as $field_name => $username) {
        if (isset($form[$field_name])) {
          $username_list = strtolower($username->getUntranslatedString());
          $form[$field_name]['#title'] = t('@$username_label', ['@$username_label' => ucfirst($username_list)]);
        }
      }
    }
  }
  else {
    // Check if override for username label is enabled.
    if ($override['username']) {
      if (empty($label['username'])) {
        $form['name']['#title_display'] = 'invisible';
      }
      else {
        $form['name']['#title'] = t('@$username_label', ['@$username_label' => $label['username']]);
      }
    }
    else {
      // Set default username label if not overridden.
      if (isset($form['name']['#title']) && !empty(trim($form['name']['#title']))) {
        $username_list = '';
        $i = 0;
        $modes = count($username_modes);
        // Create a list of username modes separated by commas and "or".
        foreach ($username_modes as $username) {
          $username_list .= strtolower($usernames[$username]->getUntranslatedString());
          $username_list .= (++$i === $modes ? '' : ($modes - $i === 1 ? ' or ' : ', '));
        }
        $form['name']['#title'] = t('@$username_label', ['@$username_label' => ucfirst($username_list)]);
      }
    }
  }

  // Set password label if overridden.
  if ($override['password']) {
    $password_label = $label['password'];
    if (empty($password_label)) {
      $form['pass']['#title_display'] = 'invisible';
    }
    else {
      $form['pass']['#title'] = t('@password_label', ['@password_label' => $password_label]);
    }
  }

  // Return the updated form array.
  return $form;
}

/**
 * Set the username field placeholders.
 *
 * @param array $form
 *   An associative array containing the structure of the form.
 *
 * @return array
 *   The updated entity bundle form array.
 */
function username_set_placeholder(array &$form): array {
  // Load the username settings configuration.
  $config = \Drupal::config('username.settings');
  // Get the username options.
  $usernames = username_options();
  // Get the override settings from the configuration.
  $override = $config->get('override');
  // Get the placeholder settings from the configuration.
  $placeholder = $config->get('placeholder');
  // Check if selective username placeholder is enabled.
  $selective = $config->get('selective.enabled');

  // Set username placeholders if selective is enabled.
  if ($selective && $override['username']) {
    $username_placeholder = $placeholder['username'];
    if (!empty($username_placeholder)) {
      // Split the placeholder if it contains '%%'.
      if (strpos($username_placeholder, '%%') !== FALSE) {
        $username_placeholder = explode('%%', $username_placeholder);
      }
      // Set the username placeholders for each username field.
      $i = 0;
      foreach (array_keys($usernames) as $field_name) {
        if (isset($form[$field_name])) {
          if (is_array($username_placeholder)) {
            if (isset($username_placeholder[$i])) {
              $form[$field_name]['#attributes']['placeholder'] = t('@username_placeholder', ['@username_placeholder' => trim($username_placeholder[$i])]);
              $i++;
            }
          }
          else {
            $form[$field_name]['#attributes']['placeholder'] = t('@username_placeholder', ['@username_placeholder' => $username_placeholder]);
          }
        }
      }
    }
  }
  elseif ($override['username'] && !empty($placeholder['username'])) {
    // Set the username placeholder from the configuration if not selective.
    $form['name']['#attributes']['placeholder'] = t('@username_placeholder', ['@username_placeholder' => $placeholder['username']]);
  }

  // Set password placeholder if overridden.
  if ($override['password'] && !empty($placeholder['password'])) {
    $form['pass']['#attributes']['placeholder'] = t('@password_placeholder', ['@password_placeholder' => $placeholder['password']]);
  }

  // Return the updated form array.
  return $form;
}

/**
 * Set the username field descriptions.
 *
 * @param array $form
 *   An associative array containing the structure of the form.
 *
 * @return array
 *   The updated entity bundle form array.
 */
function username_set_description(array &$form): array {
  // Load the username settings configuration.
  $config = \Drupal::config('username.settings');
  // Get the site name from the site configuration.
  $site_name = \Drupal::config('system.site')->get('name');
  // Get the username modes from the configuration.
  $username_modes = array_filter($config->get('username'));
  // Get the username options.
  $usernames = username_options();
  // Get the override settings from the configuration.
  $override = $config->get('override');
  // Get the description settings from the configuration.
  $description = $config->get('description');
  // Check if selective username description is enabled.
  $selective = $config->get('selective.enabled');

  // Early return if selective username description is not enabled.
  if (!$selective) {
    // Check if override for username description is enabled.
    if ($override['username']) {
      if (empty($description['username'])) {
        // Unset the existing description if the description is empty.
        unset($form['name']['#description']);
      }
      else {
        // Set the username description from the configuration.
        $form['name']['#description'] = t('@description', ['@description' => $description['username']]);
      }
    }
    else {
      // Set default username description if not overridden.
      if (isset($form['name']['#description']) && !empty(trim($form['name']['#description']))) {
        $username_list = '';
        $modes = count($username_modes);
        // Create a list of username modes separated by commas and "or".
        foreach ($username_modes as $index => $username) {
          $username_list .= strtolower($usernames[$username]->getUntranslatedString());
          $username_list .= ($index === $modes - 1 ? '.' : ($index === $modes - 2 ? ' or ' : ', '));
        }
        // Set the default username description with placeholders.
        $username_desc = t('Enter your @s @usernames', [
          '@site_name' => $site_name,
          '@usernames' => $username_list,
        ]);
        $form['name']['#description'] = $username_desc;
      }
    }

    // Set password description if not overridden.
    if ($override['password']) {
      $password_description = $description['password'];
      if (empty($password_description)) {
        // Unset the existing password description if the description is empty.
        unset($form['pass']['#description']);
      }
      else {
        // Set the password description from the configuration.
        $form['pass']['#description'] = t('@description', ['@description' => $password_description]);
      }
    }
    else {
      // Set default password description if not overridden.
      if (isset($form['pass']['#description']) && !empty(trim($form['pass']['#description']))) {
        $username_list = '';
        $modes = count($username_modes);
        // Create a list of username modes separated by commas and "or".
        foreach ($username_modes as $index => $username) {
          $username_list .= strtolower($usernames[$username]->getUntranslatedString());
          $username_list .= ($index === $modes - 1 ? '.' : ($index === $modes - 2 ? ' or ' : ', '));
        }
        // Set the default password description with placeholders.
        $form['pass']['#description'] = t('Enter the password that accompanies your @usernames', ['@usernames' => $username_list]);
      }
    }

    // Return the updated form array.
    return $form;
  }

  // Set username description if selective is enabled.
  if ($override['username']) {
    $username_description = $description['username'];
    if (empty($username_description)) {
      // Unset existing descriptions if the username description is empty.
      foreach ($usernames as $field_name => $username) {
        unset($form[$field_name]['#description']);
      }
    }
    else {
      // Split the username description if it contains '%%'.
      $username_descriptions = strpos($username_description, '%%') !== FALSE ? explode('%%', $username_description) : [$username_description];
      // Set the username descriptions for each username field.
      foreach (array_keys($usernames) as $index => $field_name) {
        if (isset($form[$field_name])) {
          $form[$field_name]['#description'] = t('@description', ['@description' => trim($username_descriptions[$index % count($username_descriptions)])]);
        }
      }
    }
  }
  else {
    // Set default username descriptions if not overridden.
    if (isset($form['name']['#description']) && !empty(trim($form['name']['#description']))) {
      foreach ($usernames as $field_name => $username) {
        if (isset($form[$field_name])) {
          $username_description = t('Enter your @s registered @username', [
            '@sitename' => $site_name,
            '@username' => strtolower($form[$field_name]['#title']->getUntranslatedString()),
          ]);
          $form[$field_name]['#description'] = $username_description;
        }
      }
    }
  }

  // Set password description if not overridden.
  if ($override['password']) {
    $password_description = $description['password'];
    if (empty($password_description)) {
      // Unset existing password description if the description is empty.
      unset($form['pass']['#description']);
    }
    else {
      // Set the password description from the configuration.
      $form['pass']['#description'] = t('@description', ['@description' => $password_description]);
    }
  }
  else {
    // Set default password description if not overridden.
    if (isset($form['pass']['#description']) && !empty(trim($form['pass']['#description']))) {
      $username_list = '';
      $modes = count($username_modes);
      // Create a list of username modes separated by commas and "or".
      foreach ($username_modes as $index => $username) {
        $username_list .= strtolower($usernames[$username]->getUntranslatedString());
        $username_list .= ($index === $modes - 1 ? '.' : ($index === $modes - 2 ? ' or ' : ', '));
      }
      // Set the default password description with placeholders.
      $form['pass']['#description'] = t('Enter the password that accompanies your @usernames', ['@usernames' => $username_list]);
    }
  }

  // Return the updated form array.
  return $form;
}

/**
 * Change the username field restricts description in the create user form.
 *
 * @param array $form
 *   An associative array containing the structure of the form.
 * @param \Drupal\Core\Config\ImmutableConfig $config
 *   The configuration object.
 *
 * @return array
 *   The updated entity bundle form array.
 */
function username_change_restricts_description(array &$form, ImmutableConfig $config): array {
  $restrictions = array_filter($config->get('restriction.mask'));

  // Set main username strings.
  $lowercase = isset($restrictions['lowercase']) ? 'lowercase' : '';
  $uppercase = isset($restrictions['uppercase']) ? 'uppercase' : '';
  $numeric = isset($restrictions['numeric']) ? 'digits' : '';

  // Unset unnecessary keys.
  unset($restrictions['lowercase']);
  unset($restrictions['uppercase']);
  unset($restrictions['numeric']);
  unset($restrictions['minlength']);
  unset($restrictions['maxlength']);

  // Provide new description for username on create new account page.
  if (count($restrictions) < 6) {
    $only = '';
    $allowed_string = ' are allowed';

    // Check main username options.
    if (!empty($lowercase) && !empty($uppercase) && !empty($numeric)) {
      $separator_1 = ', ';
      $separator_2 = ' and ';
    }
    elseif (!empty($lowercase) && !empty($uppercase) && empty($numeric)) {
      $separator_1 = ' and ';
      $separator_2 = '';
    }
    elseif (
      (empty($lowercase) && !empty($uppercase) && !empty($numeric)) ||
      (!empty($lowercase) && empty($uppercase) && !empty($numeric))
    ) {
      $separator_1 = '';
      $separator_2 = ' and ';
    }
    else {
      $only = 'Only ';
      $allowed_string = ' is allowed';
      $separator_1 = '';
      $separator_2 = '';
    }

    $char_string = empty($lowercase) && empty($uppercase) ? '' : ' characters';
    $including = count($restrictions) > 0 ? ', including ' : '.';

    // Create dynamic descriptions by username restriction options.
    $restricts_description = $only . $lowercase . $separator_1 . $uppercase . $char_string . $separator_2 . $numeric . $allowed_string . $including;
    $i = 0;
    $restricts = count($restrictions);
    foreach ($restrictions as $restriction => $allowed) {
      // Check special characters.
      if ($allowed) {
        switch ($restriction) {
          case 'space':
            $restricts_label = 'space';
            break;

          case 'period':
            $restricts_label = 'period (.)';
            break;

          case 'hyphen':
            $restricts_label = 'hyphen (-)';
            break;

          case 'apostrophe':
            $restricts_label = "apostrophe (')";
            break;

          case 'underscore':
            $restricts_label = 'underscore (_)';
            break;

          case 'sign':
            $restricts_label = '@ sign';
            break;
        }
        $restricts_description .= $restricts_label . (++$i === $restricts ? '.' : ', ');
      }
    }

    // Ensure the first letter of the description is uppercase.
    $restricts_description = ucfirst($restricts_description);

    // Set new description using translation placeholder.
    $form['account']['name']['#description'] = t('@description', ['@description' => $restricts_description]);
  }

  return $form;
}

/**
 * Provides a variety of username mode options.
 *
 * @return array
 *   An associative array of username mode options.
 */
function username_options(): array {
  // Define the default username mode options.
  $usernames = [
    'name' => t('Username'),
    'mail' => t('Email address'),
  ];

  // Invoke all implementations of hook_username_mode().
  $username_modes = \Drupal::moduleHandler()->invokeAll('username_mode', [$usernames]);

  // Merge custom username modes with the default username mode options.
  return count($username_modes) ? array_merge($usernames, $username_modes) : $usernames;
}

/**
 * Provides options for selective username switch styles.
 *
 * @return array
 *   An associative array of switch style options.
 */
function username_switch_options(): array {
  // Define the default switch style options.
  $styles = [
    '' => t('None'),
    'simple_text' => t('Simple inline text'),
    'simple_icon' => t('Simple inline icon'),
    'animate_text' => t('Animate inline radio'),
    'icon_text' => t('Inline icon and text'),
    'tab_text' => t('Tab style text'),
  ];

  // Invoke all implementations of hook_username_switch().
  $switch_styles = \Drupal::moduleHandler()->invokeAll('username_switch', [$styles]);

  // Merge custom switch styles with the default switch style options.
  $switch_styles = count($switch_styles) ? array_merge($styles, $switch_styles) : $styles;

  return $switch_styles;
}

/**
 * Provides options for automatically generating usernames.
 *
 * @return array
 *   An associative array of options for generating usernames.
 */
function username_generate_options(): array {
  // Define the default generate options.
  $generate = [
    'mail' => t('First part of user email'),
    'hash' => t('Unique hash by user email'),
    'token' => t('Custom value with token'),
  ];

  // Invoke all implementations of hook_username_generate().
  $generate_modes = \Drupal::moduleHandler()->invokeAll('username_generate', [$generate]);

  // Merge custom generate modes with the default generate options.
  $generate_modes = count($generate_modes) ? array_merge($generate, $generate_modes) : $generate;

  return $generate_modes;
}

/**
 * Provides display name options for usernames.
 *
 * @return array
 *   An associative array of display name options.
 */
function username_display_options(): array {
  // Define the default display options.
  $display = [
    'mail' => t('Use the part of the email address before "@" as the username.'),
    'custom' => t('Use tokens to generate a custom username from user fields.'),
  ];

  // Invoke all implementations of hook_username_display().
  $display_modes = \Drupal::moduleHandler()->invokeAll('username_display', [$display]);

  // Merge custom display modes with the default display options.
  if (count($display_modes)) {
    $display_modes = array_merge($display, $display_modes);
  }
  else {
    $display_modes = $display;
  }

  // Ensure 'custom' option is the last item in the array.
  $display_clone = $display_modes['custom'];
  unset($display_modes['custom']);
  $display_modes['custom'] = $display_clone;

  return $display_modes;
}

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

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