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

3
use Drupal\Core\Database\Query\SelectInterface;
4
use Drupal\Core\Entity\EntityInterface;
5
use Drupal\file\Plugin\Core\Entity\File;
6
use Drupal\Core\Template\Attribute;
7
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Crell's avatar
Crell committed
8

Dries's avatar
 
Dries committed
9 10 11 12 13
/**
 * @file
 * Enables the user registration and login system.
 */

14 15 16
/**
 * Maximum length of username text field.
 */
17
const USERNAME_MAX_LENGTH = 60;
18 19 20 21

/**
 * Maximum length of user e-mail text field.
 */
22
const EMAIL_MAX_LENGTH = 254;
23

24 25 26
/**
 * Only administrators can create user accounts.
 */
27
const USER_REGISTER_ADMINISTRATORS_ONLY = 'admin_only';
28 29 30 31

/**
 * Visitors can create their own accounts.
 */
32
const USER_REGISTER_VISITORS = 'visitors';
33 34 35 36 37

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

40 41 42 43 44 45 46 47 48 49
/**
 * Implement hook_help().
 */
function user_help($path, $arg) {
  global $user;

  switch ($path) {
    case 'admin/help#user':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
50
      $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 (used to classify users) and permissions associated with those roles. For more information, see the online handbook entry for <a href="@user">User module</a>.', array('@user' => 'http://drupal.org/documentation/modules/user')) . '</p>';
51 52 53
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Creating and managing users') . '</dt>';
54
      $output .= '<dd>' . t('The User module allows users with the appropriate <a href="@permissions">permissions</a> to create user accounts through the <a href="@people">People administration page</a>, where they can also assign users to one or more roles, and block or delete user accounts. If allowed, users without accounts (anonymous users) can create their own accounts on the <a href="@register">Create new account</a> page.', array('@permissions' => url('admin/people/permissions', array('fragment' => 'module-user')), '@people' => url('admin/people'), '@register' => url('user/register'))) . '</dd>';
55
      $output .= '<dt>' . t('User roles and permissions') . '</dt>';
56
      $output .= '<dd>' . t('<em>Roles</em> are used to group and classify users; each user can be assigned one or more roles. By default there are two 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 choices you made when you installed Drupal, the installation process may have defined more roles, and you can create additional custom roles on the <a href="@roles">Roles page</a>. 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' => url('admin/people/permissions'), '@roles' => url('admin/people/roles'))) . '</dd>';
57
      $output .= '<dt>' . t('Account settings') . '</dt>';
58
      $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, and account cancellation. On this page you can also manage settings for account personalization (including signatures and user pictures), and adapt the text for the e-mail messages that are sent automatically during the user registration process.', array('@accounts'  => url('admin/config/people/accounts'))) . '</dd>';
59 60 61 62
      $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>';
63
    case 'admin/people/permissions':
64 65
      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':
66
      $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>';
67
      $output .= '<p>' . t('Drupal has three special user roles:') . '</p>';
68 69 70
      $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>';
71
      $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>';
72 73 74 75 76 77 78 79 80 81
      $output .= '</ul>';
      return $output;
    case 'admin/config/people/accounts/fields':
      return '<p>' . t('This form lets administrators add, edit, and arrange fields for storing user data.') . '</p>';
    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>';
  }
}
82

83
/**
84
 * Implements hook_theme().
85 86 87 88
 */
function user_theme() {
  return array(
    'user_picture' => array(
89
      'variables' => array('account' => NULL),
90
      'template' => 'user-picture',
91 92
    ),
    'user_profile' => array(
93
      'render element' => 'elements',
94
      'template' => 'user-profile',
95
      'file' => 'user.pages.inc',
96
    ),
97
    'user_admin_permissions' => array(
98
      'render element' => 'form',
99
      'file' => 'user.admin.inc',
100
    ),
101
    'user_admin_roles' => array(
102
      'render element' => 'form',
103
      'file' => 'user.admin.inc',
104
    ),
105 106 107 108
    'user_permission_description' => array(
      'variables' => array('permission_item' => NULL, 'hide' => NULL),
      'file' => 'user.admin.inc',
    ),
109
    'user_signature' => array(
110
      'variables' => array('signature' => NULL),
111
    ),
112 113 114
    'username' => array(
      'variables' => array('account' => NULL),
    ),
115 116 117
  );
}

