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

3
use Drupal\Component\Utility\Crypt;
4
use Drupal\Component\Utility\Unicode;
5
use Drupal\Core\Asset\AttachedAssetsInterface;
6
use Drupal\Core\Cache\Cache;
7 8
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Field\BaseFieldDefinition;
9
use Drupal\Core\Form\FormStateInterface;
10
use Drupal\Core\Render\Element;
11
use Drupal\Core\Routing\RouteMatchInterface;
12
use Drupal\Core\Session\AccountInterface;
13
use Drupal\Core\Session\AnonymousUserSession;
14
use Drupal\Core\Site\Settings;
15
use Drupal\Core\Url;
16
use Drupal\user\Entity\Role;
17
use Drupal\user\Entity\User;
18
use Drupal\user\RoleInterface;
19
use Drupal\user\UserInterface;
Crell's avatar
Crell committed
20

21 22 23 24 25
/**
 * @file
 * Enables the user registration and login system.
 */

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
const USERNAME_MAX_LENGTH = 60;
32

33 34 35
/**
 * Only administrators can create user accounts.
 */
36
const USER_REGISTER_ADMINISTRATORS_ONLY = 'admin_only';
37 38 39 40

/**
 * Visitors can create their own accounts.
 */
41
const USER_REGISTER_VISITORS = 'visitors';
42 43 44 45 46

/**
 * Visitors can create accounts, but they don't become active without
 * administrative approval.
 */
47
const USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL = 'visitors_admin_approval';
48

49
/**
50
 * Implements hook_help().
51
 */
52
function user_help($route_name, RouteMatchInterface $route_match) {
53 54
  switch ($route_name) {
    case 'help.page.user':
55 56
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
57
      $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>.', array('!user_docs' => 'https://www.drupal.org/documentation/modules/user')) . '</p>';
58 59 60
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Creating and managing users') . '</dt>';
61
      $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.', array('!people' => \Drupal::url('entity.user.collection'))) . '</dd>';
62
      $output .= '<dt>' . t('Configuring user roles') . '</dt>';
63
      $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>.', array('!roles' => \Drupal::url('entity.user_role.collection'))) . '</dd>';
64 65 66
      $output .= '<dt>' . t('Setting permissions') . '</dt>';
      $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).', array('!permissions_user' => \Drupal::url('user.admin_permissions'))) . '</dd>';
      $output .= '<dt>' . t('Managing account settings') . '</dt>';
67
      $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).', array('!accounts'  => \Drupal::url('entity.user.admin_form'))) . '</dd>';
68
      $output .= '<dt>' . t('Managing user account fields') . '</dt>';
69
      $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>.', array('!field_help'=>(\Drupal::moduleHandler()->moduleExists('field')) ? \Drupal::url('help.page', array('name' => 'field')) : '#', '!accounts' => \Drupal::url('entity.user.admin_form'))) . '</dd>';
70 71
      $output .= '</dl>';
      return $output;
72 73

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

    case 'user.admin_permissions':
77
      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.', array('!role' => \Drupal::url('entity.user_role.collection'), '!settings' => \Drupal::url('entity.user.admin_form'))) . '</p>';
78

79
    case 'entity.user_role.collection':
80
      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.', array('!permissions' => \Drupal::url('user.admin_permissions'))) . '</p>';
81

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

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

88
    case 'entity.entity_view_display.user.default':
89 90 91
      return '<p>' . t('This form lets administrators configure how fields should be displayed when rendering a user profile page.') . '</p>';
  }
}
92

93
/**
94
 * Implements hook_theme().
95 96 97
 */
function user_theme() {
  return array(
98
    'user' => array(
99
      'render element' => 'elements',
100
    ),
101
    'username' => array(
102
      'variables' => array('account' => NULL, 'attributes' => array(), 'link_options' => array()),
103
    ),
104 105 106
  );
}

107
/**
108
 * Implements hook_js_settings_alter().
109
 */
110
function user_js_settings_alter(&$settings, AttachedAssetsInterface $assets) {
111 112 113
  // 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.
114 115 116
  // Similarly, provide a permissions hash, so that permission-dependent data
  // can be reliably cached on the client side.
  $user = \Drupal::currentUser();
117
  $settings['user']['uid'] = $user->id();
118
  $settings['user']['permissionsHash'] = \Drupal::service('user_permissions_hash_generator')->generate($user);
119 120
}

