user.module 104 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
/**
 * Maximum length of username text field.
 */
12
define('USERNAME_MAX_LENGTH', 60);
13
14
15
16

/**
 * Maximum length of user e-mail text field.
 */
17
18
define('EMAIL_MAX_LENGTH', 64);

19

Dries's avatar
Dries committed
20
21
22
/**
 * Invokes hook_user() in every module.
 *
23
 * We cannot use module_invoke() for this, because the arguments need to
Dries's avatar
Dries committed
24
25
 * be passed by reference.
 */
26
function user_module_invoke($type, &$array, &$user, $category = NULL) {
27
  foreach (module_implements('user_' . $type) as $module) {
28
    $function = $module . '_user_' . $type;
29
    $function($array, $user, $category);
Dries's avatar
   
Dries committed
30
31
32
  }
}

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

Dries's avatar
   
Dries committed
86
/**
87
 * Implement hook_fieldable_info().
Dries's avatar
   
Dries committed
88
89
90
91
 */
function user_fieldable_info() {
  $return = array(
    'user' => array(
92
93
94
95
96
97
98
99
100
101
102
103
104
      'label' => t('User'),
      'object keys' => array(
        'id' => 'uid',
      ),
      'bundles' => array(
        'user' => array(
          'label' => t('User'),
          'admin' => array(
            'path' => 'admin/settings/user',
            'access arguments' => array('administer users'),
          ),
        ),
      ),
Dries's avatar
   
Dries committed
105
106
107
108
109
110
    ),
  );
  return $return;
}

/**
111
 * Implement hook_field_build_modes().
Dries's avatar
   
Dries committed
112
113
114
115
116
117
118
119
120
121
122
 */
function user_field_build_modes($obj_type) {
  $modes = array();
  if ($obj_type == 'user') {
    $modes = array(
      'full' => t('User account'),
    );
  }
  return $modes;
}

Dries's avatar
   
