user.module 108 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.
 */

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

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

Dries's avatar
 
Dries committed
27 28
  if ($user = db_fetch_object($result)) {
    return user_load($user);
Dries's avatar
 
Dries committed
29 30 31 32 33 34
  }
  else {
    return 0;
  }
}

Dries's avatar
Dries committed
35 36 37 38 39
/**
 * Fetch a user object.
 *
 * @param $array
 *   An associative array of attributes to search for in selecting the
40
 *   user, such as user name or e-mail address.
Dries's avatar
Dries committed
41 42
 *
 * @return
43
 *   A fully-loaded $user object upon successful user load or FALSE if user cannot be loaded.
Dries's avatar
Dries committed
44
 */
Dries's avatar
 
Dries committed
45
function user_load($array = array()) {
Dries's avatar
Dries committed
46
  // Dynamically compose a SQL query:
47
  $query = array();
48
  $params = array();
49

Dries's avatar
 
Dries committed
50
  foreach ($array as $key => $value) {
51 52
    if ($key == 'uid' || $key == 'status') {
      $query[] = "$key = %d";
53
      $params[] = $value;
54
    }
55 56 57 58
    else if ($key == 'pass') {
      $query[] = "pass = '%s'";
      $params[] = md5($value);
    }
Dries's avatar
 
Dries committed
59
    else {
60
      $query[]= "LOWER($key) = LOWER('%s')";
61
      $params[] = $value;
Dries's avatar
 
Dries committed
62 63
    }
  }
64
  $result = db_query('SELECT * FROM {users} u WHERE ' . implode(' AND ', $query), $params);
Dries's avatar
 
Dries committed
65

66 67 68
  if (db_num_rows($result)) {
    $user = db_fetch_object($result);
    $user = drupal_unpack($user);
Dries's avatar
 
Dries committed
69

70
    $user->roles = array();
71 72 73 74 75 76
    if ($user->uid) {
      $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
    }
    else {
      $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
    }
77 78 79 80
    $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;
    }
81
    user_module_invoke('load', $array, $user);
82 83
  }
  else {
84
    $user = FALSE;
Dries's avatar
 
Dries committed
85
  }
Dries's avatar
 
Dries committed
86 87 88 89

  return $user;
}

90
/**
91
 * Save changes to a user account or add a new user.
92 93
 *
 * @param $account
94 95
 *   The $user object for the user to modify or add. If $user->uid is
 *   omitted, a new user will be added.
96 97 98
 *
 * @param $array
 *   An array of fields and values to save. For example array('name' => 'My name');
99
 *   Setting a field to NULL deletes it from the data column.
100 101 102 103
 *
 * @param $category
 *   (optional) The category for storing profile information in.
 */
104
function user_save($account, $array = array(), $category = 'account') {
Dries's avatar
Dries committed
105
  // Dynamically compose a SQL query:
Kjartan's avatar
Kjartan committed
106
  $user_fields = user_fields();
Dries's avatar
 
Dries committed
107
  if ($account->uid) {
108
    user_module_invoke('update', $array, $account, $category);
Dries's avatar
 
Dries committed
109

Dries's avatar
Dries committed
110
    $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
Dries's avatar
 
Dries committed
111
    foreach ($array as $key => $value) {
112
      if ($key == 'pass' && !empty($value)) {
Dries's avatar
 
Dries committed
113 114
        $query .= "$key = '%s', ";
        $v[] = md5($value);
Dries's avatar
 
Dries committed
115
      }
116
      else if ((substr($key, 0, 4) !== 'auth') && ($key != 'pass')) {
Kjartan's avatar
Kjartan committed
117
        if (in_array($key, $user_fields)) {
118
          // Save standard fields
Dries's avatar
 
Dries committed
119 120
          $query .= "$key = '%s', ";
          $v[] = $value;
Dries's avatar
 
Dries committed
121
        }
Dries's avatar
 
Dries committed
122
        else if ($key != 'roles') {
123
          // Roles is a special case: it used below.
124
          if ($value === NULL) {
125 126 127 128 129
            unset($data[$key]);
          }
          else {
            $data[$key] = $value;
          }
Dries's avatar
 
Dries committed
130
        }
Dries's avatar
 
Dries committed
131 132
      }
    }
133
    $query .= "data = '%s' ";
Dries's avatar
 
Dries committed
134
    $v[] = serialize($data);
Dries's avatar
 
Dries committed
135

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

138
    // Reload user roles if provided
139
    if (is_array($array['roles'])) {
Dries's avatar
Dries committed
140
      db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
Dries's avatar
 
Dries committed
141

142
      foreach (array_keys($array['roles']) as $rid) {
143 144 145
        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);
        }
146
      }
Dries's avatar
 
Dries committed
147 148
    }