121 122 123 124 125 126 127 128 129
/**
 * 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() {
130 131
  $field_definitions = \Drupal::entityManager()->getFieldDefinitions('user', 'user');
  return isset($field_definitions['user_picture']);
132 133
}

134
/**
135
 * Implements hook_entity_extra_field_info().
136
 */
137
function user_entity_extra_field_info() {
138 139 140 141 142 143 144 145 146 147
  $fields['user']['user']['form']['account'] = array(
    'label' => t('User name and password'),
    'description' => t('User module account form elements.'),
    'weight' => -10,
  );
  $fields['user']['user']['form']['language'] = array(
    'label' => t('Language settings'),
    'description' => t('User module form element.'),
    'weight' => 0,
  );
148
  if (\Drupal::config('system.date')->get('timezone.user.configurable')) {
149 150 151 152 153 154 155 156 157 158 159
    $fields['user']['user']['form']['timezone'] = array(
      'label' => t('Timezone'),
      'description' => t('System module form element.'),
      'weight' => 6,
    );
  }

  $fields['user']['user']['display']['member_for'] = array(
    'label' => t('Member for'),
    'description' => t('User module \'member for\' view element.'),
    'weight' => 5,
160
  );
161

162
  return $fields;
163 164
}

165
/**
166
 * Loads multiple users based on certain conditions.
167
 *
168 169 170
 * 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.
171
 *
172 173
 * @param array $uids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
174
 * @param bool $reset
175 176
 *   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.
177
 *
178
 * @return array
179 180
 *   An array of user objects, indexed by uid.
 *
181
 * @see entity_load_multiple()
182
 * @see \Drupal\user\Entity\User::load()
183 184
 * @see user_load_by_mail()
 * @see user_load_by_name()
185
 * @see \Drupal\Core\Entity\Query\QueryInterface
186 187 188
 *
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\user\Entity\User::loadMultiple().
189
 */
190
function user_load_multiple(array $uids = NULL, $reset = FALSE) {
191 192 193 194
  if ($reset) {
    \Drupal::entityManager()->getStorage('user')->resetCache($uids);
  }
  return User::loadMultiple($uids);
195
}
196 197

/**
198 199
 * Loads a user object.
 *
200
 * @param int $uid
201
 *   Integer specifying the user ID to load.
202
 * @param bool $reset
203 204 205
 *   TRUE to reset the internal cache and load from the database; FALSE
 *   (default) to load from the internal cache, if set.
 *
206
 * @return \Drupal\user\UserInterface
207
 *   A fully-loaded user object upon successful user load, or NULL if the user
208 209
 *   cannot be loaded.
 *
210 211 212
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\user\Entity\User::load().
 *
213
 * @see \Drupal\user\Entity\User::loadMultiple()
214 215
 */
function user_load($uid, $reset = FALSE) {
216 217 218 219
  if ($reset) {
    \Drupal::entityManager()->getStorage('user')->resetCache(array($uid));
  }
  return User::load($uid);
220 221 222
}

/**
223
 * Fetches a user object by email address.
224
 *
225
 * @param string $mail
226
 *   String with the account's email address.
227
 * @return object|bool
228 229 230
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
231
 * @see \Drupal\user\Entity\User::loadMultiple()
232 233
 */
function user_load_by_mail($mail) {
234
  $users = entity_load_multiple_by_properties('user', array('mail' => $mail));
235
  return $users ? reset($users) : FALSE;
236 237 238
}

/**
239
 * Fetches a user object by account name.
240
 *
241
 * @param string $name
242
 *   String with the account's user name.
243
 * @return object|bool
244 245 246
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
247
 * @see \Drupal\user\Entity\User::loadMultiple()
248 249
 */
function user_load_by_name($name) {
250
  $users = entity_load_multiple_by_properties('user', array('name' => $name));
251
  return $users ? reset($users) : FALSE;
252 253
}

254 255
/**
 * Verify the syntax of the given name.
256 257 258 259 260 261 262 263
 *
 * @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.
 *
264
 */
265
function user_validate_name($name) {
266
  $definition = BaseFieldDefinition::create('string')
267
    ->addConstraint('UserName', array());
268
  $data = \Drupal::typedDataManager()->create($definition);
269 270 271 272
  $data->setValue($name);
  $violations = $data->validate();
  if (count($violations) > 0) {
    return $violations[0]->getMessage();
273
  }
274 275
}

276 277 278
/**
 * Generate a random alphanumeric password.
 */