Dries committed
123
function user_external_load($authname) {
124
  $uid = db_query("SELECT uid FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchField();
Dries's avatar
   
Dries committed
125

126
127
  if ($uid) {
    return user_load($uid);
Dries's avatar
   
Dries committed
128
129
  }
  else {
130
    return FALSE;
Dries's avatar
   
Dries committed
131
132
133
  }
}

Dries's avatar
Dries committed
134
/**
135
 * Load multiple users based on certain conditions.
Dries's avatar
Dries committed
136
 *
137
138
139
 * This function should be used whenever you need to load more than one user
 * from the database. Users are loaded into memory and will not require
 * database access if loaded again during the same page request.
Dries's avatar
Dries committed
140
 *
141
142
143
144
145
146
147
148
 * @param $uids
 *   An array of user IDs.
 * @param $conditions
 *   An array of conditions to match against the {users} table. These
 *   should be supplied in the form array('field_name' => 'field_value').
 * @param $reset
 *   A boolean indicating that the internal cache should be reset. Use this if
 *   loading a user object which has been altered during the page request.
149
 * @return
150
151
152
153
154
 *   An array of user objects, indexed by uid.
 *
 * @see user_load()
 * @see user_load_by_mail()
 * @see user_load_by_name()
Dries's avatar
Dries committed
155
 */
156
157
158
159
function user_load_multiple($uids = array(), $conditions = array(), $reset = FALSE) {
  static $user_cache = array();
  if ($reset) {
    $user_cache = array();
160
  }
161

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
  $users = array();

  // Create a new variable which is either a prepared version of the $uids
  // array for later comparison with the user cache, or FALSE if no $uids were
  // passed. The $uids array is reduced as items are loaded from cache, and we
  // need to know if it's empty for this reason to avoid querying the database
  // when all requested users are loaded from cache.
  $passed_uids = !empty($uids) ? array_flip($uids) : FALSE;

  // Load any available users from the internal cache.
  if ($user_cache) {
    if ($uids && !$conditions) {
      $users += array_intersect_key($user_cache, $passed_uids);
      // If any users were loaded, remove them from the $uids still to load.
      $uids = array_keys(array_diff_key($passed_uids, $users));
Dries's avatar
   
Dries committed
177
178
179
    }
  }

180
181
182
183
  // Load any remaining users from the database, this is necessary if we have
  // $uids still to load, or if $conditions was passed without $uids.
  if ($uids || ($conditions && !$passed_uids)) {
    $query = db_select('users', 'u')->fields('u');
Dries's avatar
   
Dries committed
184

185
186
187
    // If the $uids array is populated, add those to the query.
    if ($uids) {
      $query->condition('u.uid', $uids, 'IN');
188
    }
189
190
    // If the conditions array is populated, add those to the query.
    if ($conditions) {
191
192
      // TODO D7: Using LIKE() to get a case insensitive comparison because Crell
      // and chx promise that dbtng will map it to ILIKE in postgres.
193
      if (isset($conditions['name'])) {
194
        $query->condition('u.name', $conditions['name'], 'LIKE');
195
196
197
        unset($conditions['name']);
      }
      if (isset($conditions['mail'])) {
198
        $query->condition('u.mail', $conditions['mail'], 'LIKE');
199
200
201
202
203
        unset($conditions['mail']);
      }
      foreach ($conditions as $field => $value) {
        $query->condition('u.' . $field, $value);
      }
204
    }
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
    $result = $query->execute();

    $queried_users = array();
    // Build an array of user picture IDs so that these can be fetched later.
    $picture_fids = array();
    foreach ($result as $record) {
      $picture_fids[] = $record->picture;
      $queried_users[$record->uid] = drupal_unpack($record);
      $queried_users[$record->uid]->roles = array();
      if ($record->uid) {
        $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
      }
      else {
        $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
      }
220
    }
221

222
223
224
225
226
227
    if (!empty($queried_users)) {
      // Add any additional roles from the database.
      $result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users)));
      foreach ($result as $record) {
        $queried_users[$record->uid]->roles[$record->rid] = $record->name;
      }
Dries's avatar
   
Dries committed
228

229
230
231
232
233
234
235
236
237
238
239
240
241
      // Add the full file objects for user pictures if enabled.
      if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) {
        $pictures = file_load_multiple($picture_fids);
        foreach ($queried_users as $account) {
          if (!empty($account->picture) && isset($pictures[$account->picture])) {
            $account->picture = $pictures[$account->picture];
          }
          else {
            $account->picture = NULL;
          }
        }
      }

242
243
      field_attach_load('user', $queried_users);

244
245
246
247
248
249
      // Invoke hook_user_load() on the users loaded from the database
      // and add them to the static cache.
      foreach (module_implements('user_load') as $module) {
        $function = $module . '_user_load';
        $function($queried_users);
      }
250

251

252
253
254
255

      $users = $users + $queried_users;
      $user_cache = $user_cache + $queried_users;
    }
256
  }
257
258
259
260
261
262
263
264
265
266

  // Ensure that the returned array is ordered the same as the original $uids
  // array if this was passed in and remove any invalid uids.
  if ($passed_uids) {
    // Remove any invalid uids from the array.
    $passed_uids = array_intersect_key($passed_uids, $users);
    foreach ($users as $user) {
      $passed_uids[$user->uid] = $user;
    }
    $users = $passed_uids;
Dries's avatar
   
Dries committed
267
  }
Dries's avatar
   
Dries committed
268

269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
  return $users;
}


