user.module 109 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

27
  if ($user = db_fetch_array($result)) {
Dries's avatar
   
Dries committed
28
    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
      sess_destroy($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
517
518
519
520
521
522
523
  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;
  }
524
525
  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');
526
527
    $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.'));
528

529
    return $form;
530
  }
531
532
533
  else if ($op == 'save' && $delta == 2) {
    variable_set('user_block_whois_new_count', $edit['user_block_whois_new_count']);
  }
534
535
536
537
538
  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
539
540
    $block = array();

Dries's avatar
   
Dries committed
541
542
    switch ($delta) {
      case 0:
Dries's avatar
Dries committed
543
544
        // 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
545

Dries's avatar
Dries committed
546
          $block['subject'] = t('User login');
547
          $block['content'] = drupal_get_form('user_login_block');
Dries's avatar
   
Dries committed
548
        }
Dries's avatar
Dries committed
549
        return $block;
Dries's avatar
Dries committed
550

551
      case 1:
Dries's avatar
   
Dries committed
552
        if ($menu = theme('menu_tree')) {
Dries's avatar
Dries committed
553
           $block['subject'] = $user->uid ? $user->name : t('Navigation');
554
           $block['content'] = $menu;
Dries's avatar
   
Dries committed
555
        }
556
        return $block;
Dries's avatar
Dries committed
557

Dries's avatar
   
Dries committed
558
      case 2:
559
        if (user_access('access content')) {
Steven Wittens's avatar
Steven Wittens committed
560
          // Retrieve a list of new users who have subsequently accessed the site successfully.
561
          $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));
562
          while ($account = db_fetch_object($result)) {
563
            $items[] = $account;
564
          }
Dries's avatar
Dries committed
565
          $output = theme('user_list', $items);
Dries's avatar
   
Dries committed
566

Dries's avatar
Dries committed
567
568
          $block['subject'] = t('Who\'s new');
          $block['content'] = $output;
569
        }
Dries's avatar
Dries committed
570
571
        return $block;

Dries's avatar
   
Dries committed
572
      case 3:
573
        if (user_access('access content')) {
Dries's avatar
Dries committed
574
          // Count users with activity in the past defined period.
575
          $time_period = time() - variable_get('user_block_seconds_online', 900);
Dries's avatar
   
Dries committed
576

Dries's avatar
Dries committed
577
          // Perform database queries to gather online user lists.
578
579
580
581
          $anonymous_count = sess_count($time_period);
          $authenticated_count = sess_count($time_period, false);
          $authenticated_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
582

Dries's avatar
Dries committed
583
          // Format the output with proper grammar.
584
585
          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
586
587
          }
          else {
588
            $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
589
590
          }

591
592
          // Display a list of currently online users.
          $max_users = variable_get('user_block_max_list_count', 10);
593
          if ($authenticated_count && $max_users) {
594
            $items = array();
595

596
            while ($max_users-- && $account = db_fetch_object($authenticated_users)) {
597
598
              $items[] = $account;
            }
599

600
601
            $output .= theme('user_list', $items, t('Online users'));
          }
602

Dries's avatar
Dries committed
603
604
          $block['subject'] = t('Who\'s online');
          $block['content'] = $output;
Dries's avatar
   
Dries committed
605
        }
Dries's avatar
   
Dries committed
606
        return $block;
Dries's avatar
   
Dries committed
607
608
    }
  }
609
610
}

Dries's avatar
   
Dries committed
611
612
613
614
615
616
617
618
619
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', '');
    }

