user.module 50.9 KB
Newer Older
1 2
<?php

3 4 5 6 7
/**
 * @file
 * Enables the user registration and login system.
 */

8
use Drupal\Component\Utility\Crypt;
9
use Drupal\Component\Render\PlainTextOutput;
10
use Drupal\Component\Utility\Unicode;
11
use Drupal\Core\Asset\AttachedAssetsInterface;
12 13 14
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Render\Element;
15
use Drupal\Core\Routing\RouteMatchInterface;
16
use Drupal\Core\Session\AccountInterface;
17
use Drupal\Core\Session\AnonymousUserSession;
18
use Drupal\Core\Site\Settings;
19
use Drupal\Core\Url;
20
use Drupal\system\Entity\Action;
21
use Drupal\user\Entity\Role;
22
use Drupal\user\Entity\User;
23
use Drupal\user\RoleInterface;
24
use Drupal\user\UserInterface;
Crell's avatar
Crell committed
25

26 27
/**
 * Maximum length of username text field.
28 29
 *
 * Keep this under 191 characters so we can use a unique constraint in MySQL.
30 31 32
 *
 * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
 *   Use \Drupal\user\UserInterface::USERNAME_MAX_LENGTH instead.
33 34
 *
 * @see https://www.drupal.org/node/2831620
35
 */
36
const USERNAME_MAX_LENGTH = 60;
37

38 39
/**
 * Only administrators can create user accounts.
40 41 42
 *
 * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
 *   Use \Drupal\user\UserInterface::REGISTER_ADMINISTRATORS_ONLY instead.
43 44
 *
 * @see https://www.drupal.org/node/2831620
45
 */
46
const USER_REGISTER_ADMINISTRATORS_ONLY = 'admin_only';
47 48 49

/**
 * Visitors can create their own accounts.
50 51 52
 *
 * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
 *   Use \Drupal\user\UserInterface::REGISTER_VISITORS instead.
53 54
 *
 * @see https://www.drupal.org/node/2831620
55
 */
56
const USER_REGISTER_VISITORS = 'visitors';
57 58 59 60

/**
 * Visitors can create accounts, but they don't become active without
 * administrative approval.
61 62 63
 *
 * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
 *   Use \Drupal\user\UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL
64
 *   instead.
65 66
 *
 * @see https://www.drupal.org/node/2831620
67
 */
68
const USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL = 'visitors_admin_approval';
69

70
/**
71
 * Implements hook_help().
72
 */