/**
 * Fetch a user object.
 *
 * @param $uid
 *   Integer specifying the user id.
 * @param $reset
 *   A boolean indicating that the internal cache should be reset.
 * @return
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load($uid, $reset = FALSE) {
  $users = user_load_multiple(array($uid), array(), $reset);
  return reset($users);
}

/**
 * Fetch a user object by email address.
 *
 * @param $mail
 *   String with the account's e-mail address.
 * @return
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load_by_mail($mail) {
  $users = user_load_multiple(array(), array('mail' => $mail));
  return reset($users);
}

/**
 * Fetch a user object by account name.
 *
 * @param $name
 *   String with the account's user name.
 * @return
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load_by_name($name) {
  $users = user_load_multiple(array(), array('name' => $name));
  return reset($users);
Dries's avatar
   
Dries committed
321
322
}

323
/**
324
 * Save changes to a user account or add a new user.
325
326
 *
 * @param $account
327
328
329
330
331
332
 *   (optional) The user object to modify or add. If you want to modify
 *   an existing user account, you will need to ensure that (a) $account 
 *   is an object, and (b) you have set $account->uid to the numeric 
 *   user ID of the user account you wish to modify. If you
 *   want to create a new user account, you can set $account->is_new to
 *   TRUE or omit the $account->uid field. 
333
 * @param $edit
334
 *   An array of fields and values to save. For example array('name'
335
 *   => 'My name'). Keys that do not belong to columns in the user-related
336
337
 *   tables are added to the a serialized array in the 'data' column
 *   and will be loaded in the $user->data array by user_load().
338
339
 *   Setting a field to NULL deletes it from the data column, if you are
 *   modifying an existing user account.
340
341
 * @param $category
 *   (optional) The category for storing profile information in.
342
343
 *
 * @return
344
 *   A fully-loaded $user object upon successful save or FALSE if the save failed.
345
 */