118
/**
119
 * Entity URI callback.
120
 */
121 122 123 124
function user_uri($user) {
  return array(
    'path' => 'user/' . $user->uid,
  );
125 126
}

127 128 129
/**
 * Entity label callback.
 *
130 131 132
 * This label callback has langcode for security reasons. The username is the
 * visual identifier for a user and needs to be consistent in all languages.
 *
133 134 135 136 137 138 139 140
 * @param $entity_type
 *   The entity type.
 * @param $entity
 *   The entity object.
 *
 * @return
 *   The name of the user.
 *
141
 * @see user_format_name()
142 143
 */
function user_label($entity_type, $entity) {
144
  return user_format_name($entity);
145 146
}

147 148 149 150 151
/**
 * Implements hook_field_info_alter().
 */
function user_field_info_alter(&$info) {
  // Add the 'user_register_form' instance setting to all field types.
152 153 154
  foreach ($info as $field_type => &$field_type_info) {
    $field_type_info += array('instance_settings' => array());
    $field_type_info['instance_settings'] += array(
155 156 157 158 159
      'user_register_form' => FALSE,
    );
  }
}

160
/**
161
 * Implements hook_field_extra_fields().
162
 */
163 164
function user_field_extra_fields() {
  $return['user']['user'] = array(
165 166
    'form' => array(
      'account' => array(
167 168
        'label' => t('User name and password'),
        'description' => t('User module account form elements.'),
169 170 171
        'weight' => -10,
      ),
      'timezone' => array(
172
        'label' => t('Timezone'),
173 174 175
        'description' => t('User module timezone form element.'),
        'weight' => 6,
      ),
176
    ),
177
    'display' => array(
178 179 180
      'member_for' => array(
        'label' => t('Member for'),
        'description' => t('User module \'member for\' view element.'),
181 182
        'weight' => 5,
      ),
183 184
    ),
  );
185

186
  return $return;
187 188
}

189 190 191 192 193 194 195 196 197
/**
 * Fetches a user object based on an external authentication source.
 *
 * @param string $authname
 *   The external authentication username.
 *
 * @return
 *   A fully-loaded user object if the user is found or FALSE if not found.
 */
Dries's avatar
 
