user.module 114 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 20
  foreach (module_list() as $module) {
    $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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
/**
 * Implementation of hook_theme()
 */
function user_theme() {
  return array(
    'user_picture' => array(
      'arguments' => array('account' => NULL),
    ),
    'user_profile' => array(
      'arguments' => array('account' => NULL, 'fields' => NULL),
    ),
    'user_list' => array(
      'arguments' => array('users' => NULL, 'title' => NULL),
    ),
    'user_admin_perm' => array(
      'arguments' => array('form' => NULL),
    ),
    'user_admin_new_role' => array(
      'arguments' => array('form' => NULL),
    ),
    'user_admin_account' => array(
      'arguments' => array('form' => NULL),
    ),
    'user_filter_form' => array(
      'arguments' => array('form' => NULL),
    ),
    'user_filters' => array(
      'arguments' => array('form' => NULL),
    ),
  );
}

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

62
  if ($user = db_fetch_array($result)) {
Dries's avatar
 
Dries committed
63
    return user_load($user);
Dries's avatar
 
Dries committed
64 65 66 67 68 69
  }
  else {
    return 0;
  }
}

Dries's avatar
Dries committed
70 71 72 73 74
/**
 * Fetch a user object.
 *
 * @param $array
 *   An associative array of attributes to search for in selecting the
75
 *   user, such as user name or e-mail address.
Dries's avatar
Dries committed
76 77
 *
 * @return
78
 *   A fully-loaded $user object upon successful user load or FALSE if user cannot be loaded.
Dries's avatar
Dries committed
79
 */
Dries's avatar
 
Dries committed
80
function user_load($array = array()) {
Dries's avatar
Dries committed
81
  // Dynamically compose a SQL query:
82
  $query = array();
83
  $params = array();
84

85 86 87 88
  if (is_numeric($array)) {
    $array = array('uid' => $array);
  }

Dries's avatar
 
Dries committed
89
  foreach ($array as $key => $value) {
90 91
    if ($key == 'uid' || $key == 'status') {
      $query[] = "$key = %d";
92
      $params[] = $value;
93
    }
94 95 96 97
    else if ($key == 'pass') {
      $query[] = "pass = '%s'";
      $params[] = md5($value);
    }
Dries's avatar
 
Dries committed
98
    else {
99
      $query[]= "LOWER($key) = LOWER('%s')";
100
      $params[] = $value;
Dries's avatar
 
Dries committed
101 102
    }
  }
103
  $result = db_query('SELECT * FROM {users} u WHERE '. implode(' AND ', $query), $params);
Dries's avatar
 
Dries committed
104

105 106 107
  if (db_num_rows($result)) {
    $user = db_fetch_object($result);
    $user = drupal_unpack($user);
Dries's avatar
 
Dries committed
108

109
    $user->roles = array();
110 111 112 113 114 115
    if ($user->uid) {
      $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
    }
    else {
      $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
    }
116 117 118 119
    $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;
    }
120
    user_module_invoke('load', $array, $user);
121 122
  }
  else {
123
    $user = FALSE;
Dries's avatar
 
Dries committed
124
  }
Dries's avatar
 
Dries committed
125 126 127 128

  return $user;
}

129
/**
130
 * Save changes to a user account or add a new user.
131 132
 *
 * @param $account
133 134
 *   The $user object for the user to modify or add. If $user->uid is
 *   omitted, a new user will be added.
135 136 137
 *
 * @param $array
 *   An array of fields and values to save. For example array('name' => 'My name');
138
 *   Setting a field to NULL deletes it from the data column.
139 140 141 142
 *
 * @param $category
 *   (optional) The category for storing profile information in.
 */
