user.module 112 KB
Newer Older
Dries's avatar
 
Dries committed
1 2 3
<?php
// $Id$

Dries's avatar
 
Dries committed
4 5 6 7 8
/**
 * @file
 * Enables the user registration and login system.
 */

9 10 11
/**
 * Maximum length of username text field.
 */
12
define('USERNAME_MAX_LENGTH', 60);
13 14 15 16

/**
 * Maximum length of user e-mail text field.
 */
17 18
define('EMAIL_MAX_LENGTH', 64);

19

Dries's avatar
Dries committed
20 21 22
/**
 * Invokes hook_user() in every module.
 *
23
 * We cannot use module_invoke() for this, because the arguments need to
Dries's avatar
Dries committed
24 25
 * be passed by reference.
 */
26
function user_module_invoke($type, &$edit, $account, $category = NULL) {
27
  foreach (module_implements('user_' . $type) as $module) {
28
    $function = $module . '_user_' . $type;
29
    $function($edit, $account, $category);
Dries's avatar
 
Dries committed
30 31 32
  }
}

33
/**
34
 * Implement hook_theme().
35 36 37 38 39
 */
function user_theme() {
  return array(
    'user_picture' => array(
      'arguments' => array('account' => NULL),
40
      'template' => 'user-picture',
41 42
    ),
    'user_profile' => array(
43
      'arguments' => array('elements' => NULL),
44
      'template' => 'user-profile',
45
      'file' => 'user.pages.inc',
46 47 48
    ),
    'user_profile_category' => array(
      'arguments' => array('element' => NULL),
49
      'template' => 'user-profile-category',
50
      'file' => 'user.pages.inc',
51 52 53
    ),
    'user_profile_item' => array(
      'arguments' => array('element' => NULL),
54
      'template' => 'user-profile-item',
55
      'file' => 'user.pages.inc',
56 57 58 59
    ),
    'user_list' => array(
      'arguments' => array('users' => NULL, 'title' => NULL),
    ),
60
    'user_admin_permissions' => array(
61
      'arguments' => array('form' => NULL),
62
      'file' => 'user.admin.inc',
63 64 65
    ),
    'user_admin_new_role' => array(
      'arguments' => array('form' => NULL),
66
      'file' => 'user.admin.inc',
67 68 69
    ),
    'user_filter_form' => array(
      'arguments' => array('form' => NULL),
70
      'file' => 'user.admin.inc',
71 72 73
    ),
    'user_filters' => array(
      'arguments' => array('form' => NULL),
74
      'file' => 'user.admin.inc',
75
    ),
76 77 78
    'user_signature' => array(
      'arguments' => array('signature' => NULL),
    ),
79 80 81
  );
}

Dries's avatar
 
Dries committed
82
/**
83
 * Implement hook_entity_info().
Dries's avatar
 
Dries committed
84
 */
85
function user_entity_info() {
Dries's avatar
 
Dries committed
86 87
  $return = array(
    'user' => array(
88
      'label' => t('User'),
89 90 91
      'controller class' => 'UserController',
      'base table' => 'users',
      'fieldable' => TRUE,
92 93 94 95 96 97 98
      'object keys' => array(
        'id' => 'uid',
      ),
      'bundles' => array(
        'user' => array(
          'label' => t('User'),
          'admin' => array(
99
            'path' => 'admin/config/people/accounts',
100 101 102 103
            'access arguments' => array('administer users'),
          ),
        ),
      ),
Dries's avatar
 
Dries committed
104 105 106 107 108 109
    ),
  );
  return $return;
}

/**
110
 * Implement hook_field_build_modes().
Dries's avatar
 
Dries committed
111 112 113 114 115 116 117 118 119 120 121
 */
function user_field_build_modes($obj_type) {
  $modes = array();
  if ($obj_type == 'user') {
    $modes = array(
      'full' => t('User account'),
    );
  }
  return $modes;
}

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
/**
 * Implement hook_field_extra_fields().
 */