Dries committed
198
function user_external_load($authname) {
199
  $uid = db_query("SELECT uid FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchField();
Dries's avatar
 
Dries committed
200

201 202
  if ($uid) {
    return user_load($uid);
Dries's avatar
 
Dries committed
203 204
  }
  else {
205
    return FALSE;
Dries's avatar
 
Dries committed
206 207 208
  }
}

Dries's avatar
Dries committed
209
/**
210
 * Loads multiple users based on certain conditions.
Dries's avatar
Dries committed
211
 *
212 213 214
 * 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
215
 *
216 217
 * @param array $uids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
218
 * @param bool $reset
219 220
 *   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.
221
 *
222
 * @return array
223 224
 *   An array of user objects, indexed by uid.
 *
225
 * @see entity_load_multiple()
226 227 228
 * @see user_load()
 * @see user_load_by_mail()
 * @see user_load_by_name()
229
 * @see \Drupal\Core\Entity\Query\QueryInterface
Dries's avatar
Dries committed
230
 */
231 232
function user_load_multiple(array $uids = NULL, $reset = FALSE) {
  return entity_load_multiple('user', $uids, $reset);
233
}
234 235

/**
236 237 238 239 240 241
 * 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
242 243 244 245
 * 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.
246
 *
247
 * @param int $uid
248
 *   Integer specifying the user ID to load.
249
 * @param bool $reset
250 251 252
 *   TRUE to reset the internal cache and load from the database; FALSE
 *   (default) to load from the internal cache, if set.
 *
253
 * @return object
254
 *   A fully-loaded user object upon successful user load, or FALSE if the user
255 256 257 258 259
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load($uid, $reset = FALSE) {
260
  return entity_load('user', $uid, $reset);
261 262 263
}

/**
264
 * Fetches a user object by email address.
265
 *
266
 * @param string $mail
267
 *   String with the account's e-mail address.
268
 * @return object|bool
269 270 271 272 273 274
 *   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) {
275
  $users = entity_load_multiple_by_properties('user', array('mail' => $mail));
276 277 278 279
  return reset($users);
}

/**
280
 * Fetches a user object by account name.
281
 *
282
 * @param string $name
283
 *   String with the account's user name.
284
 * @return object|bool
285 286 287 288 289 290
 *   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) {
291
  $users = entity_load_multiple_by_properties('user', array('name' => $name));
292
  return reset($users);
Dries's avatar
 
Dries committed
293 294
}

Dries's avatar
Dries committed
295 296 297
/**
 * Verify the syntax of the given name.
 */
Dries's avatar
 
Dries committed
298
function user_validate_name($name) {
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
  if (!$name) {
    return t('You must enter a username.');
  }
  if (substr($name, 0, 1) == ' ') {
    return t('The username cannot begin with a space.');
  }
  if (substr($name, -1) == ' ') {
    return t('The username cannot end with a space.');
  }
  if (strpos($name, '  ') !== FALSE) {
    return t('The username cannot contain multiple spaces in a row.');
  }
  if (preg_match('/[^\x{80}-\x{F7} a-z0-9@_.\'-]/i', $name)) {
    return t('The username contains an illegal character.');
  }
314
  if (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
315 316 317 318 319 320 321 322 323
                  '\x{AD}' .                // Soft-hyphen
                  '\x{2000}-\x{200F}' .     // Various space characters
                  '\x{2028}-\x{202F}' .     // Bidirectional text overrides
                  '\x{205F}-\x{206F}' .     // Various text hinting characters
                  '\x{FEFF}' .              // Byte order mark
                  '\x{FF01}-\x{FF60}' .     // Full-width latin
                  '\x{FFF9}-\x{FFFD}' .     // Replacement characters
                  '\x{0}-\x{1F}]/u',        // NULL byte and control characters
                  $name)) {
324 325
    return t('The username contains an illegal character.');
  }
326 327 328
  if (drupal_strlen($name) > USERNAME_MAX_LENGTH) {
    return t('The username %name is too long: it must be %max characters or less.', array('%name' => $name, '%max' => USERNAME_MAX_LENGTH));
  }
Dries's avatar
 
Dries committed
329 330
}

331 332 333
/**
 * Validates an image uploaded by a user.
 *
334
 * @see AccountFormController::form()
335
 */
336
function user_validate_picture(&$form, &$form_state) {
337
  // If required, validate the uploaded picture.
338 339 340 341 342
  $validators = array(
    'file_validate_is_image' => array(),
    'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')),
    'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),
  );
343

344 345 346 347 348 349 350
  // Save the file as a temporary file.
  $file = file_save_upload('picture_upload', $validators);
  if ($file === FALSE) {
    form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures'))));
  }
  elseif ($file !== NULL) {
    $form_state['values']['picture_upload'] = $file;
Dries's avatar
 
Dries committed
351 352 353
  }
}

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

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

Dries's avatar
Dries committed
367 368
  // Declare the password as a blank string.
  $pass = '';
Dries's avatar
 
Dries committed
369

Dries's avatar
Dries committed
370
  // Loop the number of times specified by $length.
Dries's avatar
 
