user.module 86.3 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
define('USERNAME_MAX_LENGTH', 60);
define('EMAIL_MAX_LENGTH', 64);

Dries's avatar
Dries committed
12 13 14
/**
 * Invokes hook_user() in every module.
 *
15
 * We cannot use module_invoke() for this, because the arguments need to
Dries's avatar
Dries committed
16 17
 * be passed by reference.
 */
18
function user_module_invoke($type, &$array, &$user, $category = NULL) {
Dries's avatar
 
Dries committed
19
  foreach (module_list() as $module) {
20
    $function = $module . '_user';
21 22 23
    if (function_exists($function)) {
      $function($type, $array, $user, $category);
    }
Dries's avatar
 
Dries committed
24 25 26
  }
}

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

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

83
  if ($user = db_fetch_array($result)) {
Dries's avatar
 
Dries committed
84
    return user_load($user);
Dries's avatar
 
Dries committed
85 86 87 88 89 90
  }
  else {
    return 0;
  }
}

91
/**
92 93 94
 * Perform standard Drupal login operations for a user object.
 *
 * The user object must already be authenticated. This function verifies
95
 * that the user account is not blocked and then performs the login,
96
 * updates the login timestamp in the database, invokes hook_user('login'),
97
 * and regenerates the session.
98 99 100 101 102 103
 *
 * @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.
104
 *    This array is passed to hook_user op login.
105 106 107 108
 * @return boolean
 *    TRUE if the login succeeds, FALSE otherwise.
 */
function user_external_login($account, $edit = array()) {
109 110
  $form = drupal_get_form('user_login');

111 112 113 114 115
  $state['values'] = $edit;
  if (empty($state['values']['name'])) {
    $state['values']['name'] = $account->name;
  }

116
  // Check if user is blocked.
117 118
  user_login_name_validate($form, $state, (array)$account);
  if (form_get_errors()) {
119
    // Invalid login.
120 121
    return FALSE;
  }
122

123
  // Valid login.
124 125
  global $user;
  $user = $account;
126
  user_authenticate_finalize($state['values']);
127 128 129
  return TRUE;
}

Dries's avatar
Dries committed
130 131 132 133 134
/**
 * Fetch a user object.
 *
 * @param $array
 *   An associative array of attributes to search for in selecting the
135
 *   user, such as user name or e-mail address.
Dries's avatar
Dries committed
136 137
 *
 * @return
138 139
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
Dries's avatar
Dries committed
140
 */
Dries's avatar
 
Dries committed
141
function user_load($array = array()) {
Dries's avatar
Dries committed
142
  // Dynamically compose a SQL query:
143
  $query = array();
144
  $params = array();
145

146 147 148
  if (is_numeric($array)) {
    $array = array('uid' => $array);
  }
149 150 151
  elseif (!is_array($array)) {
    return FALSE;
  }
152

Dries's avatar
 
Dries committed
153
  foreach ($array as $key => $value) {
154 155
    if ($key == 'uid' || $key == 'status') {
      $query[] = "$key = %d";
156
      $params[] = $value;
157
    }
158 159
    else if ($key == 'pass') {
      $query[] = "pass = '%s'";
160
      $params[] = $value;
161
    }
Dries's avatar
 
Dries committed
162
    else {
163
      $query[]= "LOWER($key) = LOWER('%s')";
164
      $params[] = $value;
Dries's avatar
 
Dries committed
165 166
    }
  }
167
  $result = db_query('SELECT * FROM {users} u WHERE ' . implode(' AND ', $query), $params);
Dries's avatar
 
Dries committed
168

169
  if ($user = db_fetch_object($result)) {
170
    $user = drupal_unpack($user);
Dries's avatar
 
Dries committed
171

172
    $user->roles = array();
173 174 175 176 177 178
    if ($user->uid) {
      $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
    }
    else {
      $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
    }
179 180 181 182
    $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;
    }
183
    user_module_invoke('load', $array, $user);
184 185
  }
  else {
186
    $user = FALSE;
Dries's avatar
 
Dries committed
187
  }