function user_field_extra_fields($bundle) {
  $extra = array();

  if ($bundle == 'user') {
    $extra['account'] = array(
      'label' => 'User name and password',
      'description' => t('User module account form elements'),
      'weight' => -10,
    );
    $extra['timezone'] = array(
      'label' => 'Timezone',
      'description' => t('User module timezone form element.'),
      'weight' => 6,
    );
    $extra['summary'] = array(
      'label' => 'History',
      'description' => t('User module history view element.'),
      'weight' => 5,
    );
  }

  return $extra;
}

Dries's avatar
 
Dries committed
149
function user_external_load($authname) {
150
  $uid = db_query("SELECT uid FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchField();
Dries's avatar
 
Dries committed
151

152 153
  if ($uid) {
    return user_load($uid);
Dries's avatar
 
Dries committed
154 155
  }
  else {
156
    return FALSE;
Dries's avatar
 
Dries committed
157 158 159
  }
}

Dries's avatar
Dries committed
160
/**
161
 * Load multiple users based on certain conditions.
Dries's avatar
Dries committed
162
 *
163 164 165
 * 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
166
 *
167 168 169 170 171 172 173 174
 * @param $uids
 *   An array of user IDs.
 * @param $conditions
 *   An array of conditions to match against the {users} table. These
 *   should be supplied in the form array('field_name' => 'field_value').
 * @param $reset
 *   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.
175
 * @return
176 177
 *   An array of user objects, indexed by uid.
 *
178
 * @see entity_load()
179 180 181
 * @see user_load()
 * @see user_load_by_mail()
 * @see user_load_by_name()
Dries's avatar
Dries committed
182
 */
183
function user_load_multiple($uids = array(), $conditions = array(), $reset = FALSE) {
184 185
  return entity_load('user', $uids, $conditions, $reset);
}
186

187 188 189 190 191 192 193 194
/**
 * Controller class for users.
 *
 * This extends the DrupalDefaultEntityController class, adding required
 * special handling for user objects.
 */
class UserController extends DrupalDefaultEntityController {
  function attachLoad(&$queried_users) {
195 196
    // Build an array of user picture IDs so that these can be fetched later.
    $picture_fids = array();
197
    foreach ($queried_users as $key => $record) {
198
      $picture_fids[] = $record->picture;
199 200
      $queried_users[$key] = drupal_unpack($record);
      $queried_users[$key]->roles = array();
201 202 203 204 205 206
      if ($record->uid) {
        $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
      }
      else {
        $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
      }
207
    }
208

209 210 211 212 213
    // Add any additional roles from the database.
    $result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users)));
    foreach ($result as $record) {
      $queried_users[$record->uid]->roles[$record->rid] = $record->name;
    }
Dries's avatar
 
Dries committed
214

215 216 217 218 219 220 221 222 223
    // Add the full file objects for user pictures if enabled.
    if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) {
      $pictures = file_load_multiple($picture_fids);
      foreach ($queried_users as $account) {
        if (!empty($account->picture) && isset($pictures[$account->picture])) {
          $account->picture = $pictures[$account->picture];
        }
        else {
          $account->picture = NULL;
224 225 226
        }
      }
    }
227 228 229
    // Call the default attachLoad() method. This will add fields and call
    // hook_user_load().
    parent::attachLoad($queried_users);
Dries's avatar
 
Dries committed
230
  }
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
}