143
function user_save($account, $array = array(), $category = 'account') {
Dries's avatar
Dries committed
144
  // Dynamically compose a SQL query:
Kjartan's avatar
Kjartan committed
145
  $user_fields = user_fields();
146
  if (is_object($account) && $account->uid) {
147
    user_module_invoke('update', $array, $account, $category);
148
    $query = '';
Dries's avatar
Dries committed
149
    $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
Dries's avatar
 
Dries committed
150
    foreach ($array as $key => $value) {
151
      if ($key == 'pass' && !empty($value)) {
Dries's avatar
 
Dries committed
152 153
        $query .= "$key = '%s', ";
        $v[] = md5($value);
Dries's avatar
 
Dries committed
154
      }
155
      else if ((substr($key, 0, 4) !== 'auth') && ($key != 'pass')) {
Kjartan's avatar
Kjartan committed
156
        if (in_array($key, $user_fields)) {
157
          // Save standard fields
Dries's avatar
 
Dries committed
158 159
          $query .= "$key = '%s', ";
          $v[] = $value;
Dries's avatar
 
Dries committed
160
        }
Dries's avatar
 
Dries committed
161
        else if ($key != 'roles') {
162
          // Roles is a special case: it used below.
163
          if ($value === NULL) {
164 165 166 167 168
            unset($data[$key]);
          }
          else {
            $data[$key] = $value;
          }
Dries's avatar
 
Dries committed
169
        }
Dries's avatar
 
Dries committed
170 171
      }
    }
172
    $query .= "data = '%s' ";
Dries's avatar
 
Dries committed
173
    $v[] = serialize($data);
Dries's avatar
 
Dries committed
174

175
    db_query("UPDATE {users} SET $query WHERE uid = %d", array_merge($v, array($account->uid)));
Dries's avatar
 
Dries committed
176

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

181
      foreach (array_keys($array['roles']) as $rid) {
182 183 184
        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);
        }
185
      }
Dries's avatar
 
Dries committed
186 187
    }

188
    // Delete a blocked user's sessions to kick them if they are online.
189
    if (isset($array['status']) && $array['status'] == 0) {
190
      sess_destroy_uid($account->uid);
191 192
    }

193
    // Refresh user object
Dries's avatar
 
Dries committed
194
    $user = user_load(array('uid' => $account->uid));
195
    user_module_invoke('after_update', $array, $user, $category);
Dries's avatar
 
Dries committed
196 197
  }
  else {
Dries's avatar
Dries committed
198
    $array['uid'] = db_next_id('{users}_uid');
Dries's avatar
 
Dries committed
199

200 201 202 203
    if (!isset($array['created'])) {    // Allow 'created' to be set by hook_auth
      $array['created'] = time();
    }

204 205 206
    // Note, we wait with saving the data column to prevent module-handled
    // fields from being saved there. We cannot invoke hook_user('insert') here
    // because we don't have a fully initialized user object yet.
Dries's avatar
 
Dries committed
207
    foreach ($array as $key => $value) {
208
      switch ($key) {
209 210 211
        case 'pass':
          $fields[] = $key;
          $values[] = md5($value);
Dries's avatar
 
Dries committed
212
          $s[] = "'%s'";
Dries's avatar
Dries committed
213
          break;
214 215 216 217 218 219 220 221 222 223 224 225 226 227
        case 'uid':        case 'mode':     case 'sort':
        case 'threshold':  case 'created':  case 'access':
        case 'login':      case 'status':
          $fields[] = $key;
          $values[] = $value;
          $s[] = "%d";
          break;
        default:
          if (substr($key, 0, 4) !== 'auth' && in_array($key, $user_fields)) {
            $fields[] = $key;
            $values[] = $value;
            $s[] = "'%s'";
          }
          break;
Dries's avatar
 
Dries committed
228 229
      }
    }
Dries's avatar
Dries committed
230
    db_query('INSERT INTO {users} ('. implode(', ', $fields) .') VALUES ('. implode(', ', $s) .')', $values);
Dries's avatar
 
Dries committed
231

232 233
    // Build the initial user object.
    $user = user_load(array('uid' => $array['uid']));
Dries's avatar
 
Dries committed
234

235 236 237 238 239
    user_module_invoke('insert', $array, $user, $category);

    // Build and save the serialized data field now
    $data = array();
    foreach ($array as $key => $value) {
240
      if ((substr($key, 0, 4) !== 'auth') && ($key != 'roles') && (!in_array($key, $user_fields)) && ($value !== NULL)) {
241 242 243 244 245
        $data[$key] = $value;
      }
    }
    db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);

