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

3
use Drupal\Core\Database\Query\SelectInterface;
4
use Drupal\Core\File\File;
5
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Crell's avatar
Crell committed
6

7 8 9 10 11
/**
 * @file
 * Enables the user registration and login system.
 */

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

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

22 23 24
/**
 * Only administrators can create user accounts.
 */
25
const USER_REGISTER_ADMINISTRATORS_ONLY = 0;
26 27 28 29

/**
 * Visitors can create their own accounts.
 */
30
const USER_REGISTER_VISITORS = 1;
31 32 33 34 35

/**
 * Visitors can create accounts, but they don't become active without
 * administrative approval.
 */
36
const USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL = 2;
37

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

  switch ($path) {
    case 'admin/help#user':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
48
      $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>';
49 50 51
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Creating and managing users') . '</dt>';
52
      $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>';
53
      $output .= '<dt>' . t('User roles and permissions') . '</dt>';
54
      $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>';
55
      $output .= '<dt>' . t('Account settings') . '</dt>';
56
      $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>';
57 58 59 60
      $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>';
61
    case 'admin/people/permissions':
62 63
      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':
64
      $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>';
65
      $output .= '<p>' . t('Drupal has three special user roles:') . '</p>';
66 67 68
      $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>';
69
      $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>';
70 71 72 73 74 75 76 77 78 79
      $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>';
  }
}
80

Dries's avatar
Dries committed
81
/**
82
 * Invokes a user hook in every module.
Dries's avatar
Dries committed
83
 *
84
 * We cannot use module_invoke() for this, because the arguments need to
Dries's avatar
Dries committed
85
 * be passed by reference.
86 87
 *
 * @param $type
88
 *   A text string that controls which user hook to invoke. Valid choices are:
89 90 91 92 93 94 95
 *   - login: Invokes hook_user_login().
 * @param $edit
 *   An associative array variable containing form values to be passed
 *   as the first parameter of the hook function.
 * @param $account
 *   The user account object to be passed as the second parameter of the hook
 *   function.
Dries's avatar
Dries committed
96
 */
97
function user_module_invoke($type, &$edit, $account) {
98
  foreach (module_implements('user_' . $type) as $module) {
99
    $function = $module . '_user_' . $type;
100
    $function($edit, $account);
101 102 103
  }
}

104
/**
105
 * Implements hook_theme().
106 107 108 109
 */
function user_theme() {
  return array(
    'user_picture' => array(
110
      'variables' => array('account' => NULL),
111
      'template' => 'user-picture',
112 113
    ),
    'user_profile' => array(
114
      'render element' => 'elements',
115
      'template' => 'user-profile',
116
      'file' => 'user.pages.inc',
117
    ),
118
    'user_list' => array(
119
      'variables' => array('users' => NULL, 'title' => NULL),
120
    ),
121
    'user_admin_permissions' => array(
122
      'render element' => 'form',
123
      'file' => 'user.admin.inc',
124
    ),
125
    'user_admin_roles' => array(
126
      'render element' => 'form',
127
      'file' => 'user.admin.inc',
128
    ),
129 130 131 132
    'user_permission_description' => array(
      'variables' => array('permission_item' => NULL, 'hide' => NULL),
      'file' => 'user.admin.inc',
    ),
133
    'user_signature' => array(
134
      'variables' => array('signature' => NULL),
135
    ),
136 137 138
    'username' => array(
      'variables' => array('account' => NULL),
    ),
139 140 141
  );
}

142
/**
143
 * Implements hook_entity_info().
144
 */