/**
 * Fetch a user object.
 *
 * @param $uid
 *   Integer specifying the user id.
 * @param $reset
 *   A boolean indicating that the internal cache should be reset.
 * @return
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load($uid, $reset = FALSE) {
  $users = user_load_multiple(array($uid), array(), $reset);
  return reset($users);
}

/**
 * Fetch a user object by email address.
 *
 * @param $mail
 *   String with the account's e-mail address.
 * @return
 *   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) {
  $users = user_load_multiple(array(), array('mail' => $mail));
  return reset($users);
}

/**
 * Fetch a user object by account name.
 *
 * @param $name
 *   String with the account's user name.
 * @return
 *   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) {
  $users = user_load_multiple(array(), array('name' => $name));
  return reset($users);
Dries's avatar
 
Dries committed
281 282
}

283
/**
284
 * Save changes to a user account or add a new user.
285 286
 *
 * @param $account
287
 *   (optional) The user object to modify or add. If you want to modify
288 289
 *   an existing user account, you will need to ensure that (a) $account
 *   is an object, and (b) you have set $account->uid to the numeric
290 291
 *   user ID of the user account you wish to modify. If you
 *   want to create a new user account, you can set $account->is_new to
292
 *   TRUE or omit the $account->uid field.
293
 * @param $edit
294
 *   An array of fields and values to save. For example array('name'
295
 *   => 'My name'). Keys that do not belong to columns in the user-related
296 297
 *   tables are added to the a serialized array in the 'data' column
 *   and will be loaded in the $user->data array by user_load().
298 299
 *   Setting a field to NULL deletes it from the data column, if you are
 *   modifying an existing user account.
300 301
 * @param $category
 *   (optional) The category for storing profile information in.
302 303
 *
 * @return
304
 *   A fully-loaded $user object upon successful save or FALSE if the save failed.
305
 */
