user.module 98.4 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, &$array, &$user, $category = NULL) {
27
  foreach (module_implements('user_' . $type) as $module) {
28
    $function = $module . '_user_' . $type;
29
    $function($array, $user, $category);
Dries's avatar
 
Dries committed
30 31 32
  }
}

33
/**
34
 * Implementation of 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('account' => 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 60 61
    ),
    'user_list' => array(
      'arguments' => array('users' => NULL, 'title' => NULL),
    ),
    'user_admin_perm' => array(
      '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_admin_account' => array(
      'arguments' => array('form' => NULL),
70
      'file' => 'user.admin.inc',
71 72 73
    ),
    'user_filter_form' => array(
      'arguments' => array('form' => NULL),
74
      'file' => 'user.admin.inc',
75 76 77
    ),
    'user_filters' => array(
      'arguments' => array('form' => NULL),
78
      'file' => 'user.admin.inc',
79
    ),
80 81 82
    'user_signature' => array(
      'arguments' => array('signature' => NULL),
    ),
83 84 85
  );
}

Dries's avatar
 
Dries committed
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
/**
 * Implementation of hook_fieldable_info().
 */
function user_fieldable_info() {
  $return = array(
    'user' => array(
      'name' => t('User'),
      'id key' => 'uid',
    ),
  );
  return $return;
}

/**
 * Implementation of hook_field_build_modes().
 */
function user_field_build_modes($obj_type) {
  $modes = array();
  if ($obj_type == 'user') {
    $modes = array(
      'full' => t('User account'),
    );
  }
  return $modes;
}

Dries's avatar
 
Dries committed
112
function user_external_load($authname) {
Dries's avatar
 
Dries committed
113
  $result = db_query("SELECT uid FROM {authmap} WHERE authname = '%s'", $authname);
Dries's avatar
 
Dries committed
114

115
  if ($user = db_fetch_array($result)) {
Dries's avatar
 
Dries committed
116
    return user_load($user);
Dries's avatar
 
Dries committed
117 118
  }
  else {
119
    return FALSE;
Dries's avatar
 
Dries committed
120 121 122
  }
}

123
/**
124 125 126
 * Perform standard Drupal login operations for a user object.
 *
 * The user object must already be authenticated. This function verifies
127
 * that the user account is not blocked and then performs the login,
128
 * updates the login timestamp in the database, invokes hook_user('login'),
129
 * and regenerates the session.
130 131 132 133 134 135
 *
 * @param $account
 *    An authenticated user object to be set as the currently logged
 *    in user.
 * @param $edit
 *    The array of form values submitted by the user, if any.
136
 *    This array is passed to hook_user op login.
137 138 139 140
 * @return boolean
 *    TRUE if the login succeeds, FALSE otherwise.
 */
function user_external_login($account, $edit = array()) {
141 142
  $form = drupal_get_form('user_login');

143 144 145 146 147
  $state['values'] = $edit;
  if (empty($state['values']['name'])) {
    $state['values']['name'] = $account->name;
  }

148
  // Check if user is blocked.
149 150
  user_login_name_validate($form, $state, (array)$account);
  if (form_get_errors()) {
151
    // Invalid login.
152 153
    return FALSE;
  }
154

155
  // Valid login.
156 157
  global $user;
  $user = $account;
158
  user_authenticate_finalize($state['values']);
159 160 161
  return TRUE;
}

Dries's avatar
Dries committed
162 163 164 165 166
/**
 * Fetch a user object.
 *
 * @param $array
 *   An associative array of attributes to search for in selecting the
167
 *   user, such as user name or e-mail address.
Dries's avatar
Dries committed
168
 *
169 170 171
 * @return
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
Dries's avatar
Dries committed
172
 */
Dries's avatar
 