346
function user_save($account, $edit = array(), $category = 'account') {
347
  $table = drupal_get_schema('users');
348
349
  $user_fields = $table['fields'];

350
  if (!empty($edit['pass'])) {
351
    // Allow alternate password hashing schemes.
352
    require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
353
    $edit['pass'] = user_hash_password(trim($edit['pass']));
354
    // Abort if the hashing failed and returned FALSE.
355
    if (!$edit['pass']) {
356
357
      return FALSE;
    }
358
359
360
  }
  else {
    // Avoid overwriting an existing password with a blank password.
361
    unset($edit['pass']);
362
363
  }

Dries's avatar
   
Dries committed
364
365
366
367
368
369
370
371
372
373
374
375
  // Get the fields form so we can recognize the fields in the $edit
  // form that should not go into the serialized data array.
  $field_form = array();
  $field_form_state = array();
  $edit = (object) $edit;
  field_attach_form('user', $edit, $field_form, $field_form_state);

  // Presave fields.
  field_attach_presave('user', $edit);

  $edit = (array) $edit;

376
  if (!isset($account->is_new)) {
377
    $account->is_new = empty($account->uid);
378
379
  }
  if (is_object($account) && !$account->is_new) {
380
    user_module_invoke('update', $edit, $account, $category);
381
    $data = unserialize(db_query('SELECT data FROM {users} WHERE uid = :uid', array(':uid' => $account->uid))->fetchField());
382
383
    // Consider users edited by an administrator as logged in, if they haven't
    // already, so anonymous users can view the profile (if allowed).
384
    if (empty($edit['access']) && empty($account->access) && user_access('administer users')) {
385
      $edit['access'] = REQUEST_TIME;
386
    }
387
    foreach ($edit as $key => $value) {
Dries's avatar
   
Dries committed
388
      // Form fields that don't pertain to the users, user_roles, or
389
      // Field API are automatically serialized into the users.data
Dries's avatar
   
Dries committed
390
      // column.
391
      if (!in_array($key, array('roles', 'is_new')) && empty($user_fields[$key]) && empty($field_form[$key])) {
392
393
        if ($value === NULL) {
          unset($data[$key]);
Dries's avatar
   
Dries committed
394
        }
395
396
        else {
          $data[$key] = $value;
Dries's avatar
   
Dries committed
397
        }
Dries's avatar
   
Dries committed
398
399
400
      }
    }

401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417

    // Process picture uploads.
    if (!empty($edit['picture']->fid)) {
      $picture = $edit['picture'];
      // If the picture is a temporary file move it to its final location and
      // make it permanent.
      if (($picture->status & FILE_STATUS_PERMANENT) == 0) {
        $info = image_get_info($picture->filepath);
        $destination = file_create_path(variable_get('user_picture_path', 'pictures') . '/picture-' . $account->uid . '.' . $info['extension']);
        if ($picture = file_move($picture, $destination, FILE_EXISTS_REPLACE)) {
          $picture->status |= FILE_STATUS_PERMANENT;
          $edit['picture'] = file_save($picture);
        }
      }
    }
    $edit['picture'] = empty($edit['picture']->fid) ? 0 : $edit['picture']->fid;

418
419
    $edit['data'] = $data;
    $edit['uid'] = $account->uid;
420
    // Save changes to the user table.
421
    $success = drupal_write_record('users', $edit, 'uid');
422
    if (!$success) {
Dries's avatar
   
Dries committed
423
424
425
426
      // The query failed - better to abort the save than risk further
      // data loss.

      // TODO: Fields change: I think this is a bug.  If no columns in
427
      // the user table are changed, drupal_write_record returns
Dries's avatar
   
Dries committed
428
      // FALSE because rowCount() (rows changed) is 0.  However,
429
      // non-users data may have been changed, e.g. fields.
Dries's avatar
   
Dries committed
430
      // return FALSE;
431
    }
Dries's avatar
   
Dries committed
432

433
    // If the picture changed or was unset, remove the old one. This step needs
434
    // to occur after updating the {users} record so that user_file_references()
435
436
437
438
439
    // doesn't report it in use and block the deletion.
    if (!empty($account->picture->fid) && ($edit['picture'] != $account->picture->fid)) {
      file_delete($account->picture);
    }

440
    // Reload user roles if provided.
441
    if (isset($edit['roles']) && is_array($edit['roles'])) {
442
443
444
      db_delete('users_roles')
        ->condition('uid', $account->uid)
        ->execute();
Dries's avatar
   
Dries committed
445

446
      $query = db_insert('users_roles')->fields(array('uid', 'rid'));
447
      foreach (array_keys($edit['roles']) as $rid) {
448
        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
449
450
451
452
          $query->values(array(
            'uid' => $account->uid,
            'rid' => $rid,
          ));
453
        }
454
      }
455
      $query->execute();
Dries's avatar
   
Dries committed
456
457
    }

458
    // Delete a blocked user's sessions to kick them if they are online.
459
    if (isset($edit['status']) && $edit['status'] == 0) {
460
      drupal_session_destroy_uid($account->uid);
461
462
    }

463
464
    // If the password changed, delete all open sessions and recreate
    // the current one.
465
    if (!empty($edit['pass'])) {
466
      drupal_session_destroy_uid($account->uid);
467
468
469
      if ($account->uid == $GLOBALS['user']->uid) {
        drupal_session_regenerate();
      }
470
471
    }

Dries's avatar
   
Dries committed
472
    // Save Field data.
473
474
    $object = (object) $edit;
    field_attach_update('user', $object);
Dries's avatar
   
Dries committed
475

476
    // Refresh user object.
477
    $user = user_load($account->uid, TRUE);
478
479

    // Send emails after we have the new user object.
480
    if (isset($edit['status']) && $edit['status'] != $account->status) {
481
      // The user's status is changing; conditionally send notification email.
482
      $op = $edit['status'] == 1 ? 'status_activated' : 'status_blocked';
483
484
485
      _user_mail_notify($op, $user);
    }

486
    user_module_invoke('after_update', $edit, $user, $category);
Dries's avatar
   
Dries committed
487
488
  }
  else {
489
    // Allow 'created' to be set by the caller.
490
    if (!isset($edit['created'])) {
491
      $edit['created'] = REQUEST_TIME;
492
    }
493
494
    // Consider users created by an administrator as already logged in, so
    // anonymous users can view the profile (if allowed).
495
    if (empty($edit['access']) && user_access('administer users')) {
496
      $edit['access'] = REQUEST_TIME;
497
    }
498

499
    $edit['mail'] = trim($edit['mail']);
500
    $success = drupal_write_record('users', $edit);
501
502
503
504
505
    if (!$success) {
      // On a failed INSERT some other existing user's uid may be returned.
      // We must abort to avoid overwriting their account.
      return FALSE;
    }
506

507
    // Build the initial user object.
508
    $user = user_load($edit['uid'], TRUE);
Dries's avatar
   
Dries committed
509

510
511
    $object = (object) $edit;
    field_attach_insert('user', $object);
Dries's avatar
   
Dries committed
512

513
    user_module_invoke('insert', $edit, $user, $category);
514

515
516
    // Note, we wait with saving the data column to prevent module-handled
    // fields from being saved there.
517
    $data = array();
518
    foreach ($edit as $key => $value) {
519
      // Form fields that don't pertain to the users, user_roles, or
520
      // Field API are automatically serialized into the user.data
Dries's avatar
   
Dries committed
521
      // column.
522
      if ((!in_array($key, array('roles', 'is_new'))) && (empty($user_fields[$key]) && empty($field_form[$key])) && ($value !== NULL)) {
523
524
525
        $data[$key] = $value;
      }
    }
526
527
    if (!empty($data)) {
      $data_array = array('uid' => $user->uid, 'data' => $data);
528
      drupal_write_record('users', $data_array, 'uid');
529
    }
530

531
    // Save user roles (delete just to be safe).
532
    if (isset($edit['roles']) && is_array($edit['roles'])) {
533
534
535
536
      db_delete('users_roles')
        ->condition('uid', $edit['uid'])
        ->execute();
      $query = db_insert('users_roles')->fields(array('uid', 'rid'));
537
      foreach (array_keys($edit['roles']) as $rid) {
538
        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
539
540
541
542
          $query->values(array(
            'uid' => $edit['uid'],
            'rid' => $rid,
          ));
543
        }
544
      }
545
      $query->execute();
546
547
    }

