user.module 86.3 KB
Newer Older
Dries's avatar
   
Dries committed
1
2
3
<?php
// $Id$

Dries's avatar
   
Dries committed
4
5
6
7
8
/**
 * @file
 * Enables the user registration and login system.
 */

9
10
11
define('USERNAME_MAX_LENGTH', 60);
define('EMAIL_MAX_LENGTH', 64);

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

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

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

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

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

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

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

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

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

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

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

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

172
    $user->roles = array();
173
174
175
176
177
178
    if ($user->uid) {
      $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
    }
    else {
      $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
    }
179
180
181
182
    $result = db_query('SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d', $user->uid);
    while ($role = db_fetch_object($result)) {
      $user->roles[$role->rid] = $role->name;
    }
183
    user_module_invoke('load', $array, $user);
184
185
  }
  else {
186
    $user = FALSE;
Dries's avatar
   
Dries committed
187
  }
Dries's avatar
   
Dries committed
188
189
190
191

  return $user;
}

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

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

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

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

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

264
      foreach (array_keys($array['roles']) as $rid) {
265
266
267
        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
          db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $account->uid, $rid);
        }
268
      }
Dries's avatar
   
Dries committed
269
270
    }

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

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

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

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

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

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

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

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

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

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

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

  return $user;
}

Dries's avatar
Dries committed
348
349
350
/**
 * Verify the syntax of the given name.
 */
Dries's avatar
   
Dries committed
351
function user_validate_name($name) {
352
  if (!strlen($name)) return t('You must enter a username.');
Dries's avatar
Dries committed
353
354
  if (substr($name, 0, 1) == ' ') return t('The username cannot begin with a space.');
  if (substr($name, -1) == ' ') return t('The username cannot end with a space.');
355
  if (strpos($name, '  ') !== FALSE) return t('The username cannot contain multiple spaces in a row.');
356
  if (ereg("[^\x80-\xF7 [:alnum:]@_.-]", $name)) return t('The username contains an illegal character.');
357
358
359
360
361
362
363
364
  if (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
                   '\x{AD}' .                // Soft-hyphen
                   '\x{2000}-\x{200F}' .     // Various space characters
                   '\x{2028}-\x{202F}' .     // Bidirectional text overrides
                   '\x{205F}-\x{206F}' .     // Various text hinting characters
                   '\x{FEFF}' .              // Byte order mark
                   '\x{FF01}-\x{FF60}' .     // Full-width latin
                   '\x{FFF9}-\x{FFFD}' .     // Replacement characters
365
                   '\x{0}]/u',               // NULL byte
366
367
368
                   $name)) {
    return t('The username contains an illegal character.');
  }
369
  if (strpos($name, '@') !== FALSE && !eregi('@([0-9a-z](-?[0-9a-z])*.)+[a-z]{2}([zmuvtg]|fo|me)?$', $name)) return t('The username is not a valid authentication ID.');
370
  if (strlen($name) > USERNAME_MAX_LENGTH) return t('The username %name is too long: it must be %max characters or less.', array('%name' => $name, '%max' => USERNAME_MAX_LENGTH));
Dries's avatar
   
Dries committed
371
372
373
}

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

382
function user_validate_picture(&$form, &$form_state) {
383
  // If required, validate the uploaded picture.
384
385
386
387
388
389
  $validators = array(
    'file_validate_is_image' => array(),
    'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')),
    'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),
  );
  if ($file = file_save_upload('picture_upload', $validators)) {
390
391
392
393
394
    // Remove the old picture.
    if (isset($form_state['values']['_account']->picture) && file_exists($form_state['values']['_account']->picture)) {
      file_delete($form_state['values']['_account']->picture);
    }

395
    // The image was saved using file_save_upload() and was added to the
Dries's avatar
Dries committed
396
    // files table as a temporary file. We'll make a copy and let the garbage
397
    // collector delete the original upload.
398
    $info = image_get_info($file->filepath);
399
    $destination = variable_get('user_picture_path', 'pictures') . '/picture-' . $form['#uid'] . '.' . $info['extension'];
400
    if (file_copy($file, $destination, FILE_EXISTS_REPLACE)) {
401
      $form_state['values']['picture'] = $file->filepath;
402
    }
403
404
    else {
      form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures'))));
405
    }
Dries's avatar
   
Dries committed
406
407
408
  }
}

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

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

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

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

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

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

436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
/**
 * Determine the permissions for one or more roles.
 *
 * @param $roles
 *   An array whose keys are the role IDs of interest, such as $user->roles.
 * @param $reset
 *   Optional parameter - if TRUE data in the static variable is rebuilt.
 *
 * @return
 *   An array indexed by role ID. Each value is an array whose keys are the
 *   permission strings for the given role ID.
 */
function user_role_permissions($roles = array(), $reset = FALSE) {
  static $stored_permissions = array();

  if ($reset) {
    // Clear the data cached in the static variable.
    $stored_permissions = array();
  }

  $role_permissions = $fetch = array();

  if ($roles) {
    foreach ($roles as $rid => $name) {
      if (isset($stored_permissions[$rid])) {
        $role_permissions[$rid] = $stored_permissions[$rid];
      }
      else {
        // Add this rid to the list of those needing to be fetched.
        $fetch[] = $rid;
        // Prepare in case no permissions are returned.
        $stored_permissions[$rid] = array();
      }
    }

    if ($fetch) {
      // Get from the database permissions that were not in the static variable.
      // Only role IDs with at least one permission assigned will return rows.
      $result = db_query("SELECT r.rid, p.permission FROM {role} r INNER JOIN {role_permission} p ON p.rid = r.rid WHERE r.rid IN (" . db_placeholders($fetch) . ")", $fetch);

      while ($row = db_fetch_array($result)) {
        $stored_permissions[$row['rid']][$row['permission']] = TRUE;
      }
      foreach ($fetch as $rid) {
        // For every rid, we know we at least assigned an empty array.
        $role_permissions[$rid] = $stored_permissions[$rid];
      }
    }
  }

  return $role_permissions;
}