145
function user_entity_info() {
146
  return array(
147
    'user' => array(
148
      'label' => t('User'),
149
      'controller class' => 'Drupal\user\UserStorageController',
150
      'base table' => 'users',
151
      'uri callback' => 'user_uri',
152
      'label callback' => 'user_label',
153
      'fieldable' => TRUE,
154
      'entity class' => 'Drupal\user\User',
155
      'entity keys' => array(
156 157 158 159 160 161
        'id' => 'uid',
      ),
      'bundles' => array(
        'user' => array(
          'label' => t('User'),
          'admin' => array(
162
            'path' => 'admin/config/people/accounts',
163 164 165 166
            'access arguments' => array('administer users'),
          ),
        ),
      ),
167 168 169
      'view modes' => array(
        'full' => array(
          'label' => t('User account'),
170
          'custom settings' => FALSE,
171 172
        ),
      ),
173 174 175 176
    ),
  );
}

177
/**
178
 * Entity uri callback.
179
 */
180 181 182 183
function user_uri($user) {
  return array(
    'path' => 'user/' . $user->uid,
  );
184 185
}

186 187 188
/**
 * Entity label callback.
 *
189 190 191
 * 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.
 *
192 193 194 195 196 197 198 199
 * @param $entity_type
 *   The entity type.
 * @param $entity
 *   The entity object.
 *
 * @return
 *   The name of the user.
 *
200
 * @see user_format_name()
201 202
 */
function user_label($entity_type, $entity) {
203
  return user_format_name($entity);
204 205
}

206 207 208 209 210
/**
 * Implements hook_field_info_alter().
 */
function user_field_info_alter(&$info) {
  // Add the 'user_register_form' instance setting to all field types.
211 212 213
  foreach ($info as $field_type => &$field_type_info) {
    $field_type_info += array('instance_settings' => array());
    $field_type_info['instance_settings'] += array(
214 215 216 217 218
      'user_register_form' => FALSE,
    );
  }
}

219
/**
220
 * Implements hook_field_extra_fields().
221
 */
222 223
function user_field_extra_fields() {
  $return['user']['user'] = array(
224 225
    'form' => array(
      'account' => array(
226 227
        'label' => t('User name and password'),
        'description' => t('User module account form elements.'),
228 229 230
        'weight' => -10,
      ),
      'timezone' => array(
231
        'label' => t('Timezone'),
232 233 234
        'description' => t('User module timezone form element.'),
        'weight' => 6,
      ),
235
    ),
236
    'display' => array(
237 238 239
      'member_for' => array(
        'label' => t('Member for'),
        'description' => t('User module \'member for\' view element.'),
240 241
        'weight' => 5,
      ),
242 243
    ),
  );
244

245
  return $return;
246 247
}

248 249 250 251 252 253 254 255 256
/**
 * 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.
 */