73
function user_help($route_name, RouteMatchInterface $route_match) {
74 75
  switch ($route_name) {
    case 'help.page.user':
76 77
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
78
      $output .= '<p>' . t('The User module allows users to register, log in, and log out. It also allows users with proper permissions to manage user roles and permissions. For more information, see the <a href=":user_docs">online documentation for the User module</a>.', [':user_docs' => 'https://www.drupal.org/documentation/modules/user']) . '</p>';
79 80 81
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Creating and managing users') . '</dt>';
82
      $output .= '<dd>' . t('Through the <a href=":people">People administration page</a> you can add and cancel user accounts and assign users to roles. By editing one particular user you can change their username, email address, password, and information in other fields.', [':people' => \Drupal::url('entity.user.collection')]) . '</dd>';
83
      $output .= '<dt>' . t('Configuring user roles') . '</dt>';
84
      $output .= '<dd>' . t('<em>Roles</em> are used to group and classify users; each user can be assigned one or more roles. Typically there are two pre-defined roles: <em>Anonymous user</em> (users that are not logged in), and <em>Authenticated user</em> (users that are registered and logged in). Depending on how your site was set up, an <em>Administrator</em> role may also be available: users with this role will automatically be assigned any new permissions whenever a module is enabled. You can create additional roles on the <a href=":roles">Roles administration page</a>.', [':roles' => \Drupal::url('entity.user_role.collection')]) . '</dd>';
85
      $output .= '<dt>' . t('Setting permissions') . '</dt>';
86
      $output .= '<dd>' . t('After creating roles, you can set permissions for each role on the <a href=":permissions_user">Permissions page</a>. Granting a permission allows users who have been assigned a particular role to perform an action on the site, such as viewing content, editing or creating  a particular type of content, administering settings for a particular module, or using a particular function of the site (such as search).', [':permissions_user' => \Drupal::url('user.admin_permissions')]) . '</dd>';
87
      $output .= '<dt>' . t('Managing account settings') . '</dt>';
88
      $output .= '<dd>' . t('The <a href=":accounts">Account settings page</a> allows you to manage settings for the displayed name of the Anonymous user role, personal contact forms, user registration settings, and account cancellation settings. On this page you can also manage settings for account personalization, and adapt the text for the email messages that users receive when they register or request a password recovery. You may also set which role is automatically assigned new permissions whenever a module is enabled (the Administrator role).', [':accounts' => \Drupal::url('entity.user.admin_form')]) . '</dd>';
89
      $output .= '<dt>' . t('Managing user account fields') . '</dt>';
90
      $output .= '<dd>' . t('Because User accounts are an entity type, you can extend them by adding fields through the Manage fields tab on the <a href=":accounts">Account settings page</a>. By adding fields for e.g., a picture, a biography, or address, you can a create a custom profile for the users of the website. For background information on entities and fields, see the <a href=":field_help">Field module help page</a>.', [':field_help' => (\Drupal::moduleHandler()->moduleExists('field')) ? \Drupal::url('help.page', ['name' => 'field']) : '#', ':accounts' => \Drupal::url('entity.user.admin_form')]) . '</dd>';
91 92
      $output .= '</dl>';
      return $output;
93 94

    case 'user.admin_create':
95
      return '<p>' . t("This web page allows administrators to register new users. Users' email addresses and usernames must be unique.") . '</p>';
96 97

    case 'user.admin_permissions':
98
      return '<p>' . t('Permissions let you control what users can do and see on your site. You can define a specific set of permissions for each role. (See the <a href=":role">Roles</a> page to create a role.) Any permissions granted to the Authenticated user role will be given to any user who is logged in to your site. From the <a href=":settings">Account settings</a> page, you can make any role into an Administrator role for the site, meaning that role will be granted all new permissions automatically. You should be careful to ensure that only trusted users are given this access and level of control of your site.', [':role' => \Drupal::url('entity.user_role.collection'), ':settings' => \Drupal::url('entity.user.admin_form')]) . '</p>';
99

100
    case 'entity.user_role.collection':
101
      return '<p>' . t('A role defines a group of users that have certain privileges. These privileges are defined on the <a href=":permissions">Permissions page</a>. Here, you can define the names and the display sort order of the roles on your site. It is recommended to order roles from least permissive (for example, Anonymous user) to most permissive (for example, Administrator user). Users who are not logged in have the Anonymous user role. Users who are logged in have the Authenticated user role, plus any other roles granted to their user account.', [':permissions' => \Drupal::url('user.admin_permissions')]) . '</p>';
102

103
    case 'entity.user.field_ui_fields':
104
      return '<p>' . t('This form lets administrators add and edit fields for storing user data.') . '</p>';
105

106
    case 'entity.entity_form_display.user.default':
107
      return '<p>' . t('This form lets administrators configure how form fields should be displayed when editing a user profile.') . '</p>';
108

109
    case 'entity.entity_view_display.user.default':
110 111 112
      return '<p>' . t('This form lets administrators configure how fields should be displayed when rendering a user profile page.') . '</p>';
  }
}
113

114
/**
115
 * Implements hook_theme().
116 117
 */
function user_theme() {
118 119
  return [
    'user' => [
120
      'render element' => 'elements',
121 122 123 124 125
    ],
    'username' => [
      'variables' => ['account' => NULL, 'attributes' => [], 'link_options' => []],
    ],
  ];
126 127
}

128
/**
129
 * Implements hook_js_settings_alter().
130
 */
131
function user_js_settings_alter(&$settings, AttachedAssetsInterface $assets) {
132 133 134
  // Provide the user ID in drupalSettings to allow JavaScript code to customize
  // the experience for the end user, rather than the server side, which would
  // break the render cache.
135 136 137
  // Similarly, provide a permissions hash, so that permission-dependent data
  // can be reliably cached on the client side.
  $user = \Drupal::currentUser();
138
  $settings['user']['uid'] = $user->id();
139
  $settings['user']['permissionsHash'] = \Drupal::service('user_permissions_hash_generator')->generate($user);
140 141
}

142 143 144 145 146 147 148 149 150
/**
 * Returns whether this site supports the default user picture feature.
 *
 * This approach preserves compatibility with node/comment templates. Alternate
 * user picture implementations (e.g., Gravatar) should provide their own
 * add/edit/delete forms and populate the 'picture' variable during the
 * preprocess stage.
 */
