user.module 59.7 KB
Newer Older
Dries's avatar
 
Dries committed
1 2
<?php

3
use Drupal\Component\Utility\Crypt;
4
use Drupal\Core\Cache\Cache;
5
use Drupal\Core\Entity\EntityInterface;
6
use Drupal\Core\Session\AccountInterface;
7
use Drupal\Core\Session\AnonymousUserSession;
8
use \Drupal\Core\Entity\Display\EntityViewDisplayInterface;
9
use Drupal\Core\Url;
10 11
use Drupal\file\Entity\File;
use Drupal\user\Entity\User;
12
use Drupal\user\UserInterface;
13
use Drupal\user\RoleInterface;
14
use Drupal\Core\Template\Attribute;
15
use Drupal\Core\TypedData\DataDefinition;
16
use Symfony\Component\HttpFoundation\RedirectResponse;
17
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
18
use Drupal\menu_link\Entity\MenuLink;
Crell's avatar
Crell committed
19

Dries's avatar
 
Dries committed
20 21 22 23 24
/**
 * @file
 * Enables the user registration and login system.
 */

25 26 27
/**
 * Maximum length of username text field.
 */
28
const USERNAME_MAX_LENGTH = 60;
29

30 31 32
/**
 * Only administrators can create user accounts.
 */
33
const USER_REGISTER_ADMINISTRATORS_ONLY = 'admin_only';
34 35 36 37

/**
 * Visitors can create their own accounts.
 */
38
const USER_REGISTER_VISITORS = 'visitors';
39 40 41 42 43

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

46 47 48 49 50 51 52 53
/**
 * Implement hook_help().
 */
function user_help($path, $arg) {
  switch ($path) {
    case 'admin/help#user':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
54
      $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://drupal.org/documentation/modules/user')) . '</p>';
55 56 57
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Creating and managing users') . '</dt>';
58 59 60 61 62
      $output .= '<dd>' . t('Through the <a href="!people">People administration page</a> you can create and cancel user accounts and assign users to roles. By editing one particular user you change the username, e-mail address, and password.', array('!people' => \Drupal::url('user.admin_account'))) . '</dd>';
      $output .= '<dt>' . t('User roles') . '</dt>';
      $output .= '<dd>' . t('<em>User roles</em> are used to group and classify users; each user can be assigned one or more roles. Typically there are three pre-defined roles: <em>Anonymous user</em> (users that are not logged in), <em>Authenticated user</em> (users that are registered and logged in), and <em>Administrator</em> (users that are registered, logged in as administrator). You can create additional roles on the <a href="!roles">Roles page</a>.', array('!roles' => \Drupal::url('user.role_list'))) . '</dd>';
      $output .= '<dt>' . t('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 a particular type of content, editing or creating 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>';
63
      $output .= '<dt>' . t('Account settings') . '</dt>';
64 65 66
      $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, default administrator role (this role will be automatically assigned new permissions whenever a module is enabled), personal contact forms, user registration, and account cancellation. On this page you can also manage settings for account personalization (including signatures), and adapt the text for the e-mail messages that users receive when they register or request a password recovery.', array('!accounts'  => \Drupal::url('user.account_settings'))) . '</dd>';
      $output .= '<dt>' . t('Managing user account fields') . '</dt>';
      $output .= '<dd>' . t('Because User accounts are an <a href="!entity_help">entity type</a>, you can extend them by adding <a href="!field_help">fields</a> 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.', array('!entity_help' => \Drupal::url('help.page', array('name' => 'entity')),'!field_help'=>\Drupal::url('help.page', array('name' => 'field')), '!accounts' => \Drupal::url('user.account_settings'))) . '</dd>';
67 68 69 70
      $output .= '</dl>';
      return $output;
    case 'admin/people/create':
      return '<p>' . t("This web page allows administrators to register new users. Users' e-mail addresses and usernames must be unique.") . '</p>';
71
    case 'admin/people/permissions':
72 73
      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). Two important roles to consider are Authenticated Users and Administrators. Any permissions granted to the Authenticated Users role will be given to any user who can log into your site. You can make any role the Administrator role for the site, meaning this will be granted all new permissions automatically. You can do this on the <a href="@settings">User Settings</a> page. You should be careful to ensure that only trusted users are given this access and level of control of your site.', array('@role' => url('admin/people/roles'), '@settings' => url('admin/config/people/accounts'))) . '</p>';
    case 'admin/people/roles':
74
      $output = '<p>' . t('Roles allow you to fine tune the security and administration of Drupal. A role defines a group of users that have certain privileges as defined on the <a href="@permissions">permissions page</a>. Examples of roles include: anonymous user, authenticated user, moderator, administrator and so on. In this area you will define the names and order of the roles on your site. It is recommended to order your roles from least permissive (anonymous user) to most permissive (administrator). To delete a role choose "edit role".', array('@permissions' => url('admin/people/permissions'))) . '</p>';
75
      $output .= '<p>' . t('Drupal has three special user roles:') . '</p>';
76 77 78
      $output .= '<ul>';
      $output .= '<li>' . t("Anonymous user: this role is used for users that don't have a user account or that are not authenticated.") . '</li>';
      $output .= '<li>' . t('Authenticated user: this role is automatically granted to all logged in users.') . '</li>';
79
      $output .= '<li>' . t('Administrator role: this role is automatically granted all new permissions when you install a new module. Configure which role is the administrator role on the <a href="@account_settings">Account settings page</a>.', array('@account_settings' => url('admin/config/people/accounts'))) . '</li>';
80 81 82
      $output .= '</ul>';
      return $output;
    case 'admin/config/people/accounts/fields':
83 84 85
      return '<p>' . t('This form lets administrators add and edit fields for storing user data.') . '</p>';
    case 'admin/config/people/accounts/form-display':
      return '<p>' . t('This form lets administrators configure how form fields should be displayed when editing a user profile.') . '</p>';
86 87 88 89 90 91
    case 'admin/config/people/accounts/display':
      return '<p>' . t('This form lets administrators configure how fields should be displayed when rendering a user profile page.') . '</p>';
    case 'admin/people/search':
      return '<p>' . t('Enter a simple pattern ("*" may be used as a wildcard match) to search for a username or e-mail address. For example, one may search for "br" and Drupal might return "brian", "brad", and "brenda@example.com".') . '</p>';
  }
}
92