548
    // Build the finished user object.
549
    $user = user_load($edit['uid'], TRUE);
Dries's avatar
   
Dries committed
550
551
552
553
554
  }

  return $user;
}

Dries's avatar
Dries committed
555
556
557
/**
 * Verify the syntax of the given name.
 */
Dries's avatar
   
Dries committed
558
function user_validate_name($name) {
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
  if (!$name) {
    return t('You must enter a username.');
  }
  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 (strpos($name, '  ') !== FALSE) {
    return t('The username cannot contain multiple spaces in a row.');
  }
  if (preg_match('/[^\x{80}-\x{F7} a-z0-9@_.\'-]/i', $name)) {
    return t('The username contains an illegal character.');
  }
574
  if (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
575
576
577
578
579
580
581
582
583
                  '\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
                  '\x{0}-\x{1F}]/u',        // NULL byte and control characters
                  $name)) {
584
585
    return t('The username contains an illegal character.');
  }
586
587
588
  if (drupal_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
589
590
591
}

function user_validate_mail($mail) {
592
  $mail = trim($mail);
593
594
595
  if (!$mail) {
    return t('You must enter an e-mail address.');
  }
596
  if (!valid_email_address($mail)) {
597
    return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
Dries's avatar
   
Dries committed
598
599
600
  }
}

601
function user_validate_picture(&$form, &$form_state) {
602
  // If required, validate the uploaded picture.
603
604
605
606
607
  $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),
  );
608

609
610
611
612
613
614
615
  // Save the file as a temporary file.
  $file = file_save_upload('picture_upload', $validators);
  if ($file === FALSE) {
    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'))));
  }
  elseif ($file !== NULL) {
    $form_state['values']['picture_upload'] = $file;
Dries's avatar
   
Dries committed
616
617
618
  }
}

Dries's avatar
Dries committed
619
620
621
/**
 * Generate a random alphanumeric password.
 */
Dries's avatar
   
Dries committed
622
623
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
624
625
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
626
  // of 'I', 1, and 'l'.
627
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
Dries's avatar
Dries committed
628

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

Dries's avatar
Dries committed
632
633
  // Declare the password as a blank string.
  $pass = '';
Dries's avatar
   
Dries committed
634

Dries's avatar
Dries committed
635
  // Loop the number of times specified by $length.
Dries's avatar
   
Dries committed
636
637
638
639
  for ($i = 0; $i < $length; $i++) {

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

  return $pass;
Dries's avatar
   
Dries committed
644
645
}