function user_picture_enabled() {
151 152
  $field_definitions = \Drupal::entityManager()->getFieldDefinitions('user', 'user');
  return isset($field_definitions['user_picture']);
153 154
}

155
/**
156
 * Implements hook_entity_extra_field_info().
157
 */
158
function user_entity_extra_field_info() {
159
  $fields['user']['user']['form']['account'] = [
160 161 162
    'label' => t('User name and password'),
    'description' => t('User module account form elements.'),
    'weight' => -10,
163 164
  ];
  $fields['user']['user']['form']['language'] = [
165 166 167
    'label' => t('Language settings'),
    'description' => t('User module form element.'),
    'weight' => 0,
168
  ];
169
  if (\Drupal::config('system.date')->get('timezone.user.configurable')) {
170
    $fields['user']['user']['form']['timezone'] = [
171 172 173
      'label' => t('Timezone'),
      'description' => t('System module form element.'),
      'weight' => 6,
174
    ];
175 176
  }

177
  $fields['user']['user']['display']['member_for'] = [
178
    'label' => t('Member for'),
179
    'description' => t("User module 'member for' view element."),
180
    'weight' => 5,
181
  ];
182

183
  return $fields;
184 185
}

186
/**
187
 * Loads multiple users based on certain conditions.
188
 *
189 190 191
 * This function should be used whenever you need to load more than one user
 * from the database. Users are loaded into memory and will not require
 * database access if loaded again during the same page request.
192
 *
193 194
 * @param array $uids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
195
 * @param bool $reset
196 197
 *   A boolean indicating that the internal cache should be reset. Use this if
 *   loading a user object which has been altered during the page request.
198
 *
199
 * @return array
200 201
 *   An array of user objects, indexed by uid.
 *
202
 * @see entity_load_multiple()
203
 * @see \Drupal\user\Entity\User::load()
204 205
 * @see user_load_by_mail()
 * @see user_load_by_name()
206
 * @see \Drupal\Core\Entity\Query\QueryInterface
207 208 209
 *
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\user\Entity\User::loadMultiple().
210
 */
211
function user_load_multiple(array $uids = NULL, $reset = FALSE) {
212 213 214 215
  if ($reset) {
    \Drupal::entityManager()->getStorage('user')->resetCache($uids);
  }
  return User::loadMultiple($uids);
216
}
217 218

/**
219 220
 * Loads a user object.
 *
221
 * @param int $uid
222
 *   Integer specifying the user ID to load.
223
 * @param bool $reset
224 225 226
 *   TRUE to reset the internal cache and load from the database; FALSE
 *   (default) to load from the internal cache, if set.
 *
227
 * @return \Drupal\user\UserInterface
228
 *   A fully-loaded user object upon successful user load, or NULL if the user
229 230
 *   cannot be loaded.
 *
231 232 233
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\user\Entity\User::load().
 *
234
 * @see \Drupal\user\Entity\User::loadMultiple()
235 236
 */
function user_load($uid, $reset = FALSE) {
237
  if ($reset) {
238
    \Drupal::entityManager()->getStorage('user')->resetCache([$uid]);
239 240
  }
  return User::load($uid);
241 242 243
}

/**
244
 * Fetches a user object by email address.
245
 *
246
 * @param string $mail
247
 *   String with the account's email address.
248
 * @return object|bool
249 250 251
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
252
 * @see \Drupal\user\Entity\User::loadMultiple()
253 254
 */
function user_load_by_mail($mail) {
255 256
  $users = \Drupal::entityTypeManager()->getStorage('user')
    ->loadByProperties(['mail' => $mail]);
257
  return $users ? reset($users) : FALSE;
258 259 260
}

/**
261
 * Fetches a user object by account name.
262
 *
263
 * @param string $name
264
 *   String with the account's user name.
265
 * @return object|bool
266 267 268
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
269
 * @see \Drupal\user\Entity\User::loadMultiple()
270 271
 */
function user_load_by_name($name) {
272 273
  $users = \Drupal::entityTypeManager()->getStorage('user')
    ->loadByProperties(['name' => $name]);
274
  return $users ? reset($users) : FALSE;
275 276
}

277 278
/**
 * Verify the syntax of the given name.
279 280 281 282 283 284 285
 *
 * @param string $name
 *   The user name to validate.
 *
 * @return string|null
 *   A translated violation message if the name is invalid or NULL if the name
 *   is valid.
286
 */