620
    if (isset($picture)) {
621
      $alt = t('@user\'s picture', array('@user' => $account->name ? $account->name : variable_get('anonymous', 'Anonymous')));
622
      $picture = theme('image', $picture, $alt, $alt, '', FALSE);
623
      if (!empty($account->uid) && user_access('access user profiles')) {
624
        $picture = l($picture, "user/$account->uid", array('title' => t('View user profile.')), NULL, NULL, FALSE, TRUE);
Dries's avatar
   
Dries committed
625
626
627
628
629
630
631
      }

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

632
633
634
/**
 * Theme a user page
 * @param $account the user object
635
636
637
638
639
 * @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.
640
641
642
 *
 * @ingroup themeable
 */
Dries's avatar
Dries committed
643
function theme_user_profile($account, $fields) {
644
  $output = '<div class="profile">';
Dries's avatar
   
Dries committed
645
  $output .= theme('user_picture', $account);
646
  foreach ($fields as $category => $items) {
647
    if (strlen($category) > 0) {
648
      $output .= '<h2 class="title">'. $category .'</h2>';
649
    }
650
651
    $output .= '<dl>';
    foreach ($items as $item) {
652
      if (isset($item['title'])) {
653
        $output .= '<dt class="'. $item['class'] .'">'. $item['title'] .'</dt>';
654
655
      }
      $output .= '<dd class="'. $item['class'] .'">'. $item['value'] .'</dd>';
656
657
    }
    $output .= '</dl>';
Dries's avatar
Dries committed
658
  }
659
  $output .= '</div>';
Dries's avatar
   
Dries committed
660
661
662
663

  return $output;
}

664
665
666
667
668
669
670
/**
 * 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) {
671
672
673
674
  if (!empty($users)) {
    foreach ($users as $user) {
      $items[] = theme('username', $user);
    }
675
  }
Dries's avatar
Dries committed
676
  return theme('item_list', $items, $title);
Dries's avatar
   
Dries committed
677
678
}

Dries's avatar
   
Dries committed
679
/**
Dries's avatar
   
Dries committed
680
 * Implementation of hook_menu().
Dries's avatar
   
Dries committed
681
 */
Dries's avatar
   
Dries committed
682
function user_menu($may_cache) {
Dries's avatar
   
Dries committed
683
  global $user;
Dries's avatar
   
Dries committed
684

Dries's avatar
   
Dries committed
685
  $items = array();
Dries's avatar
   
Dries committed
686

687
  $admin_access = user_access('administer users');
688
  $access_access = user_access('administer access control');
689
  $view_access = user_access('access user profiles');
Dries's avatar
   
Dries committed
690

Dries's avatar
   
Dries committed
691
  if ($may_cache) {
692
    $items[] = array('path' => 'user', 'title' => t('User account'),
693
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_login'), 'access' => TRUE, 'type' => MENU_CALLBACK);
694

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

Steven Wittens's avatar
Steven Wittens committed
698
    // Registration and login pages.
699
    $items[] = array('path' => 'user/login', 'title' => t('Log in'),
700
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_login'), 'access' => TRUE, 'type' => MENU_DEFAULT_LOCAL_TASK);
701
    $items[] = array('path' => 'user/register', 'title' => t('Create new account'),
702
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_register'), 'access' => !$user->uid && variable_get('user_register', 1), 'type' => MENU_LOCAL_TASK);
703
    $items[] = array('path' => 'user/password', 'title' => t('Request new password'),
704
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_pass'), 'access' => !$user->uid, 'type' => MENU_LOCAL_TASK);
705
    $items[] = array('path' => 'user/reset', 'title' => t('Reset password'),
706
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_pass_reset'), 'access' => TRUE, 'type' => MENU_CALLBACK);
707
    $items[] = array('path' => 'user/help', 'title' => t('Help'),
Dries's avatar
   
Dries committed
708
      'callback' => 'user_help_page', 'type' => MENU_CALLBACK);
Dries's avatar
   
Dries committed
709

Steven Wittens's avatar
Steven Wittens committed
710
    // Admin user pages
711
    $items[] = array('path' => 'admin/user',
712
      'title' => t('User management'),
713
714
715
      'description' => t('Manage your site\'s users, groups and access to site features.'),
      'position' => 'left',
      'callback' => 'system_admin_menu_block_page',
716
      'access' => user_access('administer site configuration'),
717
    );
718
    $items[] = array('path' => 'admin/user/user', 'title' => t('Users'),
719
      'description' => t('List, add, and edit users.'),
720
      'callback' => 'user_admin', 'callback arguments' => array('list'), 'access' => $admin_access);
721
    $items[] = array('path' => 'admin/user/user/list', 'title' => t('List'),
Dries's avatar
   
Dries committed
722
      'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
723
    $items[] = array('path' => 'admin/user/user/create', 'title' => t('Add user'),
724
      'callback' => 'user_admin', 'callback arguments' => array('create'), 'access' => $admin_access,
Dries's avatar
   
Dries committed
725
      'type' => MENU_LOCAL_TASK);
726
    $items[] = array('path' => 'admin/user/settings', 'title' => t('User settings'),
727
      'description' => t('Configure default behavior of users, including registration requirements, e-mails, and user pictures.'),
728
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_admin_settings'));
729

Steven Wittens's avatar
Steven Wittens committed
730
    // Admin access pages
731
    $items[] = array('path' => 'admin/user/access', 'title' => t('Access control'),
732
      'description' => t('Determine access to features by selecting permissions for roles.'),
733
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_admin_perm'), 'access' => $access_access);
734
    $items[] = array('path' => 'admin/user/roles', 'title' => t('Roles'),
735
      'description' => t('List, edit, or add user roles.'),
736
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_admin_new_role'), 'access' => $access_access,
737
      'type' => MENU_NORMAL_ITEM);
738
    $items[] = array('path' => 'admin/user/roles/edit', 'title' => t('Edit role'),
739
       'callback' => 'drupal_get_form', 'callback arguments' => array('user_admin_role'), 'access' => $access_access,
740
      'type' => MENU_CALLBACK);
741
    $items[] = array('path' => 'admin/user/rules', 'title' => t('Access rules'),
742
743
      'description' => t('List and create rules to disallow usernames, e-mail addresses, and IP addresses.'),
      'callback' => 'user_admin_access', 'access' => $access_access);
744
    $items[] = array('path' => 'admin/user/rules/list', 'title' => t('List'),
745
      'access' => $access_access, 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
746
    $items[] = array('path' => 'admin/user/rules/add', 'title' => t('Add rule'),
747
      'callback' => 'user_admin_access_add', 'access' => $access_access,
Dries's avatar
   
Dries committed
748
      'type' => MENU_LOCAL_TASK);
749
    $items[] = array('path' => 'admin/user/rules/check', 'title' => t('Check rules'),
750
      'callback' => 'user_admin_access_check', 'access' => $access_access,
Dries's avatar
   
Dries committed
751
      'type' => MENU_LOCAL_TASK);
752
    $items[] = array('path' => 'admin/user/rules/edit', 'title' => t('Edit rule'),
753
      'callback' => 'user_admin_access_edit', 'access' => $access_access,
754
      'type' => MENU_CALLBACK);
755
    $items[] = array('path' => 'admin/user/rules/delete', 'title' => t('Delete rule'),
756
757
      'callback' => 'drupal_get_form', 'callback arguments' => array('user_admin_access_delete_confirm'),
      'access' => $access_access, 'type' => MENU_CALLBACK);
758

759
    if (module_exists('search')) {
760
      $items[] = array('path' => 'admin/user/search', 'title' => t('Search users'),
761
        'description' => t('Search users by name.'),
762
        'callback' => 'user_admin', 'callback arguments' => array('search'), 'access' => $admin_access,
763
        'type' => MENU_NORMAL_ITEM);
764
    }
Dries's avatar
   
Dries committed
765

Steven Wittens's avatar
Steven Wittens committed
766
    // Your personal page
Dries's avatar
   
Dries committed
767
    if ($user->uid) {
768
      $items[] = array('path' => 'user/'. $user->uid, 'title' => t('My account'),
769
        'callback' => 'user_view', 'callback arguments' => array(arg(1)), 'access' => TRUE,
770
        'type' => MENU_DYNAMIC_ITEM);
Dries's avatar
   
Dries committed
771
    }
Dries's avatar
   
Dries committed
772

773
    $items[] = array('path' => 'logout', 'title' => t('Log out'),
774
      'access' => $user->uid,
Dries's avatar
   
Dries committed
775
776
      'callback' => 'user_logout',
      'weight' => 10);
Dries's avatar
   
Dries committed
777
778
  }
  else {
779
780
781
782
    // 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');

783
    if (arg(0) == 'user' && is_numeric(arg(1)) && arg(1) > 0) {
784
      $account = user_load(array('uid' => arg(1)));
785

786
787
788
789
      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
790
        $view_access &= $account->status || $admin_access;
791

792
        $items[] = array('path' => 'user/'. arg(1), 'title' => t('User'),
793
          'type' => MENU_CALLBACK, 'callback' => 'user_view',
Dries's avatar
Dries committed
794
          'callback arguments' => array(arg(1)), 'access' => $view_access);
795

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

799
        $items[] = array('path' => 'user/'. arg(1) .'/edit', 'title' => t('Edit'),
800
801
          'callback' => 'drupal_get_form', 'callback arguments' => array('user_edit'),
          'access' => $admin_access || $user->uid == arg(1), 'type' => MENU_LOCAL_TASK);
802
        $items[] = array('path' => 'user/'. arg(1) .'/delete', 'title' => t('Delete'),
803
804
805
806
          'callback' => 'user_edit', 'access' => $admin_access,
          'type' => MENU_CALLBACK);

        if (arg(2) == 'edit') {
807
          if (($categories = _user_categories($account)) && (count($categories) > 1)) {
808
809
810
811
812
813
814
815
            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
   
Dries committed
816
817
818
819
          }
        }
      }
    }
Dries's avatar
   
Dries committed
820
  }
Dries's avatar
   
Dries committed
821
822

  return $items;
Dries's avatar
   
Dries committed
823
824
}

Dries's avatar
Dries committed
825
826
827
828
/**
 * Accepts an user object, $account, or a DA name and returns an associative
 * array of modules and DA names. Called at external login.
 */
829
function user_get_authmaps($authname = NULL) {
Dries's avatar
   
Dries committed
830
  $result = db_query("SELECT authname, module FROM {authmap} WHERE authname = '%s'", $authname);
Dries's avatar
   
Dries committed
831
832
833
834
835
836
837
838
839
840
841
842
843
  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