306
function user_save($account, $edit = array(), $category = 'account') {
307
  $table = drupal_get_schema('users');
308 309
  $user_fields = $table['fields'];

310
  if (!empty($edit['pass'])) {
311
    // Allow alternate password hashing schemes.
312
    require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
313
    $edit['pass'] = user_hash_password(trim($edit['pass']));
314
    // Abort if the hashing failed and returned FALSE.
315
    if (!$edit['pass']) {
316 317
      return FALSE;
    }
318 319 320
  }
  else {
    // Avoid overwriting an existing password with a blank password.
321
    unset($edit['pass']);
322 323
  }

Dries's avatar
 
Dries committed
324 325 326 327 328 329 330 331 332 333 334 335
  // Get the fields form so we can recognize the fields in the $edit
  // form that should not go into the serialized data array.
  $field_form = array();
  $field_form_state = array();
  $edit = (object) $edit;
  field_attach_form('user', $edit, $field_form, $field_form_state);

  // Presave fields.
  field_attach_presave('user', $edit);

  $edit = (array) $edit;

336
  if (!isset($account->is_new)) {
337
    $account->is_new = empty($account->uid);
338 339
  }
  if (is_object($account) && !$account->is_new) {
340
    user_module_invoke('update', $edit, $account, $category);
341
    $data = unserialize(db_query('SELECT data FROM {users} WHERE uid = :uid', array(':uid' => $account->uid))->fetchField());
342 343
    // Consider users edited by an administrator as logged in, if they haven't
    // already, so anonymous users can view the profile (if allowed).
344
    if (empty($edit['access']) && empty($account->access) && user_access('administer users')) {
345
      $edit['access'] = REQUEST_TIME;
346
    }
347
    foreach ($edit as $key => $value) {
Dries's avatar
 
Dries committed
348
      // Form fields that don't pertain to the users, user_roles, or
349
      // Field API are automatically serialized into the users.data
Dries's avatar
 
Dries committed
350
      // column.
351
      if (!in_array($key, array('roles', 'is_new')) && empty($user_fields[$key]) && empty($field_form[$key])) {
352 353
        if ($value === NULL) {
          unset($data[$key]);
Dries's avatar
 
Dries committed
354
        }
355 356
        else {
          $data[$key] = $value;
Dries's avatar
 
Dries committed
357
        }
Dries's avatar
 
Dries committed
358 359 360
      }
    }

361 362 363 364 365 366
    // Process picture uploads.
    if (!empty($edit['picture']->fid)) {
      $picture = $edit['picture'];
      // If the picture is a temporary file move it to its final location and
      // make it permanent.
      if (($picture->status & FILE_STATUS_PERMANENT) == 0) {
367 368
        $info = image_get_info($picture->uri);
        $picture_directory =  variable_get('file_default_scheme', 'public') . '://' . variable_get('user_picture_path', 'pictures');
369

370 371 372 373
        // Prepare the pictures directory.
        file_prepare_directory($picture_directory, FILE_CREATE_DIRECTORY);
        $destination = file_stream_wrapper_uri_normalize($picture_directory . '/picture-' . $account->uid . '.' . $info['extension']);

374 375 376 377 378 379 380 381
        if ($picture = file_move($picture, $destination, FILE_EXISTS_REPLACE)) {
          $picture->status |= FILE_STATUS_PERMANENT;
          $edit['picture'] = file_save($picture);
        }
      }
    }
    $edit['picture'] = empty($edit['picture']->fid) ? 0 : $edit['picture']->fid;

382
    $edit['data'] = $data;
383
    // Do not allow 'uid' to be changed.
384
    $edit['uid'] = $account->uid;
385
    // Save changes to the user table.
386
    $success = drupal_write_record('users', $edit, 'uid');
387
    if (!$success) {
Dries's avatar
 
Dries committed
388 389 390 391
      // The query failed - better to abort the save than risk further
      // data loss.

      // TODO: Fields change: I think this is a bug.  If no columns in
392
      // the user table are changed, drupal_write_record returns
Dries's avatar
 
Dries committed
393
      // FALSE because rowCount() (rows changed) is 0.  However,
394
      // non-users data may have been changed, e.g. fields.
Dries's avatar
 
Dries committed
395
      // return FALSE;
396
    }
Dries's avatar
 
Dries committed
397

398
    // If the picture changed or was unset, remove the old one. This step needs
399
    // to occur after updating the {users} record so that user_file_references()
400 401 402 403 404
    // doesn't report it in use and block the deletion.
    if (!empty($account->picture->fid) && ($edit['picture'] != $account->picture->fid)) {
      file_delete($account->picture);
    }

405
    // Reload user roles if provided.
406
    if (isset($edit['roles']) && is_array($edit['roles'])) {
407 408 409
      db_delete('users_roles')
        ->condition('uid', $account->uid)
        ->execute();
Dries's avatar
 
Dries committed
410

411
      $query = db_insert('users_roles')->fields(array('uid', 'rid'));
412
      foreach (array_keys($edit['roles']) as $rid) {
413
        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
414 415 416 417
          $query->values(array(
            'uid' => $account->uid,
            'rid' => $rid,
          ));
418
        }
419
      }
420
      $query->execute();
Dries's avatar
 
Dries committed
421 422
    }

423
    // Delete a blocked user's sessions to kick them if they are online.
424
    if (isset($edit['status']) && $edit['status'] == 0) {
425
      drupal_session_destroy_uid($account->uid);
426 427
    }

428 429
    // If the password changed, delete all open sessions and recreate
    // the current one.
430
    if (!empty($edit['pass'])) {
431
      drupal_session_destroy_uid($account->uid);
432 433 434
      if ($account->uid == $GLOBALS['user']->uid) {
        drupal_session_regenerate();
      }
435 436
    }

Dries's avatar
 
Dries committed
437
    // Save Field data.
438 439
    $object = (object) $edit;
    field_attach_update('user', $object);
Dries's avatar
 
Dries committed
440

441
    // Refresh user object.
442
    $user = user_load($account->uid, TRUE);
443 444

    // Send emails after we have the new user object.
445
    if (isset($edit['status']) && $edit['status'] != $account->status) {
446
      // The user's status is changing; conditionally send notification email.
447
      $op = $edit['status'] == 1 ? 'status_activated' : 'status_blocked';
448 449 450
      _user_mail_notify($op, $user);
    }

451
    user_module_invoke('after_update', $edit, $user, $category);
Dries's avatar
 
Dries committed
452 453
  }
  else {
454
    // Allow 'created' to be set by the caller.
455
    if (!isset($edit['created'])) {
456
      $edit['created'] = REQUEST_TIME;
457
    }
458 459
    // Consider users created by an administrator as already logged in, so
    // anonymous users can view the profile (if allowed).
460
    if (empty($edit['access']) && user_access('administer users')) {
461
      $edit['access'] = REQUEST_TIME;
462
    }
463

464
    $edit['mail'] = trim($edit['mail']);
465
    $success = drupal_write_record('users', $edit);
466 467 468 469 470
    if (!$success) {
      // On a failed INSERT some other existing user's uid may be returned.
      // We must abort to avoid overwriting their account.
      return FALSE;
    }
471

472
    // Build the initial user object.
473
    $user = user_load($edit['uid'], TRUE);
Dries's avatar
 
Dries committed
474

475 476
    $object = (object) $edit;
    field_attach_insert('user', $object);
Dries's avatar
 
Dries committed
477

478
    user_module_invoke('insert', $edit, $user, $category);
479

480 481
    // Note, we wait with saving the data column to prevent module-handled
    // fields from being saved there.
482
    $data = array();
483
    foreach ($edit as $key => $value) {
484
      // Form fields that don't pertain to the users, user_roles, or
485
      // Field API are automatically serialized into the user.data
Dries's avatar
 
Dries committed
486
      // column.
487
      if ((!in_array($key, array('roles', 'is_new'))) && (empty($user_fields[$key]) && empty($field_form[$key])) && ($value !== NULL)) {
488 489 490
        $data[$key] = $value;
      }
    }
491 492
    if (!empty($data)) {
      $data_array = array('uid' => $user->uid, 'data' => $data);
493
      drupal_write_record('users', $data_array, 'uid');
494
    }
495

496
    // Save user roles (delete just to be safe).
497
    if (isset($edit['roles']) && is_array($edit['roles'])) {
498 499 500 501
      db_delete('users_roles')
        ->condition('uid', $edit['uid'])
        ->execute();
      $query = db_insert('users_roles')->fields(array('uid', 'rid'));
502
      foreach (array_keys($edit['roles']) as $rid) {
503
        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
504 505 506 507
          $query->values(array(
            'uid' => $edit['uid'],
            'rid' => $rid,
          ));
508
        }
509
      }
510
      $query->execute();
511 512
    }