646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
/**
 * 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.
684
      $result = db_query("SELECT rid, permission FROM {role_permission} WHERE rid IN (:fetch)", array(':fetch' => $fetch));
685

686
687
      foreach ($result as $row) {
        $stored_permissions[$row->rid][$row->permission] = TRUE;
688
689
690
691
692
693
694
695
696
697
698
      }
      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
699
700
701
702
703
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
Dries's avatar
   
Dries committed
704
705
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
706
707
708
709
 * @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
710
711
 *
 * @return
712
 *   Boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
713
714
715
716
717
 *
 * 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.
 */
718
function user_access($string, $account = NULL, $reset = FALSE) {
Dries's avatar
   
Dries committed
719
  global $user;
Dries's avatar
   
Dries committed
720
  static $perm = array();
Dries's avatar
   
Dries committed
721

722
  if ($reset) {
723
    $perm = array();
724
725
  }

726
727
728
729
  if (is_null($account)) {
    $account = $user;
  }

730
  // User #1 has all privileges:
731
  if ($account->uid == 1) {
732
    return TRUE;
Dries's avatar
   
Dries committed
733
734
  }

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

740
    $perms = array();
741
742
    foreach ($role_permissions as $one_role) {
      $perms += $one_role;
Dries's avatar
   
Dries committed
743
    }
744
    $perm[$account->uid] = $perms;
Dries's avatar
   
Dries committed
745
  }
746

747
  return isset($perm[$account->uid][$string]);
Dries's avatar
   
Dries committed
748
749
}

750
/**
751
 * Checks for usernames blocked by user administration.
752
 *
753
 * @return boolean TRUE for blocked users, FALSE for active.
754
755
 */
function user_is_blocked($name) {
756
  $deny = db_query("SELECT name FROM {users} WHERE status = 0 AND name = LOWER(:name)", array(':name' => $name))->fetchObject();
757

758
  return $deny;
759
760
}

Dries's avatar
Dries committed
761
/**
762
 * Implement hook_permission().
Dries's avatar
Dries committed
763
 */