Dries committed
173
function user_load($array = array()) {
Dries's avatar
Dries committed
174
  // Dynamically compose a SQL query:
175
  $query = array();
176
  $params = array();
177

178 179 180
  if (is_numeric($array)) {
    $array = array('uid' => $array);
  }
181 182 183
  elseif (!is_array($array)) {
    return FALSE;
  }
184

Dries's avatar
 
Dries committed
185
  foreach ($array as $key => $value) {
186 187
    if ($key == 'uid' || $key == 'status') {
      $query[] = "$key = %d";
188
      $params[] = $value;
189
    }
190
    elseif ($key == 'pass') {
191
      $query[] = "pass = '%s'";
192
      $params[] = $value;
193
    }
Dries's avatar
 
Dries committed
194
    else {
195
      $query[]= "LOWER($key) = LOWER('%s')";
196
      $params[] = $value;
Dries's avatar
 
Dries committed
197 198
    }
  }
199
  $result = db_query('SELECT * FROM {users} u WHERE ' . implode(' AND ', $query), $params);
Dries's avatar
 
Dries committed
200

201
  if ($user = db_fetch_object($result)) {
202
    $user = drupal_unpack($user);
Dries's avatar
 
Dries committed
203

204
    $user->roles = array();
205 206 207 208 209 210
    if ($user->uid) {
      $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
    }
    else {
      $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
    }
211 212 213 214
    $result = db_query('SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d', $user->uid);
    while ($role = db_fetch_object($result)) {
      $user->roles[$role->rid] = $role->name;
    }
215

Dries's avatar
 
Dries committed
216 217 218 219
    // Attach fields.
    // TODO D7 : not sure the 3rd param ($types) is needed.
    field_attach_load('user', array($user->uid => $user));

220 221 222 223 224 225 226
    if (!empty($user->picture) && ($file = file_load($user->picture))) {
      $user->picture = $file;
    }
    else {
      $user->picture = NULL;
    }

227
    user_module_invoke('load', $array, $user);
228 229
  }
  else {
230
    $user = FALSE;
Dries's avatar
 
Dries committed
231
  }
Dries's avatar
 
Dries committed
232 233 234 235

  return $user;
}

236
/**
237
 * Save changes to a user account or add a new user.
238 239
 *
 * @param $account
240 241
 *   The $user object for the user to modify or add. If $user->uid is
 *   omitted, a new user will be added.
242
 *
243
 * @param $edit
244
 *   An array of fields and values to save. For example array('name'
245
 *   => 'My name'). Keys that do not belong to columns in the user-related
246 247 248
 *   tables are added to the a serialized array in the 'data' column
 *   and will be loaded in the $user->data array by user_load().
 *   Setting a field to NULL deletes it from the data column.
249 250 251
 *
 * @param $category
 *   (optional) The category for storing profile information in.
252 253
 *
 * @return
254
 *   A fully-loaded $user object upon successful save or FALSE if the save failed.
255
 */