513
    // Build the finished user object.
514
    $user = user_load($edit['uid'], TRUE);
Dries's avatar
 
Dries committed
515 516 517 518 519
  }

  return $user;
}

Dries's avatar
Dries committed
520 521 522
/**
 * Verify the syntax of the given name.
 */
Dries's avatar
 
Dries committed
523
function user_validate_name($name) {
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
  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.');
  }
539
  if (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
540 541 542 543 544 545 546 547 548
                  '\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)) {
549 550
    return t('The username contains an illegal character.');
  }
551 552 553
  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
554 555 556
}

function user_validate_mail($mail) {
557
  $mail = trim($mail);
558 559 560
  if (!$mail) {
    return t('You must enter an e-mail address.');
  }
561
  if (!valid_email_address($mail)) {
562
    return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
Dries's avatar
 
Dries committed
563 564 565
  }
}

566
function user_validate_picture(&$form, &$form_state) {
567
  // If required, validate the uploaded picture.
568 569 570 571 572
  $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),
  );
573

574 575 576 577 578 579 580
  // 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
581 582 583
  }
}

Dries's avatar
Dries committed
584 585 586
/**
 * Generate a random alphanumeric password.
 */
Dries's avatar
 
Dries committed
587 588
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
589 590
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
591
  // of 'I', 1, and 'l'.
592
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
Dries's avatar
Dries committed
593

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

Dries's avatar
Dries committed
597 598
  // Declare the password as a blank string.
  $pass = '';
