user.module 125 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
define('EMAIL_MAX_LENGTH', 254);
18

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * Only administrators can create user accounts.
 */
define('USER_REGISTER_ADMINISTRATORS_ONLY', 0);

/**
 * Visitors can create their own accounts.
 */
define('USER_REGISTER_VISITORS', 1);

/**
 * Visitors can create accounts, but they don't become active without
 * administrative approval.
 */
define('USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL', 2);

35
36
37
38
39
40
41
42
43
44
/**
 * Implement hook_help().
 */
function user_help($path, $arg) {
  global $user;

  switch ($path) {
    case 'admin/help#user':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
45
      $output .= '<p>' . t('The User module allows users to register, log in, and log out. It also allows users with proper permissions to manage user roles (used to classify users) and permissions associated with those roles. For more information, see the online handbook entry for <a href="@user">User module</a>.', array('@user' => 'http://drupal.org/handbook/modules/user')) . '</p>';
46
47
48
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Creating and managing users') . '</dt>';
49
      $output .= '<dd>' . t('The User module allows users with the appropriate <a href="@permissions">permissions</a> to create user accounts through the <a href="@people">People administration page</a>, where they can also assign users to one or more roles, and block or delete user accounts. If allowed, users without accounts (anonymous users) can create their own accounts on the <a href="@register">Create new account</a> page.', array('@permissions' => url('admin/people/permissions', array('fragment' => 'module-user')), '@people' => url('admin/people'), '@register' => url('user/register'))) . '</dd>';
50
      $output .= '<dt>' . t('User roles and permissions') . '</dt>';
51
      $output .= '<dd>' . t('<em>Roles</em> are used to group and classify users; each user can be assigned one or more roles. By default there are two roles: <em>anonymous user</em> (users that are not logged in) and <em>authenticated user</em> (users that are registered and logged in). Depending on choices you made when you installed Drupal, the installation process may have defined more roles, and you can create additional custom roles on the <a href="@roles">Roles page</a>. After creating roles, you can set permissions for each role on the <a href="@permissions_user">Permissions page</a>. Granting a permission allows users who have been assigned a particular role to perform an action on the site, such as viewing a particular type of content, editing or creating content, administering settings for a particular module, or using a particular function of the site (such as search).', array('@permissions_user' => url('admin/people/permissions'), '@roles' => url('admin/people/permissions/roles'))) . '</dd>';
52
      $output .= '<dt>' . t('Account settings') . '</dt>';
53
      $output .= '<dd>' . t('The <a href="@accounts">Account settings page</a> allows you to manage settings for the displayed name of the anonymous user role, personal contact forms, user registration, and account cancellation. On this page you can also manage settings for account personalization (including signatures and user pictures), and adapt the text for the e-mail messages that are sent automatically during the user registration process.', array('@accounts'  => url('admin/config/people/accounts'))) . '</dd>';
54
55
56
57
      $output .= '</dl>';
      return $output;
    case 'admin/people/create':
      return '<p>' . t("This web page allows administrators to register new users. Users' e-mail addresses and usernames must be unique.") . '</p>';
58
    case 'admin/people/permissions':
59
      return '<p>' . t('Permissions let you control what users can do and see on your site. You can define a specific set of permissions for each role. (See the <a href="@role">Roles</a> page to create a role). Two important roles to consider are Authenticated Users and Administrators. Any permissions granted to the Authenticated Users role will be given to any user who can log into your site. You can make any role the Administrator role for the site, meaning this will be granted all new permissions automatically. You can do this on the <a href="@settings">User Settings</a> page. You should be careful to ensure that only trusted users are given this access and level of control of your site.', array('@role' => url('admin/people/permissions/roles'), '@settings' => url('admin/config/people/accounts'))) . '</p>';
60
    case 'admin/people/permissions/roles':
61
      $output = '<p>' . t('Roles allow you to fine tune the security and administration of Drupal. A role defines a group of users that have certain privileges as defined on the <a href="@permissions">permissions page</a>. Examples of roles include: anonymous user, authenticated user, moderator, administrator and so on. In this area you will define the names and order of the roles on your site. It is recommended to order your roles from least permissive (anonymous user) to most permissive (administrator). To delete a role choose "edit role".', array('@permissions' => url('admin/people/permissions'))) . '</p>';
62
      $output .= '<p>' . t('By default, Drupal comes with two user roles:') . '</p>';
63
64
65
66
67
68
69
70
71
72
73
74
75
      $output .= '<ul>';
      $output .= '<li>' . t("Anonymous user: this role is used for users that don't have a user account or that are not authenticated.") . '</li>';
      $output .= '<li>' . t('Authenticated user: this role is automatically granted to all logged in users.') . '</li>';
      $output .= '</ul>';
      return $output;
    case 'admin/config/people/accounts/fields':
      return '<p>' . t('This form lets administrators add, edit, and arrange fields for storing user data.') . '</p>';
    case 'admin/config/people/accounts/display':
      return '<p>' . t('This form lets administrators configure how fields should be displayed when rendering a user profile page.') . '</p>';
    case 'admin/people/search':
      return '<p>' . t('Enter a simple pattern ("*" may be used as a wildcard match) to search for a username or e-mail address. For example, one may search for "br" and Drupal might return "brian", "brad", and "brenda@example.com".') . '</p>';
  }
}
76