256
function user_save($account, $edit = array(), $category = 'account') {
257 258 259
  $table = drupal_get_schema('users');
  $user_fields = $table['fields'];

260
  if (!empty($edit['pass'])) {
261
    // Allow alternate password hashing schemes.
262
    require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
263
    $edit['pass'] = user_hash_password(trim($edit['pass']));
264
    // Abort if the hashing failed and returned FALSE.
265
    if (!$edit['pass']) {
266 267
      return FALSE;
    }
268 269 270
  }
  else {
    // Avoid overwriting an existing password with a blank password.
271
    unset($edit['pass']);
272 273
  }

Dries's avatar
 
Dries committed
274 275 276 277 278 279 280 281 282 283 284 285
  // 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;

286
  if (is_object($account) && $account->uid) {
287
    user_module_invoke('update', $edit, $account, $category);
Dries's avatar
Dries committed
288
    $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
289 290
    // Consider users edited by an administrator as logged in, if they haven't
    // already, so anonymous users can view the profile (if allowed).
291
    if (empty($edit['access']) && empty($account->access) && user_access('administer users')) {
292
      $edit['access'] = REQUEST_TIME;
293
    }
294
    foreach ($edit as $key => $value) {
Dries's avatar
 
Dries committed
295 296 297 298
      // Form fields that don't pertain to the users, user_roles, or
      // Field API are automatically serialized into the users.data
      // column.
      if ($key != 'roles' && empty($user_fields[$key]) && empty($field_form[$key])) {
299 300
        if ($value === NULL) {
          unset($data[$key]);
Dries's avatar
 
Dries committed
301
        }
302 303
        else {
          $data[$key] = $value;
Dries's avatar
 
Dries committed
304
        }
Dries's avatar
 
Dries committed
305 306 307
      }
    }

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324

    // 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) {
        $info = image_get_info($picture->filepath);
        $destination = file_create_path(variable_get('user_picture_path', 'pictures') . '/picture-' . $account->uid . '.' . $info['extension']);
        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;

325 326
    $edit['data'] = $data;
    $edit['uid'] = $account->uid;
327
    // Save changes to the users table.
328 329
    $success = drupal_write_record('users', $edit, 'uid');
    if (!$success) {
Dries's avatar
 
Dries committed
330 331 332 333 334 335 336 337
      // 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
      // the users table are changed, drupal_write_record returns
      // FALSE because rowCount() (rows changed) is 0.  However,
      // non-users data may have been changed, e.g. fields.
      // return FALSE;
338
    }
Dries's avatar
 
Dries committed
339

340 341 342 343 344 345 346
    // If the picture changed or was unset, remove the old one. This step needs
    // to occur after updating the {users} record so that user_file_references()
    // doesn't report it in use and block the deletion.
    if (!empty($account->picture->fid) && ($edit['picture'] != $account->picture->fid)) {
      file_delete($account->picture);
    }

347
    // Reload user roles if provided.
348
    if (isset($edit['roles']) && is_array($edit['roles'])) {
Dries's avatar
Dries committed
349
      db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
Dries's avatar
 
Dries committed
350

351
      foreach (array_keys($edit['roles']) as $rid) {
352 353 354
        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
          db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $account->uid, $rid);
        }
355
      }
Dries's avatar
 
Dries committed
356 357
    }

358
    // Delete a blocked user's sessions to kick them if they are online.
359
    if (isset($edit['status']) && $edit['status'] == 0) {
360
      drupal_session_destroy_uid($account->uid);
361 362
    }

363 364
    // If the password changed, delete all open sessions and recreate
    // the current one.
365
    if (!empty($edit['pass'])) {
366
      drupal_session_destroy_uid($account->uid);
367 368 369
      if ($account->uid == $GLOBALS['user']->uid) {
        drupal_session_regenerate();
      }
370 371
    }

Dries's avatar
 
Dries committed
372 373 374 375
    // Save Field data.
    $obj = (object) $edit;
    field_attach_update('user', $obj);

376
    // Refresh user object.
Dries's avatar
 
Dries committed
377
    $user = user_load(array('uid' => $account->uid));
378 379

    // Send emails after we have the new user object.
380
    if (isset($edit['status']) && $edit['status'] != $account->status) {
381
      // The user's status is changing; conditionally send notification email.
382
      $op = $edit['status'] == 1 ? 'status_activated' : 'status_blocked';
383 384 385
      _user_mail_notify($op, $user);
    }

386
    user_module_invoke('after_update', $edit, $user, $category);
Dries's avatar
 
Dries committed
387 388
  }
  else {
389
    // Allow 'created' to be set by the caller.
390
    if (!isset($edit['created'])) {
391
      $edit['created'] = REQUEST_TIME;
392
    }
393 394
    // Consider users created by an administrator as already logged in, so
    // anonymous users can view the profile (if allowed).
395
    if (empty($edit['access']) && user_access('administer users')) {
396
      $edit['access'] = REQUEST_TIME;
397
    }
398

399 400 401 402 403 404
    $success = drupal_write_record('users', $edit);
    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;
    }
405

406
    // Build the initial user object.
407
    $user = user_load(array('uid' => $edit['uid']));
Dries's avatar
 
Dries committed
408

Dries's avatar
 
Dries committed
409 410 411
    $obj = (object) $edit;
    field_attach_insert('user', $obj);

412
    user_module_invoke('insert', $edit, $user, $category);
413

414 415
    // Note, we wait with saving the data column to prevent module-handled
    // fields from being saved there.
416
    $data = array();