246
    // Save user roles (delete just to be safe).
247 248 249 250 251 252
    if (is_array($array['roles'])) {
      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);
        }
253 254 255
      }
    }

256 257
    // Build the finished user object.
    $user = user_load(array('uid' => $array['uid']));
Dries's avatar
 
Dries committed
258 259
  }

260
  // Save distributed authentication mappings
261
  $authmaps = array();
Dries's avatar
 
Dries committed
262
  foreach ($array as $key => $value) {
Dries's avatar
 
Dries committed
263
    if (substr($key, 0, 4) == 'auth') {
Dries's avatar
 
Dries committed
264 265 266
      $authmaps[$key] = $value;
    }
  }
267
  if (sizeof($authmaps) > 0) {
Dries's avatar
 
Dries committed
268
    user_set_authmaps($user, $authmaps);
Dries's avatar
 
Dries committed
269 270 271 272 273
  }

  return $user;
}

Dries's avatar
Dries committed
274 275 276
/**
 * Verify the syntax of the given name.
 */
Dries's avatar
 
Dries committed
277
function user_validate_name($name) {
278
  if (!strlen($name)) return t('You must enter a username.');
Dries's avatar
Dries committed
279 280
  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.');
281
  if (strpos($name, '  ') !== FALSE) return t('The username cannot contain multiple spaces in a row.');
282
  if (ereg("[^\x80-\xF7 [:alnum:]@_.-]", $name)) return t('The username contains an illegal character.');
283 284 285 286 287 288 289
  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
290 291
                   '\x{FFF9}-\x{FFFD}'.      // Replacement characters
                   '\x{0}]/u',               // NULL byte
292 293 294
                   $name)) {
    return t('The username contains an illegal character.');
  }
295
  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.');
296
  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
297 298 299
}

function user_validate_mail($mail) {
Dries's avatar
Dries committed
300
  if (!$mail) return t('You must enter an e-mail address.');
301
  if (!valid_email_address($mail)) {
302
    return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
Dries's avatar
 
Dries committed
303 304 305
  }
}

Dries's avatar
 