Dries's avatar
Dries committed
77
78
79
/**
 * Invokes hook_user() in every module.
 *
80
 * We cannot use module_invoke() for this, because the arguments need to
Dries's avatar
Dries committed
81
82
 * be passed by reference.
 */
83
function user_module_invoke($type, &$edit, $account, $category = NULL) {
84
  foreach (module_implements('user_' . $type) as $module) {
85
    $function = $module . '_user_' . $type;
86
    $function($edit, $account, $category);
Dries's avatar
   
Dries committed
87
88
89
  }
}

90
/**
91
 * Implements hook_theme().
92
93
94
95
 */
function user_theme() {
  return array(
    'user_picture' => array(
96
      'variables' => array('account' => NULL),
97
      'template' => 'user-picture',
98
99
    ),
    'user_profile' => array(
100
      'render element' => 'elements',
101
      'template' => 'user-profile',
102
      'file' => 'user.pages.inc',
103
104
    ),
    'user_profile_category' => array(
105
      'render element' => 'element',
106
      'template' => 'user-profile-category',
107
      'file' => 'user.pages.inc',
108
109
    ),
    'user_profile_item' => array(
110
      'render element' => 'element',
111
      'template' => 'user-profile-item',
112
      'file' => 'user.pages.inc',
113
114
    ),
    'user_list' => array(
115
      'variables' => array('users' => NULL, 'title' => NULL),
116
    ),
117
    'user_admin_permissions' => array(
118
      'render element' => 'form',
119
      'file' => 'user.admin.inc',
120
    ),
121
    'user_admin_roles' => array(
122
      'render element' => 'form',
123
      'file' => 'user.admin.inc',
124
    ),
125
126
127
128
    'user_permission_description' => array(
      'variables' => array('permission_item' => NULL, 'hide' => NULL),
      'file' => 'user.admin.inc',
    ),
129
    'user_signature' => array(
130
      'variables' => array('signature' => NULL),
131
    ),
132
133
134
  );
}

Dries's avatar
   
Dries committed
135
/**
136
 * Implements hook_entity_info().
Dries's avatar
   
Dries committed
137
 */
138
function user_entity_info() {
Dries's avatar
   
Dries committed
139
140
  $return = array(
    'user' => array(
141
      'label' => t('User'),
142
143
      'controller class' => 'UserController',
      'base table' => 'users',
144
      'uri callback' => 'user_uri',
145
      'label callback' => 'format_username',
146
      'fieldable' => TRUE,
147
      'entity keys' => array(
148
149
150
151
152
153
        'id' => 'uid',
      ),
      'bundles' => array(
        'user' => array(
          'label' => t('User'),
          'admin' => array(
154
            'path' => 'admin/config/people/accounts',
155
156
157
158
            'access arguments' => array('administer users'),
          ),
        ),
      ),
159
160
161
      'view modes' => array(
        'full' => array(
          'label' => t('User account'),
162
          'custom settings' => FALSE,
163
164
        ),
      ),
Dries's avatar
   
Dries committed
165
166
167
168
169
    ),
  );
  return $return;
}

170
/**
171
 * Entity uri callback.
172
 */
173
174
175
176
function user_uri($user) {
  return array(
    'path' => 'user/' . $user->uid,
  );
177
178
}

179
/**
180
 * Implements hook_field_extra_fields().
181
 */
182
183
function user_field_extra_fields() {
  $return['user']['user'] = array(
184
185
186
187
188
189
190
191
192
193
194
    'form' => array(
      'account' => array(
        'label' => 'User name and password',
        'description' => t('User module account form elements'),
        'weight' => -10,
      ),
      'timezone' => array(
        'label' => 'Timezone',
        'description' => t('User module timezone form element.'),
        'weight' => 6,
      ),
195
    ),
196
197
198
199
200
201
    'display' => array(
      'summary' => array(
        'label' => 'History',
        'description' => t('User module history view element.'),
        'weight' => 5,
      ),
202
203
    ),
  );
204

205
  return $return;
206
207
}