257
function user_external_load($authname) {
258
  $uid = db_query("SELECT uid FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchField();
259

260 261
  if ($uid) {
    return user_load($uid);
262 263
  }
  else {
264
    return FALSE;
265 266 267
  }
}

Dries's avatar
Dries committed
268
/**
269
 * Loads multiple users based on certain conditions.
Dries's avatar
Dries committed
270
 *
271 272 273
 * 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
274
 *
275 276 277
 * @param array|bool $uids
 *   An array of user IDs, or FALSE to load all users.
 * @param array $conditions
278 279 280
 *   (deprecated) An associative array of conditions on the {users}
 *   table, where the keys are the database fields and the values are the
 *   values those fields must have. Instead, it is preferable to use
281 282
 *   Drupal\entity\EntityFieldQuery to retrieve a list of entity IDs
 *   loadable by this function.
283
 * @param bool $reset
284 285
 *   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.
286
 *
287
 * @return array
288 289
 *   An array of user objects, indexed by uid.
 *
290
 * @see entity_load_multiple()
291 292 293
 * @see user_load()
 * @see user_load_by_mail()
 * @see user_load_by_name()
294
 * @see Drupal\entity\EntityFieldQuery
295 296
 *
 * @todo Remove $conditions in Drupal 8.
Dries's avatar
Dries committed
297
 */
298 299
function user_load_multiple($uids = array(), array $conditions = array(), $reset = FALSE) {
  return entity_load_multiple('user', $uids, $conditions, $reset);
300
}
301 302

/**
303 304 305 306 307 308
 * 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
309 310 311 312
 * 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.
313
 *
314
 * @param int $uid
315
 *   Integer specifying the user ID to load.
316
 * @param bool $reset
317 318 319
 *   TRUE to reset the internal cache and load from the database; FALSE
 *   (default) to load from the internal cache, if set.
 *
320
 * @return object
321
 *   A fully-loaded user object upon successful user load, or FALSE if the user
322 323 324 325 326
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load($uid, $reset = FALSE) {
327
  return entity_load('user', $uid, $reset);
328 329 330
}

/**
331
 * Fetches a user object by email address.
332
 *
333
 * @param string $mail
334
 *   String with the account's e-mail address.
335
 * @return object|bool
336 337 338 339 340 341
 *   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) {
342
  $users = entity_load_multiple('user', FALSE, array('mail' => $mail));
343 344 345 346
  return reset($users);
}

/**
347
 * Fetches a user object by account name.
348
 *
349
 * @param string $name
350
 *   String with the account's user name.
351
 * @return object|bool
352 353 354 355 356 357
 *   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) {
358
  $users = entity_load_multiple('user', FALSE, array('name' => $name));
359
  return reset($users);
360 361
}

Dries's avatar
Dries committed
362 363 364
/**
 * Verify the syntax of the given name.
 */
365
function user_validate_name($name) {
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
  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.');
  }
381
  if (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
382 383 384 385 386 387 388 389 390
                  '\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)) {
391 392
    return t('The username contains an illegal character.');
  }
393 394 395
  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));
  }
396 397
}

398 399 400 401 402
/**
 * Validates an image uploaded by a user.
 *
 * @see user_account_form()
 */
403
function user_validate_picture(&$form, &$form_state) {
404
  // If required, validate the uploaded picture.
405 406 407 408 409
  $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),
  );
410

411 412 413 414 415 416 417
  // 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
418 419 420
  }
}

Dries's avatar
Dries committed
421 422 423
/**
 * Generate a random alphanumeric password.
 */
424 425
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
426 427
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
428
  // of 'I', 1, and 'l'.
429
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
Dries's avatar
Dries committed
430

431 432
  // Zero-based count of characters in the allowable list:
  $len = strlen($allowable_characters) - 1;
433

Dries's avatar
Dries committed
434 435
  // Declare the password as a blank string.
  $pass = '';
436

Dries's avatar
Dries committed
437
  // Loop the number of times specified by $length.
438 439 440 441
  for ($i = 0; $i < $length; $i++) {

    // Each iteration, pick a random character from the
    // allowable string and append it to the password:
442
    $pass .= $allowable_characters[mt_rand(0, $len)];
443 444 445
  }

  return $pass;
446 447
}

448 449 450 451 452 453 454 455 456 457
/**
 * 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.
 */
458 459
function user_role_permissions($roles = array()) {
  $cache = &drupal_static(__FUNCTION__, array());
460 461 462 463 464

  $role_permissions = $fetch = array();

  if ($roles) {
    foreach ($roles as $rid => $name) {
465 466
      if (isset($cache[$rid])) {
        $role_permissions[$rid] = $cache[$rid];
467 468 469 470 471
      }
      else {
        // Add this rid to the list of those needing to be fetched.
        $fetch[] = $rid;
        // Prepare in case no permissions are returned.
472
        $cache[$rid] = array();
473 474 475 476 477 478
      }
    }

    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.
479
      $result = db_query("SELECT rid, permission FROM {role_permission} WHERE rid IN (:fetch)", array(':fetch' => $fetch));
480

481
      foreach ($result as $row) {
482
        $cache[$row->rid][$row->permission] = TRUE;
483 484 485
      }
      foreach ($fetch as $rid) {
        // For every rid, we know we at least assigned an empty array.
486
        $role_permissions[$rid] = $cache[$rid];
487 488 489 490 491 492 493
      }
    }
  }

  return $role_permissions;
}