764
function user_permission() {
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
  return array(
    'administer permissions' =>  array(
      'title' => t('Administer permissions'),
      'description' => 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' => array(
      'title' => t('Administer users'),
      'description' => t('Manage or block users, and manage their role assignments.'),
    ),
    'access user profiles' => array(
      'title' => t('Access user profiles'),
      'description' => t('View profiles of users on the site, which may contain personal information.'),
    ),
    'change own username' => array(
      'title' => t('Change own username'),
      'description' => t('Select a different username.'),
    ),
    'cancel account' => array(
      'title' => t('Cancel account'),
784
      'description' => t('Remove or disable own user account and unpublish, anonymize, or remove own submissions depending on the configured <a href="@user-settings-url">user settings</a>.', array('@user-settings-url' => url('admin/settings/user'))),
785
786
787
788
789
790
    ),
    'select account cancellation method' => array(
      'title' => t('Select method for cancelling own account'),
      'description' => t('Select the method for cancelling own user account. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
    ),
  );
Dries's avatar
   
Dries committed
791
792
}

Dries's avatar
Dries committed
793
/**
794
 * Implement hook_file_download().
Dries's avatar
Dries committed
795
796
797
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
798
799
800
function user_file_download($filepath) {
  if (strpos($filepath, variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
    $info = image_get_info(file_create_path($filepath));
801
    return array('Content-Type' => $info['mime_type']);
Dries's avatar
   
Dries committed
802
803
804
  }
}

805
/**
806
 * Implement hook_file_references().
807
808
809
 */
function user_file_references($file) {
  // Determine if the file is used by this module.
810
811
  $file_used = (bool) db_query_range('SELECT 1 FROM {users} WHERE picture = :fid', array(':fid' => $file->fid), 0, 1)->fetchField();
  if ($file_used) {
812
813
814
815
816
817
    // Return the name of the module and how many references it has to the file.
    return array('user' => $count);
  }
}

/**
818
 * Implement hook_file_delete().
819
820
821
 */
function user_file_delete($file) {
  // Remove any references to the file.
822
  db_update('users')
823
824
825
826
827
    ->fields(array('picture' => 0))
    ->condition('picture', $file->fid)
    ->execute();
}

Dries's avatar
Dries committed
828
/**
829
 * Implement hook_search().
Dries's avatar
Dries committed
830
 */
831
function user_search($op = 'search', $keys = NULL, $skip_access_check = FALSE) {
832
833
  switch ($op) {
    case 'name':
834
      if ($skip_access_check || user_access('access user profiles')) {
835
        return t('Users');
836
      }
837
    case 'search':
838
839
840
841
      if (user_access('access user profiles')) {
        $find = array();
        // Replace wildcards with MySQL/PostgreSQL wildcards.
        $keys = preg_replace('!\*+!', '%', $keys);
842
        $query = db_select('users')->extend('PagerDefault');
843
        $query->fields('users', array('name', 'uid', 'mail'));
844
845
        if (user_access('administer users')) {
          // Administrators can also search in the otherwise private email field.
846
          $query->condition(db_or()->
847
848
            where('LOWER(name) LIKE LOWER(:name)', array(':name' => "%$keys%"))->
            where('LOWER(mail) LIKE LOWER(:mail)', array(':mail' => "%$keys%")));
849
850
        }
        else {
851
852
          $query->where('LOWER(name) LIKE LOWER(:name)', array(':name' => "%$keys%"));
        }
853
854
855
        $result = $query
          ->limit(15)
          ->execute();
856
857
        foreach ($result as $account) {
          $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
858
859
        }
        return $find;
860
      }
Dries's avatar
   
Dries committed
861
862
863
  }
}

Dries's avatar
Dries committed
864
/**
865
 * Implement hook_elements().
Dries's avatar
Dries committed
866
867
868
 */
function user_elements() {
  return array(
869
870
871
872
873
874
    'user_profile_category' => array(
      '#theme_wrapper' => 'user_profile_category'
    ),
    'user_profile_item' => array(
      '#theme' => 'user_profile_item'
    ),
Dries's avatar
Dries committed
875
876
877
  );
}

Dries's avatar
Dries committed
878
/**
879
 * Implement hook_user_view().
Dries's avatar
Dries committed
880
 */
881
882
function user_user_view(&$edit, &$account, $category = NULL) {
  $account->content['user_picture'] = array(
883
    '#markup' => theme('user_picture', $account),
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
    '#weight' => -10,
  );
  if (!isset($account->content['summary'])) {
    $account->content['summary'] = array();
  }
  $account->content['summary'] += array(
    '#type' => 'user_profile_category',
    '#attributes' => array('class' => 'user-member'),
    '#weight' => 5,
    '#title' => t('History'),
  );
  $account->content['summary']['member_for'] =  array(
    '#type' => 'user_profile_item',
    '#title' => t('Member for'),
    '#markup' => format_interval(REQUEST_TIME - $account->created),
  );
}

/**
903
 * Implement hook_user_form.
904
905
906
 */
function user_user_form(&$edit, &$account, $category = NULL) {
  if ($category == 'account') {
907
    $form_state = array();
908
    return user_edit_form($form_state, (isset($account->uid) ? $account->uid : FALSE), $edit);
909
  }
910
}
911

912
/**
913
 * Implement hook_user_validate().
914
915
916
 */
function user_user_validate(&$edit, &$account, $category = NULL) {
  if ($category == 'account') {
917
918
919
920
921
922
    $uid = isset($account->uid) ? $account->uid : FALSE;
    // Validate the username when: new user account; or user is editing own account and can change username; or an admin user.
    if (!$uid || ($GLOBALS['user']->uid == $uid && user_access('change own username')) || user_access('administer users')) {
      if ($error = user_validate_name($edit['name'])) {
        form_set_error('name', $error);
      }
923
      elseif ((bool) db_query_range("SELECT 1 FROM {users} WHERE uid <> :uid AND LOWER(name) = LOWER(:name)", array(':uid' => $uid, ':name' => $edit['name']), 0, 1)->fetchField()) {
924
925
926
927
        form_set_error('name', t('The name %name is already taken.', array('%name' => $edit['name'])));
      }
    }

928
    // Validate the e-mail address, and check if it is taken by an existing user.
929
930
931
    if ($error = user_validate_mail($edit['mail'])) {
      form_set_error('mail', $error);
    }
932
    elseif ((bool) db_query_range("SELECT 1 FROM {users} WHERE uid <> :uid AND LOWER(mail) = LOWER(:mail)", array(':uid' => $uid, ':mail' => $edit['mail']), 0, 1)->fetchField()) {
933
934
935
936
937
938
939
      // Format error message dependent on whether the user is logged in or not.
      if ($GLOBALS['user']->uid) {
        form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $edit['mail'])));
      }
      else {
        form_set_error('mail', t('The e-mail address %email is already registered. <a href="@password">Have you forgotten your password?</a>', array('%email' => $edit['mail'], '@password' => url('user/password'))));
      }
940
    }
