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

Dries Buytaert's avatar
   
Dries Buytaert 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);

12
13
14
/**
 * Invokes hook_user() in every module.
 *
15
 * We cannot use module_invoke() for this, because the arguments need to
16
17
 * be passed by reference.
 */
18
function user_module_invoke($type, &$array, &$user, $category = NULL) {
Dries Buytaert's avatar
   
Dries Buytaert committed
19
20
  foreach (module_list() as $module) {
    $function = $module .'_user';
21
22
23
    if (function_exists($function)) {
      $function($type, $array, $user, $category);
    }
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
59
function user_external_load($authname) {
Dries Buytaert's avatar
   
Dries Buytaert committed
60
  $result = db_query("SELECT uid FROM {authmap} WHERE authname = '%s'", $authname);
Dries Buytaert's avatar
   
Dries Buytaert committed
61

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

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.
76
77
 *
 * @return
78
 *   A fully-loaded $user object upon successful user load or FALSE if user cannot be loaded.
79
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
80
function user_load($array = array()) {
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 Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
98
    else {
99
      $query[]= "LOWER($key) = LOWER('%s')";
100
      $params[] = $value;
Dries Buytaert's avatar
   
Dries Buytaert committed
101
102
    }
  }
103
  $result = db_query('SELECT * FROM {users} u WHERE '. implode(' AND ', $query), $params);
Dries Buytaert's avatar
   
Dries Buytaert committed
104

105
106
107
  if (db_num_rows($result)) {
    $user = db_fetch_object($result);
    $user = drupal_unpack($user);
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
124
  }
Dries Buytaert's avatar
   
Dries Buytaert 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') {
144
  // Dynamically compose a SQL query:
145
  $user_fields = user_fields();
146
  if (is_object($account) && $account->uid) {
147
    user_module_invoke('update', $array, $account, $category);
148
    $query = '';
149
    $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
Dries Buytaert's avatar
   
Dries Buytaert committed
150
    foreach ($array as $key => $value) {
151
      if ($key == 'pass' && !empty($value)) {
Dries Buytaert's avatar
   
Dries Buytaert committed
152
153
        $query .= "$key = '%s', ";
        $v[] = md5($value);
Dries Buytaert's avatar
   
Dries Buytaert committed
154
      }
155
      else if ((substr($key, 0, 4) !== 'auth') && ($key != 'pass')) {
156
        if (in_array($key, $user_fields)) {
157
          // Save standard fields
Dries Buytaert's avatar
   
Dries Buytaert committed
158
159
          $query .= "$key = '%s', ";
          $v[] = $value;
Dries Buytaert's avatar
   
Dries Buytaert committed
160
        }
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
169
        }
Dries Buytaert's avatar
   
Dries Buytaert committed
170
171
      }
    }
172
    $query .= "data = '%s' ";
Dries Buytaert's avatar
   
Dries Buytaert committed
173
    $v[] = serialize($data);
Dries Buytaert's avatar
   
Dries Buytaert committed
174

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

177
    // Reload user roles if provided
178
    if (isset($array['roles']) && is_array($array['roles'])) {
179
      db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
194
    $user = user_load(array('uid' => $account->uid));
195
    user_module_invoke('after_update', $array, $user, $category);
Dries Buytaert's avatar
   
Dries Buytaert committed
196
197
  }
  else {
198
    $array['uid'] = db_next_id('{users}_uid');
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
207
    foreach ($array as $key => $value) {
208
      switch ($key) {
209
210
211
        case 'pass':
          $fields[] = $key;
          $values[] = md5($value);
Dries Buytaert's avatar
   
Dries Buytaert committed
212
          $s[] = "'%s'";
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 Buytaert's avatar
   
Dries Buytaert committed
228
229
      }
    }
230
    db_query('INSERT INTO {users} ('. implode(', ', $fields) .') VALUES ('. implode(', ', $s) .')', $values);
Dries Buytaert's avatar
   
Dries Buytaert committed
231

232
233
    // Build the initial user object.
    $user = user_load(array('uid' => $array['uid']));
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
258
259
  }

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

  return $user;
}

274
275
276
/**
 * Verify the syntax of the given name.
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
277
function user_validate_name($name) {
278
  if (!strlen($name)) return t('You must enter a username.');
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 Buytaert's avatar
   
Dries Buytaert committed
297
298
299
}

function user_validate_mail($mail) {
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 Buytaert's avatar
   
Dries Buytaert committed
303
304
305
  }
}

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

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);
314
  list($maxwidth, $maxheight) = explode('x', variable_get('user_picture_dimensions', '85x85'));
Dries Buytaert's avatar
   
Dries Buytaert committed
315

316
  if (!$info || !$info['extension']) {
317
    form_set_error('picture_upload', t('The uploaded file was not an image.'));
Dries Buytaert's avatar
   
Dries Buytaert committed
318
  }
319
320
  else if (image_get_toolkit()) {
    image_scale($file->filepath, $file->filepath, $maxwidth, $maxheight);
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
336
337
338
  }
}

339
340
341
/**
 * Generate a random alphanumeric password.
 */
Dries Buytaert's avatar
   
Dries Buytaert 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';
348

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

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

355
  // Loop the number of times specified by $length.
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
361
362
363
  }

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

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 Buytaert's avatar
   
Dries Buytaert committed
371
372
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
373
374
 *
 * @return
375
 *   boolean TRUE if the current user has the requested permission.
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 Buytaert's avatar
   
Dries Buytaert committed
381
function user_access($string, $account = NULL) {
Dries Buytaert's avatar
   
Dries Buytaert committed
382
  global $user;
Dries Buytaert's avatar
   
Dries Buytaert committed
383
  static $perm = array();
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
392
393
  }

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 Buytaert's avatar
   
Dries Buytaert committed
398

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

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

409
  return FALSE;
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
423
424
function user_fields() {
  static $fields;
Dries Buytaert's avatar
   
Dries Buytaert committed
425

Dries Buytaert's avatar
   
Dries Buytaert committed
426
  if (!$fields) {
427
    $result = db_query('SELECT * FROM {users} WHERE uid = 1');
428
429
430
    if (db_num_rows($result)) {
      $fields = array_keys(db_fetch_array($result));
    }
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
434
    }
Dries Buytaert's avatar
   
Dries Buytaert committed
435
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
436

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

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

447
448
449
450
451
/**
 * Implementation of hook_file_download().
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
456
457
458
  }
}

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 Buytaert's avatar
   
Dries Buytaert committed
479
480
481
  }
}

482
483
484
/**
 * Implementation of hook_user().
 */
485
function user_user($type, &$edit, &$user, $category = NULL) {
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);
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
  }
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;
}

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

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 Buytaert's avatar
   