Dries's avatar
Dries committed
494 495 496 497 498
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
499 500
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
Dries's avatar
Dries committed
501 502
 *
 * @return
503
 *   Boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
504 505 506 507 508
 *
 * 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.
 */
509
function user_access($string, $account = NULL) {
510
  global $user;
511

512
  if (!isset($account)) {
513 514 515
    $account = $user;
  }

516
  // User #1 has all privileges:
517
  if ($account->uid == 1) {
518
    return TRUE;
519 520
  }

Dries's avatar
Dries committed
521 522
  // To reduce the number of SQL queries, we cache the user's permissions
  // in a static variable.
523
  // Use the advanced drupal_static() pattern, since this is called very often.
524 525 526 527 528
  static $drupal_static_fast;
  if (!isset($drupal_static_fast)) {
    $drupal_static_fast['perm'] = &drupal_static(__FUNCTION__);
  }
  $perm = &$drupal_static_fast['perm'];
529
  if (!isset($perm[$account->uid])) {
530
    $role_permissions = user_role_permissions($account->roles);
531

532
    $perms = array();
533 534
    foreach ($role_permissions as $one_role) {
      $perms += $one_role;
535
    }
536
    $perm[$account->uid] = $perms;
537
  }
538

539
  return isset($perm[$account->uid][$string]);
540 541
}

542
/**
543
 * Checks for usernames blocked by user administration.
544
 *
545 546 547 548 549 550
 * @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.
551 552
 */
function user_is_blocked($name) {
553 554 555 556 557
  return db_select('users')
    ->fields('users', array('name'))
    ->condition('name', db_like($name), 'LIKE')
    ->condition('status', 0)
    ->execute()->fetchObject();
558 559
}

Dries's avatar
Dries committed
560
/**
561
 * Implements hook_permission().
Dries's avatar
Dries committed
562
 */
563
function user_permission() {
564 565 566
  return array(
    'administer permissions' =>  array(
      'title' => t('Administer permissions'),
567
      'restrict access' => TRUE,
568 569 570
    ),
    'administer users' => array(
      'title' => t('Administer users'),
571
      'restrict access' => TRUE,
572 573
    ),
    'access user profiles' => array(
574
      'title' => t('View user profiles'),
575 576 577 578 579
    ),
    'change own username' => array(
      'title' => t('Change own username'),
    ),
    'cancel account' => array(
580
      'title' => t('Cancel own user account'),
581
      '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' => variable_get('anonymous', t('Anonymous')), '@user-settings-url' => url('admin/config/people/accounts'))),
582 583 584
    ),
    'select account cancellation method' => array(
      'title' => t('Select method for cancelling own account'),
585
      'restrict access' => TRUE,
586 587
    ),
  );
588 589
}

Dries's avatar
Dries committed
590
/**
591
 * Implements hook_file_download().
Dries's avatar
Dries committed
592 593 594
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
595 596 597
function user_file_download($uri) {
  if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
    $info = image_get_info($uri);
598
    return array('Content-Type' => $info['mime_type']);
599 600 601
  }
}

602
/**
603
 * Implements hook_file_move().
604
 */
605
function user_file_move(File $file, File $source) {
606 607 608 609 610 611 612 613 614
  // 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();
615 616 617 618
  }
}

/**
619
 * Implements hook_file_predelete().
620
 */
621
function user_file_predelete(File $file) {
622
  // Remove any references to the file.
623
  db_update('users')
624 625 626 627 628
    ->fields(array('picture' => 0))
    ->condition('picture', $file->fid)
    ->execute();
}

Dries's avatar
Dries committed
629
/**
630
 * Implements hook_search_info().
Dries's avatar
Dries committed
631
 */
632 633 634 635 636 637 638
function user_search_info() {
  return array(
    'title' => 'Users',
  );
}

/**
639
 * Implements hook_search_access().
640 641 642 643 644 645
 */