941
942
943
944
945
946
947
948
949

    // Make sure the signature isn't longer than the size of the database field.
    // Signatures are disabled by default, so make sure it exists first.
    if (isset($edit['signature'])) {
      $user_schema = drupal_get_schema('users');
      if (strlen($edit['signature']) > $user_schema['fields']['signature']['length']) {
        form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length'])));
      }
    }
950
  }
951
}
952

953
/**
954
 * Implement hook_user_submit().
955
956
957
 */
function user_user_submit(&$edit, &$account, $category = NULL) {
  if ($category == 'account') {
958
959
960
    if (!empty($edit['picture_upload'])) {
      $edit['picture'] = $edit['picture_upload'];
    }
961
    // Delete picture if requested, and if no replacement picture was given.
962
963
    elseif (!empty($edit['picture_delete'])) {
      $edit['picture'] = NULL;
964
    }
965
966
967
968
    // Remove these values so they don't end up serialized in the data field.
    $edit['picture_upload'] = NULL;
    $edit['picture_delete'] = NULL;

969
970
971
    if (isset($edit['roles'])) {
      $edit['roles'] = array_filter($edit['roles']);
    }
972
  }
973
}
974

975
/**
976
 * Implement hook_user_categories().
977
 */
978
979
980
981
982
983
function user_user_categories() {
  return array(array(
    'name' => 'account',
    'title' => t('Account settings'),
    'weight' => 1,
  ));
Dries's avatar
Dries committed
984
985
}

986
987
function user_login_block() {
  $form = array(
988
    '#action' => url($_GET['q'], array('query' => drupal_get_destination())),
989
    '#id' => 'user-login-form',
990
    '#validate' => user_login_default_validators(),
991
    '#submit' => array('user_login_submit'),
992
993
994
  );
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Username'),
995
    '#maxlength' => USERNAME_MAX_LENGTH,
996
997
998
999
1000
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['pass'] = array('#type' => 'password',
    '#title' => t('Password'),
1001
    '#maxlength' => 60,
1002
1003
1004
1005
1006
1007
1008
1009
    '#size' => 15,
    '#required' => TRUE,
  );
  $form['submit'] = array('#type' => 'submit',
    '#value' => t('Log in'),
  );
  $items = array();
  if (variable_get('user_register', 1)) {
1010
    $items[] = l(t('Create new account'), 'user/register', array('attributes' => array('title' => t('Create a new user account.'))));
1011
  }
1012
  $items[] = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
1013
  $form['links'] = array('#markup' => theme('item_list', $items));
1014
1015
1016
  return $form;
}

Dries's avatar
Dries committed
1017
/**
1018
 * Implement hook_block_list().
Dries's avatar
Dries committed
1019
 */
1020
function user_block_list() {
Dries's avatar
   
Dries committed
1021
1022
  global $user;

1023
1024
1025
  $blocks['login']['info'] = t('User login');
  // Not worth caching.
  $blocks['login']['cache'] = BLOCK_NO_CACHE;
1026

1027
  $blocks['new']['info'] = t('Who\'s new');
1028

1029
1030
1031
1032
1033
  // Too dynamic to cache.
  $blocks['online']['info'] = t('Who\'s online');
  $blocks['online']['cache'] = BLOCK_NO_CACHE;
  return $blocks;
}
1034

1035
/**
1036
 * Implement hook_block_configure().
1037
1038
1039
1040
 */
function user_block_configure($delta = '') {
  global $user;

1041
  switch ($delta) {
1042
1043
1044
1045
1046
1047