208
209
210
211
212
213
214
215
216
/**
 * Fetches a user object based on an external authentication source.
 *
 * @param string $authname
 *   The external authentication username.
 *
 * @return
 *   A fully-loaded user object if the user is found or FALSE if not found.
 */
Dries's avatar
   
Dries committed
217
function user_external_load($authname) {
218
  $uid = db_query("SELECT uid FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchField();
Dries's avatar
   
Dries committed
219

220
221
  if ($uid) {
    return user_load($uid);
Dries's avatar
   
Dries committed
222
223
  }
  else {
224
    return FALSE;
Dries's avatar
   
Dries committed
225
226
227
  }
}

Dries's avatar
Dries committed
228
/**
229
 * Load multiple users based on certain conditions.
Dries's avatar
Dries committed
230
 *
231
232
233
 * 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
234
 *
235
236
237
238
239
240
241
242
 * @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.
243
 * @return
244
245
 *   An array of user objects, indexed by uid.
 *
246
 * @see entity_load()
247
248
249
 * @see user_load()
 * @see user_load_by_mail()
 * @see user_load_by_name()
Dries's avatar
Dries committed
250
 */
251
function user_load_multiple($uids = array(), $conditions = array(), $reset = FALSE) {
252
253
  return entity_load('user', $uids, $conditions, $reset);
}
254

255
256
257
258
259
260
261
/**
 * Controller class for users.
 *
 * This extends the DrupalDefaultEntityController class, adding required
 * special handling for user objects.
 */
class UserController extends DrupalDefaultEntityController {
262
263

  function attachLoad(&$queried_users, $revision_id = FALSE) {
264
265
    // Build an array of user picture IDs so that these can be fetched later.
    $picture_fids = array();
266
    foreach ($queried_users as $key => $record) {
267
      $picture_fids[] = $record->picture;
268
      $queried_users[$key]->data = unserialize($record->data);
269
      $queried_users[$key]->roles = array();
270
271
272
273
274
275
      if ($record->uid) {
        $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
      }
      else {
        $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
      }
276
    }
277

278
279
280
281
282
    // 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
283

284
285
286
287
288
289
290
291
292
    // 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;
293
294
295
        }
      }
    }
296
297
    // Call the default attachLoad() method. This will add fields and call
    // hook_user_load().
298
    parent::attachLoad($queried_users, $revision_id);
Dries's avatar
   
Dries committed
299
  }
300
301
302
}

/**
303
304
305
306
307
308
309
310
311
 * Loads a user object.
 *
 * Drupal has a global $user object, which represents the currently-logged-in
 * user. So to avoid confusion and to avoid clobbering the global $user object,
 * it is a good idea to assign the result of this function to a different local
 * variable, generally $account. If you actually do want to act as the user you
 * are loading, it is essential to call @code session_save_session(FALSE);
 * @endcode first. See @link http://drupal.org/node/218104 Safely impersonating
 * another user @endlink for more information.
312
313
 *
 * @param $uid
314
 *   Integer specifying the user ID to load.
315
 * @param $reset
316
317
318
 *   TRUE to reset the internal cache and load from the database; FALSE
 *   (default) to load from the internal cache, if set.
 *
319
 * @return
320
 *   A fully-loaded user object upon successful user load, or FALSE if the user
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
 *   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
360
361
}

362
/**
363
 * Save changes to a user account or add a new user.
364
365
 *
 * @param $account
366
 *   (optional) The user object to modify or add. If you want to modify
367
368
 *   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
369
370
 *   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
371
 *   TRUE or omit the $account->uid field.
372
 * @param $edit
373
 *   An array of fields and values to save. For example array('name'
374
375
 *   => 'My name'). Key / value pairs added to the $edit['data'] will be
 *   serialized and saved in the {users.data} column.
376
377
 * @param $category
 *   (optional) The category for storing profile information in.
378
379
 *
 * @return
380
 *   A fully-loaded $user object upon successful save or FALSE if the save failed.
381
 */