287
function user_validate_name($name) {
288
  $definition = BaseFieldDefinition::create('string')
289
    ->addConstraint('UserName', []);
290
  $data = \Drupal::typedDataManager()->create($definition);
291 292 293 294
  $data->setValue($name);
  $violations = $data->validate();
  if (count($violations) > 0) {
    return $violations[0]->getMessage();
295
  }
296 297
}

298 299 300
/**
 * Generate a random alphanumeric password.
 */
301 302
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
303 304
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
305
  // of 'I', 1, and 'l'.
306
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
307

308 309
  // Zero-based count of characters in the allowable list:
  $len = strlen($allowable_characters) - 1;
310

311 312
  // Declare the password as a blank string.
  $pass = '';
313

314
  // Loop the number of times specified by $length.
315
  for ($i = 0; $i < $length; $i++) {
316 317 318 319
    do {
      // Find a secure random number within the range needed.
      $index = ord(Crypt::randomBytes(1));
    } while ($index > $len);
320 321 322

    // Each iteration, pick a random character from the
    // allowable string and append it to the password:
323
    $pass .= $allowable_characters[$index];
324 325 326
  }

  return $pass;
327 328
}

329 330 331
/**
 * Determine the permissions for one or more roles.
 *
332 333
 * @param array $roles
 *   An array of role IDs.
334
 *
335 336 337
 * @return array
 *   An array indexed by role ID. Each value is an array of permission strings
 *   for the given role.
338
 */
339 340 341 342
function user_role_permissions(array $roles) {
  if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
    return _user_role_permissions_update($roles);
  }
343
  $entities = Role::loadMultiple($roles);
344
  $role_permissions = [];
345
  foreach ($roles as $rid) {
346
    $role_permissions[$rid] = isset($entities[$rid]) ? $entities[$rid]->getPermissions() : [];
347
  }
348 349
  return $role_permissions;
}
350

351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
/**
 * Determine the permissions for one or more roles during update.
 *
 * A separate version is needed because during update the entity system can't
 * be used and in non-update situations the entity system is preferred because
 * of the hook system.
 *
 * @param array $roles
 *   An array of role IDs.
 *
 * @return array
 *   An array indexed by role ID. Each value is an array of permission strings
 *   for the given role.
 */
function _user_role_permissions_update($roles) {
366
  $role_permissions = [];
367
  foreach ($roles as $rid) {
368
    $role_permissions[$rid] = \Drupal::config("user.role.$rid")->get('permissions') ?: [];
369 370 371 372
  }
  return $role_permissions;
}

373
/**
374
 * Checks for usernames blocked by user administration.
375
 *
376
 * @param string $name
377 378
 *   A string containing a name of the user.
 *
379 380
 * @return bool
 *   TRUE if the user is blocked, FALSE otherwise.
381 382
 */
function user_is_blocked($name) {
383 384
  return (bool) \Drupal::entityQuery('user')
    ->condition('name', $name)
385
    ->condition('status', 0)
386
    ->execute();
387 388
}

389
/**
390
 * Implements hook_ENTITY_TYPE_view() for user entities.
391
 */
392
function user_user_view(array &$build, UserInterface $account, EntityViewDisplayInterface $display) {
393
  if ($display->getComponent('member_for')) {
394
    $build['member_for'] = [
395
      '#type' => 'item',
396
      '#markup' => '<h4 class="label">' . t('Member for') . '</h4> ' . \Drupal::service('date.formatter')->formatTimeDiffSince($account->getCreatedTime()),
397
    ];
398
  }
399 400
}

401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
/**
 * Implements hook_ENTITY_TYPE_view_alter() for user entities.
 *
 * This function adds a default alt tag to the user_picture field to maintain
 * accessibility.
 */
function user_user_view_alter(array &$build, UserInterface $account, EntityViewDisplayInterface $display) {
  if (user_picture_enabled() && !empty($build['user_picture'])) {
    foreach (Element::children($build['user_picture']) as $key) {
      $item = $build['user_picture'][$key]['#item'];
      if (!$item->get('alt')->getValue()) {
        $item->get('alt')->setValue(\Drupal::translation()->translate('Profile picture for user @username', ['@username' => $account->getUsername()]));
      }
    }
  }
}

418
/**
419
 * Implements hook_preprocess_HOOK() for block templates.
420 421
 */