Dries's avatar
 
Dries committed
188 189 190 191

  return $user;
}

192
/**
193
 * Save changes to a user account or add a new user.
194 195
 *
 * @param $account
196 197
 *   The $user object for the user to modify or add. If $user->uid is
 *   omitted, a new user will be added.
198 199
 *
 * @param $array
200 201 202 203 204
 *   An array of fields and values to save. For example array('name'
 *   => 'My name').  Keys that do not belong to columns in the user-related
 *   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.
205 206 207
 *
 * @param $category
 *   (optional) The category for storing profile information in.
208 209 210
 *
 * @return
 *   A fully-loaded $user object upon successful save or FALSE if the save failed.
211
 */
212
function user_save($account, $array = array(), $category = 'account') {
213 214 215 216
  $table = drupal_get_schema('users');
  $user_fields = $table['fields'];

  if (!empty($array['pass'])) {
217 218 219 220 221 222 223
    // Allow alternate password hashing schemes.
    require_once variable_get('password_inc', './includes/password.inc');
    $array['pass'] = user_hash_password(trim($array['pass']));
    // Abort if the hashing failed and returned FALSE.
    if (!$array['pass']) {
      return FALSE;
    }
224 225 226 227 228 229
  }
  else {
    // Avoid overwriting an existing password with a blank password.
    unset($array['pass']);
  }

230
  if (is_object($account) && $account->uid) {
231
    user_module_invoke('update', $array, $account, $category);
Dries's avatar
Dries committed
232
    $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
233 234 235 236 237
    // Consider users edited by an administrator as logged in, if they haven't
    // already, so anonymous users can view the profile (if allowed).
    if (empty($array['access']) && empty($account->access) && user_access('administer users')) {
      $array['access'] = time();
    }
Dries's avatar
 
Dries committed
238
    foreach ($array as $key => $value) {
239 240 241
      // Fields that don't pertain to the users or user_roles
      // automatically serialized into the users.data column.
      if ($key != 'roles' && empty($user_fields[$key])) {
242 243
        if ($value === NULL) {
          unset($data[$key]);
Dries's avatar
 
Dries committed
244
        }
245 246
        else {
          $data[$key] = $value;
Dries's avatar
 
Dries committed
247
        }
Dries's avatar
 
Dries committed
248 249 250
      }
    }

251 252 253 254
    $array['data'] = $data;
    $array['uid'] = $account->uid;
    // Save changes to the users table.
    $success = drupal_write_record('users', $array, 'uid');
255 256 257 258
    if (!$success) {
      // The query failed - better to abort the save than risk further data loss.
      return FALSE;
    }
Dries's avatar
 
Dries committed
259

260
    // Reload user roles if provided.
261
    if (isset($array['roles']) && is_array($array['roles'])) {
Dries's avatar
Dries committed
262
      db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
Dries's avatar
 
Dries committed
263

264
      foreach (array_keys($array['roles']) as $rid) {
265 266 267
        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);
        }
268
      }
Dries's avatar
 
Dries committed
269 270
    }

271
    // Delete a blocked user's sessions to kick them if they are online.
272
    if (isset($array['status']) && $array['status'] == 0) {
273
      sess_destroy_uid($account->uid);
274 275
    }

276 277
    // If the password changed, delete all open sessions and recreate
    // the current one.
278
    if (!empty($array['pass'])) {
279 280 281 282
      sess_destroy_uid($account->uid);
      sess_regenerate();
    }

283
    // Refresh user object.
Dries's avatar
 
Dries committed
284
    $user = user_load(array('uid' => $account->uid));
285 286 287

    // Send emails after we have the new user object.
    if (isset($array['status']) && $array['status'] != $account->status) {
288
      // The user's status is changing; conditionally send notification email.
289 290 291 292
      $op = $array['status'] == 1 ? 'status_activated' : 'status_blocked';
      _user_mail_notify($op, $user);
    }