382
function user_save($account, $edit = array(), $category = 'account') {
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
  $transaction = db_transaction();
  try {
    $table = drupal_get_schema('users');

    if (!empty($edit['pass'])) {
      // Allow alternate password hashing schemes.
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
      $edit['pass'] = user_hash_password(trim($edit['pass']));
      // Abort if the hashing failed and returned FALSE.
      if (!$edit['pass']) {
        return FALSE;
      }
    }
    else {
      // Avoid overwriting an existing password with a blank password.
      unset($edit['pass']);
399
    }
400

401
    // Presave field allowing changing of $edit.
402
403
404
    $edit = (object) $edit;
    field_attach_presave('user', $edit);
    $edit = (array) $edit;
Dries's avatar
   
Dries committed
405

406
407
408
    if (empty($account)) {
      $account = new stdClass();
    }
409
410
411
    if (!isset($account->is_new)) {
      $account->is_new = empty($account->uid);
    }
412
413
414
    // Prepopulate $edit['data'] with the current value of $account->data.
    // Modules can add to or remove from this array in hook_user_presave().
    if (!empty($account->data)) {
415
      $edit['data'] = !empty($edit['data']) ? array_merge($account->data, $edit['data']) : $account->data;
416
    }
417
    user_module_invoke('presave', $edit, $account, $category);
418

419
420
421
422
423
424
    if (is_object($account) && !$account->is_new) {
      // 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($edit['access']) && empty($account->access) && user_access('administer users')) {
        $edit['access'] = REQUEST_TIME;
      }
Dries's avatar
   
Dries committed
425

426
      // Process picture uploads.
427
      if (!$delete_previous_picture = empty($edit['picture']->fid)) {
428
429
430
        $picture = $edit['picture'];
        // If the picture is a temporary file move it to its final location and
        // make it permanent.
431
        if (!$picture->status) {
432
          $info = image_get_info($picture->uri);
433
          $picture_directory =  file_default_scheme() . '://' . variable_get('user_picture_path', 'pictures');
434
435
436

          // Prepare the pictures directory.
          file_prepare_directory($picture_directory, FILE_CREATE_DIRECTORY);
437
          $destination = file_stream_wrapper_uri_normalize($picture_directory . '/picture-' . $account->uid . '-' . REQUEST_TIME . '.' . $info['extension']);
438

439
          // Move the temporary file into the final location.
440
          if ($picture = file_move($picture, $destination, FILE_EXISTS_RENAME)) {
441
            $delete_previous_picture = TRUE;
442
            $picture->status = FILE_STATUS_PERMANENT;
443
            $edit['picture'] = file_save($picture);
444
            file_usage_add($picture, 'user', 'user', $account->uid);
445
          }
446
447
        }
      }
448
449
450
451
452
453
454

      // Delete the previous picture if it was deleted or replaced.
      if ($delete_previous_picture && !empty($account->picture->fid)) {
        file_usage_delete($account->picture, 'user', 'user', $account->uid);
        file_delete($account->picture);
      }

455
456
457
458
459
460
461
462
463
464
465
      $edit['picture'] = empty($edit['picture']->fid) ? 0 : $edit['picture']->fid;

      // Do not allow 'uid' to be changed.
      $edit['uid'] = $account->uid;
      // Save changes to the user table.
      $success = drupal_write_record('users', $edit, 'uid');
      if ($success === FALSE) {
        // The query failed - better to abort the save than risk further
        // data loss.
        return FALSE;
      }
Dries's avatar
   
Dries committed
466

467
468
469
470
471
472
473
474
475
476
477
478
479
480
      // Reload user roles if provided.
      if (isset($edit['roles']) && is_array($edit['roles'])) {
        db_delete('users_roles')
          ->condition('uid', $account->uid)
          ->execute();

        $query = db_insert('users_roles')->fields(array('uid', 'rid'));
        foreach (array_keys($edit['roles']) as $rid) {
          if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
            $query->values(array(
              'uid' => $account->uid,
              'rid' => $rid,
            ));
          }
481
        }
482
        $query->execute();
483
      }
Dries's avatar
   
Dries committed
484

485
486
487
488
      // Delete a blocked user's sessions to kick them if they are online.
      if (isset($edit['status']) && $edit['status'] == 0) {
        drupal_session_destroy_uid($account->uid);
      }
489

490
491
492
493
494
495
496
      // If the password changed, delete all open sessions and recreate
      // the current one.
      if (!empty($edit['pass'])) {
        drupal_session_destroy_uid($account->uid);
        if ($account->uid == $GLOBALS['user']->uid) {
          drupal_session_regenerate();
        }
497
      }
498

499
      // Save Field data.
500
501
      $entity = (object) $edit;
      field_attach_update('user', $entity);
Dries's avatar
   
Dries committed
502

503
504
      // Refresh user object.
      $user = user_load($account->uid, TRUE);
505

506
507
508
509
510
511
      // Send emails after we have the new user object.
      if (isset($edit['status']) && $edit['status'] != $account->status) {
        // The user's status is changing; conditionally send notification email.
        $op = $edit['status'] == 1 ? 'status_activated' : 'status_blocked';
        _user_mail_notify($op, $user);
      }
512

513
      user_module_invoke('update', $edit, $user, $category);
514
      module_invoke_all('entity_update', $user, 'user');
515
    }
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
    else {
      // Allow 'uid' to be set by the caller. There is no danger of writing an
      // existing user as drupal_write_record will do an INSERT.
      if (empty($edit['uid'])) {
        $edit['uid'] = db_next_id(db_query('SELECT MAX(uid) FROM {users}')->fetchField());
      }
      // Allow 'created' to be set by the caller.
      if (!isset($edit['created'])) {
        $edit['created'] = REQUEST_TIME;
      }
      // Consider users created by an administrator as already logged in, so
      // anonymous users can view the profile (if allowed).
      if (empty($edit['access']) && user_access('administer users')) {
        $edit['access'] = REQUEST_TIME;
      }
531

532
533
534
535
536
537
538
      $edit['mail'] = trim($edit['mail']);
      $success = drupal_write_record('users', $edit);
      if ($success === FALSE) {
        // On a failed INSERT some other existing user's uid may be returned.
        // We must abort to avoid overwriting their account.
        return FALSE;
      }
539

540
541
542
      // Build a stub user object.
      $user = (object) $edit;
      $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
Dries's avatar
   
Dries committed
543

544
      field_attach_insert('user', $user);
Dries's avatar
   
Dries committed
545

546
      user_module_invoke('insert', $edit, $user, $category);
547
      module_invoke_all('entity_insert', $user, 'user');
548

549
      // Save user roles.
550
551
552
553
554
555
556
557
558
      if (isset($edit['roles']) && is_array($edit['roles'])) {
        $query = db_insert('users_roles')->fields(array('uid', 'rid'));
        foreach (array_keys($edit['roles']) as $rid) {
          if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
            $query->values(array(
              'uid' => $edit['uid'],
              'rid' => $rid,
            ));
          }
559
        }
560
        $query->execute();
561
562
563
      }
    }