function user_preprocess_block(&$variables) {
422
  if ($variables['configuration']['provider'] == 'user') {
423
    switch ($variables['elements']['#plugin_id']) {
424
      case 'user_login_block':
425
        $variables['attributes']['role'] = 'form';
426 427 428 429 430
        break;
    }
  }
}

431 432 433
/**
 * Format a username.
 *
434
 * @param \Drupal\Core\Session\AccountInterface $account
435 436
 *   The account object for the user whose name is to be formatted.
 *
437
 * @return string
438
 *   An unsanitized string with the username to display.
439
 *
440
 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
441
 *   Use \Drupal\Core\Session\AccountInterface::getDisplayName().
442 443
 *
 * @todo Remove usage in https://www.drupal.org/node/2311219.
444
 */
445
function user_format_name(AccountInterface $account) {
446
  return $account->getDisplayName();
447 448
}

449 450 451 452 453 454 455
/**
 * Implements hook_template_preprocess_default_variables_alter().
 *
 * @see user_user_login()
 * @see user_user_logout()
 */
function user_template_preprocess_default_variables_alter(&$variables) {
456
  $user = \Drupal::currentUser();
457 458

  $variables['user'] = clone $user;
459
  // Remove password and session IDs, since themes should not need nor see them.
460 461
  unset($variables['user']->pass, $variables['user']->sid, $variables['user']->ssid);

462
  $variables['is_admin'] = $user->hasPermission('access administration pages');
463
  $variables['logged_in'] = $user->isAuthenticated();
464 465
}

466
/**
467 468 469 470
 * Prepares variables for username templates.
 *
 * Default template: username.html.twig.
 *
471 472 473
 * Modules that make any changes to variables like 'name' or 'extra' must ensure
 * that the final string is safe.
 *
474 475
 * @param array $variables
 *   An associative array containing:
476
 *   - account: The user account (\Drupal\Core\Session\AccountInterface).
477 478
 */
function template_preprocess_username(&$variables) {
479
  $account = $variables['account'] ?: new AnonymousUserSession();
480 481

  $variables['extra'] = '';
482 483 484 485 486
  $variables['uid'] = $account->id();
  if (empty($variables['uid'])) {
    if (theme_get_setting('features.comment_user_verification')) {
      $variables['extra'] = ' (' . t('not verified') . ')';
    }
487 488 489 490 491 492 493
  }

  // Set the name to a formatted name that is safe for printing and
  // that won't break tables by being too long. Keep an unshortened,
  // unsanitized version, in case other preprocess functions want to implement
  // their own shortening logic or add markup. If they do so, they must ensure
  // that $variables['name'] is safe for printing.
494 495
  $name  = $account->getDisplayName();
  $variables['name_raw'] = $account->getUsername();
496
  if (Unicode::strlen($name) > 20) {
497
    $name = Unicode::truncate($name, 15, FALSE, TRUE);
498 499 500 501
    $variables['truncated'] = TRUE;
  }
  else {
    $variables['truncated'] = FALSE;
502
  }
503
  $variables['name'] = $name;
504
  $variables['profile_access'] = \Drupal::currentUser()->hasPermission('access user profiles');
505

506
  $external = FALSE;
507 508 509
  // Populate link path and attributes if appropriate.
  if ($variables['uid'] && $variables['profile_access']) {
    // We are linking to a local user.
510
    $variables['attributes']['title'] = t('View user profile.');
511 512 513 514 515 516
    $variables['link_path'] = 'user/' . $variables['uid'];
  }
  elseif (!empty($account->homepage)) {
    // Like the 'class' attribute, the 'rel' attribute can hold a
    // space-separated set of values, so initialize it as an array to make it
    // easier for other preprocess functions to append to it.
517
    $variables['attributes']['rel'] = 'nofollow';
518 519
    $variables['link_path'] = $account->homepage;
    $variables['homepage'] = $account->homepage;
520
    $external = TRUE;
521
  }
522
  // We have a link path, so we should generate a URL.
523
  if (isset($variables['link_path'])) {
524 525 526 527 528
    if ($external) {
      $variables['attributes']['href'] = Url::fromUri($variables['link_path'], $variables['link_options'])
        ->toString();
    }
    else {
529
      $variables['attributes']['href'] = Url::fromRoute('entity.user.canonical', [
530
        'user' => $variables['uid'],
531
      ])->toString();
532
    }
533 534 535
  }
}