Dries committed
371 372 373 374
  for ($i = 0; $i < $length; $i++) {

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

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

381 382 383 384 385 386 387 388 389 390
/**
 * Determine the permissions for one or more roles.
 *
 * @param $roles
 *   An array whose keys are the role IDs of interest, such as $user->roles.
 *
 * @return
 *   An array indexed by role ID. Each value is an array whose keys are the
 *   permission strings for the given role ID.
 */
391 392
function user_role_permissions($roles = array()) {
  $cache = &drupal_static(__FUNCTION__, array());
393 394 395 396 397

  $role_permissions = $fetch = array();

  if ($roles) {
    foreach ($roles as $rid => $name) {
398 399
      if (isset($cache[$rid])) {
        $role_permissions[$rid] = $cache[$rid];
400 401 402 403 404
      }
      else {
        // Add this rid to the list of those needing to be fetched.
        $fetch[] = $rid;
        // Prepare in case no permissions are returned.
405
        $cache[$rid] = array();
406 407 408 409 410 411
      }
    }

    if ($fetch) {
      // Get from the database permissions that were not in the static variable.
      // Only role IDs with at least one permission assigned will return rows.
412
      $result = db_query("SELECT rid, permission FROM {role_permission} WHERE rid IN (:fetch)", array(':fetch' => $fetch));
413

414
      foreach ($result as $row) {
415
        $cache[$row->rid][$row->permission] = TRUE;
416 417 418
      }
      foreach ($fetch as $rid) {
        // For every rid, we know we at least assigned an empty array.
419
        $role_permissions[$rid] = $cache[$rid];
420 421 422 423 424 425 426
      }
    }
  }

  return $role_permissions;
}

Dries's avatar
Dries committed
427 428 429 430 431
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
Dries's avatar
 
Dries committed
432 433
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
Dries's avatar
Dries committed
434 435
 *
 * @return
436
 *   Boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
437 438 439 440 441
 *
 * All permission checks in Drupal should go through this function. This
 * way, we guarantee consistent behavior, and ensure that the superuser
 * can perform all actions.
 */
442
function user_access($string, $account = NULL) {
Dries's avatar
 
Dries committed
443
  global $user;
444

445
  if (!isset($account)) {
446 447 448
    $account = $user;
  }

449
  // User #1 has all privileges:
450
  if ($account->uid == 1) {
451
    return TRUE;
Dries's avatar
 
Dries committed
452 453
  }

Dries's avatar
Dries committed
454 455
  // To reduce the number of SQL queries, we cache the user's permissions
  // in a static variable.
456
  // Use the advanced drupal_static() pattern, since this is called very often.
457 458 459 460 461
  static $drupal_static_fast;
  if (!isset($drupal_static_fast)) {
    $drupal_static_fast['perm'] = &drupal_static(__FUNCTION__);
  }
  $perm = &$drupal_static_fast['perm'];
462
  if (!isset($perm[$account->uid])) {
463
    $role_permissions = user_role_permissions($account->roles);
Dries's avatar
 
Dries committed
464

465
    $perms = array();
466 467
    foreach ($role_permissions as $one_role) {
      $perms += $one_role;
Dries's avatar
 
Dries committed
468
    }
469
    $perm[$account->uid] = $perms;
Dries's avatar
 
Dries committed
470
  }
471

472
  return isset($perm[$account->uid][$string]);
Dries's avatar
 
Dries committed
473 474
}

475
/**
476
 * Checks for usernames blocked by user administration.
477
 *
478 479 480 481 482 483
 * @param $name
 *   A string containing a name of the user.
 *
 * @return
 *   Object with property 'name' (the user name), if the user is blocked;
 *   FALSE if the user is not blocked.
484 485
 */
function user_is_blocked($name) {
486 487 488 489 490
  return db_select('users')
    ->fields('users', array('name'))
    ->condition('name', db_like($name), 'LIKE')
    ->condition('status', 0)
    ->execute()->fetchObject();
491 492
}

Dries's avatar
Dries committed
493
/**
494
 * Implements hook_permission().
Dries's avatar
Dries committed
495
 */
496
function user_permission() {
497 498 499
  return array(
    'administer permissions' =>  array(
      'title' => t('Administer permissions'),
500
      'restrict access' => TRUE,
501 502 503
    ),
    'administer users' => array(
      'title' => t('Administer users'),
504
      'restrict access' => TRUE,
505 506
    ),
    'access user profiles' => array(
507
      'title' => t('View user profiles'),
508 509 510 511 512
    ),
    'change own username' => array(
      'title' => t('Change own username'),
    ),
    'cancel account' => array(
513
      'title' => t('Cancel own user account'),
514
      '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' => config('user.settings')->get('anonymous'), '@user-settings-url' => url('admin/config/people/accounts'))),
515 516 517
    ),
    'select account cancellation method' => array(
      'title' => t('Select method for cancelling own account'),
518
      'restrict access' => TRUE,
519 520
    ),
  );