564
565
566
    return $user;
  }
  catch (Exception $e) {
567
568
    $transaction->rollback();
    watchdog_exception('user', $e);
569
    throw $e;
Dries's avatar
   
Dries committed
570
571
572
  }
}

Dries's avatar
Dries committed
573
574
575
/**
 * Verify the syntax of the given name.
 */
Dries's avatar
   
Dries committed
576
function user_validate_name($name) {
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
  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.');
  }
592
  if (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
593
594
595
596
597
598
599
600
601
                  '\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)) {
602
603
    return t('The username contains an illegal character.');
  }
604
605
606
  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
607
608
}

609
610
611
612
613
614
615
616
617
618
619
620
621
/**
 * Validates a user's email address.
 *
 * Checks that a user's email address exists and follows all standard
 * validation rules. Returns error messages when the address is invalid.
 *
 * @param $mail
 *   A user's email address.
 *
 * @return
 *   If the address is invalid, a human-readable error message is returned.
 *   If the address is valid, nothing is returned.
 */
Dries's avatar
   
Dries committed
622
function user_validate_mail($mail) {
623
  $mail = trim($mail);
624
625
626
  if (!$mail) {
    return t('You must enter an e-mail address.');
  }
627
  if (!valid_email_address($mail)) {
628
    return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
Dries's avatar
   
Dries committed
629
630
631
  }
}

632
633
634
635
636
/**
 * Validates an image uploaded by a user.
 *
 * @see user_account_form()
 */
637
function user_validate_picture(&$form, &$form_state) {
638
  // If required, validate the uploaded picture.
639
640
641
642
643
  $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),
  );
644

645
646
647
648
649
650
651
  // 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
652
653
654
  }
}

Dries's avatar
Dries committed
655
656
657
/**
 * Generate a random alphanumeric password.
 */
Dries's avatar
   
Dries committed
658
659
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
660
661
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
662
  // of 'I', 1, and 'l'.
663
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
Dries's avatar
Dries committed
664

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

Dries's avatar
Dries committed
668
669
  // Declare the password as a blank string.
  $pass = '';
Dries's avatar
   
Dries committed
670

Dries's avatar
Dries committed
671
  // Loop the number of times specified by $length.
Dries's avatar
   