93
/**
94
 * Implements hook_theme().
95 96 97
 */
function user_theme() {
  return array(
98
    'user' => array(
99
      'render element' => 'elements',
100
      'file' => 'user.pages.inc',
101
      'template' => 'user',
102
    ),
103 104 105 106
    'user_permission_description' => array(
      'variables' => array('permission_item' => NULL, 'hide' => NULL),
      'file' => 'user.admin.inc',
    ),
107
    'user_signature' => array(
108
      'variables' => array('signature' => NULL),
109
    ),
110
    'username' => array(
111
      'variables' => array('account' => NULL, 'attributes' => array()),
112
    ),
113 114 115
  );
}

116 117 118 119 120
/**
 * Implements hook_page_build().
 */
function user_page_build(&$page) {
  $path = drupal_get_path('module', 'user');
121
  $page['#attached']['css'][$path . '/css/user.module.css'] = array('every_page' => TRUE);
122 123 124 125 126 127 128 129 130 131 132
}

/**
 * Implements hook_js_alter().
 */
function user_js_alter(&$javascript) {
  // If >=1 JavaScript asset has declared a dependency on drupalSettings, the
  // 'settings' key will exist. Thus when that key does not exist, return early.
  if (!isset($javascript['settings'])) {
    return;
  }
133 134 135 136

  // 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.
137 138 139
  // Similarly, provide a permissions hash, so that permission-dependent data
  // can be reliably cached on the client side.
  $user = \Drupal::currentUser();
140 141
  $javascript['settings']['data'][] = array(
    'user' => array(
142 143
      'uid' => $user->id(),
      'permissionsHash' => \Drupal::service('user.permissions_hash')->generate($user),
144
    ),
145
  );
146 147
}

148 149 150 151
/**
 * Implements hook_entity_bundle_info().
 */
function user_entity_bundle_info() {
152
  $bundles['user']['user']['label'] = t('User');
153 154 155
  return $bundles;
}

156
/**
157
 * Entity URI callback.
158
 */
159
function user_uri($user) {
160 161 162
  return new Url('user.view', array(
    'user' => $user->id(),
  ));
163 164
}

165 166 167
/**
 * Populates $entity->account for each prepared entity.
 *
168
 * Called by Drupal\Core\Entity\EntityViewBuilderInterface::buildContent()
169
 * implementations.
170
 *
171
 * @param \Drupal\user\EntityOwnerInterface[] $entities
172 173 174 175 176
 *   The entities keyed by entity ID.
 */