149
    // Delete a blocked user's sessions to kick them if they are online.
150
    if (isset($array['status']) && $array['status'] == 0) {
151
      db_query('DELETE FROM {sessions} WHERE uid = %d', $account->uid);
152 153
    }

154
    // Refresh user object
Dries's avatar
 
Dries committed
155
    $user = user_load(array('uid' => $account->uid));
156
    user_module_invoke('after_update', $array, $user, $category);
Dries's avatar
 
Dries committed
157 158
  }
  else {
Dries's avatar
Dries committed
159
    $array['uid'] = db_next_id('{users}_uid');
Dries's avatar
 
Dries committed
160

161 162 163 164
    if (!isset($array['created'])) {    // Allow 'created' to be set by hook_auth
      $array['created'] = time();
    }

165 166 167
    // 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
168
    foreach ($array as $key => $value) {
169 170 171 172
      switch($key) {
        case 'pass':
          $fields[] = $key;
          $values[] = md5($value);
Dries's avatar
 
Dries committed
173
          $s[] = "'%s'";
Dries's avatar
Dries committed
174
          break;
175 176 177 178 179 180 181 182 183 184 185 186 187 188
        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
189 190
      }
    }
Dries's avatar
Dries committed
191
    db_query('INSERT INTO {users} ('. implode(', ', $fields) .') VALUES ('. implode(', ', $s) .')', $values);
Dries's avatar
 
Dries committed
192

193 194
    // Build the initial user object.
    $user = user_load(array('uid' => $array['uid']));
Dries's avatar
 
Dries committed
195

196 197 198 199 200
    user_module_invoke('insert', $array, $user, $category);

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

207
    // Save user roles (delete just to be safe).
208 209 210 211 212 213
    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);
        }
214 215 216
      }
    }

217 218
    // Build the finished user object.
    $user = user_load(array('uid' => $array['uid']));
Dries's avatar
 
Dries committed
219 220
  }

221
  // Save distributed authentication mappings
222
  $authmaps = array();
Dries's avatar
 
Dries committed
223
  foreach ($array as $key => $value) {
Dries's avatar
 
Dries committed
224
    if (substr($key, 0, 4) == 'auth') {
Dries's avatar
 
Dries committed
225 226 227
      $authmaps[$key] = $value;
    }
  }
228
  if (sizeof($authmaps) > 0) {
Dries's avatar
 
Dries committed
229
    user_set_authmaps($user, $authmaps);
Dries's avatar
 
Dries committed
230 231 232 233 234
  }

  return $user;
}

Dries's avatar
Dries committed
235 236 237
/**
 * Verify the syntax of the given name.
 */
Dries's avatar
 
Dries committed
238
function user_validate_name($name) {
239
  if (!strlen($name)) return t('You must enter a username.');
Dries's avatar
Dries committed
240 241 242
  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 (ereg('  ', $name)) return t('The username cannot contain multiple spaces in a row.');
243
  if (ereg("[^\x80-\xF7 [:alnum:]@_.-]", $name)) return t('The username contains an illegal character.');
244 245 246 247 248 249 250 251 252 253 254
  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}]/u',   // Replacement characters
                   $name)) {
    return t('The username contains an illegal character.');
  }