Dries committed
306
function user_validate_picture($file, &$edit, $user) {
307
  global $form_values;
Dries's avatar
Dries committed
308
  // Initialize the picture:
309
  $form_values['picture'] = $user->picture;
Dries's avatar
 
Dries committed
310

Dries's avatar
Dries committed
311 312
  // Check that uploaded file is an image, with a maximum file size
  // and maximum height/width.
313
  $info = image_get_info($file->filepath);
Dries's avatar
Dries committed
314
  list($maxwidth, $maxheight) = explode('x', variable_get('user_picture_dimensions', '85x85'));
Dries's avatar
 
Dries committed
315

316
  if (!$info || !$info['extension']) {
317
    form_set_error('picture_upload', t('The uploaded file was not an image.'));
Dries's avatar
 
Dries committed
318
  }
319 320
  else if (image_get_toolkit()) {
    image_scale($file->filepath, $file->filepath, $maxwidth, $maxheight);
Dries's avatar
 
Dries committed
321
  }
322
  else if (filesize($file->filepath) > (variable_get('user_picture_file_size', '30') * 1000)) {
323
    form_set_error('picture_upload', t('The uploaded image is too large; the maximum file size is %size kB.', array('%size' => variable_get('user_picture_file_size', '30'))));
324
  }
325
  else if ($info['width'] > $maxwidth || $info['height'] > $maxheight) {
326
    form_set_error('picture_upload', t('The uploaded image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => variable_get('user_picture_dimensions', '85x85'))));
Dries's avatar
 
Dries committed
327
  }
328 329

  if (!form_get_errors()) {
330
    if ($file = file_save_upload('picture_upload', variable_get('user_picture_path', 'pictures') .'/picture-'. $user->uid .'.'. $info['extension'], 1)) {
331
      $form_values['picture'] = $file->filepath;
332 333
    }
    else {
334
      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'))));
335
    }
Dries's avatar
 
Dries committed
336 337 338
  }
}

Dries's avatar
Dries committed
339 340 341
/**
 * Generate a random alphanumeric password.
 */
Dries's avatar
 
Dries committed
342 343
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
344 345
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
346 347
  // of 'I', 1, and l.
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
Dries's avatar
Dries committed
348

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

Dries's avatar
Dries committed
352 353
  // Declare the password as a blank string.
  $pass = '';
Dries's avatar
 
Dries committed
354

Dries's avatar
Dries committed
355
  // Loop the number of times specified by $length.
Dries's avatar
 
Dries committed
356 357 358 359
  for ($i = 0; $i < $length; $i++) {

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

  return $pass;
Dries's avatar
 
Dries committed
364 365
}

Dries's avatar
Dries committed
366 367 368 369 370
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
Dries's avatar
 
Dries committed
371 372
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
Dries's avatar
Dries committed
373 374
 *
 * @return
375
 *   boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
376 377 378 379 380
 *
 * 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.
 */
Dries's avatar
 
Dries committed
381
function user_access($string, $account = NULL) {
Dries's avatar
 
Dries committed
382
  global $user;
Dries's avatar
 
Dries committed
383
  static $perm = array();
Dries's avatar
 
Dries committed
384

385 386 387 388
  if (is_null($account)) {
    $account = $user;
  }

389
  // User #1 has all privileges:
390
  if ($account->uid == 1) {
391
    return TRUE;
Dries's avatar
 
Dries committed
392 393
  }

Dries's avatar
Dries committed
394 395
  // To reduce the number of SQL queries, we cache the user's permissions
  // in a static variable.
396
  if (!isset($perm[$account->uid])) {
397
    $result = db_query("SELECT DISTINCT(p.perm) FROM {role} r INNER JOIN {permission} p ON p.rid = r.rid WHERE r.rid IN (%s)", implode(',', array_keys($account->roles)));
Dries's avatar
 
Dries committed
398

Steven Wittens's avatar
Steven Wittens committed
399
    $perm[$account->uid] = '';
Dries's avatar
 
Dries committed
400
    while ($row = db_fetch_object($result)) {
401
      $perm[$account->uid] .= "$row->perm, ";
Dries's avatar
 
Dries committed
402
    }
Dries's avatar
 
Dries committed
403
  }
404

405
  if (isset($perm[$account->uid])) {
406
    return strpos($perm[$account->uid], "$string, ") !== FALSE;
407
  }
408

409
  return FALSE;
Dries's avatar
 
Dries committed
410 411
}

412 413 414
/**
 * Checks for usernames blocked by user administration
 *
415
 * @return boolean TRUE for blocked users, FALSE for active
416 417
 */
function user_is_blocked($name) {
418
  $deny  = db_fetch_object(db_query("SELECT name FROM {users} WHERE status = 0 AND name = LOWER('%s')", $name));
419

420
  return $deny;
421 422
}

Dries's avatar
 
Dries committed
423 424
function user_fields() {
  static $fields;
Dries's avatar
 
Dries committed
425

Dries's avatar
 
Dries committed
426
  if (!$fields) {
Dries's avatar
Dries committed
427
    $result = db_query('SELECT * FROM {users} WHERE uid = 1');
Kjartan's avatar
Kjartan committed
428 429 430
    if (db_num_rows($result)) {
      $fields = array_keys(db_fetch_array($result));
    }
Dries's avatar
 
Dries committed
431 432
    else {
      // Make sure we return the default fields at least
433
      $fields = array('uid', 'name', 'pass', 'mail', 'picture', 'mode', 'sort', 'threshold', 'theme', 'signature', 'created', 'access', 'login', 'status', 'timezone', 'language', 'init', 'data');
Dries's avatar
 
Dries committed
434
    }
Dries's avatar
 
Dries committed
435
  }
Dries's avatar
 
Dries committed
436

Dries's avatar
 
Dries committed
437
  return $fields;
Dries's avatar
 
Dries committed
438 439
}

Dries's avatar
Dries committed
440 441 442
/**
 * Implementation of hook_perm().
 */
Dries's avatar
 
Dries committed
443
function user_perm() {
444
  return array('administer access control', 'administer users', 'access user profiles', 'change own username');
Dries's avatar
 
Dries committed
445 446
}

Dries's avatar
Dries committed
447 448 449 450 451
/**
 * Implementation of hook_file_download().
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
Dries's avatar
 
Dries committed
452
function user_file_download($file) {
Steven Wittens's avatar
Steven Wittens committed
453
  if (strpos($file, variable_get('user_picture_path', 'pictures') .'/picture-') === 0) {
454 455
    $info = image_get_info(file_create_path($file));
    return array('Content-type: '. $info['mime_type']);
Dries's avatar
 
Dries committed
456 457 458
  }
}

Dries's avatar
Dries committed
459 460 461
/**
 * Implementation of hook_search().
 */
462
function user_search($op = 'search', $keys = NULL, $skip_access_check = FALSE) {
463 464
  switch ($op) {
    case 'name':
465
      if ($skip_access_check || user_access('access user profiles')) {
466
        return t('Users');
467
      }
468
    case 'search':
469 470 471 472
      if (user_access('access user profiles')) {
        $find = array();
        // Replace wildcards with MySQL/PostgreSQL wildcards.
        $keys = preg_replace('!\*+!', '%', $keys);
473
        $result = pager_query("SELECT uid, name FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys);
474
        while ($account = db_fetch_object($result)) {
475
          $find[] = array('title' => $account->name, 'link' => url('user/'. $account->uid, array('absolute' => TRUE)));
476 477
        }
        return $find;
478
      }
Dries's avatar
 
Dries committed
479 480 481
  }
}

Dries's avatar
Dries committed
482 483 484
/**
 * Implementation of hook_user().
 */
485
function user_user($type, &$edit, &$user, $category = NULL) {
Dries's avatar
Dries committed
486
  if ($type == 'view') {
487
    $items['history'] = array('title' => t('Member for'),
488 489 490 491 492
      'value' => format_interval(time() - $user->created),
      'class' => 'member',
    );

    return array(t('History') => $items);
Dries's avatar
Dries committed
493
  }
494 495 496 497 498
  if ($type == 'form' && $category == 'account') {
    return user_edit_form(arg(1), $edit);
  }

  if ($type == 'validate' && $category == 'account') {
499
    return _user_edit_validate(arg(1), $edit);
500 501
  }

502 503 504 505
  if ($type == 'submit' && $category == 'account') {
    return _user_edit_submit(arg(1), $edit);
  }

506
  if ($type == 'categories') {
507
    return array(array('name' => 'account', 'title' => t('Account settings'), 'weight' => 1));
508
  }
Dries's avatar
Dries committed
509 510
}

511 512
function user_login_block() {
  $form = array(
513
    '#action' => url($_GET['q'], array('query' => drupal_get_destination())),
514
    '#id' => 'user-login-form',
Steven Wittens's avatar
Steven Wittens committed
515 516
    '#validate' => array('user_login_validate' => array()),
    '#submit' => array('user_login_submit' => array()),
517 518 519
  );
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Username'),
520
    '#maxlength' => USERNAME_MAX_LENGTH,
521 522 523 524 525
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['pass'] = array('#type' => 'password',
    '#title' => t('Password'),
526
    '#maxlength' => 60,
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['submit'] = array('#type' => 'submit',
    '#value' => t('Log in'),
  );
  $items = array();
  if (variable_get('user_register', 1)) {
    $items[] = l(t('Create new account'), 'user/register', array('title' => t('Create a new user account.')));
  }
  $items[] = l(t('Request new password'), 'user/password', array('title' => t('Request new password via e-mail.')));
  $form['links'] = array('#value' => theme('item_list', $items));
  return $form;
}

Dries's avatar
Dries committed
542 543 544
/**
 * Implementation of hook_block().
 */
545
function user_block($op = 'list', $delta = 0, $edit = array()) {
Dries's avatar
 
Dries committed
546 547
  global $user;

Dries's avatar
Dries committed
548 549 550 551 552
  if ($op == 'list') {
     $blocks[0]['info'] = t('User login');
     $blocks[1]['info'] = t('Navigation');
     $blocks[2]['info'] = t('Who\'s new');
     $blocks[3]['info'] = t('Who\'s online');
553

554
     return $blocks;
555
  }
556 557 558 559 560 561 562 563 564
  else if ($op == 'configure' && $delta == 2) {
    $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;
  }
565 566
  else if ($op == 'configure' && $delta == 3) {
    $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
567 568
    $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.'));
569

570
    return $form;
571
  }
572 573 574
  else if ($op == 'save' && $delta == 2) {
    variable_set('user_block_whois_new_count', $edit['user_block_whois_new_count']);
  }
575 576 577 578 579
  else if ($op == 'save' && $delta == 3) {
    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
580 581
    $block = array();

Dries's avatar
 
Dries committed
582 583
    switch ($delta) {
      case 0:
Dries's avatar
Dries committed
584 585
        // 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
586

Dries's avatar
Dries committed
587
          $block['subject'] = t('User login');
588
          $block['content'] = drupal_get_form('user_login_block');
Dries's avatar
 
Dries committed
589
        }
Dries's avatar
Dries committed
590
        return $block;
Dries's avatar
Dries committed
591

592
      case 1:
593
        if ($menu = menu_tree()) {
Dries's avatar
Dries committed
594
           $block['subject'] = $user->uid ? check_plain($user->name) : t('Navigation');
595
           $block['content'] = $menu;
Dries's avatar
 
Dries committed
596
        }
597
        return $block;
Dries's avatar
Dries committed
598

Dries's avatar
 
Dries committed
599
      case 2:
600
        if (user_access('access content')) {
Steven Wittens's avatar
Steven Wittens committed
601
          // Retrieve a list of new users who have subsequently accessed the site successfully.
602
          $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));
603
          while ($account = db_fetch_object($result)) {
604
            $items[] = $account;
605
          }
Dries's avatar
Dries committed
606
          $output = theme('user_list', $items);
Dries's avatar
 
Dries committed
607

Dries's avatar
Dries committed
608 609
          $block['subject'] = t('Who\'s new');
          $block['content'] = $output;
610
        }
Dries's avatar
Dries committed
611 612
        return $block;

Dries's avatar
 
Dries committed
613
      case 3:
614
        if (user_access('access content')) {
Dries's avatar
Dries committed
615
          // Count users with activity in the past defined period.
616
          $interval = time() - variable_get('user_block_seconds_online', 900);
617

618 619 620
          // Perform database queries to gather online user lists.  We use s.timestamp
          // rather than u.access because it is much faster is much faster..
          $anonymous_count = sess_count($interval);
621
          $authenticated_users = db_query('SELECT DISTINCT u.uid, u.name 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);
622
          $authenticated_count = db_num_rows($authenticated_users);
Dries's avatar
 
Dries committed
623

Dries's avatar
Dries committed
624
          // Format the output with proper grammar.
625 626
          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
627 628
          }
          else {
629
            $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
630 631
          }

632 633
          // Display a list of currently online users.
          $max_users = variable_get('user_block_max_list_count', 10);
634
          if ($authenticated_count && $max_users) {
635
            $items = array();
636

637
            while ($max_users-- && $account = db_fetch_object($authenticated_users)) {
638 639
              $items[] = $account;
            }
640

641 642
            $output .= theme('user_list', $items, t('Online users'));
          }
643

Dries's avatar
Dries committed
644 645
          $block['subject'] = t('Who\'s online');
          $block['content'] = $output;
Dries's avatar
 
Dries committed
646
        }
Dries's avatar
 
Dries committed
647
        return $block;
Dries's avatar
 
Dries committed
648 649
    }
  }
650 651
}

Dries's avatar
 
Dries committed
652 653 654 655 656 657 658 659 660
function theme_user_picture($account) {
  if (variable_get('user_pictures', 0)) {
    if ($account->picture && file_exists($account->picture)) {
      $picture = file_create_url($account->picture);
    }
    else if (variable_get('user_picture_default', '')) {
      $picture = variable_get('user_picture_default', '');
    }

661
    if (isset($picture)) {
662
      $alt = t("@user's picture", array('@user' => $account->name ? $account->name : variable_get('anonymous', t('Anonymous'))));
663
      $picture = theme('image', $picture, $alt, $alt, '', FALSE);
664
      if (!empty($account->uid) && user_access('access user profiles')) {
665
        $picture = l($picture, "user/$account->uid", array('attributes' => array('title' => t('View user profile.')), 'html' => TRUE));
Dries's avatar
 
Dries committed
666 667 668 669 670 671 672
      }

      return "<div class=\"picture\">$picture</div>";
    }
  }
}

673 674 675
/**
 * Theme a user page
 * @param $account the user object
676 677 678 679 680
 * @param $fields a multidimensional array for the fields, in the form of array (
 *   'category1' => array(item_array1, item_array2), 'category2' => array(item_array3,
 *    .. etc.). Item arrays are formatted as array(array('title' => 'item title',
 * 'value' => 'item value', 'class' => 'class-name'), ... etc.). Module names are incorporated
 * into the CSS class.
681 682 683
 *
 * @ingroup themeable
 */
Dries's avatar
Dries committed
684
function theme_user_profile($account, $fields) {
685
  $output = '<div class="profile">';
Dries's avatar
 
Dries committed
686
  $output .= theme('user_picture', $account);
687
  foreach ($fields as $category => $items) {
688
    if (strlen($category) > 0) {
689
      $output .= '<h2 class="title">'. $category .'</h2>';
690
    }
691 692
    $output .= '<dl>';
    foreach ($items as $item) {
693
      if (isset($item['title'])) {
694
        $output .= '<dt class="'. $item['class'] .'">'. $item['title'] .'</dt>';
695 696
      }
      $output .= '<dd class="'. $item['class'] .'">'. $item['value'] .'</dd>';
697 698
    }
    $output .= '</dl>';
Dries's avatar
Dries committed
699
  }
700
  $output .= '</div>';
Dries's avatar
 
Dries committed
701 702 703 704

  return $output;
}

705 706 707 708 709 710 711
/**
 * Make a list of users.
 * @param $items an array with user objects. Should contain at least the name and uid
 *
 * @ingroup themeable
 */
function theme_user_list($users, $title = NULL) {
712 713 714 715
  if (!empty($users)) {
    foreach ($users as $user) {
      $items[] = theme('username', $user);
    }
716
  }
Dries's avatar
Dries committed
717
  return theme('item_list', $items, $title);
Dries's avatar
 
Dries committed
718 719
}

720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
function user_is_anonymous() {
  return !$GLOBALS['user']->uid;
}

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

function user_register_access() {
  return !$GLOBALS['user']->uid && variable_get('user_register', 1);
}

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'))
    );
}