279 280
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
281 282
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
283
  // of 'I', 1, and 'l'.
284
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
285

286 287
  // Zero-based count of characters in the allowable list:
  $len = strlen($allowable_characters) - 1;
288

289 290
  // Declare the password as a blank string.
  $pass = '';
291

292
  // Loop the number of times specified by $length.
293
  for ($i = 0; $i < $length; $i++) {
294 295 296 297
    do {
      // Find a secure random number within the range needed.
      $index = ord(Crypt::randomBytes(1));
    } while ($index > $len);
298 299 300

    // Each iteration, pick a random character from the
    // allowable string and append it to the password:
301
    $pass .= $allowable_characters[$index];
302 303 304
  }

  return $pass;
305 306
}

307 308 309
/**
 * Determine the permissions for one or more roles.
 *
310 311
 * @param array $roles
 *   An array of role IDs.
312
 *
313 314 315
 * @return array
 *   An array indexed by role ID. Each value is an array of permission strings
 *   for the given role.
316
 */
317 318 319 320
function user_role_permissions(array $roles) {
  if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
    return _user_role_permissions_update($roles);
  }
321
  $entities = Role::loadMultiple($roles);
322
  $role_permissions = array();
323
  foreach ($roles as $rid) {
324
    $role_permissions[$rid] = isset($entities[$rid]) ? $entities[$rid]->getPermissions() : array();
325
  }
326 327
  return $role_permissions;
}
328

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
/**
 * 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) {
  $role_permissions = array();
  foreach ($roles as $rid) {
346
    $role_permissions[$rid] = \Drupal::config("user.role.$rid")->get('permissions') ?: array();
347 348 349 350
  }
  return $role_permissions;
}

351
/**
352
 * Checks for usernames blocked by user administration.
353
 *
354
 * @param string $name
355 356
 *   A string containing a name of the user.
 *
357 358
 * @return bool
 *   TRUE if the user is blocked, FALSE otherwise.
359 360
 */
function user_is_blocked($name) {
361 362
  return (bool) \Drupal::entityQuery('user')
    ->condition('name', $name)
363
    ->condition('status', 0)
364
    ->execute();
365 366
}

367
/**
368
 * Implements hook_ENTITY_TYPE_view() for user entities.
369
 */
370
function user_user_view(array &$build, UserInterface $account, EntityViewDisplayInterface $display) {
371
  if ($display->getComponent('member_for')) {
372
    $build['member_for'] = array(
373
      '#type' => 'item',
374
      '#markup' => '<h4 class="label">' . t('Member for') . '</h4> ' . \Drupal::service('date.formatter')->formatTimeDiffSince($account->getCreatedTime()),
375 376
    );
  }
377 378
}

379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
/**
 * 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()]));
      }
    }
  }
}

396
/**
397
 * Implements hook_preprocess_HOOK() for block templates.
398 399
 */
function user_preprocess_block(&$variables) {
400
  if ($variables['configuration']['provider'] == 'user') {
401
    switch ($variables['elements']['#plugin_id']) {
402
      case 'user_login_block':
403
        $variables['attributes']['role'] = 'form';
404 405 406 407 408
        break;
    }
  }
}

409 410 411
/**
 * Format a username.
 *
412
 * @param \Drupal\Core\Session\AccountInterface $account
413 414
 *   The account object for the user whose name is to be formatted.
 *
415
 * @return string
416
 *   An unsanitized string with the username to display. The code receiving
417
 *   this result must ensure that \Drupal\Component\Utility\SafeMarkup::checkPlain()
418
 *   is called on it before it is printed to the page.
419
 *
420 421 422 423
 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
 *   Use \Drupal\Core\Session\AccountInterface::getUsername().
 *
 * @todo Remove usage in https://www.drupal.org/node/2311219.
424
 */
425 426
function user_format_name(AccountInterface $account) {
  return $account->getUsername();
427 428
}

429 430 431 432 433 434 435
/**
 * Implements hook_template_preprocess_default_variables_alter().
 *
 * @see user_user_login()
 * @see user_user_logout()
 */
function user_template_preprocess_default_variables_alter(&$variables) {
436
  $user = \Drupal::currentUser();
437 438

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

442
  $variables['is_admin'] = $user->hasPermission('access administration pages');
443
  $variables['logged_in'] = $user->isAuthenticated();
444 445
}