Dries committed
672
673
674
675
  for ($i = 0; $i < $length; $i++) {

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

  return $pass;
Dries's avatar
   
Dries committed
680
681
}

682
683
684
685
686
687
688
689
690
691
/**
 * Determine the permissions for one or more roles.
 *
 * @param $roles
 *   An array whose keys are the role IDs of interest, such as $user->roles.
 *
 * @return
 *   An array indexed by role ID. Each value is an array whose keys are the
 *   permission strings for the given role ID.
 */
692
693
function user_role_permissions($roles = array()) {
  $cache = &drupal_static(__FUNCTION__, array());
694
695
696
697
698

  $role_permissions = $fetch = array();

  if ($roles) {
    foreach ($roles as $rid => $name) {
699
700
      if (isset($cache[$rid])) {
        $role_permissions[$rid] = $cache[$rid];
701
702
703
704
705
      }
      else {
        // Add this rid to the list of those needing to be fetched.
        $fetch[] = $rid;
        // Prepare in case no permissions are returned.
706
        $cache[$rid] = array();
707
708
709
710
711
712
      }
    }

    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.
713
      $result = db_query("SELECT rid, permission FROM {role_permission} WHERE rid IN (:fetch)", array(':fetch' => $fetch));
714

715
      foreach ($result as $row) {
716
        $cache[$row->rid][$row->permission] = TRUE;
717
718
719
      }
      foreach ($fetch as $rid) {
        // For every rid, we know we at least assigned an empty array.
720
        $role_permissions[$rid] = $cache[$rid];
721
722
723
724
725
726
727
      }
    }
  }

  return $role_permissions;
}

Dries's avatar
Dries committed
728
729
730
731
732
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
Dries's avatar
   
Dries committed
733
734
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
Dries's avatar
Dries committed
735
736
 *
 * @return
737
 *   Boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
738
739
740
741
742
 *
 * 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.
 */
743
function user_access($string, $account = NULL) {
Dries's avatar
   
Dries committed
744
  global $user;
745

746
  if (!isset($account)) {
747
748
749
    $account = $user;
  }

750
  // User #1 has all privileges:
751
  if ($account->uid == 1) {
752
    return TRUE;
Dries's avatar
   
Dries committed
753
754
  }

Dries's avatar
Dries committed
755
756
  // To reduce the number of SQL queries, we cache the user's permissions
  // in a static variable.
757
  // Use the advanced drupal_static() pattern, since this is called very often.
758
759
760
761
762
  static $drupal_static_fast;
  if (!isset($drupal_static_fast)) {
    $drupal_static_fast['perm'] = &drupal_static(__FUNCTION__);
  }
  $perm = &$drupal_static_fast['perm'];
763
  if (!isset($perm[$account->uid])) {
764
    $role_permissions = user_role_permissions($account->roles);
Dries's avatar
   
Dries committed
765

766
    $perms = array();
767
768
    foreach ($role_permissions as $one_role) {
      $perms += $one_role;
Dries's avatar
   
Dries committed
769
    }
770
    $perm[$account->uid] = $perms;
Dries's avatar
   
Dries committed
771
  }
772

773
  return isset($perm[$account->uid][$string]);
Dries's avatar
   
Dries committed
774
775
}

776
/**
777
 * Checks for usernames blocked by user administration.
778
 *
779
 * @return boolean TRUE for blocked users, FALSE for active.
780
781
 */
function user_is_blocked($name) {
782
783
784
785
786
  return db_select('users')
    ->fields('users', array('name'))
    ->condition('name', db_like($name), 'LIKE')
    ->condition('status', 0)
    ->execute()->fetchObject();
787
788
}

Dries's avatar
Dries committed
789
/**
790
 * Implements hook_permission().
Dries's avatar
Dries committed
791
 */
792
function user_permission() {
793
794
795
  return array(
    'administer permissions' =>  array(
      'title' => t('Administer permissions'),
796
      'restrict access' => TRUE,
797
798
799
    ),
    'administer users' => array(
      'title' => t('Administer users'),
800
      'restrict access' => TRUE,
801
802
    ),
    'access user profiles' => array(
803
      'title' => t('View user profiles'),
804
805
806
807
808
    ),
    'change own username' => array(
      'title' => t('Change own username'),
    ),
    'cancel account' => array(
809
      'title' => t('Cancel own user account'),
810
      'description' => t('Note: content may be kept, unpublished, deleted or transferred to the %anonymous-name user depending on the configured <a href="@user-settings-url">user settings</a>.', array('%anonymous-name' => variable_get('anonymous', t('Anonymous')), '@user-settings-url' => url('admin/config/people/accounts'))),
811
812
813
    ),
    'select account cancellation method' => array(
      'title' => t('Select method for cancelling own account'),
814
      'restrict access' => TRUE,
815
816
    ),
  );
Dries's avatar
   
Dries committed
817
818
}