function user_search_access() {
  return user_access('access user profiles');
}

/**
646
 * Implements hook_search_execute().
647
 */
648
function user_search_execute($keys = NULL, $conditions = NULL) {
649 650 651
  $find = array();
  // Replace wildcards with MySQL/PostgreSQL wildcards.
  $keys = preg_replace('!\*+!', '%', $keys);
652 653
  $query = db_select('users')
    ->extend('Drupal\Core\Database\Query\PagerSelectExtender');
654
  $query->fields('users', array('uid'));
655 656
  if (user_access('administer users')) {
    // Administrators can also search in the otherwise private email field.
657
    $query->fields('users', array('mail'));
658
    $query->condition(db_or()->
659 660
      condition('name', '%' . db_like($keys) . '%', 'LIKE')->
      condition('mail', '%' . db_like($keys) . '%', 'LIKE'));
661 662
  }
  else {
663
    $query->condition('name', '%' . db_like($keys) . '%', 'LIKE');
664
  }
665
  $uids = $query
666
    ->limit(15)
667 668 669 670 671 672 673
    ->execute()
    ->fetchCol();
  $accounts = user_load_multiple($uids);

  $results = array();
  foreach ($accounts as $account) {
    $result = array(
674
      'title' => user_format_name($account),
675 676 677 678
      'link' => url('user/' . $account->uid, array('absolute' => TRUE)),
    );
    if (user_access('administer users')) {
      $result['title'] .= ' (' . $account->mail . ')';
679
    }
680
    $results[] = $result;
681
  }
682 683

  return $results;
684 685
}

Dries's avatar
Dries committed
686
/**
687
 * Implements hook_user_view().
Dries's avatar
Dries committed
688
 */
689
function user_user_view($account) {
690
  $account->content['user_picture'] = array(
691
    '#markup' => theme('user_picture', array('account' => $account)),
692 693
    '#weight' => -10,
  );
694 695
  $account->content['member_for'] = array(
    '#type' => 'item',
696 697
    '#title' => t('Member for'),
    '#markup' => format_interval(REQUEST_TIME - $account->created),
698
    '#weight' => 5,
699 700 701 702
  );
}

/**
703
 * Helper function to add default user account fields to user registration and edit form.
704
 *
705 706 707
 * @see user_account_form_validate()
 * @see user_validate_current_pass()
 * @see user_validate_picture()
708
 * @see user_validate_mail()
709
 */
710
function user_account_form(&$form, &$form_state) {
711 712
  global $user;
  $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE);
713 714 715 716 717 718 719 720 721 722

  $account = $form['#user'];
  $register = ($form['#user']->uid > 0 ? FALSE : TRUE);

  $admin = user_access('administer users');

  $form['#validate'][] = 'user_account_form_validate';

  // Account information.
  $form['account'] = array(
723
    '#type'   => 'container',
724 725 726 727 728 729 730 731 732
    '#weight' => -10,
  );
  // Only show name field on registration form or user can change own username.
  $form['account']['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Username'),
    '#maxlength' => USERNAME_MAX_LENGTH,
    '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'),
    '#required' => TRUE,
733
    '#attributes' => array('class' => array('username'), 'autocomplete' => 'off'),
734 735
    '#default_value' => (!$register ? $account->name : ''),
    '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin),
736
    '#weight' => -10,
737 738
  );

739 740 741
  // The mail field is NOT required if account originally had no mail set
  // and the user performing the edit has 'administer users' permission.
  // This allows users without e-mail address to be edited and deleted.