417
    foreach ($edit as $key => $value) {
Dries's avatar
 
Dries committed
418 419 420 421
      // Form fields that don't pertain to the users, user_roles, or
      // Field API are automatically serialized into the users.data
      // column.
      if (($key != 'roles') && (empty($user_fields[$key]) && empty($field_form[$key])) && ($value !== NULL)) {
422 423 424
        $data[$key] = $value;
      }
    }
425 426 427 428
    if (!empty($data)) {
      $data_array = array('uid' => $user->uid, 'data' => $data);
      drupal_write_record('users', $data_array, 'uid');
    }
429

430
    // Save user roles (delete just to be safe).
431 432 433
    if (isset($edit['roles']) && is_array($edit['roles'])) {
      db_query('DELETE FROM {users_roles} WHERE uid = %d', $edit['uid']);
      foreach (array_keys($edit['roles']) as $rid) {
434
        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
435
          db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $edit['uid'], $rid);
436
        }
437 438 439
      }
    }

440
    // Build the finished user object.
441
    $user = user_load(array('uid' => $edit['uid']));
Dries's avatar
 
Dries committed
442 443 444 445 446
  }

  return $user;
}

Dries's avatar
Dries committed
447 448 449
/**
 * Verify the syntax of the given name.
 */
Dries's avatar
 
Dries committed
450
function user_validate_name($name) {
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
  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.');
  }
466 467 468 469 470 471 472 473
  if (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
                   '\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
474
                   '\x{0}-\x{1F}]/u',        // NULL byte and control characters
475 476 477
                   $name)) {
    return t('The username contains an illegal character.');
  }
478 479 480
  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
481 482 483
}

function user_validate_mail($mail) {
484 485 486
  if (!$mail) {
    return t('You must enter an e-mail address.');
  }
487
  if (!valid_email_address($mail)) {
488
    return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
Dries's avatar
 
Dries committed
489 490 491
  }
}

492
function user_validate_picture(&$form, &$form_state) {
493
  // If required, validate the uploaded picture.
494 495 496 497 498
  $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),
  );
499

500 501 502 503 504 505 506
  // 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
507 508 509
  }
}

Dries's avatar
Dries committed
510 511 512
/**
 * Generate a random alphanumeric password.
 */
Dries's avatar
 
Dries committed
513 514
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
515 516
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
517
  // of 'I', 1, and 'l'.
518
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
Dries's avatar
Dries committed
519

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

Dries's avatar
Dries committed
523 524
  // Declare the password as a blank string.
  $pass = '';
Dries's avatar
 
Dries committed
525

Dries's avatar
Dries committed
526
  // Loop the number of times specified by $length.
Dries's avatar
 
Dries committed
527 528 529 530
  for ($i = 0; $i < $length; $i++) {

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

  return $pass;
Dries's avatar
 
Dries committed
535 536
}

537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
/**
 * Determine the permissions for one or more roles.
 *
 * @param $roles
 *   An array whose keys are the role IDs of interest, such as $user->roles.
 * @param $reset
 *   Optional parameter - if TRUE data in the static variable is rebuilt.
 *
 * @return
 *   An array indexed by role ID. Each value is an array whose keys are the
 *   permission strings for the given role ID.
 */
function user_role_permissions($roles = array(), $reset = FALSE) {
  static $stored_permissions = array();

  if ($reset) {
    // Clear the data cached in the static variable.
    $stored_permissions = array();
  }

  $role_permissions = $fetch = array();

  if ($roles) {
    foreach ($roles as $rid => $name) {
      if (isset($stored_permissions[$rid])) {
        $role_permissions[$rid] = $stored_permissions[$rid];
      }
      else {
        // Add this rid to the list of those needing to be fetched.
        $fetch[] = $rid;
        // Prepare in case no permissions are returned.
        $stored_permissions[$rid] = array();
      }
    }

    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.
575
      $result = db_query("SELECT r.rid, p.permission FROM {role} r INNER JOIN {role_permission} p ON p.rid = r.rid WHERE r.rid IN (:fetch)", array(':fetch' => $fetch));
576 577 578 579 580 581 582 583 584 585 586 587 588 589

      while ($row = db_fetch_array($result)) {
        $stored_permissions[$row['rid']][$row['permission']] = TRUE;
      }
      foreach ($fetch as $rid) {
        // For every rid, we know we at least assigned an empty array.
        $role_permissions[$rid] = $stored_permissions[$rid];
      }
    }
  }

  return $role_permissions;
}