446
/**
447 448 449 450 451 452
 * Prepares variables for username templates.
 *
 * Default template: username.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
453
 *   - account: The user account (\Drupal\Core\Session\AccountInterface).
454
 *
455
 * Modules that make any changes to variables like 'name' or 'extra' must ensure
456
 * that the final string is safe to include directly in the output by using
457
 * \Drupal\Component\Utility\SafeMarkup::checkPlain() or
458
 * \Drupal\Component\Utility\Xss::filter().
459 460
 */
function template_preprocess_username(&$variables) {
461
  $account = $variables['account'] ?: new AnonymousUserSession();
462 463

  $variables['extra'] = '';
464 465 466 467 468
  $variables['uid'] = $account->id();
  if (empty($variables['uid'])) {
    if (theme_get_setting('features.comment_user_verification')) {
      $variables['extra'] = ' (' . t('not verified') . ')';
    }
469 470 471 472 473 474 475
  }

  // 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.
476
  $name = $variables['name_raw'] = $account->getUsername();
477
  if (Unicode::strlen($name) > 20) {
478
    $name = Unicode::truncate($name, 15, FALSE, TRUE);
479 480 481 482
    $variables['truncated'] = TRUE;
  }
  else {
    $variables['truncated'] = FALSE;
483
  }
484
  $variables['name'] = $name;
485
  $variables['profile_access'] = \Drupal::currentUser()->hasPermission('access user profiles');
486

487
  $external = FALSE;
488 489 490
  // Populate link path and attributes if appropriate.
  if ($variables['uid'] && $variables['profile_access']) {
    // We are linking to a local user.
491
    $variables['attributes']['title'] = t('View user profile.');
492 493 494 495 496 497
    $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.
498
    $variables['attributes']['rel'] = 'nofollow';
499 500
    $variables['link_path'] = $account->homepage;
    $variables['homepage'] = $account->homepage;
501
    $external = TRUE;
502
  }
503
  // We have a link path, so we should generate a URL.
504
  if (isset($variables['link_path'])) {
505 506 507 508 509 510 511 512 513
    if ($external) {
      $variables['attributes']['href'] = Url::fromUri($variables['link_path'], $variables['link_options'])
        ->toString();
    }
    else {
      $variables['attributes']['href'] = Url::fromRoute('entity.user.canonical', array(
        'user' => $variables['uid'],
      ))->toString();
    }
514 515 516
  }
}

517
/**
518
 * Finalizes the login process and logs in a user.
519
 *
520 521 522
 * 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.
523
 *
524
 * The current user is replaced with the passed in account.
525
 *
526
 * @param \Drupal\user\UserInterface $account
527
 *   The account to log in.
528 529
 *
 * @see hook_user_login()
530
 */
531
function user_login_finalize(UserInterface $account) {
532
  \Drupal::currentUser()->setAccount($account);
533
  \Drupal::logger('user')->notice('Session opened for %name.', array('%name' => $account->getUsername()));
534 535
  // Update the user table timestamp noting user has logged in.
  // This is also used to invalidate one-time login links.
536
  $account->setLastLoginTime(REQUEST_TIME);
537
  \Drupal::entityManager()
538
    ->getStorage('user')
539
    ->updateLastLoginTimestamp($account);
540

541
  // Regenerate the session ID to prevent against session fixation attacks.
542 543 544
  // 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.
545
  \Drupal::service('session')->migrate();
546
  \Drupal::service('session')->set('uid', $account->id());
547
  \Drupal::moduleHandler()->invokeAll('user_login', array($account));
548 549
}

550 551 552
/**
 * Implements hook_user_login().
 */
553
function user_user_login($account) {
554 555 556 557 558 559 560 561 562 563 564 565 566 567
  // 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');
}

568 569 570
/**
 * Generates a unique URL for a user to login and reset their password.
 *
571 572
 * @param \Drupal\user\UserInterface $account
 *   An object containing the user account.
573 574 575
 * @param array $options
 *   (optional) A keyed array of settings. Supported options are:
 *   - langcode: A language code to be used when generating locale-sensitive
576
 *    URLs. If langcode is NULL the users preferred language is used.
577
 *
578
 * @return string
579 580 581
 *   A unique URL that provides a one-time log in for the user, from which
 *   they can change their password.
 */
582
function user_pass_reset_url($account, $options = array()) {
583
  $timestamp = REQUEST_TIME;
584
  $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
585 586 587 588
  return \Drupal::url('user.reset',
    array(
      'uid' => $account->id(),
      'timestamp' => $timestamp,
589
      'hash' => user_pass_rehash($account, $timestamp),
590 591 592 593 594 595
    ),
    array(
      'absolute' => TRUE,
      'language' => \Drupal::languageManager()->getLanguage($langcode)
    )
  );
596 597
}