Dries's avatar
Dries committed
489
490
491
492
493
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
Dries's avatar
   
Dries committed
494
495
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
496
497
498
499
 * @param $reset
 *   (optional) Resets the user's permissions cache, which will result in a
 *   recalculation of the user's permissions. This is necessary to support
 *   dynamically added user roles.
Dries's avatar
Dries committed
500
501
 *
 * @return
502
 *   Boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
503
504
505
506
507
 *
 * All permission checks in Drupal should go through this function. This
 * way, we guarantee consistent behavior, and ensure that the superuser
 * can perform all actions.
 */
508
function user_access($string, $account = NULL, $reset = FALSE) {
Dries's avatar
   
Dries committed
509
  global $user;
Dries's avatar
   
Dries committed
510
  static $perm = array();
Dries's avatar
   
Dries committed
511

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

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

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

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

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

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

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

548
  return $deny;
549
550
}

Dries's avatar
Dries committed
551
552
553
/**
 * Implementation of hook_perm().
 */
Dries's avatar
   
Dries committed
554
function user_perm() {
555
556
557
558
559
560
   return array(
     'administer permissions' => t('Manage the permissions assigned to user roles. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
     'administer users' => t('Manage or block users, and manage their role assignments.'),
     'access user profiles' => t('View profiles of users on the site, which may contain personal information.'),
     'change own username' => t('Select a different username.'),
   );
Dries's avatar
   
Dries committed
561
562
}

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

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

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

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

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

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

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

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

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

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

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

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

708
    // Too dynamic to cache.
709
710
    $blocks['online']['info'] = t('Who\'s online');
    $blocks['online']['cache'] = BLOCK_NO_CACHE;
711
    return $blocks;
712
  }
713
  else if ($op == 'configure' && $delta == 'new') {
714
715
716
717
718
719
720
721
    $form['user_block_whois_new_count'] = array(
      '#type' => 'select',
      '#title' => t('Number of users to display'),
      '#default_value' => variable_get('user_block_whois_new_count', 5),
      '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
    );
    return $form;
  }
722
  else if ($op == 'configure' && $delta == 'online') {
723
    $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
724
725
    $form['user_block_seconds_online'] = array('#type' => 'select', '#title' => t('User activity'), '#default_value' => variable_get('user_block_seconds_online', 900), '#options' => $period, '#description' => t('A user is considered online for this long after they have last viewed a page.'));
    $form['user_block_max_list_count'] = array('#type' => 'select', '#title' => t('User list length'), '#default_value' => variable_get('user_block_max_list_count', 10), '#options' => drupal_map_assoc(array(0, 5, 10, 15, 20, 25, 30, 40, 50, 75, 100)), '#description' => t('Maximum number of currently online users to display.'));
726

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

Dries's avatar
   
Dries committed
739
    switch ($delta) {
740
      case 'login':
Dries's avatar
Dries committed
741
742
        // For usability's sake, avoid showing two login forms on one page.
        if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
Dries's avatar
   
Dries committed
743

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

886
887
888
/**
 * Access callback for user account editing.
 */
889
function user_edit_access($account) {
890
  return (($GLOBALS['user']->uid == $account->uid) || user_access('administer users')) && $account->uid > 0;
891
892
893
894
895
896
897
}

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

Dries's avatar
   
Dries committed
898
/**
Dries's avatar
   
Dries committed
899
 * Implementation of hook_menu().
Dries's avatar
   
Dries committed
900
 */
901
902
function user_menu() {
  $items['user/autocomplete'] = array(
903
    'title' => 'User autocomplete',
904
    'page callback' => 'user_autocomplete',
905
    'access callback' => 'user_access',
906
907
908
    'access arguments' => array('access user profiles'),
    'type' => MENU_CALLBACK,
  );
Dries's avatar
   
Dries committed
909

910
  // Registration and login pages.
911
  $items['user'] = array(
912
    'title' => 'User account',
913
914
    'page callback' => 'user_page',
    'access callback' => TRUE,
915
    'type' => MENU_CALLBACK,
916
917
918
  );

  $items['user/login'] = array(
919
    'title' => 'Log in',
920
    'access callback' => 'user_is_anonymous',
921
922
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
Dries's avatar
   
Dries committed
923

924
  $items['user/register'] = array(
925
    'title' => 'Create new account',
926
927
928
929
930
931
932
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_register'),
    'access callback' => 'user_register_access',
    'type' => MENU_LOCAL_TASK,
  );

  $items['user/password'] = array(
933
    'title' => 'Request new password',
934
935
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_pass'),
936
    'access callback' => 'user_is_anonymous',
937
938
939
    'type' => MENU_LOCAL_TASK,
  );
  $items['user/reset/%/%/%'] = array(
940
    'title' => 'Reset password',
941
942
943
944
945
946
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_pass_reset', 2, 3, 4),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );

947
  // User administration pages.
948
  $items['admin/user'] = array(
949
950
    'title' => 'User management',
    'description' => "Manage your site's users, groups and access to site features.",
951
952
    'position' => 'left',
    'page callback' => 'system_admin_menu_block_page',
953
    'access arguments' => array('access administration pages'),
Dries's avatar