Dries's avatar
Dries committed
255
  if (ereg('@', $name) && !eregi('@([0-9a-z](-?[0-9a-z])*.)+[a-z]{2}([zmuvtg]|fo|me)?$', $name)) return t('The username is not a valid authentication ID.');
256
  if (strlen($name) > 56) return t('The username %name is too long: it must be less than 56 characters.', array('%name' => $name));
Dries's avatar
 
Dries committed
257 258 259
}

function user_validate_mail($mail) {
Dries's avatar
Dries committed
260
  if (!$mail) return t('You must enter an e-mail address.');
261
  if (!valid_email_address($mail)) {
262
    return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
Dries's avatar
 
Dries committed
263 264 265
  }
}

Dries's avatar
 
Dries committed
266
function user_validate_picture($file, &$edit, $user) {
267
  global $form_values;
Dries's avatar
Dries committed
268
  // Initialize the picture:
269
  $form_values['picture'] = $user->picture;
Dries's avatar
 
Dries committed
270

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

276
  if (!$info || !$info['extension']) {
277
    form_set_error('picture_upload', t('The uploaded file was not an image.'));
Dries's avatar
 
Dries committed
278
  }
279 280
  else if (image_get_toolkit()) {
    image_scale($file->filepath, $file->filepath, $maxwidth, $maxheight);
Dries's avatar
 
Dries committed
281
  }
282
  else if (filesize($file->filepath) > (variable_get('user_picture_file_size', '30') * 1000)) {
283
    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'))));
284
  }
285
  else if ($info['width'] > $maxwidth || $info['height'] > $maxheight) {
286
    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
287
  }
288 289

  if (!form_get_errors()) {
290
    if ($file = file_save_upload('picture_upload', variable_get('user_picture_path', 'pictures') .'/picture-'. $user->uid .'.'. $info['extension'], 1)) {
291
      $form_values['picture'] = $file->filepath;
292 293
    }
    else {
294
      form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist.", array('%directory' => variable_get('user_picture_path', 'pictures'))));
295
    }
Dries's avatar
 
Dries committed
296 297 298
  }
}

Dries's avatar
Dries committed
299 300 301
/**
 * Generate a random alphanumeric password.
 */
Dries's avatar
 
Dries committed
302 303
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
304 305
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
306 307
  // of 'I', 1, and l.
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
Dries's avatar
Dries committed
308

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

Dries's avatar
Dries committed
312 313
  // Declare the password as a blank string.
  $pass = '';
Dries's avatar
 
Dries committed
314

Dries's avatar
Dries committed
315
  // Loop the number of times specified by $length.
Dries's avatar
 
Dries committed
316 317 318 319
  for ($i = 0; $i < $length; $i++) {

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

  return $pass;
Dries's avatar
 
Dries committed
324 325
}

Dries's avatar
Dries committed
326 327 328 329 330
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
Dries's avatar
 
Dries committed
331 332
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
Dries's avatar
Dries committed
333 334
 *
 * @return
335
 *   boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
336 337 338 339 340
 *
 * 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
341
function user_access($string, $account = NULL) {
Dries's avatar
 
Dries committed
342
  global $user;
Dries's avatar
 
Dries committed
343
  static $perm = array();
Dries's avatar
 
Dries committed
344

345 346 347 348
  if (is_null($account)) {
    $account = $user;
  }

349
  // User #1 has all privileges:
350
  if ($account->uid == 1) {
351
    return TRUE;
Dries's avatar
 
Dries committed
352 353
  }

Dries's avatar
Dries committed
354 355
  // To reduce the number of SQL queries, we cache the user's permissions
  // in a static variable.
356
  if (!isset($perm[$account->uid])) {
357
    $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
358

Steven Wittens's avatar
Steven Wittens committed
359
    $perm[$account->uid] = '';
Dries's avatar
 
Dries committed
360
    while ($row = db_fetch_object($result)) {
361
      $perm[$account->uid] .= "$row->perm, ";
Dries's avatar
 
Dries committed
362
    }
Dries's avatar
 
Dries committed
363
  }
364

365
  if (isset($perm[$account->uid])) {
366
    return strpos($perm[$account->uid], "$string, ") !== FALSE;
367
  }
368

369
  return FALSE;
Dries's avatar
 
Dries committed
370 371
}