293
    user_module_invoke('after_update', $array, $user, $category);
Dries's avatar
 
Dries committed
294 295
  }
  else {
296 297
    // Allow 'created' to be set by the caller.
    if (!isset($array['created'])) {
298 299
      $array['created'] = time();
    }
300 301 302 303 304
    // Consider users created by an administrator as already logged in, so
    // anonymous users can view the profile (if allowed).
    if (empty($array['access']) && user_access('administer users')) {
      $array['access'] = time();
    }
305

306
    $success = drupal_write_record('users', $array);
307
    if (!$success) {
308 309
      // On a failed INSERT some other existing user's uid may be returned.
      // We must abort to avoid overwriting their account.
310 311
      return FALSE;
    }
312

313 314
    // Build the initial user object.
    $user = user_load(array('uid' => $array['uid']));
Dries's avatar
 
Dries committed
315

316 317
    user_module_invoke('insert', $array, $user, $category);

318 319
    // Note, we wait with saving the data column to prevent module-handled
    // fields from being saved there.
320 321
    $data = array();
    foreach ($array as $key => $value) {
322
      if (($key != 'roles') && (empty($user_fields[$key])) && ($value !== NULL)) {
323 324 325
        $data[$key] = $value;
      }
    }
326 327 328 329
    if (!empty($data)) {
      $data_array = array('uid' => $user->uid, 'data' => $data);
      drupal_write_record('users', $data_array, 'uid');
    }
330

331
    // Save user roles (delete just to be safe).
332
    if (isset($array['roles']) && is_array($array['roles'])) {
333 334 335 336 337
      db_query('DELETE FROM {users_roles} WHERE uid = %d', $array['uid']);
      foreach (array_keys($array['roles']) as $rid) {
        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
          db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $array['uid'], $rid);
        }
338 339 340
      }
    }

341 342
    // Build the finished user object.
    $user = user_load(array('uid' => $array['uid']));
Dries's avatar
 
Dries committed
343 344 345 346 347
  }

  return $user;
}

Dries's avatar
Dries committed
348 349 350
/**
 * Verify the syntax of the given name.
 */
Dries's avatar
 
Dries committed
351
function user_validate_name($name) {
352
  if (!strlen($name)) return t('You must enter a username.');
Dries's avatar
Dries committed
353 354
  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.');
355
  if (strpos($name, '  ') !== FALSE) return t('The username cannot contain multiple spaces in a row.');
356
  if (ereg("[^\x80-\xF7 [:alnum:]@_.-]", $name)) return t('The username contains an illegal character.');
357 358 359 360 361 362 363 364
  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
365
                   '\x{0}]/u',               // NULL byte
366 367 368
                   $name)) {
    return t('The username contains an illegal character.');
  }
369
  if (strpos($name, '@') !== FALSE && !eregi('@([0-9a-z](-?[0-9a-z])*.)+[a-z]{2}([zmuvtg]|fo|me)?$', $name)) return t('The username is not a valid authentication ID.');
370
  if (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
371 372 373
}

function user_validate_mail($mail) {
374 375 376
  if (!$mail) {
    return t('You must enter an e-mail address.');
  }
377
  if (!valid_email_address($mail)) {
378
    return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
Dries's avatar
 
Dries committed
379 380 381
  }
}