Dries's avatar
Dries committed
590 591 592 593 594
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
Dries's avatar
 
Dries committed
595 596
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
597 598 599 600
 * @param $reset
 *   (optional) Resets the user's permissions cache, which will result in a
 *   recalculation of the user's permissions. This is necessary to support
 *   dynamically added user roles.
Dries's avatar
Dries committed
601 602
 *
 * @return
603
 *   Boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
604 605 606 607 608
 *
 * 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.
 */
609
function user_access($string, $account = NULL, $reset = FALSE) {
Dries's avatar
 
Dries committed
610
  global $user;
Dries's avatar
 
Dries committed
611
  static $perm = array();
Dries's avatar
 
Dries committed
612

613
  if ($reset) {
614
    $perm = array();
615 616
  }

617 618 619 620
  if (is_null($account)) {
    $account = $user;
  }

621
  // User #1 has all privileges:
622
  if ($account->uid == 1) {
623
    return TRUE;
Dries's avatar
 
Dries committed
624 625
  }

Dries's avatar
Dries committed
626 627
  // To reduce the number of SQL queries, we cache the user's permissions
  // in a static variable.
628
  if (!isset($perm[$account->uid])) {
629
    $role_permissions = user_role_permissions($account->roles, $reset);
Dries's avatar
 
Dries committed
630

631
    $perms = array();
632 633
    foreach ($role_permissions as $one_role) {
      $perms += $one_role;
Dries's avatar
 
Dries committed
634
    }
635
    $perm[$account->uid] = $perms;
Dries's avatar
 
Dries committed
636
  }
637

638
  return isset($perm[$account->uid][$string]);
Dries's avatar
 
Dries committed
639 640
}

641
/**
642
 * Checks for usernames blocked by user administration.
643
 *
644
 * @return boolean TRUE for blocked users, FALSE for active.
645 646
 */
function user_is_blocked($name) {
647
  $deny = db_fetch_object(db_query("SELECT name FROM {users} WHERE status = 0 AND name = LOWER('%s')", $name));
648

649
  return $deny;
650 651
}

Dries's avatar
Dries committed
652 653 654
/**
 * Implementation of hook_perm().
 */
Dries's avatar
 
Dries committed
655
function user_perm() {
656
   return array(
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
     '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.'),
     ),
673 674 675 676 677 678 679 680
     'cancel account' => array(
       'title' => t('Cancel account'),
       '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/user/settings'))),
     ),
     '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.'))),
     ),
681
   );
Dries's avatar
 
Dries committed
682 683
}

Dries's avatar
Dries committed
684 685 686 687 688
/**
 * Implementation of hook_file_download().
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
689 690 691
function user_file_download($filepath) {
  if (strpos($filepath, variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
    $info = image_get_info(file_create_path($filepath));
692
    return array('Content-type: ' . $info['mime_type']);
Dries's avatar
 
Dries committed
693 694 695
  }
}

696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
/**
 * Implementation of hook_file_references().
 */
function user_file_references($file) {
  // Determine if the file is used by this module.
  $count = db_query('SELECT COUNT(*) FROM {users} WHERE picture = :fid', array(':fid' => $file->fid))->fetchField();
  if ($count) {
    // Return the name of the module and how many references it has to the file.
    return array('user' => $count);
  }
}

/**
 * Implementation of hook_file_delete().
 */
function user_file_delete($file) {
  // Remove any references to the file.
  db_update('users')
    ->fields(array('picture' => 0))
    ->condition('picture', $file->fid)
    ->execute();
}

Dries's avatar
Dries committed
719 720 721
/**
 * Implementation of hook_search().
 */