536
/**
537
 * Finalizes the login process and logs in a user.
538
 *
539 540 541
 * The function logs in the user, records a watchdog message about the new
 * session, saves the login timestamp, calls hook_user_login(), and generates a
 * new session.
542
 *
543
 * The current user is replaced with the passed in account.
544
 *
545
 * @param \Drupal\user\UserInterface $account
546
 *   The account to log in.
547 548
 *
 * @see hook_user_login()
549
 */
550
function user_login_finalize(UserInterface $account) {
551
  \Drupal::currentUser()->setAccount($account);
552
  \Drupal::logger('user')->notice('Session opened for %name.', ['%name' => $account->getUsername()]);
553 554
  // Update the user table timestamp noting user has logged in.
  // This is also used to invalidate one-time login links.
555
  $account->setLastLoginTime(REQUEST_TIME);
556
  \Drupal::entityManager()
557
    ->getStorage('user')
558
    ->updateLastLoginTimestamp($account);
559

560
  // Regenerate the session ID to prevent against session fixation attacks.
561 562 563
  // This is called before hook_user_login() in case one of those functions
  // fails or incorrectly does a redirect which would leave the old session
  // in place.
564
  \Drupal::service('session')->migrate();
565
  \Drupal::service('session')->set('uid', $account->id());
566
  \Drupal::moduleHandler()->invokeAll('user_login', [$account]);
567 568
}

569 570 571
/**
 * Implements hook_user_login().
 */
572
function user_user_login($account) {
573 574 575 576 577 578 579 580 581 582 583 584 585 586
  // Reset static cache of default variables in template_preprocess() to reflect
  // the new user.
  drupal_static_reset('template_preprocess');
}

/**
 * Implements hook_user_logout().
 */
function user_user_logout($account) {
  // Reset static cache of default variables in template_preprocess() to reflect
  // the new user.
  drupal_static_reset('template_preprocess');
}

587
/**
588
 * Generates a unique URL for a user to log in and reset their password.
589
 *
590 591
 * @param \Drupal\user\UserInterface $account
 *   An object containing the user account.
592 593 594
 * @param array $options
 *   (optional) A keyed array of settings. Supported options are:
 *   - langcode: A language code to be used when generating locale-sensitive
595
 *    URLs. If langcode is NULL the users preferred language is used.
596
 *
597
 * @return string
598 599 600
 *   A unique URL that provides a one-time log in for the user, from which
 *   they can change their password.
 */
601
function user_pass_reset_url($account, $options = []) {
602
  $timestamp = REQUEST_TIME;
603
  $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
604
  return \Drupal::url('user.reset',
605
    [
606 607
      'uid' => $account->id(),
      'timestamp' => $timestamp,
608
      'hash' => user_pass_rehash($account, $timestamp),
609 610
    ],
    [
611 612
      'absolute' => TRUE,
      'language' => \Drupal::languageManager()->getLanguage($langcode)
613
    ]
614
  );
615 616
}

617
/**
618 619
 * Generates a URL to confirm an account cancellation request.
 *
620
 * @param \Drupal\user\UserInterface $account
621
 *   The user account object.
622 623 624
 * @param array $options
 *   (optional) A keyed array of settings. Supported options are:
 *   - langcode: A language code to be used when generating locale-sensitive
625
 *     URLs. If langcode is NULL the users preferred language is used.
626
 *
627
 * @return string
628 629
 *   A unique URL that may be used to confirm the cancellation of the user
 *   account.
630 631
 *
 * @see user_mail_tokens()
632
 * @see \Drupal\user\Controller\UserController::confirmCancel()
633
 */
634
function user_cancel_url(UserInterface $account, $options = []) {
635
  $timestamp = REQUEST_TIME;
636
  $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
637
  $url_options = ['absolute' => TRUE, 'language' => \Drupal::languageManager()->getLanguage($langcode)];
638 639 640
  return \Drupal::url('user.cancel_confirm', [
    'user' => $account->id(),
    'timestamp' => $timestamp,
641
    'hashed_pass' => user_pass_rehash($account, $timestamp)
642
  ], $url_options);
643 644
}