function user_attach_accounts(array $entities) {
  $uids = array();
  foreach ($entities as $entity) {
177
    $uids[] = $entity->getOwnerId();
178 179 180
  }
  $uids = array_unique($uids);
  $accounts = user_load_multiple($uids);
181
  $anonymous = entity_create('user', array('uid' => 0));
182

183
  foreach ($entities as $id => $entity) {
184 185
    if (isset($accounts[$entity->getOwnerId()])) {
      $entities[$id]->setOwner($accounts[$entity->getOwnerId()]);
186 187
    }
    else {
188
      $entities[$id]->setOwner($anonymous);
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
    }
  }
}

/**
 * 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() {
  return (bool) field_info_instance('user', 'user_picture', 'user');
}

205
/**
206
 * Implements hook_entity_extra_field_info().
207
 */
208
function user_entity_extra_field_info() {
209 210 211 212 213
  $fields['user']['user']['form']['account'] = array(
    'label' => t('User name and password'),
    'description' => t('User module account form elements.'),
    'weight' => -10,
  );
214
  if (\Drupal::config('user.settings')->get('signatures')) {
215 216 217 218 219 220 221 222 223 224 225
    $fields['user']['user']['form']['signature_settings'] = array(
      'label' => t('Signature settings'),
      'description' => t('User module form element.'),
      'weight' => 1,
    );
  }
  $fields['user']['user']['form']['language'] = array(
    'label' => t('Language settings'),
    'description' => t('User module form element.'),
    'weight' => 0,
  );
226
  if (\Drupal::config('system.date')->get('timezone.user.configurable')) {
227 228 229 230 231 232 233 234 235 236 237
    $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,
238
  );
239

240
  return $fields;
241 242
}

Dries's avatar
Dries committed
243
/**
244
 * Loads multiple users based on certain conditions.
Dries's avatar
Dries committed
245
 *
246 247 248
 * 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.
Dries's avatar
Dries committed
249
 *
250 251
 * @param array $uids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
252
 * @param bool $reset
253 254
 *   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.
255
 *
256
 * @return array
257 258
 *   An array of user objects, indexed by uid.
 *
259
 * @see entity_load_multiple()
260 261 262
 * @see user_load()
 * @see user_load_by_mail()
 * @see user_load_by_name()
263
 * @see \Drupal\Core\Entity\Query\QueryInterface
Dries's avatar
Dries committed
264
 */
265
function user_load_multiple(array $uids = NULL, $reset = FALSE) {
266
  return entity_load_multiple('user', $uids, $reset);
267
}
268 269

/**
270 271 272 273 274 275
 * Loads a user object.
 *
 * Drupal has a global $user object, which represents the currently-logged-in
 * user. So to avoid confusion and to avoid clobbering the global $user object,
 * it is a good idea to assign the result of this function to a different local
 * variable, generally $account. If you actually do want to act as the user you
276 277 278 279
 * are loading, it is essential to call drupal_save_session(FALSE); first.
 * See
 * @link http://drupal.org/node/218104 Safely impersonating another user @endlink
 * for more information.
280
 *
281
 * @param int $uid
282
 *   Integer specifying the user ID to load.
283
 * @param bool $reset
284 285 286
 *   TRUE to reset the internal cache and load from the database; FALSE
 *   (default) to load from the internal cache, if set.
 *
287
 * @return \Drupal\user\UserInterface
288
 *   A fully-loaded user object upon successful user load, or NULL if the user
289 290 291 292 293
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load($uid, $reset = FALSE) {
294
  return entity_load('user', $uid, $reset);
295 296 297
}

/**
298
 * Fetches a user object by email address.
299
 *
300
 * @param string $mail
301
 *   String with the account's e-mail address.
302
 * @return object|bool
303 304 305 306 307 308
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load_by_mail($mail) {
309
  $users = entity_load_multiple_by_properties('user', array('mail' => $mail));
310
  return $users ? reset($users) : FALSE;
311 312 313
}

/**
314
 * Fetches a user object by account name.
315
 *
316
 * @param string $name
317
 *   String with the account's user name.
318
 * @return object|bool
319 320 321 322 323 324
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load_by_name($name) {
325
  $users = entity_load_multiple_by_properties('user', array('name' => $name));
326
  return $users ? reset($users) : FALSE;
Dries's avatar
 
Dries committed
327 328
}

Dries's avatar
Dries committed
329 330
/**
 * Verify the syntax of the given name.
331 332 333 334 335 336 337 338
 *
 * @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.
 *
Dries's avatar
Dries committed
339
 */
Dries's avatar
 