Dries's avatar
Dries committed
819
/**
820
 * Implements hook_file_download().
Dries's avatar
Dries committed
821
822
823
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
824
825
826
function user_file_download($uri) {
  if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
    $info = image_get_info($uri);
827
    return array('Content-Type' => $info['mime_type']);
Dries's avatar
   
Dries committed
828
829
830
  }
}

831
/**
832
 * Implements hook_file_move().
833
 */
834
835
836
837
838
839
840
841
842
843
function user_file_move($file, $source) {
  // If a user's picture is replaced with a new one, update the record in
  // the users table.
  if (isset($file->fid) && isset($source->fid) && $file->fid != $source->fid) {
    db_update('users')
      ->fields(array(
        'picture' => $file->fid,
      ))
      ->condition('picture', $source->fid)
      ->execute();
844
845
846
847
  }
}

/**
848
 * Implements hook_file_delete().
849
850
851
 */
function user_file_delete($file) {
  // Remove any references to the file.
852
  db_update('users')
853
854
855
856
857
    ->fields(array('picture' => 0))
    ->condition('picture', $file->fid)
    ->execute();
}

Dries's avatar
Dries committed
858
/**
859
 * Implements hook_search_info().
Dries's avatar
Dries committed
860
 */
861
862
863
864
865
866
867
function user_search_info() {
  return array(
    'title' => 'Users',
  );
}

/**
868
 * Implements hook_search_access().
869
870
871
872
873
874
 */
function user_search_access() {
  return user_access('access user profiles');
}

/**
875
 * Implements hook_search_execute().
876
 */