598
/**
599 600
 * Generates a URL to confirm an account cancellation request.
 *
601
 * @param \Drupal\user\UserInterface $account
602
 *   The user account object.
603 604 605
 * @param array $options
 *   (optional) A keyed array of settings. Supported options are:
 *   - langcode: A language code to be used when generating locale-sensitive
606
 *     URLs. If langcode is NULL the users preferred language is used.
607
 *
608
 * @return string
609 610
 *   A unique URL that may be used to confirm the cancellation of the user
 *   account.
611 612
 *
 * @see user_mail_tokens()
613
 * @see \Drupal\user\Controller\UserController::confirmCancel()
614
 */
615
function user_cancel_url(UserInterface $account, $options = array()) {
616
  $timestamp = REQUEST_TIME;
617
  $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
618
  $url_options = array('absolute' => TRUE, 'language' => \Drupal::languageManager()->getLanguage($langcode));
619 620 621
  return \Drupal::url('user.cancel_confirm', [
    'user' => $account->id(),
    'timestamp' => $timestamp,
622
    'hashed_pass' => user_pass_rehash($account, $timestamp)
623
  ], $url_options);
624 625
}

626 627 628 629 630 631
/**
 * 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
632 633 634
 * 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.
635 636
 * For a usage example, see user_cancel_url() and
 * \Drupal\user\Controller\UserController::confirmCancel().
637
 *
638 639
 * @param \Drupal\user\UserInterface $account
 *   An object containing the user account.
640 641
 * @param int $timestamp
 *   A UNIX timestamp, typically REQUEST_TIME.
642
 *
643
 * @return string
644 645
 *   A string that is safe for use in URLs and SQL statements.
 */
646 647 648 649 650 651
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());
652 653
}

654
/**
655 656 657 658 659 660
 * 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.
 *
661
 * @param array $edit
662
 *   An array of submitted form values.
663
 * @param int $uid
664
 *   The user ID of the user account to cancel.
665
 * @param string $method
666
 *   The account cancellation method to use.
667
 *
668
 * @see _user_cancel()
669
 */
670
function user_cancel($edit, $uid, $method) {
671
  $account = User::load($uid);
672 673 674

  if (!$account) {
    drupal_set_message(t('The user account %id does not exist.', array('%id' => $uid)), 'error');
675
    \Drupal::logger('user')->error('Attempted to cancel non-existing user account: %id.', array('%id' => $uid));
676 677 678 679 680 681 682 683 684 685
    return;
  }

  // Initialize batch (to set title).
  $batch = array(
    'title' => t('Cancelling account'),
    'operations' => array(),
  );
  batch_set($batch);

686
  // When the 'user_cancel_delete' method is used, user_delete() is called,
687 688 689
  // 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.
690 691
  if ($method != 'user_cancel_delete') {
    // Allow modules to add further sets to this batch.
692
    \Drupal::moduleHandler()->invokeAll('user_cancel', array($edit, $account, $method));
693
  }
694 695 696 697 698 699 700 701

  // Finish the batch and actually cancel the account.
  $batch = array(
    'title' => t('Cancelling user account'),
    'operations' => array(
      array('_user_cancel', array($edit, $account, $method)),
    ),
  );
702 703

  // After cancelling account, ensure that user is logged out.
704
  if ($account->id() == \Drupal::currentUser()->id()) {
705 706 707 708 709
    // 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';
  }

710 711 712 713 714 715 716
  batch_set($batch);

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

/**
717 718 719
 * Implements callback_batch_operation().
 *
 * Last step for cancelling a user account.
720 721 722
 *
 * Since batch and session API require a valid user account, the actual
 * cancellation of a user account needs to happen last.
723 724 725 726 727 728
 * @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.
729 730 731 732
 *
 * @see user_cancel()
 */
function _user_cancel($edit, $account, $method) {
733
  $logger = \Drupal::logger('user');
734 735 736 737 738 739 740 741 742

  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);
      }
743
      $account->block();
744
      $account->save();
745
      drupal_set_message(t('%name has been disabled.', array('%name' => $account->getUsername())));
746
      $logger->notice('Blocked user: %name %email.', array('%name' => $account->getUsername(), '%email' => '<' . $account->getEmail() . '>'));