Dries committed
340
function user_validate_name($name) {
341 342
  $definition = DataDefinition::create('string')
    ->setConstraints(array('UserName' => array()));
343
  $data = \Drupal::typedDataManager()->create($definition);
344 345 346 347
  $data->setValue($name);
  $violations = $data->validate();
  if (count($violations) > 0) {
    return $violations[0]->getMessage();
348
  }
Dries's avatar
 
Dries committed
349 350
}

Dries's avatar
Dries committed
351 352 353
/**
 * Generate a random alphanumeric password.
 */
Dries's avatar
 
Dries committed
354 355
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
356 357
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
358
  // of 'I', 1, and 'l'.
359
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
Dries's avatar
Dries committed
360

361 362
  // Zero-based count of characters in the allowable list:
  $len = strlen($allowable_characters) - 1;
Dries's avatar
 
Dries committed
363

Dries's avatar
Dries committed
364 365
  // Declare the password as a blank string.
  $pass = '';
Dries's avatar
 
Dries committed
366

Dries's avatar
Dries committed
367
  // Loop the number of times specified by $length.
Dries's avatar
 
Dries committed
368
  for ($i = 0; $i < $length; $i++) {
369 370 371 372
    do {
      // Find a secure random number within the range needed.
      $index = ord(Crypt::randomBytes(1));
    } while ($index > $len);
Dries's avatar
 
Dries committed
373 374 375

    // Each iteration, pick a random character from the
    // allowable string and append it to the password:
376
    $pass .= $allowable_characters[$index];
Dries's avatar
 
Dries committed
377 378 379
  }

  return $pass;
Dries's avatar
 
Dries committed
380 381
}

382 383 384
/**
 * Determine the permissions for one or more roles.
 *
385 386
 * @param array $roles
 *   An array of role IDs.
387
 *
388 389 390
 * @return array
 *   An array indexed by role ID. Each value is an array of permission strings
 *   for the given role.
391
 */
392 393 394 395 396 397
function user_role_permissions(array $roles) {
  if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
    return _user_role_permissions_update($roles);
  }
  $entities = entity_load_multiple('user_role', $roles);
  $role_permissions = array();
398
  foreach ($roles as $rid) {
399
    $role_permissions[$rid] = isset($entities[$rid]) ? $entities[$rid]->getPermissions() : array();
400
  }
401 402
  return $role_permissions;
}
403

404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
/**
 * 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) {
421
    $role_permissions[$rid] = \Drupal::config("user.role.$rid")->get('permissions') ?: array();
422 423 424 425
  }
  return $role_permissions;
}

Dries's avatar
Dries committed
426 427 428 429 430
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
431
 * @param \Drupal\Core\Session\AccountInterface $account
Dries's avatar
 
Dries committed
432
 *   (optional) The account to check, if not given use currently logged in user.
Dries's avatar
Dries committed
433
 *
434
 * @return bool
435
 *   Boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
436
 *
437 438
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\Core\Session\AccountInterface::hasPermission().
Dries's avatar
Dries committed
439
 */
440
function user_access($string, AccountInterface $account = NULL) {
Dries's avatar
 
Dries committed
441
  global $user;
442

443
  if (!isset($account)) {
444 445
    // In the installer request session is not set, so we have to fall back
    // to the global $user. In all other cases the session key is preferred.
446
    $account = \Drupal::currentUser() ?: $user;
447 448
  }

449
  return $account->hasPermission($string);
Dries's avatar
 
Dries committed
450 451
}

452
/**
453
 * Checks for usernames blocked by user administration.
454
 *
455 456 457
 * @param $name
 *   A string containing a name of the user.
 *
458 459
 * @return bool
 *   TRUE if the user is blocked, FALSE otherwise.
460 461
 */
function user_is_blocked($name) {
462 463
  return (bool) \Drupal::entityQuery('user')
    ->condition('name', $name)
464
    ->condition('status', 0)
465
    ->execute();
466 467
}

Dries's avatar
Dries committed
468
/**
469
 * Implements hook_permission().
Dries's avatar
Dries committed
470
 */