877
function user_search_execute($keys = NULL, $conditions = NULL) {
878
879
880
881
  $find = array();
  // Replace wildcards with MySQL/PostgreSQL wildcards.
  $keys = preg_replace('!\*+!', '%', $keys);
  $query = db_select('users')->extend('PagerDefault');
882
  $query->fields('users', array('name', 'uid'));
883
884
  if (user_access('administer users')) {
    // Administrators can also search in the otherwise private email field.
885
    $query->fields('users', array('mail'));
886
    $query->condition(db_or()->
887
888
      condition('name', '%' . db_like($keys) . '%', 'LIKE')->
      condition('mail', '%' . db_like($keys) . '%', 'LIKE'));
889
890
  }
  else {
891
    $query->condition('name', '%' . db_like($keys) . '%', 'LIKE');
892
893
894
895
  }
  $result = $query
    ->limit(15)
    ->execute();
896
897
898
899
900
901
902
903
904
  if (user_access('administer users')) {
    foreach ($result as $account) {
      $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
    }
  }
  else {
    foreach ($result as $account) {
      $find[] = array('title' => $account->name, 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
    }
Dries's avatar
   
Dries committed
905
  }
906
  return $find;
Dries's avatar
   
Dries committed
907
908
}

Dries's avatar
Dries committed
909
/**
910
 * Implements hook_element_info().
Dries's avatar
Dries committed
911
 */
912
913
914
915
916
917
function user_element_info() {
  $types['user_profile_category'] = array(
    '#theme_wrappers' => array('user_profile_category'),
  );
  $types['user_profile_item'] = array(
    '#theme' => 'user_profile_item',
Dries's avatar
Dries committed
918
  );
919
  return $types;
Dries's avatar
Dries committed
920
921
}

Dries's avatar
Dries committed
922
/**
923
 * Implements hook_user_view().
Dries's avatar
Dries committed
924
 */
925
function user_user_view($account) {
926
  $account->content['user_picture'] = array(
927
    '#markup' => theme('user_picture', array('account' => $account)),
928
929
930
931
932
933
934
    '#weight' => -10,
  );
  if (!isset($account->content['summary'])) {
    $account->content['summary'] = array();
  }
  $account->content['summary'] += array(
    '#type' => 'user_profile_category',
935
    '#attributes' => array('class' => array('user-member')),
936
937
938
    '#weight' => 5,
    '#title' => t('History'),
  );
939
  $account->content['summary']['member_for'] = array(
940
941
942
943
944
945
946
    '#type' => 'user_profile_item',
    '#title' => t('Member for'),
    '#markup' => format_interval(REQUEST_TIME - $account->created),
  );
}

/**
947
 * Helper function to add default user account fields to user registration and edit form.
948
 *
949
950
951
 * @see user_account_form_validate()
 * @see user_validate_current_pass()
 * @see user_validate_picture()
952
 * @see user_validate_mail()
953
 */
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
function user_account_form(&$form, &$form_state) {
  global $user;

  $account = $form['#user'];
  $register = ($form['#user']->uid > 0 ? FALSE : TRUE);

  $admin = user_access('administer users');

  $form['#validate'][] = 'user_account_form_validate';

  // Account information.
  $form['account'] = array(
    '#weight' => -10,
  );
  // Only show name field on registration form or user can change own username.
  $form['account']['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Username'),
    '#maxlength' => USERNAME_MAX_LENGTH,
    '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'),
    '#required' => TRUE,
    '#attributes' => array('class' => array('username')),
    '#default_value' => (!$register ? $account->name : ''),
    '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin),
978
    '#weight' => -10,
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
  );

  $form['account']['mail'] = array(
    '#type' => 'textfield',
    '#title' => t('E-mail address'),
    '#maxlength' => EMAIL_MAX_LENGTH,
    '#description' => t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'),
    '#required' => TRUE,
    '#default_value' => (!$register ? $account->mail : ''),
  );

  // Display password field only for existing users or when user is allowed to
  // assign a password during registration.
  if (!$register) {
    $form['account']['pass'] = array(
      '#type' => 'password_confirm',
      '#size' => 25,
      '#description' => t('To change the current user password, enter the new password in both fields.'),
    );
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
    // To skip the current password field, the user must have logged in via a
    // one-time link and have the token in the URL.
    $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]);
    $protected_values = array();
    $current_pass_description = '';
    // The user may only change their own password without their current
    // password if they logged in via a one-time login link.
    if (!$pass_reset) {
      $protected_values['mail'] = $form['account']['mail']['#title'];
      $protected_values['pass'] = t('Password');
      $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
      $current_pass_description = t('Enter your current password to change the %mail or %pass. !request_new.', array('%mail' => $protected_values['mail'], '%pass' => $protected_values['pass'], '!request_new' => $request_new));
    }
    // The user must enter their current password to change to a new one.
    if ($user->uid == $account->uid) {
      $form['account']['current_pass_required_values'] = array(
        '#type' => 'value',
        '#value' => $protected_values,
      );
      $form['account']['current_pass'] = array(
        '#type' => 'password',
        '#title' => t('Current password'),
        '#size' => 25,
        '#access' => !empty($protected_values),
        '#description' => $current_pass_description,
        '#weight' => -5,
      );
      $form['#validate'][] = 'user_validate_current_pass';
    }
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
  }
  elseif (!variable_get('user_email_verification', TRUE) || $admin) {
    $form['account']['pass'] = array(
      '#type' => 'password_confirm',
      '#size' => 25,
      '#description' => t('Provide a password for the new account in both fields.'),
      '#required' => TRUE,
    );
  }

  if ($admin) {
1038
    $status = isset($account->status) ? $account->status : 1;
1039
1040
  }
  else {
1041
    $status = $register ? variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS : $account->status;
1042
1043
1044
1045
1046
1047
1048
1049
1050
  }
  $form['account']['status'] = array(
    '#type' => 'radios',
    '#title' => t('Status'),
    '#default_value' => $status,
    '#options' => array(t('Blocked'), t('Active')),
    '#access' => $admin,
  );

1051
  $roles = array_map('check_plain', user_roles(TRUE));
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
  // The disabled checkbox subelement for the 'authenticated user' role
  // must be generated separately and added to the checkboxes element,
  // because of a limitation in Form API not supporting a single disabled
  // checkbox within a set of checkboxes.
  // @todo This should be solved more elegantly. See issue #119038.
  $checkbox_authenticated = array(
    '#type' => 'checkbox',
    '#title' => $roles[DRUPAL_AUTHENTICATED_RID],
    '#default_value' => TRUE,
    '#disabled' => TRUE,
  );
  unset($roles[DRUPAL_AUTHENTICATED_RID]);
  $form['account']['roles'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Roles'),
    '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()),
    '#options' => $roles,
    '#access' => $roles && user_access('administer permissions'),
    DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
  );

  $form['account']['notify'] = array(
    '#type' => 'checkbox',
    '#title' => t('Notify user of new account'),
    '#access' => $register && $admin,
  );

  // Signature.
  $form['signature_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Signature settings'),
    '#weight' => 1,
    '#access' => (!$register &&