722
function user_search($op = 'search', $keys = NULL, $skip_access_check = FALSE) {
723 724
  switch ($op) {
    case 'name':
725
      if ($skip_access_check || user_access('access user profiles')) {
726
        return t('Users');
727
      }
728
    case 'search':
729 730 731 732
      if (user_access('access user profiles')) {
        $find = array();
        // Replace wildcards with MySQL/PostgreSQL wildcards.
        $keys = preg_replace('!\*+!', '%', $keys);
733 734 735 736
        if (user_access('administer users')) {
          // Administrators can also search in the otherwise private email field.
          $result = pager_query("SELECT name, uid, mail FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%') OR LOWER(mail) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys, $keys);
          while ($account = db_fetch_object($result)) {
737
            $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
738 739 740
          }
        }
        else {
741
          $result = pager_query("SELECT name, uid FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys);
742
          while ($account = db_fetch_object($result)) {
743
            $find[] = array('title' => $account->name, 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
744
          }
745 746
        }
        return $find;
747
      }
Dries's avatar
 
Dries committed
748 749 750
  }
}

Dries's avatar
Dries committed
751 752 753 754 755
/**
 * Implementation of hook_elements().
 */
function user_elements() {
  return array(
756 757 758 759 760 761
    'user_profile_category' => array(
      '#theme_wrapper' => 'user_profile_category'
    ),
    'user_profile_item' => array(
      '#theme' => 'user_profile_item'
    ),
Dries's avatar
Dries committed
762 763 764
  );
}

Dries's avatar
Dries committed
765
/**
766
 * Implementation of hook_user_view().
Dries's avatar
Dries committed
767
 */
768 769
function user_user_view(&$edit, &$account, $category = NULL) {
  $account->content['user_picture'] = array(
770
    '#markup' => theme('user_picture', $account),
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793
    '#weight' => -10,
  );
  if (!isset($account->content['summary'])) {
    $account->content['summary'] = array();
  }
  $account->content['summary'] += array(
    '#type' => 'user_profile_category',
    '#attributes' => array('class' => 'user-member'),
    '#weight' => 5,
    '#title' => t('History'),
  );
  $account->content['summary']['member_for'] =  array(
    '#type' => 'user_profile_item',
    '#title' => t('Member for'),
    '#markup' => format_interval(REQUEST_TIME - $account->created),
  );
}

/**
 * Implementation of hook_user_form.
 */
function user_user_form(&$edit, &$account, $category = NULL) {
  if ($category == 'account') {
794
    $form_state = array();
795
    return user_edit_form($form_state, (isset($account->uid) ? $account->uid : FALSE), $edit);
796
  }
797
}
798

799
/**
800
 * Implementation of hook_user_validate().
801 802 803
 */
function user_user_validate(&$edit, &$account, $category = NULL) {
  if ($category == 'account') {
804 805 806 807 808 809 810 811 812 813 814
    $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);
      }
      elseif (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND LOWER(name) = LOWER('%s')", $uid, $edit['name'])) > 0) {
        form_set_error('name', t('The name %name is already taken.', array('%name' => $edit['name'])));
      }
    }

815
    // Validate the e-mail address, and check if it is taken by an existing user.
816 817 818 819
    if ($error = user_validate_mail($edit['mail'])) {
      form_set_error('mail', $error);
    }
    elseif (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND LOWER(mail) = LOWER('%s')", $uid, $edit['mail'])) > 0) {
820 821 822 823 824 825 826
      // 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'))));
      }
827
    }
828
  }
829
}
830

831
/**
832
 * Implementation of hook_user_submit().
833 834 835
 */
function user_user_submit(&$edit, &$account, $category = NULL) {
  if ($category == 'account') {
836 837 838
    if (!empty($edit['picture_upload'])) {
      $edit['picture'] = $edit['picture_upload'];
    }
839
    // Delete picture if requested, and if no replacement picture was given.
840 841
    elseif (!empty($edit['picture_delete'])) {
      $edit['picture'] = NULL;
842
    }
843 844 845 846
    // Remove these values so they don't end up serialized in the data field.
    $edit['picture_upload'] = NULL;
    $edit['picture_delete'] = NULL;

847 848 849
    if (isset($edit['roles'])) {
      $edit['roles'] = array_filter($edit['roles']);
    }
850
  }
851
}
852