471
function user_permission() {
472 473 474
  return array(
    'administer permissions' =>  array(
      'title' => t('Administer permissions'),
475
      'restrict access' => TRUE,
476
    ),
477 478 479 480 481
    'administer account settings' => array(
      'title' => t('Administer account settings'),
      'description' => t('Configure site-wide settings and behavior for <a href="@url">user accounts and registration</a>.', array('@url' => url('admin/config/people'))),
      'restrict access' => TRUE,
    ),
482 483
    'administer users' => array(
      'title' => t('Administer users'),
484
      'restrict access' => TRUE,
485 486
    ),
    'access user profiles' => array(
487
      'title' => t('View user profiles'),
488 489 490 491 492
    ),
    'change own username' => array(
      'title' => t('Change own username'),
    ),
    'cancel account' => array(
493
      'title' => t('Cancel own user account'),
494
      'description' => t('Note: content may be kept, unpublished, deleted or transferred to the %anonymous-name user depending on the configured <a href="@user-settings-url">user settings</a>.', array('%anonymous-name' => \Drupal::config('user.settings')->get('anonymous'), '@user-settings-url' => url('admin/config/people/accounts'))),
495 496 497
    ),
    'select account cancellation method' => array(
      'title' => t('Select method for cancelling own account'),
498
      'restrict access' => TRUE,
499 500
    ),
  );
Dries's avatar
 
Dries committed
501 502
}

Dries's avatar
Dries committed
503
/**
504
 * Implements hook_user_view().
Dries's avatar
Dries committed
505
 */
506
function user_user_view(UserInterface $account, EntityViewDisplayInterface $display) {
507 508 509 510
  if ($display->getComponent('member_for')) {
    $account->content['member_for'] = array(
      '#type' => 'item',
      '#title' => t('Member for'),
511
      '#markup' => format_interval(REQUEST_TIME - $account->getCreatedTime()),
512 513
    );
  }
514 515
}

516 517 518 519 520 521 522 523 524
/**
 * Sets the value of the user register and profile forms' langcode element.
 */
function _user_language_selector_langcode_value($element, $input, &$form_state) {
  // Only add to the description if the form element have a description.
  if (isset($form_state['complete_form']['language']['preferred_langcode']['#description'])) {
    $form_state['complete_form']['language']['preferred_langcode']['#description'] .= ' ' . t("This is also assumed to be the primary language of this account's profile information.");
  }
  return $form_state['values']['preferred_langcode'];
525 526
}

527
/**
528
 * Form validation handler for the current password on the user account form.
529
 *
530
 * @see AccountFormController::form()
531 532
 */
function user_validate_current_pass(&$form, &$form_state) {
533
  $account = $form_state['user'];
534 535 536 537
  foreach ($form_state['values']['current_pass_required_values'] as $key => $name) {
    // This validation only works for required textfields (like mail) or
    // form values like password_confirm that have their own validation
    // that prevent them from being empty if they are changed.
538
    $current_value = $account->hasField($key) ? $account->get($key)->value : $account->$key;
539
    if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $current_value)) {
540
      $current_pass_failed = empty($form_state['values']['current_pass']) || !\Drupal::service('password')->check($form_state['values']['current_pass'], $account);
541
      if ($current_pass_failed) {
542 543
        form_set_error('current_pass', $form_state, t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name)));
        form_set_error($key, $form_state);
544 545 546 547 548 549 550
      }
      // We only need to check the password once.
      break;
    }
  }
}

551
/**
552
 * Implements hook_preprocess_HOOK() for block templates.
553 554
 */
function user_preprocess_block(&$variables) {
555 556
  if ($variables['configuration']['module'] == 'user') {
    switch ($variables['elements']['#plugin_id']) {
557
      case 'user_login_block':
558
        $variables['attributes']['role'] = 'form';
559 560 561 562 563
        break;
    }
  }
}

564 565 566
/**
 * Format a username.
 *
567
 * @param \Drupal\Core\Session\Interface $account
568 569 570 571 572 573
 *   The account object for the user whose name is to be formatted.
 *
 * @return
 *   An unsanitized string with the username to display. The code receiving
 *   this result must ensure that check_plain() is called on it before it is
 *   printed to the page.
574
 *
575 576
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\Core\Session\Interface::getUsername().
577
 */
578 579
function user_format_name(AccountInterface $account) {
  return $account->getUsername();
580 581
}

582 583 584 585 586 587 588
/**
 * Implements hook_template_preprocess_default_variables_alter().
 *
 * @see user_user_login()
 * @see user_user_logout()
 */
function user_template_preprocess_default_variables_alter(&$variables) {
589
  $user = \Drupal::currentUser();
590

591 592 593 594 595 596
  // If this function is called from the installer after Drupal has been
  // installed then $user will not be set.
  if (!is_object($user)) {
    return;
  }

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

601
  $variables['is_admin'] = $user->hasPermission('access administration pages');
602
  $variables['logged_in'] = $user->isAuthenticated();
603 604
}