382
function user_validate_picture(&$form, &$form_state) {
383
  // If required, validate the uploaded picture.
384 385 386 387 388 389
  $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),
  );
  if ($file = file_save_upload('picture_upload', $validators)) {
390 391 392 393 394
    // Remove the old picture.
    if (isset($form_state['values']['_account']->picture) && file_exists($form_state['values']['_account']->picture)) {
      file_delete($form_state['values']['_account']->picture);
    }

395
    // The image was saved using file_save_upload() and was added to the
Dries's avatar
Dries committed
396
    // files table as a temporary file. We'll make a copy and let the garbage
397
    // collector delete the original upload.
398
    $info = image_get_info($file->filepath);
399
    $destination = variable_get('user_picture_path', 'pictures') . '/picture-' . $form['#uid'] . '.' . $info['extension'];
400
    if (file_copy($file, $destination, FILE_EXISTS_REPLACE)) {
401
      $form_state['values']['picture'] = $file->filepath;
402
    }
403 404
    else {
      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'))));
405
    }
Dries's avatar
 
Dries committed
406 407 408
  }
}

Dries's avatar
Dries committed
409 410 411
/**
 * Generate a random alphanumeric password.
 */
Dries's avatar
 
Dries committed
412 413
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
414 415
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
416
  // of 'I', 1, and 'l'.
417
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
Dries's avatar
Dries committed
418

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

Dries's avatar
Dries committed
422 423
  // Declare the password as a blank string.
  $pass = '';
Dries's avatar
 
Dries committed
424

Dries's avatar
Dries committed
425
  // Loop the number of times specified by $length.
Dries's avatar
 
Dries committed
426 427 428 429
  for ($i = 0; $i < $length; $i++) {

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

  return $pass;
Dries's avatar
 
Dries committed
434 435
}

436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
/**
 * 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.
      $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 (" . db_placeholders($fetch) . ")", $fetch);

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

512 513 514 515
  if ($reset) {
    unset($perm);
  }

516 517 518 519
  if (is_null($account)) {
    $account = $user;
  }

520
  // User #1 has all privileges:
521
  if ($account->uid == 1) {
522
    return TRUE;
Dries's avatar
 
Dries committed
523 524
  }

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

530
    $perms = array();
531 532
    foreach ($role_permissions as $one_role) {
      $perms += $one_role;
Dries's avatar
 
Dries committed
533
    }
534
    $perm[$account->uid] = $perms;
Dries's avatar
 
Dries committed
535
  }
536

537
  return isset($perm[$account->uid][$string]);
Dries's avatar
 
Dries committed
538 539
}

540
/**
541
 * Checks for usernames blocked by user administration.
542
 *
543
 * @return boolean TRUE for blocked users, FALSE for active.
544 545
 */
function user_is_blocked($name) {
546
  $deny = db_fetch_object(db_query("SELECT name FROM {users} WHERE status = 0 AND name = LOWER('%s')", $name));
547

548
  return $deny;
549 550
}

Dries's avatar
Dries committed
551 552 553
/**
 * Implementation of hook_perm().
 */
Dries's avatar
 
Dries committed
554
function user_perm() {
555 556 557 558 559 560
   return array(
     'administer permissions' => 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' => t('Manage or block users, and manage their role assignments.'),
     'access user profiles' => t('View profiles of users on the site, which may contain personal information.'),
     'change own username' => t('Select a different username.'),
   );
Dries's avatar
 
Dries committed
561 562
}

Dries's avatar
Dries committed
563 564 565 566 567
/**
 * Implementation of hook_file_download().
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
Dries's avatar
 
Dries committed
568
function user_file_download($file) {
569
  if (strpos($file, variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
570
    $info = image_get_info(file_create_path($file));
571
    return array('Content-type: ' . $info['mime_type']);
Dries's avatar
 
Dries committed
572 573 574
  }
}

Dries's avatar
Dries committed
575 576 577
/**
 * Implementation of hook_search().
 */
578
function user_search($op = 'search', $keys = NULL, $skip_access_check = FALSE) {
579 580
  switch ($op) {
    case 'name':
581
      if ($skip_access_check || user_access('access user profiles')) {
582
        return t('Users');
583
      }
584
    case 'search':
585 586 587 588
      if (user_access('access user profiles')) {
        $find = array();
        // Replace wildcards with MySQL/PostgreSQL wildcards.
        $keys = preg_replace('!\*+!', '%', $keys);
589 590 591 592
        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)) {
593
            $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
594 595 596
          }
        }
        else {
597
          $result = pager_query("SELECT name, uid FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys);
598
          while ($account = db_fetch_object($result)) {
599
            $find[] = array('title' => $account->name, 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
600
          }
601 602
        }
        return $find;
603
      }
Dries's avatar
 
Dries committed
604 605 606
  }
}