Dries Buytaert committed
580
581
    $block = array();

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

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

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

Dries Buytaert's avatar
   
Dries Buytaert 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
          }
606
          $output = theme('user_list', $items);
Dries Buytaert's avatar
   
Dries Buytaert committed
607

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

Dries Buytaert's avatar
   
Dries Buytaert committed
613
      case 3:
614
        if (user_access('access content')) {
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 Buytaert's avatar
   
Dries Buytaert committed
623

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 Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert 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

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

Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert 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
 */
684
function theme_user_profile($account, $fields) {
685
  $output = '<div class="profile">';
Dries Buytaert's avatar
   
Dries Buytaert 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>';
699
  }
700
  $output .= '</div>';
Dries Buytaert's avatar
   
Dries Buytaert 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
  }
717
  return theme('item_list', $items, $title);
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
753
/**
Dries Buytaert's avatar
   
Dries Buytaert committed
754
 * Implementation of hook_menu().
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
949
    }
Dries Buytaert's avatar
   
Dries Buytaert committed
950
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
951
  return $items;
Dries Buytaert's avatar
   
Dries Buytaert 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;
}

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 Buytaert's avatar
   
Dries Buytaert committed
971
  $result = db_query("SELECT authname, module FROM {authmap} WHERE authname = '%s'", $authname);
Dries Buytaert's avatar