Dries's avatar
 
Dries committed
599

Dries's avatar
Dries committed
600
  // Loop the number of times specified by $length.
Dries's avatar
 
Dries committed
601 602 603 604
  for ($i = 0; $i < $length; $i++) {

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

  return $pass;
Dries's avatar
 
Dries committed
609 610
}

611 612 613 614 615 616 617 618 619 620
/**
 * 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.
 */
621 622
function user_role_permissions($roles = array()) {
  $cache = &drupal_static(__FUNCTION__, array());
623 624 625 626 627

  $role_permissions = $fetch = array();

  if ($roles) {
    foreach ($roles as $rid => $name) {
628 629
      if (isset($cache[$rid])) {
        $role_permissions[$rid] = $cache[$rid];
630 631 632 633 634
      }
      else {
        // Add this rid to the list of those needing to be fetched.
        $fetch[] = $rid;
        // Prepare in case no permissions are returned.
635
        $cache[$rid] = array();
636 637 638 639 640 641
      }
    }

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

644
      foreach ($result as $row) {
645
        $cache[$row->rid][$row->permission] = TRUE;
646 647 648
      }
      foreach ($fetch as $rid) {
        // For every rid, we know we at least assigned an empty array.
649
        $role_permissions[$rid] = $cache[$rid];
650 651 652 653 654 655 656
      }
    }
  }

  return $role_permissions;
}

Dries's avatar
Dries committed
657 658 659 660 661
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
Dries's avatar
 
Dries committed
662 663
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
Dries's avatar
Dries committed
664 665
 *
 * @return
666
 *   Boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
667 668 669 670 671
 *
 * 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.
 */
672
function user_access($string, $account = NULL) {
Dries's avatar
 
Dries committed
673
  global $user;
674
  $perm = &drupal_static(__FUNCTION__, array());
675

676
  if (!isset($account)) {
677 678 679
    $account = $user;
  }

680
  // User #1 has all privileges:
681
  if ($account->uid == 1) {
682
    return TRUE;
Dries's avatar
 
Dries committed
683 684
  }

Dries's avatar
Dries committed
685 686
  // To reduce the number of SQL queries, we cache the user's permissions
  // in a static variable.
687
  if (!isset($perm[$account->uid])) {
688
    $role_permissions = user_role_permissions($account->roles);
Dries's avatar
 
Dries committed
689

690
    $perms = array();
691 692
    foreach ($role_permissions as $one_role) {
      $perms += $one_role;
Dries's avatar
 
Dries committed
693
    }
694
    $perm[$account->uid] = $perms;
Dries's avatar
 
Dries committed
695
  }
696

697
  return isset($perm[$account->uid][$string]);
Dries's avatar
 
Dries committed
698 699
}

700
/**
701
 * Checks for usernames blocked by user administration.
702
 *
703
 * @return boolean TRUE for blocked users, FALSE for active.
704 705
 */
function user_is_blocked($name) {
706
  $deny = db_query("SELECT name FROM {users} WHERE status = 0 AND name = LOWER(:name)", array(':name' => $name))->fetchObject();
707

708
  return $deny;
709 710
}

Dries's avatar
Dries committed
711
/**
712
 * Implement hook_permission().
Dries's avatar
Dries committed
713
 */