747 748 749 750 751 752 753 754
      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);
      }
755
      $account->delete();
756
      drupal_set_message(t('%name has been deleted.', array('%name' => $account->getUsername())));
757
      $logger->notice('Deleted user: %name %email.', array('%name' => $account->getUsername(), '%email' => '<' . $account->getEmail() . '>'));
758 759 760
      break;
  }

761 762 763 764
  // 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().
765 766
  if ($account->id() == \Drupal::currentUser()->id()) {
    \Drupal::currentUser()->setAccount(new AnonymousUserSession());
767
  }
768 769
}

770
/**
771 772
 * Implements callback_batch_finished().
 *
773 774 775 776 777 778 779
 * 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.
780
  \Drupal::service('session')->migrate();
781 782
}

783 784 785 786 787 788 789 790 791 792 793 794
/**
 * 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() {
795
  $user_settings = \Drupal::config('user.settings');
796 797 798 799
  $anonymous_name = $user_settings->get('anonymous');
  $methods = array(
    'user_cancel_block' => array(
      'title' => t('Disable the account and keep its content.'),
800
      '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.'),
801 802 803 804 805 806 807 808 809 810 811 812
    ),
    'user_cancel_block_unpublish' => array(
      '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.'),
    ),
    'user_cancel_reassign' => array(
      'title' => t('Delete the account and make its content belong to the %anonymous-name user.', array('%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.', array('%anonymous-name' => $anonymous_name)),
    ),
    'user_cancel_delete' => array(
      '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.'),
813
      'access' => \Drupal::currentUser()->hasPermission('administer users'),
814 815 816
    ),
  );
  // Allow modules to customize account cancellation methods.
817
  \Drupal::moduleHandler()->alter('user_cancel_methods', $methods);
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838

  // Turn all methods into real form elements.
  $form = array(
    '#options' => array(),
    '#default_value' => $user_settings->get('cancel_method'),
  );
  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;
}

839 840 841
/**
 * Delete a user.
 *
842
 * @param int $uid
843 844 845 846 847 848 849 850 851
 *   A user ID.
 */
function user_delete($uid) {
  user_delete_multiple(array($uid));
}

/**
 * Delete multiple user accounts.
 *
852
 * @param int[] $uids
853
 *   An array of user IDs.
854
 *
855 856
 * @see hook_ENTITY_TYPE_predelete()
 * @see hook_ENTITY_TYPE_delete()
857 858
 */
function user_delete_multiple(array $uids) {
859
  entity_delete_multiple('user', $uids);
860 861
}

862
/**
863 864 865 866
 * Generate an array for rendering the given user.
 *
 * When viewing a user profile, the $page array contains:
 *
867 868
 * - $page['content']['member_for']:
 *   Contains the default "Member for" profile data for a user.
869
 * - $page['content']['#user']:
870 871
 *   The user account of the profile being viewed.
 *
872
 * To theme user profiles, copy core/modules/user/templates/user.html.twig
873
 * to your theme directory, and edit it as instructed in that file's comments.
874
 *
875
 * @param \Drupal\user\UserInterface $account
876
 *   A user object.
877
 * @param string $view_mode
878
 *   View mode, e.g. 'full'.
879
 * @param string|null $langcode
880 881
 *   (optional) A language code to use for rendering. Defaults to the global
 *   content language of the current request.
882
 *
883
 * @return array
884 885
 *   An array as expected by drupal_render().
 */
886
function user_view($account, $view_mode = 'full', $langcode = NULL) {
887
  return entity_view($account, $view_mode, $langcode);
888 889 890
}

/**
891
 * Constructs a drupal_render() style array from an array of loaded users.
892
 *
893
 * @param \Drupal\user\UserInterface[] $account
894
 *   An array of user accounts as returned by User::loadMultiple().
895
 * @param string $view_mode
896
 *   (optional) View mode, e.g., 'full', 'teaser', etc. Defaults to 'teaser.'
897
 * @param string|null $langcode
898 899
 *   (optional) A language code to use for rendering. Defaults to the global
 *   content language of the current request.
900
 *
901
 * @return array
902
 *   An array in the format expected by drupal_render().
903
 */
904
function user_view_multiple($accounts, $view_mode = 'full', $langcode = NULL) {
905
  return entity_view_multiple($accounts, $view_mode, $langcode);
906 907
}

908
/**
909
 * Implements hook_mail().
910 911
 */