Dries's avatar
 
Dries committed
521 522
}

Dries's avatar
Dries committed
523
/**
524
 * Implements hook_file_download().
Dries's avatar
Dries committed
525 526 527
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
528 529 530
function user_file_download($uri) {
  if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
    $info = image_get_info($uri);
531
    return array('Content-Type' => $info['mime_type']);
Dries's avatar
 
Dries committed
532 533 534
  }
}

535
/**
536
 * Implements hook_file_move().
537
 */
538
function user_file_move(File $file, File $source) {
539 540 541 542 543 544 545 546 547
  // If a user's picture is replaced with a new one, update the record in
  // the users table.
  if (isset($file->fid) && isset($source->fid) && $file->fid != $source->fid) {
    db_update('users')
      ->fields(array(
        'picture' => $file->fid,
      ))
      ->condition('picture', $source->fid)
      ->execute();
548 549 550 551
  }
}

/**
552
 * Implements hook_file_predelete().
553
 */
554
function user_file_predelete(File $file) {
555
  // Remove any references to the file.
556
  db_update('users')
557 558 559 560 561
    ->fields(array('picture' => 0))
    ->condition('picture', $file->fid)
    ->execute();
}

Dries's avatar
Dries committed
562
/**
563
 * Implements hook_search_info().
Dries's avatar
Dries committed
564
 */
565 566 567 568 569 570 571
function user_search_info() {
  return array(
    'title' => 'Users',
  );
}

/**
572
 * Implements hook_search_access().
573 574 575 576 577 578
 */
function user_search_access() {
  return user_access('access user profiles');
}

/**
579
 * Implements hook_search_execute().
580
 */
581
function user_search_execute($keys = NULL, $conditions = NULL) {
582 583 584
  $find = array();
  // Replace wildcards with MySQL/PostgreSQL wildcards.
  $keys = preg_replace('!\*+!', '%', $keys);
585 586
  $query = db_select('users')
    ->extend('Drupal\Core\Database\Query\PagerSelectExtender');
587
  $query->fields('users', array('uid'));
588 589
  if (user_access('administer users')) {
    // Administrators can also search in the otherwise private email field.
590
    $query->fields('users', array('mail'));
591
    $query->condition(db_or()->
592 593
      condition('name', '%' . db_like($keys) . '%', 'LIKE')->
      condition('mail', '%' . db_like($keys) . '%', 'LIKE'));
594 595
  }
  else {
596
    $query->condition('name', '%' . db_like($keys) . '%', 'LIKE');
597
  }
598
  $uids = $query
599
    ->limit(15)
600 601 602 603 604 605 606
    ->execute()
    ->fetchCol();
  $accounts = user_load_multiple($uids);

  $results = array();
  foreach ($accounts as $account) {
    $result = array(
607
      'title' => user_format_name($account),
608 609 610 611
      'link' => url('user/' . $account->uid, array('absolute' => TRUE)),
    );
    if (user_access('administer users')) {
      $result['title'] .= ' (' . $account->mail . ')';
612
    }
613
    $results[] = $result;
Dries's avatar
 
Dries committed
614
  }
615 616

  return $results;
Dries's avatar
 
Dries committed
617 618
}