853 854 855
/**
 * Implementation of hook_user_categories.
 */
856
function user_user_categories($edit, $account, $category = NULL) {
857
    return array(array('name' => 'account', 'title' => t('Account settings'), 'weight' => 1));
Dries's avatar
Dries committed
858 859
}

860 861
function user_login_block() {
  $form = array(
862
    '#action' => url($_GET['q'], array('query' => drupal_get_destination())),
863
    '#id' => 'user-login-form',
864
    '#validate' => user_login_default_validators(),
865
    '#submit' => array('user_login_submit'),
866 867 868
  );
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Username'),
869
    '#maxlength' => USERNAME_MAX_LENGTH,
870 871 872 873 874
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['pass'] = array('#type' => 'password',
    '#title' => t('Password'),
875
    '#maxlength' => 60,
876 877 878 879 880 881 882 883
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['submit'] = array('#type' => 'submit',
    '#value' => t('Log in'),
  );
  $items = array();
  if (variable_get('user_register', 1)) {
884
    $items[] = l(t('Create new account'), 'user/register', array('attributes' => array('title' => t('Create a new user account.'))));
885
  }
886
  $items[] = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
887
  $form['links'] = array('#markup' => theme('item_list', $items));
888 889 890
  return $form;
}

Dries's avatar
Dries committed
891
/**
892
 * Implementation of hook_block_list().
Dries's avatar
Dries committed
893
 */
894
function user_block_list() {
Dries's avatar
 
Dries committed
895 896
  global $user;

897 898 899
  $blocks['login']['info'] = t('User login');
  // Not worth caching.
  $blocks['login']['cache'] = BLOCK_NO_CACHE;
900

901 902 903 904
  $blocks['navigation']['info'] = t('Navigation');
  // Menu blocks can't be cached because each menu item can have
  // a custom access callback. menu.inc manages its own caching.
  $blocks['navigation']['cache'] = BLOCK_NO_CACHE;
905

906
  $blocks['new']['info'] = t('Who\'s new');
907

908 909 910 911 912
  // Too dynamic to cache.
  $blocks['online']['info'] = t('Who\'s online');
  $blocks['online']['cache'] = BLOCK_NO_CACHE;
  return $blocks;
}
913

914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
/**
 * Implementation of hook_block_configure().
 */
function user_block_configure($delta = '') {
  global $user;

  switch($delta) {
    case 'new':
      $form['user_block_whois_new_count'] = array(
        '#type' => 'select',
        '#title' => t('Number of users to display'),
        '#default_value' => variable_get('user_block_whois_new_count', 5),
        '#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');
      $form['user_block_seconds_online'] = array('#type' => 'select', '#title' => t('User activity'), '#default_value' => variable_get('user_block_seconds_online', 900), '#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' => variable_get('user_block_max_list_count', 10), '#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.'));
934
      $form['user_block_cache'] = array('#markup' => '<p>If page caching is disabled, the block shows the number of anonymous and authenticated users, respectively. If page caching is enabled, only the number of authenticated users is displayed.</p>');
935
      return $form;
936
  }
937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953
}

/**
 * Implementation of hook_block_save().
 */
function user_block_save($delta = '', $edit = array()) {
  global $user;

  switch ($delta) {
    case 'new':
      variable_set('user_block_whois_new_count', $edit['user_block_whois_new_count']);
      break;

    case 'online':
      variable_set('user_block_seconds_online', $edit['user_block_seconds_online']);
      variable_set('user_block_max_list_count', $edit['user_block_max_list_count']);
      break;
954
  }
955
}
Dries's avatar
 
Dries committed
956

957 958 959 960 961
/**
 * Implementation of hook_block_view().
 */
function user_block_view($delta = '') {
  global $user;
Dries's avatar
 
Dries committed
962

963
  $block = array();
Dries's avatar
Dries committed
964

965 966 967 968
  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)))) {
Dries's avatar
Dries committed
969

970 971 972 <