372 373 374
/**
 * Checks for usernames blocked by user administration
 *
375
 * @return boolean TRUE for blocked users, FALSE for active
376 377 378 379 380 381 382 383
 */
function user_is_blocked($name) {
  $allow = db_fetch_object(db_query("SELECT * FROM {users} WHERE status = 1 AND name = LOWER('%s')", $name));
  $deny  = db_fetch_object(db_query("SELECT * FROM {users} WHERE status = 0 AND name = LOWER('%s')", $name));

  return $deny && !$allow;
}

Dries's avatar
 
Dries committed
384 385
function user_fields() {
  static $fields;
Dries's avatar
 
Dries committed
386

Dries's avatar
 
Dries committed
387
  if (!$fields) {
Dries's avatar
Dries committed
388
    $result = db_query('SELECT * FROM {users} WHERE uid = 1');
Kjartan's avatar
Kjartan committed
389 390 391
    if (db_num_rows($result)) {
      $fields = array_keys(db_fetch_array($result));
    }
Dries's avatar
 
Dries committed
392 393
    else {
      // Make sure we return the default fields at least
394
      $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
395
    }
Dries's avatar
 
Dries committed
396
  }
Dries's avatar
 
Dries committed
397

Dries's avatar
 
Dries committed
398
  return $fields;
Dries's avatar
 
Dries committed
399 400
}

Dries's avatar
Dries committed
401 402 403
/**
 * Implementation of hook_perm().
 */
Dries's avatar
 
Dries committed
404
function user_perm() {
405
  return array('administer access control', 'administer users', 'access user profiles', 'change own username');
Dries's avatar
 
Dries committed
406 407
}

Dries's avatar
Dries committed
408 409 410 411 412
/**
 * Implementation of hook_file_download().
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
Dries's avatar
 
Dries committed
413
function user_file_download($file) {
Steven Wittens's avatar
Steven Wittens committed
414
  if (strpos($file, variable_get('user_picture_path', 'pictures') .'/picture-') === 0) {
415 416
    $info = image_get_info(file_create_path($file));
    return array('Content-type: '. $info['mime_type']);
Dries's avatar
 
Dries committed
417 418 419
  }
}

Dries's avatar
Dries committed
420 421 422
/**
 * Implementation of hook_search().
 */