Dries's avatar
Dries committed
619
/**
620
 * Implements hook_user_view().
Dries's avatar
Dries committed
621
 */
622
function user_user_view($account) {
623
  $account->content['user_picture'] = array(
624
    '#markup' => theme('user_picture', array('account' => $account)),
625 626
    '#weight' => -10,
  );
627 628
  $account->content['member_for'] = array(
    '#type' => 'item',
629 630
    '#title' => t('Member for'),
    '#markup' => format_interval(REQUEST_TIME - $account->created),
631
    '#weight' => 5,
632 633 634
  );
}

635 636 637 638 639 640 641 642 643
/**
 * 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'];
644 645
}

646
/**
647
 * Form validation handler for the current password on the user account form.
648
 *
649
 * @see AccountFormController::form()
650 651
 */
function user_validate_current_pass(&$form, &$form_state) {
652
  $account = $form_state['user'];
653 654 655 656 657
  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.
    if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) {
658
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc');
659
      $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account);
660 661 662 663 664 665 666 667 668 669
      if ($current_pass_failed) {
        form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name)));
        form_set_error($key);
      }
      // We only need to check the password once.
      break;
    }
  }
}

Dries's avatar
Dries committed
670
/**
671
 * Implements hook_block_info().
Dries's avatar
Dries committed
672
 */
673
function user_block_info() {
Dries's avatar
 
Dries committed
674 675
  global $user;

676 677
  $blocks['login']['info'] = t('User login');
  // Not worth caching.
678
  $blocks['login']['cache'] = DRUPAL_NO_CACHE;
679

680
  $blocks['new']['info'] = t('Who\'s new');
681
  $blocks['new']['properties']['administrative'] = TRUE;
682

683 684
  // Too dynamic to cache.
  $blocks['online']['info'] = t('Who\'s online');
685
  $blocks['online']['cache'] = DRUPAL_NO_CACHE;
686 687
  $blocks['online']['properties']['administrative'] = TRUE;

688 689
  return $blocks;
}
690

691
/**
692
 * Implements hook_block_configure().
693 694 695 696
 */
function user_block_configure($delta = '') {
  global $user;

697 698
  $config = config('user.block');

699
  switch ($delta) {
700 701 702 703
    case 'new':
      $form['user_block_whois_new_count'] = array(
        '#type' => 'select',
        '#title' => t('Number of users to display'),
704
        '#default_value' => $config->get('whois_new_count'),
705 706 707 708 709 710
        '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
      );
      return $form;

    case 'online':
      $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
711 712 713 714 715 716 717 718 719 720 721 722 723 724
      $form['user_block_seconds_online'] = array(
        '#type' => 'select',
        '#title' => t('User activity'),
        '#default_value' => $config->get('seconds_online'),
        '#options' => $period,
        '#description' => t('A user is considered online for this long after they have last viewed a page.')
      );
      $form['user_block_max_list_count'] = array(
        '#type' => 'select',
        '#title' => t('User list length'),
        '#default_value' => $config->get('max_list_count'),
        '#options' => drupal_map_assoc(array(0, 5, 10, 15, 20, 25, 30, 40, 50, 75, 100)),
        '#description' => t('Maximum number of currently online users to display.')
      );
725
      return $form;
726
  }
727 728 729
}

/**
730
 * Implements hook_block_save().
731 732 733
 */
function user_block_save($delta = '', $edit = array()) {
  global $user;
734
  $config = config('user.block');
735 736 737

  switch ($delta) {
    case 'new':
738
      $config->set('whois_new_count', $edit['user_block_whois_new_count'])->save();
739 740 741
      break;

    case 'online':
742 743
      $config->set('seconds_online', $edit['user_block_seconds_online'])->save();
      $config->set('max_list_count', $edit['user_block_max_list_count'])->save();
744
      break;
745
  }
746
}
Dries's avatar
 
Dries committed
747