742
  $form['account']['mail'] = array(
743
    '#type' => 'email',
744 745
    '#title' => t('E-mail address'),
    '#description' => t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'),
746
    '#required' => !(empty($account->mail) && user_access('administer users')),
747
    '#default_value' => (!$register ? $account->mail : ''),
748
    '#attributes' => array('autocomplete' => 'off'),
749 750 751 752 753 754 755 756 757 758
  );

  // Display password field only for existing users or when user is allowed to
  // assign a password during registration.
  if (!$register) {
    $form['account']['pass'] = array(
      '#type' => 'password_confirm',
      '#size' => 25,
      '#description' => t('To change the current user password, enter the new password in both fields.'),
    );
759 760 761 762 763 764 765 766 767 768 769
    // To skip the current password field, the user must have logged in via a
    // one-time link and have the token in the URL.
    $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]);
    $protected_values = array();
    $current_pass_description = '';
    // The user may only change their own password without their current
    // password if they logged in via a one-time login link.
    if (!$pass_reset) {
      $protected_values['mail'] = $form['account']['mail']['#title'];
      $protected_values['pass'] = t('Password');
      $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
770
      $current_pass_description = t('Required if you want to change the %mail or %pass below. !request_new.', array('%mail' => $protected_values['mail'], '%pass' => $protected_values['pass'], '!request_new' => $request_new));
771 772 773 774 775 776 777 778 779 780 781 782 783 784
    }
    // The user must enter their current password to change to a new one.
    if ($user->uid == $account->uid) {
      $form['account']['current_pass_required_values'] = array(
        '#type' => 'value',
        '#value' => $protected_values,
      );
      $form['account']['current_pass'] = array(
        '#type' => 'password',
        '#title' => t('Current password'),
        '#size' => 25,
        '#access' => !empty($protected_values),
        '#description' => $current_pass_description,
        '#weight' => -5,
785
        '#attributes' => array('autocomplete' => 'off'),
786 787 788
      );
      $form['#validate'][] = 'user_validate_current_pass';
    }
789 790 791 792 793 794 795 796 797 798 799
  }
  elseif (!variable_get('user_email_verification', TRUE) || $admin) {
    $form['account']['pass'] = array(
      '#type' => 'password_confirm',
      '#size' => 25,
      '#description' => t('Provide a password for the new account in both fields.'),
      '#required' => TRUE,
    );
  }

  if ($admin) {
800
    $status = isset($account->status) ? $account->status : 1;
801 802
  }
  else {
803
    $status = $register ? variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS : $account->status;
804 805 806 807 808 809
  }
  $form['account']['status'] = array(
    '#type' => 'radios',
    '#title' => t('Status'),
    '#default_value' => $status,
    '#options' => array(t('Blocked'), t('Active')),
810
    '#access' => $admin,
811 812
  );

813
  $roles = array_map('check_plain', user_roles(TRUE));
814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
  // The disabled checkbox subelement for the 'authenticated user' role
  // must be generated separately and added to the checkboxes element,
  // because of a limitation in Form API not supporting a single disabled
  // checkbox within a set of checkboxes.
  // @todo This should be solved more elegantly. See issue #119038.
  $checkbox_authenticated = array(
    '#type' => 'checkbox',
    '#title' => $roles[DRUPAL_AUTHENTICATED_RID],
    '#default_value' => TRUE,
    '#disabled' => TRUE,
  );
  unset($roles[DRUPAL_AUTHENTICATED_RID]);
  $form['account']['roles'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Roles'),
    '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()),
    '#options' => $roles,
    '#access' => $roles && user_access('administer permissions'),
    DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
  );

  $form['account']['notify'] = array(
    '#type' => 'checkbox',
    '#title' => t('Notify user of new account'),
    '#access' => $register && $admin,
  );

  // Signature.
  $form['signature_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Signature settings'),
    '#weight' => 1,
    '#access' => (!$register && variable_get('user_signatures', 0)),
  );
848