423
function user_search($op = 'search', $keys = NULL) {
424 425
  switch ($op) {
    case 'name':
426 427 428
      if (user_access('access user profiles')) {
        return t('users');
      }
429
    case 'search':
430 431 432 433 434 435
      if (user_access('access user profiles')) {
        $find = array();
        // Replace wildcards with MySQL/PostgreSQL wildcards.
        $keys = preg_replace('!\*+!', '%', $keys);
        $result = pager_query("SELECT * FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys);
        while ($account = db_fetch_object($result)) {
436
          $find[] = array('title' => $account->name, 'link' => url('user/'. $account->uid));
437 438
        }
        return $find;
439
      }
Dries's avatar
 
Dries committed
440 441 442
  }
}

Dries's avatar
Dries committed
443 444 445
/**
 * Implementation of hook_user().
 */
446
function user_user($type, &$edit, &$user, $category = NULL) {
Dries's avatar
Dries committed
447
  if ($type == 'view') {
448
    $items['history'] = array('title' => t('Member for'),
449 450 451 452 453
      'value' => format_interval(time() - $user->created),
      'class' => 'member',
    );

    return array(t('History') => $items);
Dries's avatar
Dries committed
454
  }
455 456 457 458 459
  if ($type == 'form' && $category == 'account') {
    return user_edit_form(arg(1), $edit);
  }

  if ($type == 'validate' && $category == 'account') {
460
    return _user_edit_validate(arg(1), $edit);
461 462
  }

463 464 465 466
  if ($type == 'submit' && $category == 'account') {
    return _user_edit_submit(arg(1), $edit);
  }

467 468 469
  if ($type == 'categories') {
    return array(array('name' => 'account', 'title' => t('account settings'), 'weight' => 1));
  }
Dries's avatar
Dries committed
470 471
}

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
function user_login_block() {
  $form = array(
    '#action' => url($_GET['q'], drupal_get_destination()),
    '#id' => 'user-login-form',
    '#base' => 'user_login',
  );
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Username'),
    '#maxlength' => 60,
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['pass'] = array('#type' => 'password',
    '#title' => t('Password'),
    '#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
501 502 503
/**
 * Implementation of hook_block().
 */
504
function user_block($op = 'list', $delta = 0, $edit = array()) {
Dries's avatar
 
Dries committed
505 506
  global $user;

Dries's avatar
Dries committed
507 508 509 510 511
  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');
512

513
     return $blocks;
514
  }
515 516
  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');
517 518
    $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.'));
519

520
    return $form;
521 522 523 524 525 526
  }
  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
527 528
    $block = array();

Dries's avatar
 
Dries committed
529 530
    switch ($delta) {
      case 0:
Dries's avatar
Dries committed
531 532
        // 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
533

Dries's avatar
Dries committed
534
          $block['subject'] = t('User login');
535
          $block['content'] = drupal_get_form('user_login_block');
Dries's avatar
 
Dries committed
536
        }
Dries's avatar
Dries committed
537
        return $block;
Dries's avatar
Dries committed
538

539
      case 1:
Dries's avatar
 
Dries committed
540
        if ($menu = theme('menu_tree')) {
Dries's avatar
Dries committed
541
           $block['subject'] = $user->uid ? $user->name : t('Navigation');
542
           $block['content'] = $menu;
Dries's avatar
 
Dries committed
543
        }
544
        return $block;
Dries's avatar
Dries committed
545

Dries's avatar
 
Dries committed
546
      case 2:
547
        if (user_access('access content')) {
Steven Wittens's avatar
Steven Wittens committed
548
          // Retrieve a list of new users who have subsequently accessed the site successfully.
549
          $result = db_query_range('SELECT uid, name FROM {users} WHERE status != 0 AND access != 0 ORDER BY created DESC', 0, 5);
550
          while ($account = db_fetch_object($result)) {
551
            $items[] = $account;
552
          }
Dries's avatar
Dries committed
553
          $output = theme('user_list', $items);
Dries's avatar
 
Dries committed
554

Dries's avatar
Dries committed
555 556
          $block['subject'] = t('Who\'s new');
          $block['content'] = $output;
557
        }
Dries's avatar
Dries committed
558 559
        return $block;

Dries's avatar
 
Dries committed
560
      case 3:
561
        if (user_access('access content')) {
Dries's avatar
Dries committed
562
          // Count users with activity in the past defined period.
563
          $time_period = variable_get('user_block_seconds_online', 900);
Dries's avatar
 
Dries committed
564

Dries's avatar
Dries committed
565
          // Perform database queries to gather online user lists.
Dries's avatar
 
Dries committed
566
          $guests = db_fetch_object(db_query('SELECT COUNT(sid) AS count FROM {sessions} WHERE timestamp >= %d AND uid = 0', time() - $time_period));
567
          $users = db_query('SELECT uid, name, access FROM {users} WHERE access >= %d AND uid != 0 ORDER BY access DESC', time() - $time_period);
Dries's avatar
 
Dries committed
568
          $total_users = db_num_rows($users);
Dries's avatar
 
Dries committed
569

Dries's avatar
Dries committed
570
          // Format the output with proper grammar.
Dries's avatar
 
Dries committed
571
          if ($total_users == 1 && $guests->count == 1) {
572
            $output = t('There is currently %members and %visitors online.', array('%members' => format_plural($total_users, '1 user', '@count users'), '%visitors' => format_plural($guests->count, '1 guest', '@count guests')));
Dries's avatar
 
Dries committed
573 574
          }
          else {
575
            $output = t('There are currently %members and %visitors online.', array('%members' => format_plural($total_users, '1 user', '@count users'), '%visitors' => format_plural($guests->count, '1 guest', '@count guests')));
Dries's avatar
 
Dries committed
576 577
          }

578 579
          // Display a list of currently online users.
          $max_users = variable_get('user_block_max_list_count', 10);
580 581
          if ($total_users && $max_users) {
            $items = array();
582

583 584 585
            while ($max_users-- && $account = db_fetch_object($users)) {
              $items[] = $account;
            }
586

587 588
            $output .= theme('user_list', $items, t('Online users'));
          }
589

Dries's avatar
Dries committed
590 591
          $block['subject'] = t('Who\'s online');
          $block['content'] = $output;
Dries's avatar
 
Dries committed
592
        }
Dries's avatar
 
Dries committed
593
        return $block;
Dries's avatar
 
Dries committed
594 595
    }
  }