714
function user_permission() {
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
  return array(
    'administer permissions' =>  array(
      'title' => t('Administer permissions'),
      'description' => t('Manage the permissions assigned to user roles. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
    ),
    'administer users' => array(
      'title' => t('Administer users'),
      'description' => t('Manage or block users, and manage their role assignments.'),
    ),
    'access user profiles' => array(
      'title' => t('Access user profiles'),
      'description' => t('View profiles of users on the site, which may contain personal information.'),
    ),
    'change own username' => array(
      'title' => t('Change own username'),
      'description' => t('Select a different username.'),
    ),
    'cancel account' => array(
      'title' => t('Cancel account'),
734
      'description' => t('Remove or disable own user account and unpublish, anonymize, or remove own submissions depending on the configured <a href="@user-settings-url">user settings</a>.', array('@user-settings-url' => url('admin/config/people/accounts'))),
735 736 737 738 739 740
    ),
    'select account cancellation method' => array(
      'title' => t('Select method for cancelling own account'),
      'description' => t('Select the method for cancelling own user account. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
    ),
  );
Dries's avatar
 
Dries committed
741 742
}

Dries's avatar
Dries committed
743
/**
744
 * Implement hook_file_download().
Dries's avatar
Dries committed
745 746 747
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
748
function user_file_download($filepath) {
749 750
  if (strpos(file_uri_target($filepath), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
    $info = image_get_info($filepath);
751
    return array('Content-Type' => $info['mime_type']);
Dries's avatar
 
Dries committed
752 753 754
  }
}

755
/**
756
 * Implement hook_file_references().
757 758 759
 */
function user_file_references($file) {
  // Determine if the file is used by this module.
760
  $file_used = (bool) db_query_range('SELECT 1 FROM {users} WHERE picture = :fid', 0, 1, array(':fid' => $file->fid))->fetchField();
761
  if ($file_used) {
762 763 764 765 766 767
    // Return the name of the module and how many references it has to the file.
    return array('user' => $count);
  }
}

/**
768
 * Implement hook_file_delete().
769 770 771
 */
function user_file_delete($file) {
  // Remove any references to the file.
772
  db_update('users')
773 774 775 776 777
    ->fields(array('picture' => 0))
    ->condition('picture', $file->fid)
    ->execute();
}

Dries's avatar
Dries committed
778
/**
779
 * Implement hook_search_info().
Dries's avatar
Dries committed
780
 */
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
function user_search_info() {
  return array(
    'title' => 'Users',
  );
}

/**
 * Implement hook_search_access().
 */
function user_search_access() {
  return user_access('access user profiles');
}

/**
 * Implement hook_search_execute().
 */
function user_search_execute($keys = NULL) {
  $find = array();
  // Replace wildcards with MySQL/PostgreSQL wildcards.
  $keys = preg_replace('!\*+!', '%', $keys);
  $query = db_select('users')->extend('PagerDefault');
  $query->fields('users', array('name', 'uid', 'mail'));
  if (user_access('administer users')) {
    // Administrators can also search in the otherwise private email field.
    $query->condition(db_or()->
      where('LOWER(name) LIKE LOWER(:name)', array(':name' => "%$keys%"))->
      where('LOWER(mail) LIKE LOWER(:mail)', array(':mail' => "%$keys%")));
  }
  else {
    $query->where('LOWER(name) LIKE LOWER(:name)', array(':name' => "%$keys%"));
  }
  $result = $query
    ->limit(15)
    ->execute();
  foreach ($result as $account) {
    $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
Dries's avatar
 
Dries committed
817
  }
818
  return $find;
Dries's avatar
 
Dries committed
819 820
}

Dries's avatar
Dries committed
821
/**
822
 * Implement hook_element_info().
Dries's avatar
Dries committed
823
 */
824 825 826 827 828 829
function user_element_info() {
  $types['user_profile_category'] = array(
    '#theme_wrappers' => array('user_profile_category'),
  );
  $types['user_profile_item'] = array(
    '#theme' => 'user_profile_item',
Dries's avatar
Dries committed
830
  );
831
  return $types;
Dries's avatar
Dries committed
832 833
}

Dries's avatar
Dries committed
834
/**
835
 * Implement hook_user_view().
Dries's avatar
Dries committed
836
 */
837
function user_user_view($account) {
838
  $account->content['user_picture'] = array(
839
    '#markup' => theme('user_picture', array('account' => $account)),
840 841 842 843 844 845 846
    '#weight' => -10,
  );
  if (!isset($account->content['summary'])) {
    $account->content['summary'] = array();
  }
  $account->content['summary'] += array(
    '#type' => 'user_profile_category',
847
    '#attributes' => array('class' => array('user-member')),
848 849 850
    '#weight' => 5,
    '#title' => t('History'),
  );
851
  $account->content['summary']['member_for'] = array(
852 853 854 855 856 857 858
    '#type' => 'user_profile_item',
    '#title' => t('Member for'),
    '#markup' => format_interval(REQUEST_TIME - $account->created),
  );
}

/**
859
 * Implement hook_user_validate().
860
 */
861
function user_user_validate(&$edit, $account, $category) {
862
  if ($category == 'account') {
863 864 865 866 867 868
    $uid = isset($account->uid) ? $account->uid : FALSE;
    // Validate the username when: new user account; or user is editing own account and can change username; or an admin user.
    if (!$uid || ($GLOBALS['user']->uid == $uid && user_access('change own username')) || user_access('administer users')) {
      if ($error = user_validate_name($edit['name'])) {
        form_set_error('name', $error);
      }
869
      elseif ((bool) db_query_range("SELECT 1 FROM {users} WHERE uid <> :uid AND LOWER(name) = LOWER(:name)", 0, 1, array(':uid' => $uid, ':name' => $edit['name']))->fetchField()) {
870 871 872 873
        form_set_error('name', t('The name %name is already taken.', array('%name' => $edit['name'])));
      }
    }

874
    // Validate the e-mail address, and check if it is taken by an existing user.
875 876 877
    if ($error = user_validate_mail($edit['mail'])) {
      form_set_error('mail', $error);
    }
878
    elseif ((bool) db_query_range("SELECT 1 FROM {users} WHERE uid <> :uid AND LOWER(mail) = LOWER(:mail)", 0, 1, array(':uid' => $uid, ':mail' => $edit['mail']))->fetchField()) {
879 880 881 882 883 884 885
      // 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' => $edit['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' => $edit['mail'], '@password' => url('user/password'))));
      }
886
    }
887 888 889 890 891 892 893 894 895

    // 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($edit['signature'])) {
      $user_schema = drupal_get_schema('users');
      if (strlen($edit['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'])));
      }
    }