849
  $form['signature_settings']['signature'] = array(
850
    '#type' => 'text_format',
851 852 853
    '#title' => t('Signature'),
    '#default_value' => isset($account->signature) ? $account->signature : '',
    '#description' => t('Your signature will be publicly displayed at the end of your comments.'),
854
    '#format' => isset($account->signature_format) ? $account->signature_format : NULL,
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
  );

  // Picture/avatar.
  $form['picture'] = array(
    '#type' => 'fieldset',
    '#title' => t('Picture'),
    '#weight' => 1,
    '#access' => (!$register && variable_get('user_pictures', 0)),
  );
  $form['picture']['picture'] = array(
    '#type' => 'value',
    '#value' => isset($account->picture) ? $account->picture : NULL,
  );
  $form['picture']['picture_current'] = array(
    '#markup' => theme('user_picture', array('account' => $account)),
  );
  $form['picture']['picture_delete'] = array(
    '#type' => 'checkbox',
    '#title' => t('Delete picture'),
    '#access' => !empty($account->picture->fid),
    '#description' => t('Check this box to delete your current picture.'),
  );
  $form['picture']['picture_upload'] = array(
    '#type' => 'file',
    '#title' => t('Upload picture'),
    '#size' => 48,
881
    '#description' => t('Your virtual face or picture. Pictures larger than @dimensions pixels will be scaled down.', array('@dimensions' => variable_get('user_picture_dimensions', '85x85'))) . ' ' . filter_xss_admin(variable_get('user_picture_guidelines', '')),
882 883
  );
  $form['#validate'][] = 'user_validate_picture';
884 885

  if (module_exists('language') && language_multilingual()) {
886
    $languages = language_list();
887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946

    // If the user is being created, we set the user language to the page language.
    $user_preferred_language = $register ? $language_interface : user_preferred_language($account);

    $names = array();
    foreach ($languages as $langcode => $item) {
      $names[$langcode] = $item->name;
    }
    // Is default the interface language?
    $interface_language_is_default = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_DEFAULT;
    $form['language'] = array(
      '#type' => 'fieldset',
      '#title' => t('Language settings'),
      // Display language selector when either creating a user on the admin
      // interface or editing a user account.
      '#access' => !$register || user_access('administer users'),
    );
    $form['language']['preferred_langcode'] = array(
      '#type' => (count($names) <= 5 ? 'radios' : 'select'),
      '#title' => t('Language'),
      '#default_value' => $user_preferred_language->langcode,
      '#options' => $names,
      '#description' => $interface_language_is_default ? t("This account's preferred language for e-mails and site presentation.") : t("This account's preferred language for e-mails."),
    );
  }
  else {
    $form['language'] = array(
      '#type' => 'container',
    );
    $form['language']['preferred_langcode'] = array(
      '#type' => 'value',
      '#value' => language_default()->langcode,
    );
  }

  // User entities contain both a langcode property (for identifying the
  // language of the entity data) and a preferred_langcode property (see above).
  // Rather than provide a UI forcing the user to choose both separately,
  // assume that the user profile data is in the user's preferred language. This
  // element provides that synchronization. For use-cases where this
  // synchronization is not desired, a module can alter or remove this element.
  $form['language']['langcode'] = array(
    '#type' => 'value',
    '#value_callback' => '_user_language_selector_langcode_value',
    // For the synchronization to work, this element must have a larger weight
    // than the preferred_langcode element. Set a large weight here in case
    // a module alters the weight of the other element.
    '#weight' => 100,
  );
}

/**
 * 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'];
947 948
}

949 950
/**
 * Form validation handler for the current password on the user_account_form().
951
 *
952
 * @see user_account_form()
953 954 955 956 957 958 959 960
 */
function user_validate_current_pass(&$form, &$form_state) {
  $account = $form['#user'];
  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)) {
961
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc');
962
      $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account);
963 964 965 966 967 968 969 970 971 972
      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;
    }
  }
}

973 974
/**
 * Form validation handler for user_account_form().
975
 *
976
 * @see user_account_form()
977 978
 */