605 606 607
/**
 * Preprocesses variables for theme_username().
 *
608
 * Modules that make any changes to variables like 'name' or 'extra' must ensure
609 610 611 612
 * that the final string is safe to include directly in the output by using
 * check_plain() or filter_xss().
 */
function template_preprocess_username(&$variables) {
613
  $account = $variables['account'] ?: new AnonymousUserSession();
614 615

  $variables['extra'] = '';
616 617 618 619 620
  $variables['uid'] = $account->id();
  if (empty($variables['uid'])) {
    if (theme_get_setting('features.comment_user_verification')) {
      $variables['extra'] = ' (' . t('not verified') . ')';
    }
621 622 623 624 625 626 627
  }

  // 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.
628
  $name = $variables['name_raw'] = $account->getUsername();
629 630
  if (drupal_strlen($name) > 20) {
    $name = drupal_substr($name, 0, 15) . '...';
631 632 633 634
    $variables['truncated'] = TRUE;
  }
  else {
    $variables['truncated'] = FALSE;
635 636
  }
  $variables['name'] = check_plain($name);
637
  $variables['profile_access'] = \Drupal::currentUser()->hasPermission('access user profiles');
638

639 640 641
  // Populate link path and attributes if appropriate.
  if ($variables['uid'] && $variables['profile_access']) {
    // We are linking to a local user.
642
    $variables['link_options']['attributes']['title'] = t('View user profile.');
643 644 645 646 647 648
    $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.
649
    $variables['link_options']['attributes']['rel'] = 'nofollow';
650 651 652 653 654 655
    $variables['link_path'] = $account->homepage;
    $variables['homepage'] = $account->homepage;
  }
  // We do not want the l() function to check_plain() a second time.
  $variables['link_options']['html'] = TRUE;
  // Set a default class.
656
  $variables['link_options']['attributes']['class'] = array('username');
657 658 659 660 661 662 663 664 665
}

/**
 * Returns HTML for a username, potentially linked to the user's page.
 *
 * @param $variables
 *   An associative array containing:
 *   - account: The user object to format.
 *   - name: The user's name, sanitized.
666
 *   - truncated: A boolean indicating if $variables['name'] has been shortened.
667 668 669 670 671
 *   - extra: Additional text to append to the user's name, sanitized.
 *   - link_path: The path or URL of the user's profile page, home page, or
 *     other desired page to link to for more information about the user.
 *   - link_options: An array of options to pass to the l() function's $options
 *     parameter if linking the user's name to the user's page.
672 673
 *   - attributes: An array of attributes to instantiate the
 *     Drupal\Core\Template\Attribute class if not linking to the user's page.
674 675 676 677 678 679 680 681 682 683 684 685 686
 *
 * @see template_preprocess_username()
 */
function theme_username($variables) {
  if (isset($variables['link_path'])) {
    // We have a link path, so we should generate a link using l().
    // Additional classes may be added as array elements like
    // $variables['link_options']['attributes']['class'][] = 'myclass';
    $output = l($variables['name'] . $variables['extra'], $variables['link_path'], $variables['link_options']);
  }
  else {
    // Modules may have added important attributes so they must be included
    // in the output. Additional classes may be added as array elements like
687
    // $variables['attributes']['class'][] = 'myclass';
688
    $output = '<span' . new Attribute($variables['attributes']) . '>' . $variables['name'] . $variables['extra'] . '</span>';
689 690 691 692
  }
  return $output;
}

693
/**
694
 * Implements hook_menu_link_presave().
695
 */
696
function user_menu_link_presave(MenuLink $menu_link) {
697 698 699
  // The path 'user' must be accessible for anonymous users, but only visible
  // for authenticated users. Authenticated users should see "My account", but
  // anonymous users should not see it at all. Therefore, invoke
700
  // user_menu_link_load() to conditionally hide the link.
701
  if ($menu_link->machine_name == 'user.page') {
702
    $menu_link->options['alter'] = TRUE;
703
  }
704 705
}

706 707 708 709 710 711
/**
 * Implements hook_menu_breadcrumb_alter().
 */
function user_menu_breadcrumb_alter(&$active_trail, $item) {
  // Remove "My account" from the breadcrumb when $item is descendant-or-self
  // of system path user/%.
712
  if (isset($active_trail[1]['module']) && $active_trail[1]['machine_name'] == 'user.page' && strpos($item['path'], 'user/%') === 0) {
713 714 715 716
    array_splice($active_trail, 1, 1);
  }
}

717
/**
718
 * Implements hook_translated_menu_link_alter().
719
 */