896
  }
897
}
898

899
/**
900
 * Implement hook_user_submit().
901
 */
902
function user_user_submit(&$edit, $account, $category) {
903
  if ($category == 'account') {
904 905 906
    if (!empty($edit['picture_upload'])) {
      $edit['picture'] = $edit['picture_upload'];
    }
907
    // Delete picture if requested, and if no replacement picture was given.
908 909
    elseif (!empty($edit['picture_delete'])) {
      $edit['picture'] = NULL;
910
    }
911 912 913 914
    // Remove these values so they don't end up serialized in the data field.
    $edit['picture_upload'] = NULL;
    $edit['picture_delete'] = NULL;

915 916 917
    if (isset($edit['roles'])) {
      $edit['roles'] = array_filter($edit['roles']);
    }
918
  }
919
}
920

921
/**
922
 * Implement hook_user_categories().
923
 */
924 925 926 927 928 929
function user_user_categories() {
  return array(array(
    'name' => 'account',
    'title' => t('Account settings'),
    'weight' => 1,
  ));
Dries's avatar
Dries committed
930 931
}

932 933 934 935 936
function user_login_block($form) {
  $form['#action'] = url($_GET['q'], array('query' => drupal_get_destination()));
  $form['#id'] = 'user-login-form';
  $form['#validate'] = user_login_default_validators();
  $form['#submit'][] = 'user_login_submit';
937 938
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Username'),
939
    '#maxlength' => USERNAME_MAX_LENGTH,
940 941 942 943 944
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['pass'] = array('#type' => 'password',
    '#title' => t('Password'),
945
    '#maxlength' => 60,
946 947 948 949 950 951 952 953
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['submit'] = array('#type' => 'submit',
    '#value' => t('Log in'),
  );
  $items = array();