596 597
}

Dries's avatar
 
Dries committed
598 599 600 601 602 603 604 605 606
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', '');
    }

607
    if (isset($picture)) {
608
      $alt = t('@user\'s picture', array('@user' => $account->name ? $account->name : variable_get('anonymous', 'Anonymous')));
609
      $picture = theme('image', $picture, $alt, $alt, '', FALSE);
610
      if (!empty($account->uid) && user_access('access user profiles')) {
611
        $picture = l($picture, "user/$account->uid", array('title' => t('View user profile.')), NULL, NULL, FALSE, TRUE);
Dries's avatar
 
Dries committed
612 613 614 615 616 617 618
      }

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

619 620 621
/**
 * Theme a user page
 * @param $account the user object
622 623 624 625 626
 * @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.
627 628 629
 *
 * @ingroup themeable
 */
Dries's avatar
Dries committed
630
function theme_user_profile($account, $fields) {
631
  $output = '<div class="profile">';
Dries's avatar
 
Dries committed
632
  $output .= theme('user_picture', $account);
633
  foreach ($fields as $category => $items) {
634
    if (strlen($category) > 0) {
635
      $output .= '<h2 class="title">'. $category .'</h2>';
636
    }
637 638
    $output .= '<dl>';
    foreach ($items as $item) {
639
      if (isset($item['title'])) {
640
        $output .= '<dt class="'. $item['class'] .'">'. $item['title'] .'</dt>';
641 642
      }
      $output .= '<dd class="'. $item['class'] .'">'. $item['value'] .'</dd>';
643 644
    }
    $output .= '</dl>';
Dries's avatar
Dries committed
645
  }
646
  $output .= '</div>';
Dries's avatar
 
Dries committed
647 648 649 650

  return $output;
}

651 652 653 654 655 656 657
/**
 * 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) {
658 659 660 661
  if (!empty($users)) {
    foreach ($users as $user) {
      $items[] = theme('username', $user);
    }
662
  }
Dries's avatar
Dries committed
663
  return theme('item_list', $items, $title);
Dries's avatar
 
Dries committed
664 665
}

Dries's avatar
 
Dries committed
666
/**
Dries's avatar
 
Dries committed
667
 * Implementation of hook_menu().
Dries's avatar
 
Dries committed
668
 */
Dries's avatar
 
Dries committed
669
function user_menu($may_cache) {
Dries's avatar
 
Dries committed
670
  global $user;
Dries's avatar
 
Dries committed
671

Dries's avatar
 
Dries committed
672
  $items = array();
Dries's avatar
 
Dries committed
673

674
  $admin_access = user_access('administer users');
675
  $access_access = user_access('administer access control');
676
  $view_access = user_access('access user profiles');
Dries's avatar
 
Dries committed
677

Dries's avatar
 
Dries committed
678
  if ($may_cache) {
Dries's avatar
 
Dries committed
679
    $items[] = array('path' => 'user', 'title' => t('user account'),
680
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_login'), 'access' => TRUE, 'type' => MENU_CALLBACK);
681

Steven Wittens's avatar
Steven Wittens committed
682
    $items[] = array('path' => 'user/autocomplete', 'title' => t('user autocomplete'),
683
      'callback' => 'user_autocomplete', 'access' => $view_access, 'type' => MENU_CALLBACK);
Steven Wittens's avatar
Steven Wittens committed
684

Steven Wittens's avatar
Steven Wittens committed
685
    // Registration and login pages.
Dries's avatar
 
Dries committed
686
    $items[] = array('path' => 'user/login', 'title' => t('log in'),
687
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_login'), 'type' => MENU_DEFAULT_LOCAL_TASK);
688
    $items[] = array('path' => 'user/register', 'title' => t('create new account'),
689
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_register'), 'access' => $user->uid == 0 && variable_get('user_register', 1), 'type' => MENU_LOCAL_TASK);
Dries's avatar
 
Dries committed
690
    $items[] = array('path' => 'user/password', 'title' => t('request new password'),
691
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_pass'), 'access' => $user->uid == 0, 'type' => MENU_LOCAL_TASK);
692
    $items[] = array('path' => 'user/reset', 'title' => t('reset password'),
693
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_pass_reset'), 'access' => TRUE, 'type' => MENU_CALLBACK);
Dries's avatar
 
Dries committed
694 695
    $items[] = array('path' => 'user/help', 'title' => t('help'),
      'callback' => 'user_help_page', 'type' => MENU_CALLBACK);
Dries's avatar
 
Dries committed
696

Steven Wittens's avatar
Steven Wittens committed
697
    // Admin user pages
698 699 700 701 702 703 704 705 706
    $items[] = array('path' => 'admin/user',
      'title' => t('user management'),
      'description' => t('Manage your site\'s users, groups and access to site features.'),
      'position' => 'left',
      'callback' => 'system_admin_menu_block_page',
      'access' => user_access('access configuration pages'),
    );
    $items[] = array('path' => 'admin/user/user', 'title' => t('users'),
      'description' => t('List, add, and edit users.'),
707
      'callback' => 'user_admin', 'callback arguments' => array('list'), 'access' => $admin_access);
708
    $items[] = array('path' => 'admin/user/user/list', 'title' => t('list'),
Dries's avatar
 
Dries committed
709
      'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
710
    $items[] = array('path' => 'admin/user/user/create', 'title' => t('add user'),
711
      'callback' => 'user_admin', 'callback arguments' => array('create'), 'access' => $admin_access,
Dries's avatar
 
Dries committed
712
      'type' => MENU_LOCAL_TASK);
713 714
    $items[] = array('path' => 'admin/user/settings', 'title' => t('user settings'),
      'description' => t('Configure default behavior of users, including registration requirements, e-mails, and user pictures.'),
715
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_admin_settings'));
716

Steven Wittens's avatar
Steven Wittens committed
717
    // Admin access pages
718 719
    $items[] = array('path' => 'admin/user/access', 'title' => t('access control'),
      'description' => t('Determine access to features by selecting permissions for roles.'),
720
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_admin_perm'), 'access' => $access_access);
721 722
    $items[] = array('path' => 'admin/user/roles', 'title' => t('roles'),
      'description' => t('List, edit, or add user roles.'),
723
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_admin_new_role'), 'access' => $access_access,
724 725
      'type' => MENU_NORMAL_ITEM);
    $items[] = array('path' => 'admin/user/roles/edit', 'title' => t('edit role'),
726
       'callback' => 'drupal_get_form', 'callback arguments' => array('user_admin_role'), 'access' => $access_access,
727
      'type' => MENU_CALLBACK);
728 729 730 731
    $items[] = array('path' => 'admin/user/rules', 'title' => t('access rules'),
      'description' => t('List and create rules to disallow usernames, e-mail addresses, and IP addresses.'),
      'callback' => 'user_admin_access', 'access' => $access_access);
    $items[] = array('path' => 'admin/user/rules/list', 'title' => t('list'),
732
      'access' => $access_access, 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
733
    $items[] = array('path' => 'admin/user/rules/add', 'title' => t('add rule'),
734
      'callback' => 'user_admin_access_add', 'access' => $access_access,
Dries's avatar
 
Dries committed
735
      'type' => MENU_LOCAL_TASK);
736
    $items[] = array('path' => 'admin/user/rules/check', 'title' => t('check rules'),
737
      'callback' => 'user_admin_access_check', 'access' => $access_access,
Dries's avatar
 
Dries committed
738
      'type' => MENU_LOCAL_TASK);
739
    $items[] = array('path' => 'admin/user/rules/edit', 'title' => t('edit rule'),
740
      'callback' => 'user_admin_access_edit', 'access' => $access_access,
741
      'type' => MENU_CALLBACK);
742
    $items[] = array('path' => 'admin/user/rules/delete', 'title' => t('delete rule'),
743 744
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_admin_access_delete_confirm'),
      'access' => $access_access, 'type' => MENU_CALLBACK);
745

746
    if (module_exists('search')) {
747 748
      $items[] = array('path' => 'admin/user/search', 'title' => t('search users'),
        'description' => t('Search users by name.'),
749
        'callback' => 'user_admin', 'callback arguments' => array('search'), 'access' => $admin_access,
750
        'type' => MENU_NORMAL_ITEM);
751
    }
Dries's avatar
 
Dries committed
752

Steven Wittens's avatar
Steven Wittens committed
753
    // Your personal page
Dries's avatar
 
Dries committed
754 755
    if ($user->uid) {
      $items[] = array('path' => 'user/'. $user->uid, 'title' => t('my account'),
756
        'callback' => 'user_view', 'callback arguments' => array(arg(1)), 'access' => TRUE,
757
        'type' => MENU_DYNAMIC_ITEM);
Dries's avatar
 
Dries committed
758
    }
Dries's avatar
 
Dries committed
759 760 761 762 763

    $items[] = array('path' => 'logout', 'title' => t('log out'),
      'access' => $user->uid != 0,
      'callback' => 'user_logout',
      'weight' => 10);
Dries's avatar
 
Dries committed
764 765
  }
  else {
766 767 768 769
    // Add the CSS for this module. We put this in !$may_cache so it is only
    // added once per request.
    drupal_add_css(drupal_get_path('module', 'user') .'/user.css', 'core');

770
    if (arg(0) == 'user' && is_numeric(arg(1)) && arg(1) > 0) {
771
      $account = user_load(array('uid' => arg(1)));
772

773 774 775 776
      if ($user !== FALSE) {
        // Always let a user view their own account
        $view_access |= $user->uid == arg(1);
        // Only admins can view blocked accounts
Dries's avatar
Dries committed
777
        $view_access &= $account->status || $admin_access;
778 779 780

        $items[] = array('path' => 'user/'. arg(1), 'title' => t('user'),
          'type' => MENU_CALLBACK, 'callback' => 'user_view',
Dries's avatar
Dries committed
781
          'callback arguments' => array(arg(1)), 'access' => $view_access);
782 783 784

        $items[] = array('path' => 'user/'. arg(1) .'/view', 'title' => t('view'),
          'access' => $view_access, 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
785

786
        $items[] = array('path' => 'user/'. arg(1) .'/edit', 'title' => t('edit'),
787 788
          'callback' => 'drupal_get_form', 'callback arguments' => array('user_edit'),
          'access' => $admin_access || $user->uid == arg(1), 'type' => MENU_LOCAL_TASK);
789 790 791 792 793
        $items[] = array('path' => 'user/'. arg(1) .'/delete', 'title' => t('delete'),
          'callback' => 'user_edit', 'access' => $admin_access,
          'type' => MENU_CALLBACK);

        if (arg(2) == 'edit') {
794
          if (($categories = _user_categories($account)) && (count($categories) > 1)) {
795 796 797 798 799 800 801 802
            foreach ($categories as $key => $category) {
              $items[] = array(
                'path' => 'user/'. arg(1) .'/edit/'. $category['name'],
                'title' => $category['title'],
                'type' => $category['name'] == 'account' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
                'weight' => $category['weight'],
                'access' => ($admin_access || $user->uid == arg(1)));
            }
Dries's avatar