720
function user_translated_menu_link_alter(MenuLink &$menu_link) {
721
  // Hide the "User account" link for anonymous users.
722
  if ($menu_link->machine_name == 'user.page' && \Drupal::currentUser()->isAnonymous()) {
723
    $menu_link->hidden = 1;
724 725 726
  }
}

727
/**
728
 * Try to validate the user's login credentials locally.
729
 *
730 731 732 733 734 735
 * @param $name
 *   User name to authenticate.
 * @param $password
 *   A plain-text password, such as trimmed text from form values.
 * @return
 *   The user's uid on success, or FALSE on failure to authenticate.
736 737 738
 *
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\user\UserAuth::authenticate() instead.
739
 */
740
function user_authenticate($name, $password) {
741
  return \Drupal::service('user.auth')->authenticate($name, $password);
742 743
}

744
/**
745
 * Finalizes the login process and logs in a user.
746
 *
747 748 749
 * 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.
750
 *
751 752
 * The global $user object is replaced with the passed in account.
 *
753
 * @param \Drupal\user\UserInterface $account
754
 *   The account to log in.
755 756
 *
 * @see hook_user_login()
757
 */
758
function user_login_finalize(UserInterface $account) {
759
  global $user;
760
  $user = $account;
761
  watchdog('user', 'Session opened for %name.', array('%name' => $account->getUsername()));
762 763
  // Update the user table timestamp noting user has logged in.
  // This is also used to invalidate one-time login links.
764
  $account->setLastLoginTime(REQUEST_TIME);
765
  \Drupal::entityManager()
766
    ->getStorage('user')
767
    ->updateLastLoginTimestamp($account);
768

769 770 771
  // Regenerate the session ID to prevent against session fixation attacks.
  // This is called before hook_user in case one of those functions fails
  // or incorrectly does a redirect which would leave the old session in place.
772
  drupal_session_regenerate();
773

774
  \Drupal::moduleHandler()->invokeAll('user_login', array($account));
775 776
}

777 778 779
/**
 * Implements hook_user_login().
 */
780
function user_user_login($account) {
781 782 783 784 785 786 787 788 789 790 791 792 793 794
  // 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');
}

795 796 797 798
/**
 * Generates a unique URL for a user to login and reset their password.
 *
 * @param object $account
799 800 801 802
 *   An object containing the user account, which must contain at least the
 *   following properties:
 *   - uid: The user ID number.
 *   - login: The UNIX timestamp of the user's last login.
803 804 805
 * @param array $options
 *   (optional) A keyed array of settings. Supported options are:
 *   - langcode: A language code to be used when generating locale-sensitive
806
 *    URLs. If langcode is NULL the users preferred language is used.
807 808 809 810 811
 *
 * @return
 *   A unique URL that provides a one-time log in for the user, from which
 *   they can change their password.
 */
812
function user_pass_reset_url($account, $options = array()) {
813
  $timestamp = REQUEST_TIME;
814
  $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
815
  $url_options = array('absolute' => TRUE, 'language' => \Drupal::languageManager()->getLanguage($langcode));
816
  return url("user/reset/" . $account->id() . "/$timestamp/" . user_pass_rehash($account->getPassword(), $timestamp, $account->getLastLoginTime()), $url_options);
817 818
}

819
/**
820 821 822 823 824
 * Generates a URL to confirm an account cancellation request.
 *
 * @param object $account
 *   The user account object, which must contain at least the following
 *   properties:
825
 *   - uid: The user ID number.
826
 *   - pass: The hashed user password string.
827
 *   - login: The UNIX timestamp of the user's last login.
828 829 830
 * @param array $options
 *   (optional) A keyed array of settings. Supported options are:
 *   - langcode: A language code to be used when generating locale-sensitive
831
  *    URLs. If langcode is NULL the users preferred language is used.
832 833 834 835
 *
 * @return
 *   A unique URL that may be used to confirm the cancellation of the user
 *   account.
836 837 838 839
 *
 * @see user_mail_tokens()
 * @see user_cancel_confirm()
 */