Dries's avatar
Dries committed
607 608 609 610 611 612 613 614 615 616
/**
 * Implementation of hook_elements().
 */
function user_elements() {
  return array(
    'user_profile_category' => array(),
    'user_profile_item' => array(),
  );
}

Dries's avatar
Dries committed
617 618 619
/**
 * Implementation of hook_user().
 */
620
function user_user($type, &$edit, &$account, $category = NULL) {
Dries's avatar
Dries committed
621
  if ($type == 'view') {
622 623 624 625 626 627 628 629 630 631
    $account->content['user_picture'] = array(
      '#value' => theme('user_picture', $account),
      '#weight' => -10,
    );
    if (!isset($account->content['summary'])) {
      $account->content['summary'] = array();
    }
    $account->content['summary'] += array(
      '#type' => 'user_profile_category',
      '#attributes' => array('class' => 'user-member'),
632
      '#weight' => 5,
633 634 635 636 637 638
      '#title' => t('History'),
    );
    $account->content['summary']['member_for'] =  array(
      '#type' => 'user_profile_item',
      '#title' => t('Member for'),
      '#value' => format_interval(time() - $account->created),
639
    );
Dries's avatar
Dries committed
640
  }
641
  if ($type == 'form' && $category == 'account') {
642 643
    $form_state = array();
    return user_edit_form($form_state, arg(1), $edit);
644 645 646
  }

  if ($type == 'validate' && $category == 'account') {
647
    return _user_edit_validate(arg(1), $edit);
648 649
  }

650 651 652 653
  if ($type == 'submit' && $category == 'account') {
    return _user_edit_submit(arg(1), $edit);
  }

654
  if ($type == 'categories') {
655
    return array(array('name' => 'account', 'title' => t('Account settings'), 'weight' => 1));
656
  }
Dries's avatar
Dries committed
657 658
}