744 745
function user_edit_access($account) {
  return ($GLOBALS['user']->uid == $account->uid) || array('administer users');
746 747 748 749 750 751 752
}

function user_load_self($arg) {
  $arg[1] = user_load($GLOBALS['user']->uid);
  return $arg;
}

Dries's avatar
 
Dries committed
753
/**
Dries's avatar
 
Dries committed
754
 * Implementation of hook_menu().
Dries's avatar
 
Dries committed
755
 */
756 757 758 759
function user_menu() {
  $items['user/autocomplete'] = array(
    'title' => t('User autocomplete'),
    'page callback' => 'user_autocomplete',
760
    'access callback' => 'user_access',
761 762 763
    'access arguments' => array('access user profiles'),
    'type' => MENU_CALLBACK,
  );
Dries's avatar
 
Dries committed
764

765
  // Registration and login pages.
766
  $items['user'] = array(
767 768 769 770
    'title' => t('Log in'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_login'),
    'access callback' => 'user_is_anonymous',
771
    'type' => MENU_CALLBACK,
772 773 774 775
  );

  $items['user/login'] = array(
    'title' => t('Log in'),
776 777
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
Dries's avatar
 
Dries committed
778

779 780 781 782 783 784 785 786 787 788 789 790
  $items['user/register'] = array(
    'title' => t('Create new account'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_register'),
    'access callback' => 'user_register_access',
    'type' => MENU_LOCAL_TASK,
  );

  $items['user/password'] = array(
    'title' => t('Request new password'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_pass'),
791
    'access callback' => 'user_is_anonymous',
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 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889
    'type' => MENU_LOCAL_TASK,
  );
  $items['user/reset/%/%/%'] = array(
    'title' => t('Reset password'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_pass_reset', 2, 3, 4),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['user/help'] = array(
    'title' => t('Help'),
    'page callback' => 'user_help_page',
    'type' => MENU_CALLBACK,
  );

  // Admin user pages
  $items['admin/user'] = array(
    'title' => t('User management'),
    'description' => t('Manage your site\'s users, groups and access to site features.'),
    'position' => 'left',
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array('administer site configuration'),
  );
  $items['admin/user/user'] = array(
    'title' => t('Users'),
    'description' => t('List, add, and edit users.'),
    'page callback' => 'user_admin',
    'page arguments' => array('list'),
    'access arguments' => array('administer users'));
  $items['admin/user/user/list'] = array(
    'title' => t('List'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/user/user/create'] = array(
    'title' => t('Add user'),
    'page arguments' => array('create'),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/user/settings'] = array(
    'title' => t('User settings'),
    'description' => t('Configure default behavior of users, including registration requirements, e-mails, and user pictures.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_admin_settings'),
  );

  // Admin access pages
  $items['admin/user/access'] = array(
    'title' => t('Access control'),
    'description' => t('Determine access to features by selecting permissions for roles.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_admin_perm'),
    'access arguments' => array('administer access control'),
  );
  $items['admin/user/roles'] = array(
    'title' => t('Roles'),
    'description' => t('List, edit, or add user roles.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_admin_new_role'),
    'access arguments' => array('administer access control'),
  );
  $items['admin/user/roles/edit'] = array(
    'title' => t('Edit role'),
    'page arguments' => array('user_admin_role'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/user/rules'] = array(
    'title' => t('Access rules'),
    'description' => t('List and create rules to disallow usernames, e-mail addresses, and IP addresses.'),
    'page callback' => 'user_admin_access',
    'access arguments' => array('administer access control'),
  );
  $items['admin/user/rules/list'] = array(
    'title' => t('List'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/user/rules/add'] = array(
    'title' => t('Add rule'),
    'page callback' => 'user_admin_access_add',
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/user/rules/check'] = array(
    'title' => t('Check rules'),
    'page callback' => 'user_admin_access_check',
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/user/rules/edit'] = array(
    'title' => t('Edit rule'),
    'page callback' => 'user_admin_access_edit',
    'type' => MENU_CALLBACK,
  );
  $items['admin/user/rules/delete'] = array(
    'title' => t('Delete rule'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_admin_access_delete_confirm'),
    'type' => MENU_CALLBACK,
  );
Dries's avatar
 
Dries committed
890

891 892 893 894 895 896 897 898
  if (module_exists('search')) {
    $items['admin/user/search'] = array(
      'title' => t('Search users'),
      'description' => t('Search users by name.'),
      'page callback' => 'user_admin',
      'page arguments' => array('search'),
      'access arguments' => array('administer users'),
    );
Dries's avatar
 
Dries committed
899
  }
900 901 902 903 904 905 906 907

  $items['logout'] = array(
    'title' => t('Log out'),
    'access callback' => 'user_is_logged_in',
    'page callback' => 'user_logout',
    'weight' => 10,
  );

908
  $items['user/%user_current'] = array(
909 910 911 912 913
    'title' => t('My account'),
    'page callback' => 'user_view',
    'page arguments' => array(1),
    'access callback' => 'user_view_access',
    'access arguments' => array(1),
914
    'parent' => '',
915 916
  );

917
  $items['user/%user/view'] = array(
918 919 920 921 922
    'title' => t('View'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

923
  $items['user/%user/delete'] = array(
924 925 926 927 928 929 930
    'title' => t('Delete'),
    'page callback' => 'user_edit',
    'access callback' => 'user_access',
    'access arguments' => array('administer users'),
    'type' => MENU_CALLBACK,
  );

931
  $items['user/%user/edit'] = array(
932 933 934