840
function user_cancel_url($account, $options = array()) {
841
  $timestamp = REQUEST_TIME;
842
  $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
843
  $url_options = array('absolute' => TRUE, 'language' => \Drupal::languageManager()->getLanguage($langcode));
844
  return url("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account->getPassword(), $timestamp, $account->getLastLoginTime()), $url_options);
845 846
}

847 848 849 850 851 852 853 854
/**
 * 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
 * same information, and compared to the hash value from the URL. The URL
 * normally contains both the time stamp and the numeric user ID. The login
855 856
 * timestamp and hashed password are retrieved from the database as necessary.
 * For a usage example, see user_cancel_url() and user_cancel_confirm().
857
 *
858
 * @param string $password
859
 *   The hashed user account password value.
860 861 862 863
 * @param int $timestamp
 *   A UNIX timestamp, typically REQUEST_TIME.
 * @param int $login
 *   The UNIX timestamp of the user's last login.
864 865 866 867
 *
 * @return
 *   A string that is safe for use in URLs and SQL statements.
 */
868
function user_pass_rehash($password, $timestamp, $login) {
869
  return Crypt::hmacBase64($timestamp . $login, drupal_get_hash_salt() . $password);
870 871
}

872
/**
873 874 875 876 877 878 879 880 881 882 883 884
 * 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.
 *
 * @param $edit
 *   An array of submitted form values.
 * @param $uid
 *   The user ID of the user account to cancel.
 * @param $method
 *   The account cancellation method to use.
885
 *
886
 * @see _user_cancel()
887
 */
888
function user_cancel($edit, $uid, $method) {
889
  $account = user_load($uid);
890 891 892

  if (!$account) {
    drupal_set_message(t('The user account %id does not exist.', array('%id' => $uid)), 'error');
893
    watchdog('user', 'Attempted to cancel non-existing user account: %id.', array('%id' => $uid), WATCHDOG_ERROR);
894 895 896 897 898 899 900 901 902 903
    return;
  }

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

904 905 906
  // When the 'user_cancel_delete' method is used, user_delete() is called,
  // which invokes hook_user_predelete() and hook_user_delete(). Modules
  // should use those hooks to respond to the account deletion.
907 908
  if ($method != 'user_cancel_delete') {
    // Allow modules to add further sets to this batch.
909
    \Drupal::moduleHandler()->invokeAll('user_cancel', array($edit, $account, $method));
910
  }
911 912 913 914 915 916 917 918

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

  // After cancelling account, ensure that user is logged out.
921
  if ($account->id() == \Drupal::currentUser()->id()) {
922 923 924 925 926
    // 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';
  }

927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951
  batch_set($batch);

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

/**
 * Last batch processing step for cancelling a user account.
 *
 * Since batch and session API require a valid user account, the actual
 * cancellation of a user account needs to happen last.
 *
 * @see user_cancel()
 */
function _user_cancel($edit, $account, $method) {
  global $user;

  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);
      }
952
      $account->block();
953
      $account->save();
954 955
      drupal_set_message(t('%name has been disabled.', array('%name' => $account->getUsername())));
      watchdog('user', 'Blocked user: %name %email.', array('%name' => $account->getUsername(), '%email' => '<' . $account->getEmail() . '>'), WATCHDOG_NOTICE);
956 957 958 959 960 961 962 963
      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);
      }
964
      $account->delete();
965 966
      drupal_set_message(t('%name has been deleted.', array('%name' => $account->getUsername())));
      watchdog('user', 'Deleted user: %name %email.', array('%name' => $account->getUsername(), '%email' => '<' . $account->getEmail() . '>'), WATCHDOG_NOTICE);
967 968 969
      break;
  }

970 971 972 973
  // 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().
974
  if ($account->id() == $user->id()) {
975
    $user = new AnonymousUserSession();
976 977 978
  }

  // Clear the cache for anonymous users.
979
  Cache::invalidateTags(array('content' => TRUE));
980 981
}

982 983 984 985 986 987 988 989 990 991 992
/**
 * 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.
  drupal_session_regenerate();
}

993 994 995 996 997 998 999 1000 1001 1002 1003 1004
/**
 * 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() {
1005
  $user_settings = \Drupal::config('user.settings');
1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022
  $anonymous_name = $user_settings->get('anonymous');
  $methods = array(
    'user_cancel_block' => array(
      'title' => t('Disable the account and keep its content.'),
      '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 user name.'),
    ),
    '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.'),
1023
      'access' => \Drupal::currentUser()->hasPermission('administer users'),
1024 1025 1026
    ),
  );
  // Allow modules to customize account cancellation methods.
1027
  \Drupal::moduleHandler()->alter('user_cancel_methods', $methods);
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048

  // 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;
}

1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063
/**
 * Delete a user.
 *
 * @param $uid
 *   A user ID.
 */
function user_delete($uid) {
  user_delete_multiple(array($uid));
}

/**
 * Delete multiple user accounts.
 *
 * @param $uids
 *   An array of user IDs.
1064 1065 1066
 *
 * @see hook_user_predelete()
 * @see hook_user_delete()