659 660
function user_login_block() {
  $form = array(
661
    '#action' => url($_GET['q'], array('query' => drupal_get_destination())),
662
    '#id' => 'user-login-form',
663
    '#validate' => user_login_default_validators(),
664
    '#submit' => array('user_login_submit'),
665 666 667
  );
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Username'),
668
    '#maxlength' => USERNAME_MAX_LENGTH,
669 670 671 672 673
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['pass'] = array('#type' => 'password',
    '#title' => t('Password'),
674
    '#maxlength' => 60,
675 676 677 678 679 680 681 682
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['submit'] = array('#type' => 'submit',
    '#value' => t('Log in'),
  );
  $items = array();
  if (variable_get('user_register', 1)) {
683
    $items[] = l(t('Create new account'), 'user/register', array('attributes' => array('title' => t('Create a new user account.'))));
684
  }
685
  $items[] = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
686 687 688 689
  $form['links'] = array('#value' => theme('item_list', $items));
  return $form;
}

Dries's avatar
Dries committed
690 691 692
/**
 * Implementation of hook_block().
 */
693
function user_block($op = 'list', $delta = '', $edit = array()) {
Dries's avatar
 
Dries committed
694 695
  global $user;

Dries's avatar
Dries committed
696
  if ($op == 'list') {
697
    $blocks['login']['info'] = t('User login');
698
    // Not worth caching.
699
    $blocks['login']['cache'] = BLOCK_NO_CACHE;
700

701
    $blocks['navigation']['info'] = t('Navigation');
702 703
    // Menu blocks can't be cached because each menu item can have
    // a custom access callback. menu.inc manages its own caching.
704
    $blocks['navigation']['cache'] = BLOCK_NO_CACHE;
705

706
    $blocks['new']['info'] = t('Who\'s new');
707

708
    // Too dynamic to cache.
709 710
    $blocks['online']['info'] = t('Who\'s online');
    $blocks['online']['cache'] = BLOCK_NO_CACHE;
711
    return $blocks;
712
  }
713
  else if ($op == 'configure' && $delta == 'new') {
714 715 716 717 718 719 720 721
    $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;
  }
722
  else if ($op == 'configure' && $delta == 'online') {
723
    $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
724 725
    $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.'));
726

727
    return $form;
728
  }
729
  else if ($op == 'save' && $delta == 'new') {
730 731
    variable_set('user_block_whois_new_count', $edit['user_block_whois_new_count']);
  }
732
  else if ($op == 'save' && $delta == 'online') {
733 734 735 736
    variable_set('user_block_seconds_online', $edit['user_block_seconds_online']);
    variable_set('user_block_max_list_count', $edit['user_block_max_list_count']);
  }
  else if ($op == 'view') {
Dries's avatar
 
Dries committed
737 738
    $block = array();

Dries's avatar
 
Dries committed
739
    switch ($delta) {
740
      case 'login':
Dries's avatar
Dries committed
741 742
        // 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
743

Dries's avatar
Dries committed
744
          $block['subject'] = t('User login');
745
          $block['content'] = drupal_get_form('user_login_block');
Dries's avatar
 
Dries committed
746
        }
Dries's avatar
Dries committed
747
        return $block;
Dries's avatar
Dries committed
748

749
      case 'navigation':
750
        if ($menu = menu_tree()) {
751 752
          $block['subject'] = $user->uid ? check_plain($user->name) : t('Navigation');
          $block['content'] = $menu;
Dries's avatar
 
Dries committed
753
        }
754
        return $block;
Dries's avatar
Dries committed
755

756
      case 'new':
757
        if (user_access('access content')) {
Steven Wittens's avatar
Steven Wittens committed
758
          // Retrieve a list of new users who have subsequently accessed the site successfully.
759
          $result = db_query_range('SELECT uid, name FROM {users} WHERE status != 0 AND access != 0 ORDER BY created DESC', 0, variable_get('user_block_whois_new_count', 5));
760
          while ($account = db_fetch_object($result)) {
761
            $items[] = $account;
762
          }
Dries's avatar
Dries committed
763
          $output = theme('user_list', $items);
Dries's avatar
 
Dries committed
764

Dries's avatar
Dries committed
765 766
          $block['subject'] = t('Who\'s new');
          $block['content'] = $output;
767
        }
Dries's avatar
Dries committed
768 769
        return $block;

770
      case 'online':
771
        if (user_access('access content')) {
772
          // Count users active within the defined period.
773
          $interval = time() - variable_get('user_block_seconds_online', 900);
774

775
          // Perform database queries to gather online user lists.  We use s.timestamp
776
          // rather than u.access because it is much faster.
777
          $anonymous_count = sess_count($interval);
778
          $authenticated_users = db_query('SELECT DISTINCT u.uid, u.name, s.timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.timestamp >= %d AND s.uid > 0 ORDER BY s.timestamp DESC', $interval);
779 780 781 782 783 784 785 786 787 788
          $authenticated_count = 0;
          $max_users = variable_get('user_block_max_list_count', 10);
          $items = array();
          while ($account = db_fetch_object($authenticated_users)) {
            if ($max_users > 0) {
              $items[] = $account;
              $max_users--;
            }
            $authenticated_count++;
          }
Dries's avatar
 
Dries committed
789

Dries's avatar
Dries committed
790
          // Format the output with proper grammar.
791 792
          if ($anonymous_count == 1 && $authenticated_count == 1) {
            $output = t('There is currently %members and %visitors online.', array('%members' => format_plural($authenticated_count, '1 user', '@count users'), '%visitors' => format_plural($anonymous_count, '1 guest', '@count guests')));
Dries's avatar
 
Dries committed
793 794
          }
          else {
795
            $output = t('There are currently %members and %visitors online.', array('%members' => format_plural($authenticated_count, '1 user', '@count users'), '%visitors' => format_plural($anonymous_count, '1 guest', '@count guests')));
Dries's avatar
 
Dries committed
796 797
          }

798 799
          // Display a list of currently online users.
          $max_users = variable_get('user_block_max_list_count', 10);
800
          if ($authenticated_count && $max_users) {
801 802
            $output .= theme('user_list', $items, t('Online users'));
          }
803

Dries's avatar
Dries committed
804 805
          $block['subject'] = t('Who\'s online');
          $block['content'] = $output;
Dries's avatar
 
Dries committed
806
        }
Dries's avatar
 
Dries committed
807
        return $block;
Dries's avatar
 
Dries committed
808 809
    }
  }
810 811
}