function user_mail($key, &$message, $params) {
912
  $token_service = \Drupal::token();
913
  $language_manager = \Drupal::languageManager();
914
  $langcode = $message['langcode'];
915
  $variables = array('user' => $params['account']);
916

917
  $language = \Drupal::languageManager()->getLanguage($params['account']->getPreferredLangcode());
918 919
  $original_language = $language_manager->getConfigOverrideLanguage();
  $language_manager->setConfigOverrideLanguage($language);
920
  $mail_config = \Drupal::config('user.mail');
921 922

   // We do not sanitize the token replacement, since the output of this
923
   // replacement is intended for an email message, not a web browser.
924
  $token_options = array('langcode' => $langcode, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE);
925 926
  $message['subject'] .= $token_service->replace($mail_config->get($key . '.subject'), $variables, $token_options);
  $message['body'][] = $token_service->replace($mail_config->get($key . '.body'), $variables, $token_options);
927

928 929
  $language_manager->setConfigOverrideLanguage($original_language);

930 931 932 933 934
}

/**
 * Token callback to add unsafe tokens for user mails.
 *
935 936 937
 * This function is used by \Drupal\Core\Utility\Token::replace() to set up
 * some additional tokens that can be used in email messages generated by
 * user_mail().
938
 *
939
 * @param array $replacements
940 941
 *   An associative array variable containing mappings from token names to
 *   values (for use with strtr()).
942
 * @param array $data
943 944 945
 *   An associative array of token replacement values. If the 'user' element
 *   exists, it must contain a user account object with the following
 *   properties:
946
 *   - login: The UNIX timestamp of the user's last login.
947
 *   - pass: The hashed account login password.
948 949 950
 * @param array $options
 *   A keyed array of settings and flags to control the token replacement
 *   process. See \Drupal\Core\Utility\Token::replace().
951 952 953
 */
function user_mail_tokens(&$replacements, $data, $options) {
  if (isset($data['user'])) {
954 955
    $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user'], $options);
    $replacements['[user:cancel-url]'] = user_cancel_url($data['user'], $options);
956 957 958
  }
}

959 960
/*** Administrative features ***********************************************/

961
/**
962
 * Retrieves the names of roles matching specified conditions.
963
 *
964 965 966 967 968 969 970
 * @param bool $membersonly
 *   (optional) Set this to TRUE to exclude the 'anonymous' role. Defaults to
 *   FALSE.
 * @param string|null $permission
 *   (optional) A string containing a permission. If set, only roles
 *    containing that permission are returned. Defaults to NULL, which
 *    returns all roles.
971
 *
972
 * @return array
973 974
 *   An associative array with the role id as the key and the role name as
 *   value.
975
 */
976
function user_role_names($membersonly = FALSE, $permission = NULL) {
977
  return array_map(function ($item) {
978 979 980 981
    return $item->label();
  }, user_roles($membersonly, $permission));
}

982
/**
983
 * Implements hook_ENTITY_TYPE_insert() for user_role entities.
984 985
 */
function user_user_role_insert(RoleInterface $role) {
986
  // Ignore the authenticated and anonymous roles or the role is being synced.
987
  if (in_array($role->id(), array(RoleInterface::AUTHENTICATED_ID, RoleInterface::ANONYMOUS_ID)) || $role->isSyncing()) {
988 989 990
    return;
  }

991 992 993 994 995 996 997 998 999 1000 1001
  $add_id = 'user_add_role_action.' . $role->id();
  if (!entity_load('action', $add_id)) {
    $action = entity_create('action', array(
      'id' => $add_id,
      'type' => 'user',
      'label' => t('Add the @label role to the selected users', array('@label' => $role->label())),
      'configuration' => array(
        'rid' => $role->id(),
      ),
      'plugin' => 'user_add_role_action',
    ));
1002
    $action->trustData()->save();
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
  }
  $remove_id = 'user_remove_role_action.' . $role->id();
  if (!entity_load('action', $remove_id)) {
    $action = entity_create('action', array(
      'id' => $remove_id,
      'type' => 'user',
      'label' => t('Remove the @label role from the selected users', array('@label' => $role->label())),
      'configuration' => array(
        'rid' => $role->id(),
      ),
      'plugin' => 'user_remove_role_action',
    ));
1015
    $action->trustData()->save();
1016
  }
1017 1018 1019
}

/**
1020
 * Implements hook_ENTITY_TYPE_delete() for user_role entities.
1021 1022
 */