748
/**
749
 * Implements hook_block_view().
750 751 752
 */
function user_block_view($delta = '') {
  global $user;
Dries's avatar
 
Dries committed
753

754
  $block = array();
755
  $block_config = config('user.block');
Dries's avatar
Dries committed
756

757 758 759 760
  switch ($delta) {
    case 'login':
      // For usability's sake, avoid showing two login forms on one page.
      if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
        // Customize the login form.
        $form = drupal_get_form('user_login_form');
        unset($form['name']['#attributes']['autofocus']);
        unset($form['name']['#description']);
        unset($form['pass']['#description']);
        $form['name']['#size'] = 15;
        $form['pass']['#size'] = 15;
        $form['#action'] = url(current_path(), array('query' => drupal_get_destination(), 'external' => FALSE));
        // Build action links.
        $items = array();
        if (config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) {
          $items['create_account'] = l(t('Create new account'), 'user/register', array(
            'attributes' => array(
              'title' => t('Create a new user account.'),
              'class' => array('create-account-link'),
            ),
          ));
        }
        $items['request_password'] = l(t('Request new password'), 'user/password', array(
          'attributes' => array(
            'title' => t('Request new password via e-mail.'),
            'class' => array('request-password-link'),
          ),
        ));
        // Build a block as renderable array.
786
        $block['subject'] = t('User login');
787 788 789 790 791 792 793
        $block['content'] = array(
          'user_login_form' => $form,
          'user_links' => array(
            '#theme' => 'item_list',
            '#items' => $items,
          )
        );
794 795
      }
      return $block;
Dries's avatar
 
Dries committed
796

797 798 799
    case 'new':
      if (user_access('access content')) {
        // Retrieve a list of new users who have subsequently accessed the site successfully.
800 801 802
        $from = 0;
        $count = $block_config->get('whois_new_count');
        $items = db_query_range('SELECT uid, name FROM {users} WHERE status <> 0 AND access <> 0 ORDER BY created DESC', $from, $count)->fetchAll();
803

804
        $block['subject'] = t('Who\'s new');
805 806 807 808 809 810 811
        $block['content'] = array(
          '#theme' => 'item_list__user__new',
          '#items' => array(),
        );
        foreach ($items as $account) {
          $block['content']['#items'][] = theme('username', array('account' => $account));
        }
812 813
      }
      return $block;
Dries's avatar
 
Dries committed
814

815 816 817
    case 'online':
      if (user_access('access content')) {
        // Count users active within the defined period.
818
        $interval = REQUEST_TIME - $block_config->get('seconds_online');
Dries's avatar
 
Dries committed
819

820
        // Perform database queries to gather online user lists. We use s.timestamp
821 822
        // rather than u.access because it is much faster.
        $authenticated_count = db_query("SELECT COUNT(DISTINCT s.uid) FROM {sessions} s WHERE s.timestamp >= :timestamp AND s.uid > 0", array(':timestamp' => $interval))->fetchField();
823

824 825 826 827 828 829
        $block['subject'] = t('Who\'s online');
        $block['content'] = array(
          '#theme' => 'item_list__user__online',
          '#items' => array(),
          '#prefix' => '<p>' . format_plural($authenticated_count, 'There is currently 1 user online.', 'There are currently @count users online.') . '</p>',
        );
830 831

        // Display a list of currently online users.
832
        $max_users = $block_config->get('max_list_count');
833
        if ($authenticated_count && $max_users) {
834
          $items = db_query_range('SELECT u.uid, u.name, MAX(s.timestamp) AS max_timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.timestamp >= :interval AND s.uid > 0 GROUP BY u.uid, u.name ORDER BY max_timestamp DESC', 0, $max_users, array(':interval' => $interval))->fetchAll();
835

836 837 838 839
          foreach ($items as $account) {
            $block['content']['#items'][] = theme('username', array('account' => $account));
          }
        }
840 841
      }
      return $block;
Dries's avatar
 
Dries committed
842
  }
843 844
}

845
/**