function user_account_form_validate($form, &$form_state) {
979 980 981 982 983
  $account = $form['#user'];
  // Validate new or changing username.
  if (isset($form_state['values']['name'])) {
    if ($error = user_validate_name($form_state['values']['name'])) {
      form_set_error('name', $error);
984
    }
985 986 987 988 989 990 991 992 993 994 995 996 997 998
    // Cast the user ID as an integer. It might have been set to NULL, which
    // could lead to unexpected results.
    else {
      $name_taken = (bool) db_select('users')
        ->fields('users', array('uid'))
        ->condition('uid', (int) $account->uid, '<>')
        ->condition('name', db_like($form_state['values']['name']), 'LIKE')
        ->range(0, 1)
        ->execute()
        ->fetchField();

      if ($name_taken) {
        form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name'])));
      }
999 1000
    }
  }
1001

1002
  $mail = $form_state['values']['mail'];
1003

1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
  if (!empty($mail)) {
    $mail_taken = (bool) db_select('users')
      ->fields('users', array('uid'))
      ->condition('uid', (int) $account->uid, '<>')
      ->condition('mail', db_like($mail), 'LIKE')
      ->range(0, 1)
      ->execute()
      ->fetchField();

    if ($mail_taken) {
      // Format error message dependent on whether the user is logged in or not.
      if ($GLOBALS['user']->uid) {
        form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $mail)));
      }
      else {
        form_set_error('mail', t('The e-mail address %email is already registered. <a href="@password">Have you forgotten your password?</a>', array('%email' => $mail, '@password' => url('user/password'))));
      }
1021
    }
1022
  }
1023

1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034
  // Make sure the signature isn't longer than the size of the database field.
  // Signatures are disabled by default, so make sure it exists first.
  if (isset($form_state['values']['signature'])) {
    // Move text format for user signature into 'signature_format'.
    $form_state['values']['signature_format'] = $form_state['values']['signature']['format'];
    // Move text value for user signature into 'signature'.
    $form_state['values']['signature'] = $form_state['values']['signature']['value'];

    $user_schema = drupal_get_schema('users');
    if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) {
      form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length'])));
1035
    }
1036
  }
1037
}
1038

1039
function user_login_block($form) {
1040
  $form['#action'] = url(current_path(), array('query' => drupal_get_destination(), 'external' => FALSE));
1041 1042 1043
  $form['#id'] = 'user-login-form';
  $form['#validate'] = user_login_default_validators();
  $form['#submit'][] = 'user_login_submit';
1044 1045
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Username'),
1046
    '#maxlength' => USERNAME_MAX_LENGTH,
1047 1048 1049 1050 1051
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['pass'] = array('#type' => 'password',
    '#title' => t('Password'),
1052
    '#maxlength' => 60,
1053 1054 1055
    '#size' => 15,
    '#required' => TRUE,
  );
1056 1057
  $form['actions'] = array('#type' => 'actions');
  $form['actions']['submit'] = array('#type' => 'submit',
1058 1059 1060
    '#value' => t('Log in'),
  );
  $items = array();
1061
  if (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)) {
1062
    $items[] = l(t('Create new account'), 'user/register', array('attributes' => array('title' => t('Create a new user account.'))));
1063
  }
1064
  $items[] = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
1065
  $form['links'] = array('#theme' => 'item_list', '#items' => $items);
1066 1067 1068
  return $form;
}

Dries's avatar
Dries committed
1069
/**
1070
 * Implements hook_block_info().
Dries's avatar
Dries committed
1071
 */
1072
function user_block_info() {
1073 1074
  global $user;

1075 1076
  $blocks['login']['info'] = t('User login');
  // Not worth caching.
1077
  $blocks['login']['cache'] = DRUPAL_NO_CACHE;
1078

1079
  $blocks['new']['info'] = t('Who\'s new');
1080
  $blocks['new']['properties']['administrative'] = TRUE;
1081

1082 1083
  // Too dynamic to cache.
  $blocks['online']['info'] = t('Who\'s online');
1084
  $blocks['online']['cache'] = DRUPAL_NO_CACHE;
1085 1086
  $blocks['online']['properties']['administrative'] = TRUE;

1087 1088
  return $blocks;
}
1089