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
935
936
937
938
939
    'title' => t('Edit'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_edit'),
    'access callback' => 'user_edit_access',
    'access arguments' => array(1),
    'type' => MENU_LOCAL_TASK,
  );

940
941
  $empty_account = new stdClass();
  if (($categories = _user_categories($empty_account)) && (count($categories) > 1)) {
942
    foreach ($categories as $key => $category) {
943
      $items['user/%user/edit/'. $category['name']] = array(
944
945
946
947
948
        'title' => $category['title'],
        'page arguments' => array('user_edit', 3),
        'type' => $category['name'] == 'account' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
        'weight' => $category['weight'],
      );
Dries's avatar
   
Dries committed
949
    }
Dries's avatar
   
Dries committed
950
  }
Dries's avatar
   
Dries committed
951
  return $items;
Dries's avatar
   
Dries committed
952
953
}

954
955
956
957
function user_init() {
  drupal_add_css(drupal_get_path('module', 'user') .'/user.css', 'module');
}

958
959
960
961
962
963
964
965
function user_current_load($arg) {
  return user_load($arg);
}

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

Dries's avatar
Dries committed
966
967
968
969
/**
 * Accepts an user object, $account, or a DA name and returns an associative
 * array of modules and DA names. Called at external login.
 */
970
function user_get_authmaps($authname = NULL) {
Dries's avatar
   
Dries committed
971
  $result = db_query("SELECT authname, module FROM {authmap} WHERE authname = '%s'", $authname);
Dries's avatar
   
Dries committed
972
973
974
975
976
977
978
979
980
981
982
983
984
  if (db_num_rows($result) > 0) {
    while ($authmap = db_fetch_object($result)) {
      $authmaps[$authmap->module] = $authmap->authname;
    }
    return $authmaps;
  }
  else {
    return 0;
  }
}

function user_set_authmaps($account, $authmaps) {
  foreach ($authmaps as $key => $value) {
Dries's avatar
Dries committed
985
    $module = explode('_', $key, 2);
Dries's avatar
   
Dries committed
986
    if ($value) {
987
      db_query("UPDATE {authmap} SET authname = '%s' WHERE uid = %d AND module = '%s'", $value, $account->uid, $module[1]);
Dries's avatar
   
Dries committed
988
989
      if (!db_affected_rows()) {
        db_query("INSERT INTO {authmap} (authname, uid, module) VALUES ('%s', %d, '%s')", $value, $account->uid, $module[1]);
Dries's avatar
   
Dries committed
990
991
992
      }
    }
    else {
993
      db_query("DELETE FROM {authmap} WHERE uid = %d AND module = '%s'", $account->uid, $module[1]);
Dries's avatar
   
Dries committed
994
995
996
997
998
    }
  }
}

function user_auth_help_links() {
999
  $links = array();
Dries's avatar
   
Dries committed
1000
  foreach (module_list() as $module) {
Dries's avatar
Dries committed
1001
    if (module_hook($module, 'auth')) {
1002
      $links[] = l(module_invoke($module, 'info', 'name'), 'user/help', array('fragment' => $module));
Dries's avatar
   
Dries committed
1003
1004
1005
1006
1007
1008
1009
    }
  }
  return $links;
}

/*** User features *********************************************************/

1010
1011


1012
function user_login($msg = '') {
1013
  global $user;
Dries's avatar
   
Dries committed
1014

Dries's avatar
Dries committed
1015
  // If we are already logged on, go to the user page instead.
Dries's avatar
   
Dries committed
1016
  if ($user->uid) {
Dries's avatar