function user_user_role_delete(RoleInterface $role) {
1023 1024 1025 1026
  // Delete role references for all users.
  $user_storage = \Drupal::entityManager()->getStorage('user');
  $user_storage->deleteRoleReferences(array($role->id()));

1027
  // Ignore the authenticated and anonymous roles or the role is being synced.
1028
  if (in_array($role->id(), array(RoleInterface::AUTHENTICATED_ID, RoleInterface::ANONYMOUS_ID)) || $role->isSyncing()) {
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
    return;
  }

  $actions = entity_load_multiple('action', array(
    'user_add_role_action.' . $role->id(),
    'user_remove_role_action.' . $role->id(),
  ));
  foreach ($actions as $action) {
    $action->delete();
  }
}

1041 1042 1043
/**
 * Retrieve an array of roles matching specified conditions.
 *
1044 1045 1046 1047 1048 1049 1050
 * @param bool $membersonly
 *   (optional) Set this to TRUE to exclude the 'anonymous' role. Defaults to
 *   FALSE.
 * @param string|null $permission
 *   (optional) A string containing a permission. If set, only roles
 *   containing that permission are returned. Defaults to NULL, which
 *   returns all roles.
1051
 *
1052
 * @return \Drupal\user\RoleInterface[]
1053 1054 1055
 *   An associative array with the role id as the key and the role object as
 *   value.
 */
1056
function user_roles($membersonly = FALSE, $permission = NULL) {
1057 1058 1059 1060 1061
  $user_roles = &drupal_static(__FUNCTION__);

  // Do not cache roles for specific permissions. This data is not requested
  // frequently enough to justify the additional memory use.
  if (empty($permission)) {
1062
    $cid = $membersonly ? RoleInterface::AUTHENTICATED_ID : RoleInterface::ANONYMOUS_ID;
1063 1064 1065 1066 1067
    if (isset($user_roles[$cid])) {
      return $user_roles[$cid];
    }
  }

1068
  $roles = Role::loadMultiple();
1069
  if ($membersonly) {
1070
    unset($roles[RoleInterface::ANONYMOUS_ID]);
1071 1072 1073
  }

  if (!empty($permission)) {
1074 1075 1076
    $roles = array_filter($roles, function ($role) use ($permission) {
      return $role->hasPermission($permission);
    });
1077
  }
1078

1079 1080 1081 1082
  if (empty($permission)) {
    $user_roles[$cid] = $roles;
  }

1083
  return $roles;
1084 1085
}

1086
/**
1087 1088
 * Fetches a user role by role ID.
 *
1089
 * @param string $rid
1090
 *   A string representing the role ID.
1091
 *
1092
 * @return \Drupal\user\RoleInterface|null
1093
 *   A fully-loaded role object if a role with the given ID exists, or NULL
1094
 *   otherwise.
1095 1096 1097
 *
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\user\Entity\Role::load().
1098 1099
 */
function user_role_load($rid) {
1100
  return Role::load($rid);
1101 1102 1103
}

/**
1104
 * Change permissions for a user role.
1105
 *
1106 1107
 * This function may be used to grant and revoke multiple permissions at once.
 * For example, when a form exposes checkboxes to configure permissions for a
1108 1109
 * role, the form submit handler may directly pass the submitted values for the
 * checkboxes form element to this function.
1110
 *
1111
 * @param mixed $rid
1112
 *   The ID of a user role to alter.
1113 1114 1115 1116 1117 1118
 * @param array $permissions
 *   (optional) An associative array, where the key holds the permission name
 *   and the value determines whether to grant or revoke that permission. Any
 *   value that evaluates to TRUE will cause the permission to be granted.
 *   Any value that evaluates to FALSE will cause the permission to be
 *   revoked.
1119 1120
 *   @code
 *     array(
1121 1122 1123 1124 1125
 *       'administer nodes' => 0,                // Revoke 'administer nodes'
 *       'administer blocks' => FALSE,           // Revoke 'administer blocks'
 *       'access user profiles' => 1,            // Grant 'access user profiles'
 *       'access content' => TRUE,               // Grant 'access content'
 *       'access comments' => 'access comments', // Grant 'access comments'
1126 1127 1128 1129 1130 1131
 *     )
 *   @endcode
 *   Existing permissions are not changed, unless specified in $permissions.
 *
 * @see user_role_grant_permissions()
 * @see user_role_revoke_permissions()
1132
 */
1133 1134 1135 1136