645 646 647 648 649 650
/**
 * Creates a unique hash value for use in time-dependent per-user URLs.
 *
 * This hash is normally used to build a unique and secure URL that is sent to
 * the user by email for purposes such as resetting the user's password. In
 * order to validate the URL, the same hash can be generated again, from the
651 652 653
 * same information, and compared to the hash value from the URL. The hash
 * contains the time stamp, the user's last login time, the numeric user ID,
 * and the user's email address.
654 655
 * For a usage example, see user_cancel_url() and
 * \Drupal\user\Controller\UserController::confirmCancel().
656
 *
657 658
 * @param \Drupal\user\UserInterface $account
 *   An object containing the user account.
659 660
 * @param int $timestamp
 *   A UNIX timestamp, typically REQUEST_TIME.
661
 *
662
 * @return string
663 664
 *   A string that is safe for use in URLs and SQL statements.
 */
665 666 667 668 669 670
function user_pass_rehash(UserInterface $account, $timestamp) {
  $data = $timestamp;
  $data .= $account->getLastLoginTime();
  $data .= $account->id();
  $data .= $account->getEmail();
  return Crypt::hmacBase64($data, Settings::getHashSalt() . $account->getPassword());
671 672
}

673
/**
674 675 676 677 678 679
 * Cancel a user account.
 *
 * Since the user cancellation process needs to be run in a batch, either
 * Form API will invoke it, or batch_process() needs to be invoked after calling
 * this function and should define the path to redirect to.
 *
680
 * @param array $edit
681
 *   An array of submitted form values.
682
 * @param int $uid
683
 *   The user ID of the user account to cancel.
684
 * @param string $method
685
 *   The account cancellation method to use.
686
 *
687
 * @see _user_cancel()
688
 */
689
function user_cancel($edit, $uid, $method) {
690
  $account = User::load($uid);
691 692

  if (!$account) {
693
    \Drupal::messenger()->addError(t('The user account %id does not exist.', ['%id' => $uid]));
694
    \Drupal::logger('user')->error('Attempted to cancel non-existing user account: %id.', ['%id' => $uid]);
695 696 697 698
    return;
  }

  // Initialize batch (to set title).
699
  $batch = [
700
    'title' => t('Cancelling account'),
701 702
    'operations' => [],
  ];
703 704
  batch_set($batch);

705
  // When the 'user_cancel_delete' method is used, user_delete() is called,
706 707 708
  // which invokes hook_ENTITY_TYPE_predelete() and hook_ENTITY_TYPE_delete()
  // for the user entity. Modules should use those hooks to respond to the
  // account deletion.
709 710
  if ($method != 'user_cancel_delete') {
    // Allow modules to add further sets to this batch.
711
    \Drupal::moduleHandler()->invokeAll('user_cancel', [$edit, $account, $method]);
712
  }
713 714

  // Finish the batch and actually cancel the account.
715
  $batch = [
716
    'title' => t('Cancelling user account'),
717 718 719 720
    'operations' => [
      ['_user_cancel', [$edit, $account, $method]],
    ],
  ];
721 722

  // After cancelling account, ensure that user is logged out.
723
  if ($account->id() == \Drupal::currentUser()->id()) {
724 725 726 727 728
    // Batch API stores data in the session, so use the finished operation to
    // manipulate the current user's session id.
    $batch['finished'] = '_user_cancel_session_regenerate';
  }

729 730 731 732 733 734 735
  batch_set($batch);

  // Batch processing is either handled via Form API or has to be invoked
  // manually.
}

/**
736 737 738
 * Implements callback_batch_operation().
 *
 * Last step for cancelling a user account.
739 740 741
 *
 * Since batch and session API require a valid user account, the actual
 * cancellation of a user account needs to happen last.
742 743 744 745 746 747
 * @param array $edit
 *   An array of submitted form values.
 * @param \Drupal\user\UserInterface $account
 *   The user ID of the user account to cancel.
 * @param string $method
 *   The account cancellation method to use.
748 749 750 751
 *
 * @see user_cancel()
 */