812 813 814 815 816 817 818 819 820 821
/**
 * Process variables for user-picture.tpl.php.
 *
 * The $variables array contains the following arguments:
 * - $account
 *
 * @see user-picture.tpl.php
 */
function template_preprocess_user_picture(&$variables) {
  $variables['picture'] = '';
Dries's avatar
 
Dries committed
822
  if (variable_get('user_pictures', 0)) {
823
    $account = $variables['account'];
824
    if (!empty($account->picture) && file_exists($account->picture)) {
Dries's avatar
 
Dries committed
825 826 827 828 829 830
      $picture = file_create_url($account->picture);
    }
    else if (variable_get('user_picture_default', '')) {
      $picture = variable_get('user_picture_default', '');
    }

831
    if (isset($picture)) {
832
      $alt = t("@user's picture", array('@user' => $account->name ? $account->name : variable_get('anonymous', t('Anonymous'))));
833
      $variables['picture'] = theme('image', $picture, $alt, $alt, '', FALSE);
834
      if (!empty($account->uid) && user_access('access user profiles')) {
835 836
        $attributes = array('attributes' => array('title' => t('View user profile.')), 'html' => TRUE);
        $variables['picture'] = l($variables['picture'], "user/$account->uid", $attributes);
Dries's avatar
 
Dries committed
837 838 839 840 841
      }
    }
  }
}

842 843
/**
 * Make a list of users.
844 845 846 847 848
 *
 * @param $users
 *   An array with user objects. Should contain at least the name and uid.
 * @param $title
 *  (optional) Title to pass on to theme_item_list().
849 850 851 852
 *
 * @ingroup themeable
 */
function theme_user_list($users, $title = NULL) {
853 854 855 856
  if (!empty($users)) {
    foreach ($users as $user) {
      $items[] = theme('username', $user);
    }
857
  }
Dries's avatar
Dries committed
858
  return theme('item_list', $items, $title);
Dries's avatar
 
Dries committed
859 860
}

861
function user_is_anonymous() {
862 863
  // Menu administrators can see items for anonymous when administering.
  return !$GLOBALS['user']->uid || !empty($GLOBALS['menu_admin']);
864 865 866 867 868 869 870
}

function user_is_logged_in() {
  return (bool)$GLOBALS['user']->uid;
}

function user_register_access() {
871
  return user_is_anonymous() && variable_get('user_register', 1);
872 873 874 875 876 877 878 879 880 881 882 883 884 885
}

function user_view_access($account) {
  return $account && $account->uid &&
    (
      // Always let users view their own profile.
      ($GLOBALS['user']->uid == $account->uid) ||
      // Administrators can view all accounts.
      user_access('administer users') ||
      // The user is not blocked and logged in at least once.
      ($account->access && $account->status && user_access('access user profiles'))
    );
}

886 887 888
/**
 * Access callback for user account editing.
 */
889
function user_edit_access($account) {
890
  return (($GLOBALS['user']->uid == $account->uid) || user_access('administer