function _user_cancel($edit, $account, $method) {
752
  $logger = \Drupal::logger('user');
753 754 755 756 757 758 759 760 761

  switch ($method) {
    case 'user_cancel_block':
    case 'user_cancel_block_unpublish':
    default:
      // Send account blocked notification if option was checked.
      if (!empty($edit['user_cancel_notify'])) {
        _user_mail_notify('status_blocked', $account);
      }
762
      $account->block();
763
      $account->save();
764
      \Drupal::messenger()->addStatus(t('%name has been disabled.', ['%name' => $account->getDisplayName()]));
765
      $logger->notice('Blocked user: %name %email.', ['%name' => $account->getAccountName(), '%email' => '<' . $account->getEmail() . '>']);
766 767 768 769 770 771 772 773
      break;

    case 'user_cancel_reassign':
    case 'user_cancel_delete':
      // Send account canceled notification if option was checked.
      if (!empty($edit['user_cancel_notify'])) {
        _user_mail_notify('status_canceled', $account);
      }
774
      $account->delete();
775
      \Drupal::messenger()->addStatus(t('%name has been deleted.', ['%name' => $account->getDisplayName()]));
776
      $logger->notice('Deleted user: %name %email.', ['%name' => $account->getAccountName(), '%email' => '<' . $account->getEmail() . '>']);
777 778 779
      break;
  }

780 781 782 783
  // After cancelling account, ensure that user is logged out. We can't destroy
  // their session though, as we might have information in it, and we can't
  // regenerate it because batch API uses the session ID, we will regenerate it
  // in _user_cancel_session_regenerate().
784 785
  if ($account->id() == \Drupal::currentUser()->id()) {
    \Drupal::currentUser()->setAccount(new AnonymousUserSession());
786
  }
787 788
}

789
/**
790 791
 * Implements callback_batch_finished().
 *
792 793 794 795 796 797 798
 * Finished batch processing callback for cancelling a user account.
 *
 * @see user_cancel()
 */
function _user_cancel_session_regenerate() {
  // Regenerate the users session instead of calling session_destroy() as we
  // want to preserve any messages that might have been set.
799
  \Drupal::service('session')->migrate();
800 801
}

802 803 804 805 806 807 808 809 810 811 812 813
/**
 * Helper function to return available account cancellation methods.
 *
 * See documentation of hook_user_cancel_methods_alter().
 *
 * @return array
 *   An array containing all account cancellation methods as form elements.
 *
 * @see hook_user_cancel_methods_alter()
 * @see user_admin_settings()
 */
function user_cancel_methods() {
814
  $user_settings = \Drupal::config('user.settings');
815
  $anonymous_name = $user_settings->get('anonymous');
816 817
  $methods = [
    'user_cancel_block' => [
818
      'title' => t('Disable the account and keep its content.'),
819
      'description' => t('Your account will be blocked and you will no longer be able to log in. All of your content will remain attributed to your username.'),
820 821
    ],
    'user_cancel_block_unpublish' => [
822 823
      'title' => t('Disable the account and unpublish its content.'),
      'description' => t('Your account will be blocked and you will no longer be able to log in. All of your content will be hidden from everyone but administrators.'),
824 825 826 827 828 829
    ],
    'user_cancel_reassign' => [
      'title' => t('Delete the account and make its content belong to the %anonymous-name user.', ['%anonymous-name' => $anonymous_name]),
      'description' => t('Your account will be removed and all account information deleted. All of your content will be assigned to the %anonymous-name user.', ['%anonymous-name' => $anonymous_name]),
    ],
    'user_cancel_delete' => [
830 831
      'title' => t('Delete the account and its content.'),
      'description' => t('Your account will be removed and all account information deleted. All of your content will also be deleted.'),
832
      'access' => \Drupal::currentUser()->hasPermission('administer users'),
833 834
    ],
  ];
835
  // Allow modules to customize account cancellation methods.
836
  \Drupal::moduleHandler()->alter('user_cancel_methods', $methods);
837 838

  // Turn all methods into real form elements.
839 840
  $form = [
    '#options' => [],
841
    '#default_value' => $user_settings->get('cancel_method'),
842
  ];
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
  foreach ($methods as $name => $method) {
    $form['#options'][$name] = $method['title'];
    // Add the description for the confirmation form. This description is never
    // shown for the cancel method option, only on the confirmation form.
    // Therefore, we use a custom #confirm_description property.
    if (isset($method['description'])) {
      $form[$name]['#confirm_description'] = $method['description'];
    }
    if (isset($method['access'])) {
      $form[$name]['#access'] = $method['access'];
    }
  }
  return $form;
}

858 859 860
/**
 * Delete a user.
 *
861
 * @param int $uid
862 863 864
 *   A user ID.
 */
function user_delete($uid) {
865
  user_delete_multiple([$uid]);
866 867 868 869 870
}

/**
 * Delete multiple user accounts.
 *
871
 * @param int[] $uids
872
 *   An array of user IDs.
873
 *
874