Projet

Général

Profil

Paste
Télécharger (141 ko) Statistiques
| Branche: | Révision:

root / drupal7 / modules / user / user.module @ 134c7813

1
<?php
2

    
3
/**
4
 * @file
5
 * Enables the user registration and login system.
6
 */
7

    
8
/**
9
 * Maximum length of username text field.
10
 */
11
define('USERNAME_MAX_LENGTH', 60);
12

    
13
/**
14
 * Maximum length of user e-mail text field.
15
 */
16
define('EMAIL_MAX_LENGTH', 254);
17

    
18
/**
19
 * Only administrators can create user accounts.
20
 */
21
define('USER_REGISTER_ADMINISTRATORS_ONLY', 0);
22

    
23
/**
24
 * Visitors can create their own accounts.
25
 */
26
define('USER_REGISTER_VISITORS', 1);
27

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

    
34
/**
35
 * Implements hook_help().
36
 */
37
function user_help($path, $arg) {
38
  global $user;
39

    
40
  switch ($path) {
41
    case 'admin/help#user':
42
      $output = '';
43
      $output .= '<h3>' . t('About') . '</h3>';
44
      $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/documentation/modules/user')) . '</p>';
45
      $output .= '<h3>' . t('Uses') . '</h3>';
46
      $output .= '<dl>';
47
      $output .= '<dt>' . t('Creating and managing users') . '</dt>';
48
      $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>';
49
      $output .= '<dt>' . t('User roles and permissions') . '</dt>';
50
      $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>';
51
      $output .= '<dt>' . t('Account settings') . '</dt>';
52
      $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>';
53
      $output .= '</dl>';
54
      return $output;
55
    case 'admin/people/create':
56
      return '<p>' . t("This web page allows administrators to register new users. Users' e-mail addresses and usernames must be unique.") . '</p>';
57
    case 'admin/people/permissions':
58
      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>';
59
    case 'admin/people/permissions/roles':
60
      $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>';
61
      $output .= '<p>' . t('By default, Drupal comes with two user roles:') . '</p>';
62
      $output .= '<ul>';
63
      $output .= '<li>' . t("Anonymous user: this role is used for users that don't have a user account or that are not authenticated.") . '</li>';
64
      $output .= '<li>' . t('Authenticated user: this role is automatically granted to all logged in users.') . '</li>';
65
      $output .= '</ul>';
66
      return $output;
67
    case 'admin/config/people/accounts/fields':
68
      return '<p>' . t('This form lets administrators add, edit, and arrange fields for storing user data.') . '</p>';
69
    case 'admin/config/people/accounts/display':
70
      return '<p>' . t('This form lets administrators configure how fields should be displayed when rendering a user profile page.') . '</p>';
71
    case 'admin/people/search':
72
      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>';
73
  }
74
}
75

    
76
/**
77
 * Invokes a user hook in every module.
78
 *
79
 * We cannot use module_invoke() for this, because the arguments need to
80
 * be passed by reference.
81
 *
82
 * @param $type
83
 *   A text string that controls which user hook to invoke.  Valid choices are:
84
 *   - cancel: Invokes hook_user_cancel().
85
 *   - insert: Invokes hook_user_insert().
86
 *   - login: Invokes hook_user_login().
87
 *   - presave: Invokes hook_user_presave().
88
 *   - update: Invokes hook_user_update().
89
 * @param $edit
90
 *   An associative array variable containing form values to be passed
91
 *   as the first parameter of the hook function.
92
 * @param $account
93
 *   The user account object to be passed as the second parameter of the hook
94
 *   function.
95
 * @param $category
96
 *   The category of user information being acted upon.
97
 */
98
function user_module_invoke($type, &$edit, $account, $category = NULL) {
99
  foreach (module_implements('user_' . $type) as $module) {
100
    $function = $module . '_user_' . $type;
101
    $function($edit, $account, $category);
102
  }
103
}
104

    
105
/**
106
 * Implements hook_theme().
107
 */
108
function user_theme() {
109
  return array(
110
    'user_picture' => array(
111
      'variables' => array('account' => NULL),
112
      'template' => 'user-picture',
113
    ),
114
    'user_profile' => array(
115
      'render element' => 'elements',
116
      'template' => 'user-profile',
117
      'file' => 'user.pages.inc',
118
    ),
119
    'user_profile_category' => array(
120
      'render element' => 'element',
121
      'template' => 'user-profile-category',
122
      'file' => 'user.pages.inc',
123
    ),
124
    'user_profile_item' => array(
125
      'render element' => 'element',
126
      'template' => 'user-profile-item',
127
      'file' => 'user.pages.inc',
128
    ),
129
    'user_list' => array(
130
      'variables' => array('users' => NULL, 'title' => NULL),
131
    ),
132
    'user_admin_permissions' => array(
133
      'render element' => 'form',
134
      'file' => 'user.admin.inc',
135
    ),
136
    'user_admin_roles' => array(
137
      'render element' => 'form',
138
      'file' => 'user.admin.inc',
139
    ),
140
    'user_permission_description' => array(
141
      'variables' => array('permission_item' => NULL, 'hide' => NULL),
142
      'file' => 'user.admin.inc',
143
    ),
144
    'user_signature' => array(
145
      'variables' => array('signature' => NULL),
146
    ),
147
  );
148
}
149

    
150
/**
151
 * Implements hook_entity_info().
152
 */
153
function user_entity_info() {
154
  $return = array(
155
    'user' => array(
156
      'label' => t('User'),
157
      'controller class' => 'UserController',
158
      'base table' => 'users',
159
      'uri callback' => 'user_uri',
160
      'label callback' => 'format_username',
161
      'fieldable' => TRUE,
162
      // $user->language is only the preferred user language for the user
163
      // interface textual elements. As it is not necessarily related to the
164
      // language assigned to fields, we do not define it as the entity language
165
      // key.
166
      'entity keys' => array(
167
        'id' => 'uid',
168
      ),
169
      'bundles' => array(
170
        'user' => array(
171
          'label' => t('User'),
172
          'admin' => array(
173
            'path' => 'admin/config/people/accounts',
174
            'access arguments' => array('administer users'),
175
          ),
176
        ),
177
      ),
178
      'view modes' => array(
179
        'full' => array(
180
          'label' => t('User account'),
181
          'custom settings' => FALSE,
182
        ),
183
      ),
184
    ),
185
  );
186
  return $return;
187
}
188

    
189
/**
190
 * Implements callback_entity_info_uri().
191
 */
192
function user_uri($user) {
193
  return array(
194
    'path' => 'user/' . $user->uid,
195
  );
196
}
197

    
198
/**
199
 * Implements hook_field_info_alter().
200
 */
201
function user_field_info_alter(&$info) {
202
  // Add the 'user_register_form' instance setting to all field types.
203
  foreach ($info as $field_type => &$field_type_info) {
204
    $field_type_info += array('instance_settings' => array());
205
    $field_type_info['instance_settings'] += array(
206
      'user_register_form' => FALSE,
207
    );
208
  }
209
}
210

    
211
/**
212
 * Implements hook_field_extra_fields().
213
 */
214
function user_field_extra_fields() {
215
  $return['user']['user'] = array(
216
    'form' => array(
217
      'account' => array(
218
        'label' => t('User name and password'),
219
        'description' => t('User module account form elements.'),
220
        'weight' => -10,
221
      ),
222
      'timezone' => array(
223
        'label' => t('Timezone'),
224
        'description' => t('User module timezone form element.'),
225
        'weight' => 6,
226
      ),
227
    ),
228
    'display' => array(
229
      'summary' => array(
230
        'label' => t('History'),
231
        'description' => t('User module history view element.'),
232
        'weight' => 5,
233
      ),
234
    ),
235
  );
236

    
237
  return $return;
238
}
239

    
240
/**
241
 * Fetches a user object based on an external authentication source.
242
 *
243
 * @param string $authname
244
 *   The external authentication username.
245
 *
246
 * @return
247
 *   A fully-loaded user object if the user is found or FALSE if not found.
248
 */
249
function user_external_load($authname) {
250
  $uid = db_query("SELECT uid FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchField();
251

    
252
  if ($uid) {
253
    return user_load($uid);
254
  }
255
  else {
256
    return FALSE;
257
  }
258
}
259

    
260
/**
261
 * Load multiple users based on certain conditions.
262
 *
263
 * This function should be used whenever you need to load more than one user
264
 * from the database. Users are loaded into memory and will not require
265
 * database access if loaded again during the same page request.
266
 *
267
 * @param $uids
268
 *   An array of user IDs.
269
 * @param $conditions
270
 *   (deprecated) An associative array of conditions on the {users}
271
 *   table, where the keys are the database fields and the values are the
272
 *   values those fields must have. Instead, it is preferable to use
273
 *   EntityFieldQuery to retrieve a list of entity IDs loadable by
274
 *   this function.
275
 * @param $reset
276
 *   A boolean indicating that the internal cache should be reset. Use this if
277
 *   loading a user object which has been altered during the page request.
278
 *
279
 * @return
280
 *   An array of user objects, indexed by uid.
281
 *
282
 * @see entity_load()
283
 * @see user_load()
284
 * @see user_load_by_mail()
285
 * @see user_load_by_name()
286
 * @see EntityFieldQuery
287
 *
288
 * @todo Remove $conditions in Drupal 8.
289
 */
290
function user_load_multiple($uids = array(), $conditions = array(), $reset = FALSE) {
291
  return entity_load('user', $uids, $conditions, $reset);
292
}
293

    
294
/**
295
 * Controller class for users.
296
 *
297
 * This extends the DrupalDefaultEntityController class, adding required
298
 * special handling for user objects.
299
 */
300
class UserController extends DrupalDefaultEntityController {
301

    
302
  function attachLoad(&$queried_users, $revision_id = FALSE) {
303
    // Build an array of user picture IDs so that these can be fetched later.
304
    $picture_fids = array();
305
    foreach ($queried_users as $key => $record) {
306
      $picture_fids[] = $record->picture;
307
      $queried_users[$key]->data = unserialize($record->data);
308
      $queried_users[$key]->roles = array();
309
      if ($record->uid) {
310
        $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
311
      }
312
      else {
313
        $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
314
      }
315
    }
316

    
317
    // Add any additional roles from the database.
318
    $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)));
319
    foreach ($result as $record) {
320
      $queried_users[$record->uid]->roles[$record->rid] = $record->name;
321
    }
322

    
323
    // Add the full file objects for user pictures if enabled.
324
    if (!empty($picture_fids) && variable_get('user_pictures', 0)) {
325
      $pictures = file_load_multiple($picture_fids);
326
      foreach ($queried_users as $account) {
327
        if (!empty($account->picture) && isset($pictures[$account->picture])) {
328
          $account->picture = $pictures[$account->picture];
329
        }
330
        else {
331
          $account->picture = NULL;
332
        }
333
      }
334
    }
335
    // Call the default attachLoad() method. This will add fields and call
336
    // hook_user_load().
337
    parent::attachLoad($queried_users, $revision_id);
338
  }
339
}
340

    
341
/**
342
 * Loads a user object.
343
 *
344
 * Drupal has a global $user object, which represents the currently-logged-in
345
 * user. So to avoid confusion and to avoid clobbering the global $user object,
346
 * it is a good idea to assign the result of this function to a different local
347
 * variable, generally $account. If you actually do want to act as the user you
348
 * are loading, it is essential to call drupal_save_session(FALSE); first.
349
 * See
350
 * @link http://drupal.org/node/218104 Safely impersonating another user @endlink
351
 * for more information.
352
 *
353
 * @param $uid
354
 *   Integer specifying the user ID to load.
355
 * @param $reset
356
 *   TRUE to reset the internal cache and load from the database; FALSE
357
 *   (default) to load from the internal cache, if set.
358
 *
359
 * @return
360
 *   A fully-loaded user object upon successful user load, or FALSE if the user
361
 *   cannot be loaded.
362
 *
363
 * @see user_load_multiple()
364
 */
365
function user_load($uid, $reset = FALSE) {
366
  $users = user_load_multiple(array($uid), array(), $reset);
367
  return reset($users);
368
}
369

    
370
/**
371
 * Fetch a user object by email address.
372
 *
373
 * @param $mail
374
 *   String with the account's e-mail address.
375
 * @return
376
 *   A fully-loaded $user object upon successful user load or FALSE if user
377
 *   cannot be loaded.
378
 *
379
 * @see user_load_multiple()
380
 */
381
function user_load_by_mail($mail) {
382
  $users = user_load_multiple(array(), array('mail' => $mail));
383
  return reset($users);
384
}
385

    
386
/**
387
 * Fetch a user object by account name.
388
 *
389
 * @param $name
390
 *   String with the account's user name.
391
 * @return
392
 *   A fully-loaded $user object upon successful user load or FALSE if user
393
 *   cannot be loaded.
394
 *
395
 * @see user_load_multiple()
396
 */
397
function user_load_by_name($name) {
398
  $users = user_load_multiple(array(), array('name' => $name));
399
  return reset($users);
400
}
401

    
402
/**
403
 * Save changes to a user account or add a new user.
404
 *
405
 * @param $account
406
 *   (optional) The user object to modify or add. If you want to modify
407
 *   an existing user account, you will need to ensure that (a) $account
408
 *   is an object, and (b) you have set $account->uid to the numeric
409
 *   user ID of the user account you wish to modify. If you
410
 *   want to create a new user account, you can set $account->is_new to
411
 *   TRUE or omit the $account->uid field.
412
 * @param $edit
413
 *   An array of fields and values to save. For example array('name'
414
 *   => 'My name'). Key / value pairs added to the $edit['data'] will be
415
 *   serialized and saved in the {users.data} column.
416
 * @param $category
417
 *   (optional) The category for storing profile information in.
418
 *
419
 * @return
420
 *   A fully-loaded $user object upon successful save or FALSE if the save failed.
421
 *
422
 * @todo D8: Drop $edit and fix user_save() to be consistent with others.
423
 */
424
function user_save($account, $edit = array(), $category = 'account') {
425
  $transaction = db_transaction();
426
  try {
427
    if (!empty($edit['pass'])) {
428
      // Allow alternate password hashing schemes.
429
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
430
      $edit['pass'] = user_hash_password(trim($edit['pass']));
431
      // Abort if the hashing failed and returned FALSE.
432
      if (!$edit['pass']) {
433
        return FALSE;
434
      }
435
    }
436
    else {
437
      // Avoid overwriting an existing password with a blank password.
438
      unset($edit['pass']);
439
    }
440
    if (isset($edit['mail'])) {
441
      $edit['mail'] = trim($edit['mail']);
442
    }
443

    
444
    // Load the stored entity, if any.
445
    if (!empty($account->uid) && !isset($account->original)) {
446
      $account->original = entity_load_unchanged('user', $account->uid);
447
    }
448

    
449
    if (empty($account)) {
450
      $account = new stdClass();
451
    }
452
    if (!isset($account->is_new)) {
453
      $account->is_new = empty($account->uid);
454
    }
455
    // Prepopulate $edit['data'] with the current value of $account->data.
456
    // Modules can add to or remove from this array in hook_user_presave().
457
    if (!empty($account->data)) {
458
      $edit['data'] = !empty($edit['data']) ? array_merge($account->data, $edit['data']) : $account->data;
459
    }
460

    
461
    // Invoke hook_user_presave() for all modules.
462
    user_module_invoke('presave', $edit, $account, $category);
463

    
464
    // Invoke presave operations of Field Attach API and Entity API. Those APIs
465
    // require a fully-fledged and updated entity object. Therefore, we need to
466
    // copy any new property values of $edit into it.
467
    foreach ($edit as $key => $value) {
468
      $account->$key = $value;
469
    }
470
    field_attach_presave('user', $account);
471
    module_invoke_all('entity_presave', $account, 'user');
472

    
473
    if (is_object($account) && !$account->is_new) {
474
      // Process picture uploads.
475
      if (!empty($account->picture->fid) && (!isset($account->original->picture->fid) || $account->picture->fid != $account->original->picture->fid)) {
476
        $picture = $account->picture;
477
        // If the picture is a temporary file move it to its final location and
478
        // make it permanent.
479
        if (!$picture->status) {
480
          $info = image_get_info($picture->uri);
481
          $picture_directory =  file_default_scheme() . '://' . variable_get('user_picture_path', 'pictures');
482

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

    
487
          // Move the temporary file into the final location.
488
          if ($picture = file_move($picture, $destination, FILE_EXISTS_RENAME)) {
489
            $picture->status = FILE_STATUS_PERMANENT;
490
            $account->picture = file_save($picture);
491
            file_usage_add($picture, 'user', 'user', $account->uid);
492
          }
493
        }
494
        // Delete the previous picture if it was deleted or replaced.
495
        if (!empty($account->original->picture->fid)) {
496
          file_usage_delete($account->original->picture, 'user', 'user', $account->uid);
497
          file_delete($account->original->picture);
498
        }
499
      }
500
      elseif (isset($edit['picture_delete']) && $edit['picture_delete']) {
501
        file_usage_delete($account->original->picture, 'user', 'user', $account->uid);
502
        file_delete($account->original->picture);
503
      }
504
      // Save the picture object, if it is set. drupal_write_record() expects
505
      // $account->picture to be a FID.
506
      $picture = empty($account->picture) ? NULL : $account->picture;
507
      $account->picture = empty($account->picture->fid) ? 0 : $account->picture->fid;
508

    
509
      // Do not allow 'uid' to be changed.
510
      $account->uid = $account->original->uid;
511
      // Save changes to the user table.
512
      $success = drupal_write_record('users', $account, 'uid');
513
      // Restore the picture object.
514
      $account->picture = $picture;
515
      if ($success === FALSE) {
516
        // The query failed - better to abort the save than risk further
517
        // data loss.
518
        return FALSE;
519
      }
520

    
521
      // Reload user roles if provided.
522
      if ($account->roles != $account->original->roles) {
523
        db_delete('users_roles')
524
          ->condition('uid', $account->uid)
525
          ->execute();
526

    
527
        $query = db_insert('users_roles')->fields(array('uid', 'rid'));
528
        foreach (array_keys($account->roles) as $rid) {
529
          if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
530
            $query->values(array(
531
              'uid' => $account->uid,
532
              'rid' => $rid,
533
            ));
534
          }
535
        }
536
        $query->execute();
537
      }
538

    
539
      // Delete a blocked user's sessions to kick them if they are online.
540
      if ($account->original->status != $account->status && $account->status == 0) {
541
        drupal_session_destroy_uid($account->uid);
542
      }
543

    
544
      // If the password changed, delete all open sessions and recreate
545
      // the current one.
546
      if ($account->pass != $account->original->pass) {
547
        drupal_session_destroy_uid($account->uid);
548
        if ($account->uid == $GLOBALS['user']->uid) {
549
          drupal_session_regenerate();
550
        }
551
      }
552

    
553
      // Save Field data.
554
      field_attach_update('user', $account);
555

    
556
      // Send emails after we have the new user object.
557
      if ($account->status != $account->original->status) {
558
        // The user's status is changing; conditionally send notification email.
559
        $op = $account->status == 1 ? 'status_activated' : 'status_blocked';
560
        _user_mail_notify($op, $account);
561
      }
562

    
563
      // Update $edit with any interim changes to $account.
564
      foreach ($account as $key => $value) {
565
        if (!property_exists($account->original, $key) || $value !== $account->original->$key) {
566
          $edit[$key] = $value;
567
        }
568
      }
569
      user_module_invoke('update', $edit, $account, $category);
570
      module_invoke_all('entity_update', $account, 'user');
571
    }
572
    else {
573
      // Allow 'uid' to be set by the caller. There is no danger of writing an
574
      // existing user as drupal_write_record will do an INSERT.
575
      if (empty($account->uid)) {
576
        $account->uid = db_next_id(db_query('SELECT MAX(uid) FROM {users}')->fetchField());
577
      }
578
      // Allow 'created' to be set by the caller.
579
      if (!isset($account->created)) {
580
        $account->created = REQUEST_TIME;
581
      }
582
      $success = drupal_write_record('users', $account);
583
      if ($success === FALSE) {
584
        // On a failed INSERT some other existing user's uid may be returned.
585
        // We must abort to avoid overwriting their account.
586
        return FALSE;
587
      }
588

    
589
      // Make sure $account is properly initialized.
590
      $account->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
591

    
592
      field_attach_insert('user', $account);
593
      $edit = (array) $account;
594
      user_module_invoke('insert', $edit, $account, $category);
595
      module_invoke_all('entity_insert', $account, 'user');
596

    
597
      // Save user roles. Skip built-in roles, and ones that were already saved
598
      // to the database during hook calls.
599
      $rids_to_skip = array_merge(array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID), db_query('SELECT rid FROM {users_roles} WHERE uid = :uid', array(':uid' => $account->uid))->fetchCol());
600
      if ($rids_to_save = array_diff(array_keys($account->roles), $rids_to_skip)) {
601
        $query = db_insert('users_roles')->fields(array('uid', 'rid'));
602
        foreach ($rids_to_save as $rid) {
603
          $query->values(array(
604
            'uid' => $account->uid,
605
            'rid' => $rid,
606
          ));
607
        }
608
        $query->execute();
609
      }
610
    }
611
    // Clear internal properties.
612
    unset($account->is_new);
613
    unset($account->original);
614
    // Clear the static loading cache.
615
    entity_get_controller('user')->resetCache(array($account->uid));
616

    
617
    return $account;
618
  }
619
  catch (Exception $e) {
620
    $transaction->rollback();
621
    watchdog_exception('user', $e);
622
    throw $e;
623
  }
624
}
625

    
626
/**
627
 * Verify the syntax of the given name.
628
 */
629
function user_validate_name($name) {
630
  if (!$name) {
631
    return t('You must enter a username.');
632
  }
633
  if (substr($name, 0, 1) == ' ') {
634
    return t('The username cannot begin with a space.');
635
  }
636
  if (substr($name, -1) == ' ') {
637
    return t('The username cannot end with a space.');
638
  }
639
  if (strpos($name, '  ') !== FALSE) {
640
    return t('The username cannot contain multiple spaces in a row.');
641
  }
642
  if (preg_match('/[^\x{80}-\x{F7} a-z0-9@_.\'-]/i', $name)) {
643
    return t('The username contains an illegal character.');
644
  }
645
  if (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
646
                  '\x{AD}' .                // Soft-hyphen
647
                  '\x{2000}-\x{200F}' .     // Various space characters
648
                  '\x{2028}-\x{202F}' .     // Bidirectional text overrides
649
                  '\x{205F}-\x{206F}' .     // Various text hinting characters
650
                  '\x{FEFF}' .              // Byte order mark
651
                  '\x{FF01}-\x{FF60}' .     // Full-width latin
652
                  '\x{FFF9}-\x{FFFD}' .     // Replacement characters
653
                  '\x{0}-\x{1F}]/u',        // NULL byte and control characters
654
                  $name)) {
655
    return t('The username contains an illegal character.');
656
  }
657
  if (drupal_strlen($name) > USERNAME_MAX_LENGTH) {
658
    return t('The username %name is too long: it must be %max characters or less.', array('%name' => $name, '%max' => USERNAME_MAX_LENGTH));
659
  }
660
}
661

    
662
/**
663
 * Validates a user's email address.
664
 *
665
 * Checks that a user's email address exists and follows all standard
666
 * validation rules. Returns error messages when the address is invalid.
667
 *
668
 * @param $mail
669
 *   A user's email address.
670
 *
671
 * @return
672
 *   If the address is invalid, a human-readable error message is returned.
673
 *   If the address is valid, nothing is returned.
674
 */
675
function user_validate_mail($mail) {
676
  if (!$mail) {
677
    return t('You must enter an e-mail address.');
678
  }
679
  if (!valid_email_address($mail)) {
680
    return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
681
  }
682
}
683

    
684
/**
685
 * Validates an image uploaded by a user.
686
 *
687
 * @see user_account_form()
688
 */
689
function user_validate_picture(&$form, &$form_state) {
690
  // If required, validate the uploaded picture.
691
  $validators = array(
692
    'file_validate_is_image' => array(),
693
    'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')),
694
    'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),
695
  );
696

    
697
  // Save the file as a temporary file.
698
  $file = file_save_upload('picture_upload', $validators);
699
  if ($file === FALSE) {
700
    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'))));
701
  }
702
  elseif ($file !== NULL) {
703
    $form_state['values']['picture_upload'] = $file;
704
  }
705
}
706

    
707
/**
708
 * Generate a random alphanumeric password.
709
 */
710
function user_password($length = 10) {
711
  // This variable contains the list of allowable characters for the
712
  // password. Note that the number 0 and the letter 'O' have been
713
  // removed to avoid confusion between the two. The same is true
714
  // of 'I', 1, and 'l'.
715
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
716

    
717
  // Zero-based count of characters in the allowable list:
718
  $len = strlen($allowable_characters) - 1;
719

    
720
  // Declare the password as a blank string.
721
  $pass = '';
722

    
723
  // Loop the number of times specified by $length.
724
  for ($i = 0; $i < $length; $i++) {
725
    do {
726
      // Find a secure random number within the range needed.
727
      $index = ord(drupal_random_bytes(1));
728
    } while ($index > $len);
729

    
730
    // Each iteration, pick a random character from the
731
    // allowable string and append it to the password:
732
    $pass .= $allowable_characters[$index];
733
  }
734

    
735
  return $pass;
736
}
737

    
738
/**
739
 * Determine the permissions for one or more roles.
740
 *
741
 * @param $roles
742
 *   An array whose keys are the role IDs of interest, such as $user->roles.
743
 *
744
 * @return
745
 *   If $roles is a non-empty array, an array indexed by role ID is returned.
746
 *   Each value is an array whose keys are the permission strings for the given
747
 *   role ID. If $roles is empty nothing is returned.
748
 */
749
function user_role_permissions($roles = array()) {
750
  $cache = &drupal_static(__FUNCTION__, array());
751

    
752
  $role_permissions = $fetch = array();
753

    
754
  if ($roles) {
755
    foreach ($roles as $rid => $name) {
756
      if (isset($cache[$rid])) {
757
        $role_permissions[$rid] = $cache[$rid];
758
      }
759
      else {
760
        // Add this rid to the list of those needing to be fetched.
761
        $fetch[] = $rid;
762
        // Prepare in case no permissions are returned.
763
        $cache[$rid] = array();
764
      }
765
    }
766

    
767
    if ($fetch) {
768
      // Get from the database permissions that were not in the static variable.
769
      // Only role IDs with at least one permission assigned will return rows.
770
      $result = db_query("SELECT rid, permission FROM {role_permission} WHERE rid IN (:fetch)", array(':fetch' => $fetch));
771

    
772
      foreach ($result as $row) {
773
        $cache[$row->rid][$row->permission] = TRUE;
774
      }
775
      foreach ($fetch as $rid) {
776
        // For every rid, we know we at least assigned an empty array.
777
        $role_permissions[$rid] = $cache[$rid];
778
      }
779
    }
780
  }
781

    
782
  return $role_permissions;
783
}
784

    
785
/**
786
 * Determine whether the user has a given privilege.
787
 *
788
 * @param $string
789
 *   The permission, such as "administer nodes", being checked for.
790
 * @param $account
791
 *   (optional) The account to check, if not given use currently logged in user.
792
 *
793
 * @return
794
 *   Boolean TRUE if the current user has the requested permission.
795
 *
796
 * All permission checks in Drupal should go through this function. This
797
 * way, we guarantee consistent behavior, and ensure that the superuser
798
 * can perform all actions.
799
 */
800
function user_access($string, $account = NULL) {
801
  global $user;
802

    
803
  if (!isset($account)) {
804
    $account = $user;
805
  }
806

    
807
  // User #1 has all privileges:
808
  if ($account->uid == 1) {
809
    return TRUE;
810
  }
811

    
812
  // To reduce the number of SQL queries, we cache the user's permissions
813
  // in a static variable.
814
  // Use the advanced drupal_static() pattern, since this is called very often.
815
  static $drupal_static_fast;
816
  if (!isset($drupal_static_fast)) {
817
    $drupal_static_fast['perm'] = &drupal_static(__FUNCTION__);
818
  }
819
  $perm = &$drupal_static_fast['perm'];
820
  if (!isset($perm[$account->uid])) {
821
    $role_permissions = user_role_permissions($account->roles);
822

    
823
    $perms = array();
824
    foreach ($role_permissions as $one_role) {
825
      $perms += $one_role;
826
    }
827
    $perm[$account->uid] = $perms;
828
  }
829

    
830
  return isset($perm[$account->uid][$string]);
831
}
832

    
833
/**
834
 * Checks for usernames blocked by user administration.
835
 *
836
 * @param $name
837
 *   A string containing a name of the user.
838
 *
839
 * @return
840
 *   Object with property 'name' (the user name), if the user is blocked;
841
 *   FALSE if the user is not blocked.
842
 */
843
function user_is_blocked($name) {
844
  return db_select('users')
845
    ->fields('users', array('name'))
846
    ->condition('name', db_like($name), 'LIKE')
847
    ->condition('status', 0)
848
    ->execute()->fetchObject();
849
}
850

    
851
/**
852
 * Checks if a user has a role.
853
 *
854
 * @param int $rid
855
 *   A role ID.
856
 *
857
 * @param object|null $account
858
 *   (optional) A user account. Defaults to the current user.
859
 *
860
 * @return bool
861
 *   TRUE if the user has the role, or FALSE if not.
862
 */
863
function user_has_role($rid, $account = NULL) {
864
  if (!$account) {
865
    $account = $GLOBALS['user'];
866
  }
867

    
868
  return isset($account->roles[$rid]);
869
}
870

    
871
/**
872
 * Implements hook_permission().
873
 */
874
function user_permission() {
875
  return array(
876
    'administer permissions' =>  array(
877
      'title' => t('Administer permissions'),
878
      'restrict access' => TRUE,
879
    ),
880
    'administer users' => array(
881
      'title' => t('Administer users'),
882
      'restrict access' => TRUE,
883
    ),
884
    'access user profiles' => array(
885
      'title' => t('View user profiles'),
886
    ),
887
    'change own username' => array(
888
      'title' => t('Change own username'),
889
    ),
890
    'cancel account' => array(
891
      'title' => t('Cancel own user account'),
892
      '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'))),
893
    ),
894
    'select account cancellation method' => array(
895
      'title' => t('Select method for cancelling own account'),
896
      'restrict access' => TRUE,
897
    ),
898
  );
899
}
900

    
901
/**
902
 * Implements hook_file_download().
903
 *
904
 * Ensure that user pictures (avatars) are always downloadable.
905
 */
906
function user_file_download($uri) {
907
  if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
908
    $info = image_get_info($uri);
909
    return array('Content-Type' => $info['mime_type']);
910
  }
911
}
912

    
913
/**
914
 * Implements hook_file_move().
915
 */
916
function user_file_move($file, $source) {
917
  // If a user's picture is replaced with a new one, update the record in
918
  // the users table.
919
  if (isset($file->fid) && isset($source->fid) && $file->fid != $source->fid) {
920
    db_update('users')
921
      ->fields(array(
922
        'picture' => $file->fid,
923
      ))
924
      ->condition('picture', $source->fid)
925
      ->execute();
926
  }
927
}
928

    
929
/**
930
 * Implements hook_file_delete().
931
 */
932
function user_file_delete($file) {
933
  // Remove any references to the file.
934
  db_update('users')
935
    ->fields(array('picture' => 0))
936
    ->condition('picture', $file->fid)
937
    ->execute();
938
}
939

    
940
/**
941
 * Implements hook_search_info().
942
 */
943
function user_search_info() {
944
  return array(
945
    'title' => 'Users',
946
  );
947
}
948

    
949
/**
950
 * Implements hook_search_access().
951
 */
952
function user_search_access() {
953
  return user_access('access user profiles');
954
}
955

    
956
/**
957
 * Implements hook_search_execute().
958
 */
959
function user_search_execute($keys = NULL, $conditions = NULL) {
960
  $find = array();
961
  // Escape for LIKE matching.
962
  $keys = db_like($keys);
963
  // Replace wildcards with MySQL/PostgreSQL wildcards.
964
  $keys = preg_replace('!\*+!', '%', $keys);
965
  $query = db_select('users')->extend('PagerDefault');
966
  $query->fields('users', array('uid'));
967
  if (user_access('administer users')) {
968
    // Administrators can also search in the otherwise private email field,
969
    // and they don't need to be restricted to only active users.
970
    $query->fields('users', array('mail'));
971
    $query->condition(db_or()->
972
      condition('name', '%' . $keys . '%', 'LIKE')->
973
      condition('mail', '%' . $keys . '%', 'LIKE'));
974
  }
975
  else {
976
    // Regular users can only search via usernames, and we do not show them
977
    // blocked accounts.
978
    $query->condition('name', '%' . $keys . '%', 'LIKE')
979
      ->condition('status', 1);
980
  }
981
  $uids = $query
982
    ->limit(15)
983
    ->execute()
984
    ->fetchCol();
985
  $accounts = user_load_multiple($uids);
986

    
987
  $results = array();
988
  foreach ($accounts as $account) {
989
    $result = array(
990
      'title' => format_username($account),
991
      'link' => url('user/' . $account->uid, array('absolute' => TRUE)),
992
    );
993
    if (user_access('administer users')) {
994
      $result['title'] .= ' (' . $account->mail . ')';
995
    }
996
    $results[] = $result;
997
  }
998

    
999
  return $results;
1000
}
1001

    
1002
/**
1003
 * Implements hook_element_info().
1004
 */
1005
function user_element_info() {
1006
  $types['user_profile_category'] = array(
1007
    '#theme_wrappers' => array('user_profile_category'),
1008
  );
1009
  $types['user_profile_item'] = array(
1010
    '#theme' => 'user_profile_item',
1011
  );
1012
  return $types;
1013
}
1014

    
1015
/**
1016
 * Implements hook_user_view().
1017
 */
1018
function user_user_view($account) {
1019
  $account->content['user_picture'] = array(
1020
    '#markup' => theme('user_picture', array('account' => $account)),
1021
    '#weight' => -10,
1022
  );
1023
  if (!isset($account->content['summary'])) {
1024
    $account->content['summary'] = array();
1025
  }
1026
  $account->content['summary'] += array(
1027
    '#type' => 'user_profile_category',
1028
    '#attributes' => array('class' => array('user-member')),
1029
    '#weight' => 5,
1030
    '#title' => t('History'),
1031
  );
1032
  $account->content['summary']['member_for'] = array(
1033
    '#type' => 'user_profile_item',
1034
    '#title' => t('Member for'),
1035
    '#markup' => format_interval(REQUEST_TIME - $account->created),
1036
  );
1037
}
1038

    
1039
/**
1040
 * Helper function to add default user account fields to user registration and edit form.
1041
 *
1042
 * @see user_account_form_validate()
1043
 * @see user_validate_current_pass()
1044
 * @see user_validate_picture()
1045
 * @see user_validate_mail()
1046
 */
1047
function user_account_form(&$form, &$form_state) {
1048
  global $user;
1049

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

    
1053
  $admin = user_access('administer users');
1054

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

    
1057
  // Account information.
1058
  $form['account'] = array(
1059
    '#type'   => 'container',
1060
    '#weight' => -10,
1061
  );
1062
  // Only show name field on registration form or user can change own username.
1063
  $form['account']['name'] = array(
1064
    '#type' => 'textfield',
1065
    '#title' => t('Username'),
1066
    '#maxlength' => USERNAME_MAX_LENGTH,
1067
    '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'),
1068
    '#required' => TRUE,
1069
    '#attributes' => array('class' => array('username')),
1070
    '#default_value' => (!$register ? $account->name : ''),
1071
    '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin),
1072
    '#weight' => -10,
1073
  );
1074

    
1075
  $form['account']['mail'] = array(
1076
    '#type' => 'textfield',
1077
    '#title' => t('E-mail address'),
1078
    '#maxlength' => EMAIL_MAX_LENGTH,
1079
    '#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.'),
1080
    '#required' => TRUE,
1081
    '#default_value' => (!$register ? $account->mail : ''),
1082
  );
1083

    
1084
  // Display password field only for existing users or when user is allowed to
1085
  // assign a password during registration.
1086
  if (!$register) {
1087
    $form['account']['pass'] = array(
1088
      '#type' => 'password_confirm',
1089
      '#size' => 25,
1090
      '#description' => t('To change the current user password, enter the new password in both fields.'),
1091
    );
1092
    // To skip the current password field, the user must have logged in via a
1093
    // one-time link and have the token in the URL.
1094
    $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]);
1095
    $protected_values = array();
1096
    $current_pass_description = '';
1097
    // The user may only change their own password without their current
1098
    // password if they logged in via a one-time login link.
1099
    if (!$pass_reset) {
1100
      $protected_values['mail'] = $form['account']['mail']['#title'];
1101
      $protected_values['pass'] = t('Password');
1102
      $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
1103
      $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));
1104
    }
1105
    // The user must enter their current password to change to a new one.
1106
    if ($user->uid == $account->uid) {
1107
      $form['account']['current_pass_required_values'] = array(
1108
        '#type' => 'value',
1109
        '#value' => $protected_values,
1110
      );
1111
      $form['account']['current_pass'] = array(
1112
        '#type' => 'password',
1113
        '#title' => t('Current password'),
1114
        '#size' => 25,
1115
        '#access' => !empty($protected_values),
1116
        '#description' => $current_pass_description,
1117
        '#weight' => -5,
1118
        // Do not let web browsers remember this password, since we are trying
1119
        // to confirm that the person submitting the form actually knows the
1120
        // current one.
1121
        '#attributes' => array('autocomplete' => 'off'),
1122
      );
1123
      $form['#validate'][] = 'user_validate_current_pass';
1124
    }
1125
  }
1126
  elseif (!variable_get('user_email_verification', TRUE) || $admin) {
1127
    $form['account']['pass'] = array(
1128
      '#type' => 'password_confirm',
1129
      '#size' => 25,
1130
      '#description' => t('Provide a password for the new account in both fields.'),
1131
      '#required' => TRUE,
1132
    );
1133
  }
1134

    
1135
  if ($admin) {
1136
    $status = isset($account->status) ? $account->status : 1;
1137
  }
1138
  else {
1139
    $status = $register ? variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS : $account->status;
1140
  }
1141
  $form['account']['status'] = array(
1142
    '#type' => 'radios',
1143
    '#title' => t('Status'),
1144
    '#default_value' => $status,
1145
    '#options' => array(t('Blocked'), t('Active')),
1146
    '#access' => $admin,
1147
  );
1148

    
1149
  $roles = array_map('check_plain', user_roles(TRUE));
1150
  // The disabled checkbox subelement for the 'authenticated user' role
1151
  // must be generated separately and added to the checkboxes element,
1152
  // because of a limitation in Form API not supporting a single disabled
1153
  // checkbox within a set of checkboxes.
1154
  // @todo This should be solved more elegantly. See issue #119038.
1155
  $checkbox_authenticated = array(
1156
    '#type' => 'checkbox',
1157
    '#title' => $roles[DRUPAL_AUTHENTICATED_RID],
1158
    '#default_value' => TRUE,
1159
    '#disabled' => TRUE,
1160
  );
1161
  unset($roles[DRUPAL_AUTHENTICATED_RID]);
1162
  $form['account']['roles'] = array(
1163
    '#type' => 'checkboxes',
1164
    '#title' => t('Roles'),
1165
    '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()),
1166
    '#options' => $roles,
1167
    '#access' => $roles && user_access('administer permissions'),
1168
    DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
1169
  );
1170

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

    
1177
  // Signature.
1178
  $form['signature_settings'] = array(
1179
    '#type' => 'fieldset',
1180
    '#title' => t('Signature settings'),
1181
    '#weight' => 1,
1182
    '#access' => (!$register && variable_get('user_signatures', 0)),
1183
  );
1184

    
1185
  $form['signature_settings']['signature'] = array(
1186
    '#type' => 'text_format',
1187
    '#title' => t('Signature'),
1188
    '#default_value' => isset($account->signature) ? $account->signature : '',
1189
    '#description' => t('Your signature will be publicly displayed at the end of your comments.'),
1190
    '#format' => isset($account->signature_format) ? $account->signature_format : NULL,
1191
  );
1192

    
1193
  // Picture/avatar.
1194
  $form['picture'] = array(
1195
    '#type' => 'fieldset',
1196
    '#title' => t('Picture'),
1197
    '#weight' => 1,
1198
    '#access' => (!$register && variable_get('user_pictures', 0)),
1199
  );
1200
  $form['picture']['picture'] = array(
1201
    '#type' => 'value',
1202
    '#value' => isset($account->picture) ? $account->picture : NULL,
1203
  );
1204
  $form['picture']['picture_current'] = array(
1205
    '#markup' => theme('user_picture', array('account' => $account)),
1206
  );
1207
  $form['picture']['picture_delete'] = array(
1208
    '#type' => 'checkbox',
1209
    '#title' => t('Delete picture'),
1210
    '#access' => !empty($account->picture->fid),
1211
    '#description' => t('Check this box to delete your current picture.'),
1212
  );
1213
  $form['picture']['picture_upload'] = array(
1214
    '#type' => 'file',
1215
    '#title' => t('Upload picture'),
1216
    '#size' => 48,
1217
    '#description' => t('Your virtual face or picture. Pictures larger than @dimensions pixels will be scaled down.', array('@dimensions' => variable_get('user_picture_dimensions', '85x85'))) . ' ' . filter_xss_admin(variable_get('user_picture_guidelines', '')),
1218
  );
1219
  $form['#validate'][] = 'user_validate_picture';
1220
}
1221

    
1222
/**
1223
 * Form validation handler for the current password on the user_account_form().
1224
 *
1225
 * @see user_account_form()
1226
 */
1227
function user_validate_current_pass(&$form, &$form_state) {
1228
  $account = $form['#user'];
1229
  foreach ($form_state['values']['current_pass_required_values'] as $key => $name) {
1230
    // This validation only works for required textfields (like mail) or
1231
    // form values like password_confirm that have their own validation
1232
    // that prevent them from being empty if they are changed.
1233
    if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) {
1234
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
1235
      $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account);
1236
      if ($current_pass_failed) {
1237
        form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name)));
1238
        form_set_error($key);
1239
      }
1240
      // We only need to check the password once.
1241
      break;
1242
    }
1243
  }
1244
}
1245

    
1246
/**
1247
 * Form validation handler for user_account_form().
1248
 *
1249
 * @see user_account_form()
1250
 */
1251
function user_account_form_validate($form, &$form_state) {
1252
  if ($form['#user_category'] == 'account' || $form['#user_category'] == 'register') {
1253
    $account = $form['#user'];
1254
    // Validate new or changing username.
1255
    if (isset($form_state['values']['name'])) {
1256
      if ($error = user_validate_name($form_state['values']['name'])) {
1257
        form_set_error('name', $error);
1258
      }
1259
      elseif ((bool) db_select('users')->fields('users', array('uid'))->condition('uid', $account->uid, '<>')->condition('name', db_like($form_state['values']['name']), 'LIKE')->range(0, 1)->execute()->fetchField()) {
1260
        form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name'])));
1261
      }
1262
    }
1263

    
1264
    // Trim whitespace from mail, to prevent confusing 'e-mail not valid'
1265
    // warnings often caused by cutting and pasting.
1266
    $mail = trim($form_state['values']['mail']);
1267
    form_set_value($form['account']['mail'], $mail, $form_state);
1268

    
1269
    // Validate the e-mail address, and check if it is taken by an existing user.
1270
    if ($error = user_validate_mail($form_state['values']['mail'])) {
1271
      form_set_error('mail', $error);
1272
    }
1273
    elseif ((bool) db_select('users')->fields('users', array('uid'))->condition('uid', $account->uid, '<>')->condition('mail', db_like($form_state['values']['mail']), 'LIKE')->range(0, 1)->execute()->fetchField()) {
1274
      // Format error message dependent on whether the user is logged in or not.
1275
      if ($GLOBALS['user']->uid) {
1276
        form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $form_state['values']['mail'])));
1277
      }
1278
      else {
1279
        form_set_error('mail', t('The e-mail address %email is already registered. <a href="@password">Have you forgotten your password?</a>', array('%email' => $form_state['values']['mail'], '@password' => url('user/password'))));
1280
      }
1281
    }
1282

    
1283
    // Make sure the signature isn't longer than the size of the database field.
1284
    // Signatures are disabled by default, so make sure it exists first.
1285
    if (isset($form_state['values']['signature'])) {
1286
      // Move text format for user signature into 'signature_format'.
1287
      $form_state['values']['signature_format'] = $form_state['values']['signature']['format'];
1288
      // Move text value for user signature into 'signature'.
1289
      $form_state['values']['signature'] = $form_state['values']['signature']['value'];
1290

    
1291
      $user_schema = drupal_get_schema('users');
1292
      if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) {
1293
        form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length'])));
1294
      }
1295
    }
1296
  }
1297
}
1298

    
1299
/**
1300
 * Implements hook_user_presave().
1301
 */
1302
function user_user_presave(&$edit, $account, $category) {
1303
  if ($category == 'account' || $category == 'register') {
1304
    if (!empty($edit['picture_upload'])) {
1305
      $edit['picture'] = $edit['picture_upload'];
1306
    }
1307
    // Delete picture if requested, and if no replacement picture was given.
1308
    elseif (!empty($edit['picture_delete'])) {
1309
      $edit['picture'] = NULL;
1310
    }
1311
  }
1312

    
1313
  // Filter out roles with empty values to avoid granting extra roles when
1314
  // processing custom form submissions.
1315
  if (isset($edit['roles'])) {
1316
    $edit['roles'] = array_filter($edit['roles']);
1317
  }
1318

    
1319
  // Move account cancellation information into $user->data.
1320
  foreach (array('user_cancel_method', 'user_cancel_notify') as $key) {
1321
    if (isset($edit[$key])) {
1322
      $edit['data'][$key] = $edit[$key];
1323
    }
1324
  }
1325
}
1326

    
1327
/**
1328
 * Implements hook_user_categories().
1329
 */
1330
function user_user_categories() {
1331
  return array(array(
1332
    'name' => 'account',
1333
    'title' => t('Account settings'),
1334
    'weight' => 1,
1335
  ));
1336
}
1337

    
1338
function user_login_block($form) {
1339
  $form['#action'] = url(current_path(), array('query' => drupal_get_destination(), 'external' => FALSE));
1340
  $form['#id'] = 'user-login-form';
1341
  $form['#validate'] = user_login_default_validators();
1342
  $form['#submit'][] = 'user_login_submit';
1343
  $form['name'] = array('#type' => 'textfield',
1344
    '#title' => t('Username'),
1345
    '#maxlength' => USERNAME_MAX_LENGTH,
1346
    '#size' => 15,
1347
    '#required' => TRUE,
1348
  );
1349
  $form['pass'] = array('#type' => 'password',
1350
    '#title' => t('Password'),
1351
    '#size' => 15,
1352
    '#required' => TRUE,
1353
  );
1354
  $form['actions'] = array('#type' => 'actions');
1355
  $form['actions']['submit'] = array('#type' => 'submit',
1356
    '#value' => t('Log in'),
1357
  );
1358
  $items = array();
1359
  if (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)) {
1360
    $items[] = l(t('Create new account'), 'user/register', array('attributes' => array('title' => t('Create a new user account.'))));
1361
  }
1362
  $items[] = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
1363
  $form['links'] = array('#markup' => theme('item_list', array('items' => $items)));
1364
  return $form;
1365
}
1366

    
1367
/**
1368
 * Implements hook_block_info().
1369
 */
1370
function user_block_info() {
1371
  global $user;
1372

    
1373
  $blocks['login']['info'] = t('User login');
1374
  // Not worth caching.
1375
  $blocks['login']['cache'] = DRUPAL_NO_CACHE;
1376

    
1377
  $blocks['new']['info'] = t('Who\'s new');
1378
  $blocks['new']['properties']['administrative'] = TRUE;
1379

    
1380
  // Too dynamic to cache.
1381
  $blocks['online']['info'] = t('Who\'s online');
1382
  $blocks['online']['cache'] = DRUPAL_NO_CACHE;
1383
  $blocks['online']['properties']['administrative'] = TRUE;
1384

    
1385
  return $blocks;
1386
}
1387

    
1388
/**
1389
 * Implements hook_block_configure().
1390
 */
1391
function user_block_configure($delta = '') {
1392
  global $user;
1393

    
1394
  switch ($delta) {
1395
    case 'new':
1396
      $form['user_block_whois_new_count'] = array(
1397
        '#type' => 'select',
1398
        '#title' => t('Number of users to display'),
1399
        '#default_value' => variable_get('user_block_whois_new_count', 5),
1400
        '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
1401
      );
1402
      return $form;
1403

    
1404
    case 'online':
1405
      $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
1406
      $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.'));
1407
      $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.'));
1408
      return $form;
1409
  }
1410
}
1411

    
1412
/**
1413
 * Implements hook_block_save().
1414
 */
1415
function user_block_save($delta = '', $edit = array()) {
1416
  global $user;
1417

    
1418
  switch ($delta) {
1419
    case 'new':
1420
      variable_set('user_block_whois_new_count', $edit['user_block_whois_new_count']);
1421
      break;
1422

    
1423
    case 'online':
1424
      variable_set('user_block_seconds_online', $edit['user_block_seconds_online']);
1425
      variable_set('user_block_max_list_count', $edit['user_block_max_list_count']);
1426
      break;
1427
  }
1428
}
1429

    
1430
/**
1431
 * Implements hook_block_view().
1432
 */
1433
function user_block_view($delta = '') {
1434
  global $user;
1435

    
1436
  $block = array();
1437

    
1438
  switch ($delta) {
1439
    case 'login':
1440
      // For usability's sake, avoid showing two login forms on one page.
1441
      if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
1442

    
1443
        $block['subject'] = t('User login');
1444
        $block['content'] = drupal_get_form('user_login_block');
1445
      }
1446
      return $block;
1447

    
1448
    case 'new':
1449
      if (user_access('access content')) {
1450
        // Retrieve a list of new users who have subsequently accessed the site successfully.
1451
        $items = 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))->fetchAll();
1452
        $output = theme('user_list', array('users' => $items));
1453

    
1454
        $block['subject'] = t('Who\'s new');
1455
        $block['content'] = $output;
1456
      }
1457
      return $block;
1458

    
1459
    case 'online':
1460
      if (user_access('access content')) {
1461
        // Count users active within the defined period.
1462
        $interval = REQUEST_TIME - variable_get('user_block_seconds_online', 900);
1463

    
1464
        // Perform database queries to gather online user lists. We use s.timestamp
1465
        // rather than u.access because it is much faster.
1466
        $authenticated_count = db_query("SELECT COUNT(DISTINCT s.uid) FROM {sessions} s WHERE s.timestamp >= :timestamp AND s.uid > 0", array(':timestamp' => $interval))->fetchField();
1467

    
1468
        $output = '<p>' . format_plural($authenticated_count, 'There is currently 1 user online.', 'There are currently @count users online.') . '</p>';
1469

    
1470
        // Display a list of currently online users.
1471
        $max_users = variable_get('user_block_max_list_count', 10);
1472
        if ($authenticated_count && $max_users) {
1473
          $items = db_query_range('SELECT u.uid, u.name, MAX(s.timestamp) AS max_timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.timestamp >= :interval AND s.uid > 0 GROUP BY u.uid, u.name ORDER BY max_timestamp DESC', 0, $max_users, array(':interval' => $interval))->fetchAll();
1474
          $output .= theme('user_list', array('users' => $items));
1475
        }
1476

    
1477
        $block['subject'] = t('Who\'s online');
1478
        $block['content'] = $output;
1479
      }
1480
      return $block;
1481
  }
1482
}
1483

    
1484
/**
1485
 * Process variables for user-picture.tpl.php.
1486
 *
1487
 * The $variables array contains the following arguments:
1488
 * - $account: A user, node or comment object with 'name', 'uid' and 'picture'
1489
 *   fields.
1490
 *
1491
 * @see user-picture.tpl.php
1492
 */
1493
function template_preprocess_user_picture(&$variables) {
1494
  $variables['user_picture'] = '';
1495
  if (variable_get('user_pictures', 0)) {
1496
    $account = $variables['account'];
1497
    if (!empty($account->picture)) {
1498
      // @TODO: Ideally this function would only be passed file objects, but
1499
      // since there's a lot of legacy code that JOINs the {users} table to
1500
      // {node} or {comments} and passes the results into this function if we
1501
      // a numeric value in the picture field we'll assume it's a file id
1502
      // and load it for them. Once we've got user_load_multiple() and
1503
      // comment_load_multiple() functions the user module will be able to load
1504
      // the picture files in mass during the object's load process.
1505
      if (is_numeric($account->picture)) {
1506
        $account->picture = file_load($account->picture);
1507
      }
1508
      if (!empty($account->picture->uri)) {
1509
        $filepath = $account->picture->uri;
1510
      }
1511
    }
1512
    elseif (variable_get('user_picture_default', '')) {
1513
      $filepath = variable_get('user_picture_default', '');
1514
    }
1515
    if (isset($filepath)) {
1516
      $alt = t("@user's picture", array('@user' => format_username($account)));
1517
      // If the image does not have a valid Drupal scheme (for eg. HTTP),
1518
      // don't load image styles.
1519
      if (module_exists('image') && file_valid_uri($filepath) && $style = variable_get('user_picture_style', '')) {
1520
        $variables['user_picture'] = theme('image_style', array('style_name' => $style, 'path' => $filepath, 'alt' => $alt, 'title' => $alt));
1521
      }
1522
      else {
1523
        $variables['user_picture'] = theme('image', array('path' => $filepath, 'alt' => $alt, 'title' => $alt));
1524
      }
1525
      if (!empty($account->uid) && user_access('access user profiles')) {
1526
        $attributes = array('attributes' => array('title' => t('View user profile.')), 'html' => TRUE);
1527
        $variables['user_picture'] = l($variables['user_picture'], "user/$account->uid", $attributes);
1528
      }
1529
    }
1530
  }
1531
}
1532

    
1533
/**
1534
 * Returns HTML for a list of users.
1535
 *
1536
 * @param $variables
1537
 *   An associative array containing:
1538
 *   - users: An array with user objects. Should contain at least the name and
1539
 *     uid.
1540
 *   - title: (optional) Title to pass on to theme_item_list().
1541
 *
1542
 * @ingroup themeable
1543
 */
1544
function theme_user_list($variables) {
1545
  $users = $variables['users'];
1546
  $title = $variables['title'];
1547
  $items = array();
1548

    
1549
  if (!empty($users)) {
1550
    foreach ($users as $user) {
1551
      $items[] = theme('username', array('account' => $user));
1552
    }
1553
  }
1554
  return theme('item_list', array('items' => $items, 'title' => $title));
1555
}
1556

    
1557
/**
1558
 * Determines if the current user is anonymous.
1559
 *
1560
 * @return bool
1561
 *   TRUE if the user is anonymous, FALSE if the user is authenticated.
1562
 */
1563
function user_is_anonymous() {
1564
  // Menu administrators can see items for anonymous when administering.
1565
  return !$GLOBALS['user']->uid || !empty($GLOBALS['menu_admin']);
1566
}
1567

    
1568
/**
1569
 * Determines if the current user is logged in.
1570
 *
1571
 * @return bool
1572
 *   TRUE if the user is logged in, FALSE if the user is anonymous.
1573
 */
1574
function user_is_logged_in() {
1575
  return (bool) $GLOBALS['user']->uid;
1576
}
1577

    
1578
/**
1579
 * Determines if the current user has access to the user registration page.
1580
 *
1581
 * @return bool
1582
 *   TRUE if the user is not already logged in and can register for an account.
1583
 */
1584
function user_register_access() {
1585
  return user_is_anonymous() && variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
1586
}
1587

    
1588
/**
1589
 * User view access callback.
1590
 *
1591
 * @param $account
1592
 *   Can either be a full user object or a $uid.
1593
 */
1594
function user_view_access($account) {
1595
  $uid = is_object($account) ? $account->uid : (int) $account;
1596

    
1597
  // Never allow access to view the anonymous user account.
1598
  if ($uid) {
1599
    // Admins can view all, users can view own profiles at all times.
1600
    if ($GLOBALS['user']->uid == $uid || user_access('administer users')) {
1601
      return TRUE;
1602
    }
1603
    elseif (user_access('access user profiles')) {
1604
      // At this point, load the complete account object.
1605
      if (!is_object($account)) {
1606
        $account = user_load($uid);
1607
      }
1608
      return (is_object($account) && $account->status);
1609
    }
1610
  }
1611
  return FALSE;
1612
}
1613

    
1614
/**
1615
 * Access callback for user account editing.
1616
 */
1617
function user_edit_access($account) {
1618
  return (($GLOBALS['user']->uid == $account->uid) || user_access('administer users')) && $account->uid > 0;
1619
}
1620

    
1621
/**
1622
 * Menu access callback; limit access to account cancellation pages.
1623
 *
1624
 * Limit access to users with the 'cancel account' permission or administrative
1625
 * users, and prevent the anonymous user from cancelling the account.
1626
 */
1627
function user_cancel_access($account) {
1628
  return ((($GLOBALS['user']->uid == $account->uid) && user_access('cancel account')) || user_access('administer users')) && $account->uid > 0;
1629
}
1630

    
1631
/**
1632
 * Implements hook_menu().
1633
 */
1634
function user_menu() {
1635
  $items['user/autocomplete'] = array(
1636
    'title' => 'User autocomplete',
1637
    'page callback' => 'user_autocomplete',
1638
    'access callback' => 'user_access',
1639
    'access arguments' => array('access user profiles'),
1640
    'type' => MENU_CALLBACK,
1641
    'file' => 'user.pages.inc',
1642
  );
1643

    
1644
  // Registration and login pages.
1645
  $items['user'] = array(
1646
    'title' => 'User account',
1647
    'title callback' => 'user_menu_title',
1648
    'page callback' => 'user_page',
1649
    'access callback' => TRUE,
1650
    'file' => 'user.pages.inc',
1651
    'weight' => -10,
1652
    'menu_name' => 'user-menu',
1653
  );
1654

    
1655
  $items['user/login'] = array(
1656
    'title' => 'Log in',
1657
    'access callback' => 'user_is_anonymous',
1658
    'type' => MENU_DEFAULT_LOCAL_TASK,
1659
  );
1660

    
1661
  $items['user/register'] = array(
1662
    'title' => 'Create new account',
1663
    'page callback' => 'drupal_get_form',
1664
    'page arguments' => array('user_register_form'),
1665
    'access callback' => 'user_register_access',
1666
    'type' => MENU_LOCAL_TASK,
1667
  );
1668

    
1669
  $items['user/password'] = array(
1670
    'title' => 'Request new password',
1671
    'page callback' => 'drupal_get_form',
1672
    'page arguments' => array('user_pass'),
1673
    'access callback' => TRUE,
1674
    'type' => MENU_LOCAL_TASK,
1675
    'file' => 'user.pages.inc',
1676
  );
1677
  $items['user/reset/%/%/%'] = array(
1678
    'title' => 'Reset password',
1679
    'page callback' => 'drupal_get_form',
1680
    'page arguments' => array('user_pass_reset', 2, 3, 4),
1681
    'access callback' => TRUE,
1682
    'type' => MENU_CALLBACK,
1683
    'file' => 'user.pages.inc',
1684
  );
1685

    
1686
  $items['user/logout'] = array(
1687
    'title' => 'Log out',
1688
    'access callback' => 'user_is_logged_in',
1689
    'page callback' => 'user_logout',
1690
    'weight' => 10,
1691
    'menu_name' => 'user-menu',
1692
    'file' => 'user.pages.inc',
1693
  );
1694

    
1695
  // User listing pages.
1696
  $items['admin/people'] = array(
1697
    'title' => 'People',
1698
    'description' => 'Manage user accounts, roles, and permissions.',
1699
    'page callback' => 'user_admin',
1700
    'page arguments' => array('list'),
1701
    'access arguments' => array('administer users'),
1702
    'position' => 'left',
1703
    'weight' => -4,
1704
    'file' => 'user.admin.inc',
1705
  );
1706
  $items['admin/people/people'] = array(
1707
    'title' => 'List',
1708
    'description' => 'Find and manage people interacting with your site.',
1709
    'access arguments' => array('administer users'),
1710
    'type' => MENU_DEFAULT_LOCAL_TASK,
1711
    'weight' => -10,
1712
    'file' => 'user.admin.inc',
1713
  );
1714

    
1715
  // Permissions and role forms.
1716
  $items['admin/people/permissions'] = array(
1717
    'title' => 'Permissions',
1718
    'description' => 'Determine access to features by selecting permissions for roles.',
1719
    'page callback' => 'drupal_get_form',
1720
    'page arguments' => array('user_admin_permissions'),
1721
    'access arguments' => array('administer permissions'),
1722
    'file' => 'user.admin.inc',
1723
    'type' => MENU_LOCAL_TASK,
1724
  );
1725
  $items['admin/people/permissions/list'] = array(
1726
    'title' => 'Permissions',
1727
    'description' => 'Determine access to features by selecting permissions for roles.',
1728
    'type' => MENU_DEFAULT_LOCAL_TASK,
1729
    'weight' => -8,
1730
  );
1731
  $items['admin/people/permissions/roles'] = array(
1732
    'title' => 'Roles',
1733
    'description' => 'List, edit, or add user roles.',
1734
    'page callback' => 'drupal_get_form',
1735
    'page arguments' => array('user_admin_roles'),
1736
    'access arguments' => array('administer permissions'),
1737
    'file' => 'user.admin.inc',
1738
    'type' => MENU_LOCAL_TASK,
1739
    'weight' => -5,
1740
  );
1741
  $items['admin/people/permissions/roles/edit/%user_role'] = array(
1742
    'title' => 'Edit role',
1743
    'page arguments' => array('user_admin_role', 5),
1744
    'access callback' => 'user_role_edit_access',
1745
    'access arguments' => array(5),
1746
  );
1747
  $items['admin/people/permissions/roles/delete/%user_role'] = array(
1748
    'title' => 'Delete role',
1749
    'page callback' => 'drupal_get_form',
1750
    'page arguments' => array('user_admin_role_delete_confirm', 5),
1751
    'access callback' => 'user_role_edit_access',
1752
    'access arguments' => array(5),
1753
    'file' => 'user.admin.inc',
1754
  );
1755

    
1756
  $items['admin/people/create'] = array(
1757
    'title' => 'Add user',
1758
    'page arguments' => array('create'),
1759
    'access arguments' => array('administer users'),
1760
    'type' => MENU_LOCAL_ACTION,
1761
  );
1762

    
1763
  // Administration pages.
1764
  $items['admin/config/people'] = array(
1765
    'title' => 'People',
1766
    'description' => 'Configure user accounts.',
1767
    'position' => 'left',
1768
    'weight' => -20,
1769
    'page callback' => 'system_admin_menu_block_page',
1770
    'access arguments' => array('access administration pages'),
1771
    'file' => 'system.admin.inc',
1772
    'file path' => drupal_get_path('module', 'system'),
1773
  );
1774
  $items['admin/config/people/accounts'] = array(
1775
    'title' => 'Account settings',
1776
    'description' => 'Configure default behavior of users, including registration requirements, e-mails, fields, and user pictures.',
1777
    'page callback' => 'drupal_get_form',
1778
    'page arguments' => array('user_admin_settings'),
1779
    'access arguments' => array('administer users'),
1780
    'file' => 'user.admin.inc',
1781
    'weight' => -10,
1782
  );
1783
  $items['admin/config/people/accounts/settings'] = array(
1784
    'title' => 'Settings',
1785
    'type' => MENU_DEFAULT_LOCAL_TASK,
1786
    'weight' => -10,
1787
  );
1788

    
1789
  $items['user/%user'] = array(
1790
    'title' => 'My account',
1791
    'title callback' => 'user_page_title',
1792
    'title arguments' => array(1),
1793
    'page callback' => 'user_view_page',
1794
    'page arguments' => array(1),
1795
    'access callback' => 'user_view_access',
1796
    'access arguments' => array(1),
1797
    // By assigning a different menu name, this item (and all registered child
1798
    // paths) are no longer considered as children of 'user'. When accessing the
1799
    // user account pages, the preferred menu link that is used to build the
1800
    // active trail (breadcrumb) will be found in this menu (unless there is
1801
    // more specific link), so the link to 'user' will not be in the breadcrumb.
1802
    'menu_name' => 'navigation',
1803
  );
1804

    
1805
  $items['user/%user/view'] = array(
1806
    'title' => 'View',
1807
    'type' => MENU_DEFAULT_LOCAL_TASK,
1808
    'weight' => -10,
1809
  );
1810

    
1811
  $items['user/%user/cancel'] = array(
1812
    'title' => 'Cancel account',
1813
    'page callback' => 'drupal_get_form',
1814
    'page arguments' => array('user_cancel_confirm_form', 1),
1815
    'access callback' => 'user_cancel_access',
1816
    'access arguments' => array(1),
1817
    'file' => 'user.pages.inc',
1818
  );
1819

    
1820
  $items['user/%user/cancel/confirm/%/%'] = array(
1821
    'title' => 'Confirm account cancellation',
1822
    'page callback' => 'user_cancel_confirm',
1823
    'page arguments' => array(1, 4, 5),
1824
    'access callback' => 'user_cancel_access',
1825
    'access arguments' => array(1),
1826
    'file' => 'user.pages.inc',
1827
  );
1828

    
1829
  $items['user/%user/edit'] = array(
1830
    'title' => 'Edit',
1831
    'page callback' => 'drupal_get_form',
1832
    'page arguments' => array('user_profile_form', 1),
1833
    'access callback' => 'user_edit_access',
1834
    'access arguments' => array(1),
1835
    'type' => MENU_LOCAL_TASK,
1836
    'file' => 'user.pages.inc',
1837
  );
1838

    
1839
  $items['user/%user_category/edit/account'] = array(
1840
    'title' => 'Account',
1841
    'type' => MENU_DEFAULT_LOCAL_TASK,
1842
    'load arguments' => array('%map', '%index'),
1843
  );
1844

    
1845
  if (($categories = _user_categories()) && (count($categories) > 1)) {
1846
    foreach ($categories as $key => $category) {
1847
      // 'account' is already handled by the MENU_DEFAULT_LOCAL_TASK.
1848
      if ($category['name'] != 'account') {
1849
        $items['user/%user_category/edit/' . $category['name']] = array(
1850
          'title callback' => 'check_plain',
1851
          'title arguments' => array($category['title']),
1852
          'page callback' => 'drupal_get_form',
1853
          'page arguments' => array('user_profile_form', 1, 3),
1854
          'access callback' => isset($category['access callback']) ? $category['access callback'] : 'user_edit_access',
1855
          'access arguments' => isset($category['access arguments']) ? $category['access arguments'] : array(1),
1856
          'type' => MENU_LOCAL_TASK,
1857
          'weight' => $category['weight'],
1858
          'load arguments' => array('%map', '%index'),
1859
          'tab_parent' => 'user/%/edit',
1860
          'file' => 'user.pages.inc',
1861
        );
1862
      }
1863
    }
1864
  }
1865
  return $items;
1866
}
1867

    
1868
/**
1869
 * Implements hook_menu_site_status_alter().
1870
 */
1871
function user_menu_site_status_alter(&$menu_site_status, $path) {
1872
  if ($menu_site_status == MENU_SITE_OFFLINE) {
1873
    // If the site is offline, log out unprivileged users.
1874
    if (user_is_logged_in() && !user_access('access site in maintenance mode')) {
1875
      module_load_include('pages.inc', 'user', 'user');
1876
      user_logout();
1877
    }
1878

    
1879
    if (user_is_anonymous()) {
1880
      switch ($path) {
1881
        case 'user':
1882
          // Forward anonymous user to login page.
1883
          drupal_goto('user/login');
1884
        case 'user/login':
1885
        case 'user/password':
1886
          // Disable offline mode.
1887
          $menu_site_status = MENU_SITE_ONLINE;
1888
          break;
1889
        default:
1890
          if (strpos($path, 'user/reset/') === 0) {
1891
            // Disable offline mode.
1892
            $menu_site_status = MENU_SITE_ONLINE;
1893
          }
1894
          break;
1895
      }
1896
    }
1897
  }
1898
  if (user_is_logged_in()) {
1899
    if ($path == 'user/login') {
1900
      // If user is logged in, redirect to 'user' instead of giving 403.
1901
      drupal_goto('user');
1902
    }
1903
    if ($path == 'user/register') {
1904
      // Authenticated user should be redirected to user edit page.
1905
      drupal_goto('user/' . $GLOBALS['user']->uid . '/edit');
1906
    }
1907
  }
1908
}
1909

    
1910
/**
1911
 * Implements hook_menu_link_alter().
1912
 */
1913
function user_menu_link_alter(&$link) {
1914
  // The path 'user' must be accessible for anonymous users, but only visible
1915
  // for authenticated users. Authenticated users should see "My account", but
1916
  // anonymous users should not see it at all. Therefore, invoke
1917
  // user_translated_menu_link_alter() to conditionally hide the link.
1918
  if ($link['link_path'] == 'user' && isset($link['module']) && $link['module'] == 'system') {
1919
    $link['options']['alter'] = TRUE;
1920
  }
1921

    
1922
  // Force the Logout link to appear on the top-level of 'user-menu' menu by
1923
  // default (i.e., unless it has been customized).
1924
  if ($link['link_path'] == 'user/logout' && isset($link['module']) && $link['module'] == 'system' && empty($link['customized'])) {
1925
    $link['plid'] = 0;
1926
  }
1927
}
1928

    
1929
/**
1930
 * Implements hook_translated_menu_link_alter().
1931
 */
1932
function user_translated_menu_link_alter(&$link) {
1933
  // Hide the "User account" link for anonymous users.
1934
  if ($link['link_path'] == 'user' && $link['module'] == 'system' && !$GLOBALS['user']->uid) {
1935
    $link['hidden'] = 1;
1936
  }
1937
}
1938

    
1939
/**
1940
 * Implements hook_admin_paths().
1941
 */
1942
function user_admin_paths() {
1943
  $paths = array(
1944
    'user/*/cancel' => TRUE,
1945
    'user/*/edit' => TRUE,
1946
    'user/*/edit/*' => TRUE,
1947
  );
1948
  return $paths;
1949
}
1950

    
1951
/**
1952
 * Returns $arg or the user ID of the current user if $arg is '%' or empty.
1953
 *
1954
 * Deprecated. Use %user_uid_optional instead.
1955
 *
1956
 * @todo D8: Remove.
1957
 */
1958
function user_uid_only_optional_to_arg($arg) {
1959
  return user_uid_optional_to_arg($arg);
1960
}
1961

    
1962
/**
1963
 * Load either a specified or the current user account.
1964
 *
1965
 * @param $uid
1966
 *   An optional user ID of the user to load. If not provided, the current
1967
 *   user's ID will be used.
1968
 * @return
1969
 *   A fully-loaded $user object upon successful user load, FALSE if user
1970
 *   cannot be loaded.
1971
 *
1972
 * @see user_load()
1973
 * @todo rethink the naming of this in Drupal 8.
1974
 */
1975
function user_uid_optional_load($uid = NULL) {
1976
  if (!isset($uid)) {
1977
    $uid = $GLOBALS['user']->uid;
1978
  }
1979
  return user_load($uid);
1980
}
1981

    
1982
/**
1983
 * Return a user object after checking if any profile category in the path exists.
1984
 */
1985
function user_category_load($uid, &$map, $index) {
1986
  static $user_categories, $accounts;
1987

    
1988
  // Cache $account - this load function will get called for each profile tab.
1989
  if (!isset($accounts[$uid])) {
1990
    $accounts[$uid] = user_load($uid);
1991
  }
1992
  $valid = TRUE;
1993
  if ($account = $accounts[$uid]) {
1994
    // Since the path is like user/%/edit/category_name, the category name will
1995
    // be at a position 2 beyond the index corresponding to the % wildcard.
1996
    $category_index = $index + 2;
1997
    // Valid categories may contain slashes, and hence need to be imploded.
1998
    $category_path = implode('/', array_slice($map, $category_index));
1999
    if ($category_path) {
2000
      // Check that the requested category exists.
2001
      $valid = FALSE;
2002
      if (!isset($user_categories)) {
2003
        $user_categories = _user_categories();
2004
      }
2005
      foreach ($user_categories as $category) {
2006
        if ($category['name'] == $category_path) {
2007
          $valid = TRUE;
2008
          // Truncate the map array in case the category name had slashes.
2009
          $map = array_slice($map, 0, $category_index);
2010
          // Assign the imploded category name to the last map element.
2011
          $map[$category_index] = $category_path;
2012
          break;
2013
        }
2014
      }
2015
    }
2016
  }
2017
  return $valid ? $account : FALSE;
2018
}
2019

    
2020
/**
2021
 * Returns $arg or the user ID of the current user if $arg is '%' or empty.
2022
 *
2023
 * @todo rethink the naming of this in Drupal 8.
2024
 */
2025
function user_uid_optional_to_arg($arg) {
2026
  // Give back the current user uid when called from eg. tracker, aka.
2027
  // with an empty arg. Also use the current user uid when called from
2028
  // the menu with a % for the current account link.
2029
  return empty($arg) || $arg == '%' ? $GLOBALS['user']->uid : $arg;
2030
}
2031

    
2032
/**
2033
 * Menu item title callback for the 'user' path.
2034
 *
2035
 * Anonymous users should see "User account", but authenticated users are
2036
 * expected to see "My account".
2037
 */
2038
function user_menu_title() {
2039
  return user_is_logged_in() ? t('My account') : t('User account');
2040
}
2041

    
2042
/**
2043
 * Menu item title callback - use the user name.
2044
 */
2045
function user_page_title($account) {
2046
  return is_object($account) ? format_username($account) : '';
2047
}
2048

    
2049
/**
2050
 * Discover which external authentication module(s) authenticated a username.
2051
 *
2052
 * @param $authname
2053
 *   A username used by an external authentication module.
2054
 * @return
2055
 *   An associative array with module as key and username as value.
2056
 */
2057
function user_get_authmaps($authname = NULL) {
2058
  $authmaps = db_query("SELECT module, authname FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchAllKeyed();
2059
  return count($authmaps) ? $authmaps : 0;
2060
}
2061

    
2062
/**
2063
 * Save mappings of which external authentication module(s) authenticated
2064
 * a user. Maps external usernames to user ids in the users table.
2065
 *
2066
 * @param $account
2067
 *   A user object.
2068
 * @param $authmaps
2069
 *   An associative array with a compound key and the username as the value.
2070
 *   The key is made up of 'authname_' plus the name of the external authentication
2071
 *   module.
2072
 * @see user_external_login_register()
2073
 */
2074
function user_set_authmaps($account, $authmaps) {
2075
  foreach ($authmaps as $key => $value) {
2076
    $module = explode('_', $key, 2);
2077
    if ($value) {
2078
      db_merge('authmap')
2079
        ->key(array(
2080
          'uid' => $account->uid,
2081
          'module' => $module[1],
2082
        ))
2083
        ->fields(array('authname' => $value))
2084
        ->execute();
2085
    }
2086
    else {
2087
      db_delete('authmap')
2088
        ->condition('uid', $account->uid)
2089
        ->condition('module', $module[1])
2090
        ->execute();
2091
    }
2092
  }
2093
}
2094

    
2095
/**
2096
 * Form builder; the main user login form.
2097
 *
2098
 * @ingroup forms
2099
 */
2100
function user_login($form, &$form_state) {
2101
  global $user;
2102

    
2103
  // If we are already logged on, go to the user page instead.
2104
  if ($user->uid) {
2105
    drupal_goto('user/' . $user->uid);
2106
  }
2107

    
2108
  // Display login form:
2109
  $form['name'] = array('#type' => 'textfield',
2110
    '#title' => t('Username'),
2111
    '#size' => 60,
2112
    '#maxlength' => USERNAME_MAX_LENGTH,
2113
    '#required' => TRUE,
2114
  );
2115

    
2116
  $form['name']['#description'] = t('Enter your @s username.', array('@s' => variable_get('site_name', 'Drupal')));
2117
  $form['pass'] = array('#type' => 'password',
2118
    '#title' => t('Password'),
2119
    '#description' => t('Enter the password that accompanies your username.'),
2120
    '#required' => TRUE,
2121
  );
2122
  $form['#validate'] = user_login_default_validators();
2123
  $form['actions'] = array('#type' => 'actions');
2124
  $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
2125

    
2126
  return $form;
2127
}
2128

    
2129
/**
2130
 * Set up a series for validators which check for blocked users,
2131
 * then authenticate against local database, then return an error if
2132
 * authentication fails. Distributed authentication modules are welcome
2133
 * to use hook_form_alter() to change this series in order to
2134
 * authenticate against their user database instead of the local users
2135
 * table. If a distributed authentication module is successful, it
2136
 * should set $form_state['uid'] to a user ID.
2137
 *
2138
 * We use three validators instead of one since external authentication
2139
 * modules usually only need to alter the second validator.
2140
 *
2141
 * @see user_login_name_validate()
2142
 * @see user_login_authenticate_validate()
2143
 * @see user_login_final_validate()
2144
 * @return array
2145
 *   A simple list of validate functions.
2146
 */
2147
function user_login_default_validators() {
2148
  return array('user_login_name_validate', 'user_login_authenticate_validate', 'user_login_final_validate');
2149
}
2150

    
2151
/**
2152
 * A FAPI validate handler. Sets an error if supplied username has been blocked.
2153
 */
2154
function user_login_name_validate($form, &$form_state) {
2155
  if (!empty($form_state['values']['name']) && user_is_blocked($form_state['values']['name'])) {
2156
    // Blocked in user administration.
2157
    form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
2158
  }
2159
}
2160

    
2161
/**
2162
 * A validate handler on the login form. Check supplied username/password
2163
 * against local users table. If successful, $form_state['uid']
2164
 * is set to the matching user ID.
2165
 */
2166
function user_login_authenticate_validate($form, &$form_state) {
2167
  $password = trim($form_state['values']['pass']);
2168
  if (!empty($form_state['values']['name']) && !empty($password)) {
2169
    // Do not allow any login from the current user's IP if the limit has been
2170
    // reached. Default is 50 failed attempts allowed in one hour. This is
2171
    // independent of the per-user limit to catch attempts from one IP to log
2172
    // in to many different user accounts.  We have a reasonably high limit
2173
    // since there may be only one apparent IP for all users at an institution.
2174
    if (!flood_is_allowed('failed_login_attempt_ip', variable_get('user_failed_login_ip_limit', 50), variable_get('user_failed_login_ip_window', 3600))) {
2175
      $form_state['flood_control_triggered'] = 'ip';
2176
      return;
2177
    }
2178
    $account = db_query("SELECT * FROM {users} WHERE name = :name AND status = 1", array(':name' => $form_state['values']['name']))->fetchObject();
2179
    if ($account) {
2180
      if (variable_get('user_failed_login_identifier_uid_only', FALSE)) {
2181
        // Register flood events based on the uid only, so they apply for any
2182
        // IP address. This is the most secure option.
2183
        $identifier = $account->uid;
2184
      }
2185
      else {
2186
        // The default identifier is a combination of uid and IP address. This
2187
        // is less secure but more resistant to denial-of-service attacks that
2188
        // could lock out all users with public user names.
2189
        $identifier = $account->uid . '-' . ip_address();
2190
      }
2191
      $form_state['flood_control_user_identifier'] = $identifier;
2192

    
2193
      // Don't allow login if the limit for this user has been reached.
2194
      // Default is to allow 5 failed attempts every 6 hours.
2195
      if (!flood_is_allowed('failed_login_attempt_user', variable_get('user_failed_login_user_limit', 5), variable_get('user_failed_login_user_window', 21600), $identifier)) {
2196
        $form_state['flood_control_triggered'] = 'user';
2197
        return;
2198
      }
2199
    }
2200
    // We are not limited by flood control, so try to authenticate.
2201
    // Set $form_state['uid'] as a flag for user_login_final_validate().
2202
    $form_state['uid'] = user_authenticate($form_state['values']['name'], $password);
2203
  }
2204
}
2205

    
2206
/**
2207
 * The final validation handler on the login form.
2208
 *
2209
 * Sets a form error if user has not been authenticated, or if too many
2210
 * logins have been attempted. This validation function should always
2211
 * be the last one.
2212
 */
2213
function user_login_final_validate($form, &$form_state) {
2214
  if (empty($form_state['uid'])) {
2215
    // Always register an IP-based failed login event.
2216
    flood_register_event('failed_login_attempt_ip', variable_get('user_failed_login_ip_window', 3600));
2217
    // Register a per-user failed login event.
2218
    if (isset($form_state['flood_control_user_identifier'])) {
2219
      flood_register_event('failed_login_attempt_user', variable_get('user_failed_login_user_window', 21600), $form_state['flood_control_user_identifier']);
2220
    }
2221

    
2222
    if (isset($form_state['flood_control_triggered'])) {
2223
      if ($form_state['flood_control_triggered'] == 'user') {
2224
        form_set_error('name', format_plural(variable_get('user_failed_login_user_limit', 5), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
2225
      }
2226
      else {
2227
        // We did not find a uid, so the limit is IP-based.
2228
        form_set_error('name', t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
2229
      }
2230
    }
2231
    else {
2232
      // Use $form_state['input']['name'] here to guarantee that we send
2233
      // exactly what the user typed in. $form_state['values']['name'] may have
2234
      // been modified by validation handlers that ran earlier than this one.
2235
      $query = isset($form_state['input']['name']) ? array('name' => $form_state['input']['name']) : array();
2236
      form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password', array('query' => $query)))));
2237
      watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
2238
    }
2239
  }
2240
  elseif (isset($form_state['flood_control_user_identifier'])) {
2241
    // Clear past failures for this user so as not to block a user who might
2242
    // log in and out more than once in an hour.
2243
    flood_clear_event('failed_login_attempt_user', $form_state['flood_control_user_identifier']);
2244
  }
2245
}
2246

    
2247
/**
2248
 * Try to validate the user's login credentials locally.
2249
 *
2250
 * @param $name
2251
 *   User name to authenticate.
2252
 * @param $password
2253
 *   A plain-text password, such as trimmed text from form values.
2254
 * @return
2255
 *   The user's uid on success, or FALSE on failure to authenticate.
2256
 */
2257
function user_authenticate($name, $password) {
2258
  $uid = FALSE;
2259
  if (!empty($name) && !empty($password)) {
2260
    $account = user_load_by_name($name);
2261
    if ($account) {
2262
      // Allow alternate password hashing schemes.
2263
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
2264
      if (user_check_password($password, $account)) {
2265
        // Successful authentication.
2266
        $uid = $account->uid;
2267

    
2268
        // Update user to new password scheme if needed.
2269
        if (user_needs_new_hash($account)) {
2270
          user_save($account, array('pass' => $password));
2271
        }
2272
      }
2273
    }
2274
  }
2275
  return $uid;
2276
}
2277

    
2278
/**
2279
 * Finalize the login process. Must be called when logging in a user.
2280
 *
2281
 * The function records a watchdog message about the new session, saves the
2282
 * login timestamp, calls hook_user_login(), and generates a new session.
2283
 *
2284
 * @param array $edit
2285
 *   The array of form values submitted by the user.
2286
 *
2287
 * @see hook_user_login()
2288
 */
2289
function user_login_finalize(&$edit = array()) {
2290
  global $user;
2291
  watchdog('user', 'Session opened for %name.', array('%name' => $user->name));
2292
  // Update the user table timestamp noting user has logged in.
2293
  // This is also used to invalidate one-time login links.
2294
  $user->login = REQUEST_TIME;
2295
  db_update('users')
2296
    ->fields(array('login' => $user->login))
2297
    ->condition('uid', $user->uid)
2298
    ->execute();
2299

    
2300
  // Regenerate the session ID to prevent against session fixation attacks.
2301
  // This is called before hook_user in case one of those functions fails
2302
  // or incorrectly does a redirect which would leave the old session in place.
2303
  drupal_session_regenerate();
2304

    
2305
  user_module_invoke('login', $edit, $user);
2306
}
2307

    
2308
/**
2309
 * Submit handler for the login form. Load $user object and perform standard login
2310
 * tasks. The user is then redirected to the My Account page. Setting the
2311
 * destination in the query string overrides the redirect.
2312
 */
2313
function user_login_submit($form, &$form_state) {
2314
  global $user;
2315
  $user = user_load($form_state['uid']);
2316
  $form_state['redirect'] = 'user/' . $user->uid;
2317

    
2318
  user_login_finalize($form_state);
2319
}
2320

    
2321
/**
2322
 * Helper function for authentication modules. Either logs in or registers
2323
 * the current user, based on username. Either way, the global $user object is
2324
 * populated and login tasks are performed.
2325
 */
2326
function user_external_login_register($name, $module) {
2327
  $account = user_external_load($name);
2328
  if (!$account) {
2329
    // Register this new user.
2330
    $userinfo = array(
2331
      'name' => $name,
2332
      'pass' => user_password(),
2333
      'init' => $name,
2334
      'status' => 1,
2335
      'access' => REQUEST_TIME
2336
    );
2337
    $account = user_save(drupal_anonymous_user(), $userinfo);
2338
    // Terminate if an error occurred during user_save().
2339
    if (!$account) {
2340
      drupal_set_message(t("Error saving user account."), 'error');
2341
      return;
2342
    }
2343
    user_set_authmaps($account, array("authname_$module" => $name));
2344
  }
2345

    
2346
  // Log user in.
2347
  $form_state['uid'] = $account->uid;
2348
  user_login_submit(array(), $form_state);
2349
}
2350

    
2351
/**
2352
 * Generates a unique URL for a user to login and reset their password.
2353
 *
2354
 * @param object $account
2355
 *   An object containing the user account, which must contain at least the
2356
 *   following properties:
2357
 *   - uid: The user ID number.
2358
 *   - login: The UNIX timestamp of the user's last login.
2359
 *
2360
 * @return
2361
 *   A unique URL that provides a one-time log in for the user, from which
2362
 *   they can change their password.
2363
 */
2364
function user_pass_reset_url($account) {
2365
  $timestamp = REQUEST_TIME;
2366
  return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
2367
}
2368

    
2369
/**
2370
 * Generates a URL to confirm an account cancellation request.
2371
 *
2372
 * @param object $account
2373
 *   The user account object, which must contain at least the following
2374
 *   properties:
2375
 *   - uid: The user ID number.
2376
 *   - pass: The hashed user password string.
2377
 *   - login: The UNIX timestamp of the user's last login.
2378
 *
2379
 * @return
2380
 *   A unique URL that may be used to confirm the cancellation of the user
2381
 *   account.
2382
 *
2383
 * @see user_mail_tokens()
2384
 * @see user_cancel_confirm()
2385
 */
2386
function user_cancel_url($account) {
2387
  $timestamp = REQUEST_TIME;
2388
  return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
2389
}
2390

    
2391
/**
2392
 * Creates a unique hash value for use in time-dependent per-user URLs.
2393
 *
2394
 * This hash is normally used to build a unique and secure URL that is sent to
2395
 * the user by email for purposes such as resetting the user's password. In
2396
 * order to validate the URL, the same hash can be generated again, from the
2397
 * same information, and compared to the hash value from the URL. The URL
2398
 * normally contains both the time stamp and the numeric user ID. The login
2399
 * timestamp and hashed password are retrieved from the database as necessary.
2400
 * For a usage example, see user_cancel_url() and user_cancel_confirm().
2401
 *
2402
 * @param string $password
2403
 *   The hashed user account password value.
2404
 * @param int $timestamp
2405
 *   A UNIX timestamp, typically REQUEST_TIME.
2406
 * @param int $login
2407
 *   The UNIX timestamp of the user's last login.
2408
 * @param int $uid
2409
 *   The user ID of the user account.
2410
 *
2411
 * @return
2412
 *   A string that is safe for use in URLs and SQL statements.
2413
 */
2414
function user_pass_rehash($password, $timestamp, $login, $uid) {
2415
  // Backwards compatibility: Try to determine a $uid if one was not passed.
2416
  // (Since $uid is a required parameter to this function, a PHP warning will
2417
  // be generated if it's not provided, which is an indication that the calling
2418
  // code should be updated. But the code below will try to generate a correct
2419
  // hash in the meantime.)
2420
  if (!isset($uid)) {
2421
    $uids = db_query_range('SELECT uid FROM {users} WHERE pass = :password AND login = :login AND uid > 0', 0, 2, array(':password' => $password, ':login' => $login))->fetchCol();
2422
    // If exactly one user account matches the provided password and login
2423
    // timestamp, proceed with that $uid.
2424
    if (count($uids) == 1) {
2425
      $uid = reset($uids);
2426
    }
2427
    // Otherwise there is no safe hash to return, so return a random string
2428
    // that will never be treated as a valid token.
2429
    else {
2430
      return drupal_random_key();
2431
    }
2432
  }
2433

    
2434
  return drupal_hmac_base64($timestamp . $login . $uid, drupal_get_hash_salt() . $password);
2435
}
2436

    
2437
/**
2438
 * Cancel a user account.
2439
 *
2440
 * Since the user cancellation process needs to be run in a batch, either
2441
 * Form API will invoke it, or batch_process() needs to be invoked after calling
2442
 * this function and should define the path to redirect to.
2443
 *
2444
 * @param $edit
2445
 *   An array of submitted form values.
2446
 * @param $uid
2447
 *   The user ID of the user account to cancel.
2448
 * @param $method
2449
 *   The account cancellation method to use.
2450
 *
2451
 * @see _user_cancel()
2452
 */
2453
function user_cancel($edit, $uid, $method) {
2454
  global $user;
2455

    
2456
  $account = user_load($uid);
2457

    
2458
  if (!$account) {
2459
    drupal_set_message(t('The user account %id does not exist.', array('%id' => $uid)), 'error');
2460
    watchdog('user', 'Attempted to cancel non-existing user account: %id.', array('%id' => $uid), WATCHDOG_ERROR);
2461
    return;
2462
  }
2463

    
2464
  // Initialize batch (to set title).
2465
  $batch = array(
2466
    'title' => t('Cancelling account'),
2467
    'operations' => array(),
2468
  );
2469
  batch_set($batch);
2470

    
2471
  // Modules use hook_user_delete() to respond to deletion.
2472
  if ($method != 'user_cancel_delete') {
2473
    // Allow modules to add further sets to this batch.
2474
    module_invoke_all('user_cancel', $edit, $account, $method);
2475
  }
2476

    
2477
  // Finish the batch and actually cancel the account.
2478
  $batch = array(
2479
    'title' => t('Cancelling user account'),
2480
    'operations' => array(
2481
      array('_user_cancel', array($edit, $account, $method)),
2482
    ),
2483
  );
2484

    
2485
  // After cancelling account, ensure that user is logged out.
2486
  if ($account->uid == $user->uid) {
2487
    // Batch API stores data in the session, so use the finished operation to
2488
    // manipulate the current user's session id.
2489
    $batch['finished'] = '_user_cancel_session_regenerate';
2490
  }
2491

    
2492
  batch_set($batch);
2493

    
2494
  // Batch processing is either handled via Form API or has to be invoked
2495
  // manually.
2496
}
2497

    
2498
/**
2499
 * Implements callback_batch_operation().
2500
 *
2501
 * Last step for cancelling a user account.
2502
 *
2503
 * Since batch and session API require a valid user account, the actual
2504
 * cancellation of a user account needs to happen last.
2505
 *
2506
 * @see user_cancel()
2507
 */
2508
function _user_cancel($edit, $account, $method) {
2509
  global $user;
2510

    
2511
  switch ($method) {
2512
    case 'user_cancel_block':
2513
    case 'user_cancel_block_unpublish':
2514
    default:
2515
      // Send account blocked notification if option was checked.
2516
      if (!empty($edit['user_cancel_notify'])) {
2517
        _user_mail_notify('status_blocked', $account);
2518
      }
2519
      user_save($account, array('status' => 0));
2520
      drupal_set_message(t('%name has been disabled.', array('%name' => $account->name)));
2521
      watchdog('user', 'Blocked user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
2522
      break;
2523

    
2524
    case 'user_cancel_reassign':
2525
    case 'user_cancel_delete':
2526
      // Send account canceled notification if option was checked.
2527
      if (!empty($edit['user_cancel_notify'])) {
2528
        _user_mail_notify('status_canceled', $account);
2529
      }
2530
      user_delete($account->uid);
2531
      drupal_set_message(t('%name has been deleted.', array('%name' => $account->name)));
2532
      watchdog('user', 'Deleted user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
2533
      break;
2534
  }
2535

    
2536
  // After cancelling account, ensure that user is logged out. We can't destroy
2537
  // their session though, as we might have information in it, and we can't
2538
  // regenerate it because batch API uses the session ID, we will regenerate it
2539
  // in _user_cancel_session_regenerate().
2540
  if ($account->uid == $user->uid) {
2541
    $user = drupal_anonymous_user();
2542
  }
2543

    
2544
  // Clear the cache for anonymous users.
2545
  cache_clear_all();
2546
}
2547

    
2548
/**
2549
 * Implements callback_batch_finished().
2550
 *
2551
 * Finished batch processing callback for cancelling a user account.
2552
 *
2553
 * @see user_cancel()
2554
 */
2555
function _user_cancel_session_regenerate() {
2556
  // Regenerate the users session instead of calling session_destroy() as we
2557
  // want to preserve any messages that might have been set.
2558
  drupal_session_regenerate();
2559
}
2560

    
2561
/**
2562
 * Delete a user.
2563
 *
2564
 * @param $uid
2565
 *   A user ID.
2566
 */
2567
function user_delete($uid) {
2568
  user_delete_multiple(array($uid));
2569
}
2570

    
2571
/**
2572
 * Delete multiple user accounts.
2573
 *
2574
 * @param $uids
2575
 *   An array of user IDs.
2576
 */
2577
function user_delete_multiple(array $uids) {
2578
  if (!empty($uids)) {
2579
    $accounts = user_load_multiple($uids, array());
2580

    
2581
    $transaction = db_transaction();
2582
    try {
2583
      foreach ($accounts as $uid => $account) {
2584
        module_invoke_all('user_delete', $account);
2585
        module_invoke_all('entity_delete', $account, 'user');
2586
        field_attach_delete('user', $account);
2587
        drupal_session_destroy_uid($account->uid);
2588
      }
2589

    
2590
      db_delete('users')
2591
        ->condition('uid', $uids, 'IN')
2592
        ->execute();
2593
      db_delete('users_roles')
2594
        ->condition('uid', $uids, 'IN')
2595
        ->execute();
2596
      db_delete('authmap')
2597
        ->condition('uid', $uids, 'IN')
2598
        ->execute();
2599
    }
2600
    catch (Exception $e) {
2601
      $transaction->rollback();
2602
      watchdog_exception('user', $e);
2603
      throw $e;
2604
    }
2605
    entity_get_controller('user')->resetCache();
2606
  }
2607
}
2608

    
2609
/**
2610
 * Page callback wrapper for user_view().
2611
 */
2612
function user_view_page($account) {
2613
  // An administrator may try to view a non-existent account,
2614
  // so we give them a 404 (versus a 403 for non-admins).
2615
  return is_object($account) ? user_view($account) : MENU_NOT_FOUND;
2616
}
2617

    
2618
/**
2619
 * Generate an array for rendering the given user.
2620
 *
2621
 * When viewing a user profile, the $page array contains:
2622
 *
2623
 * - $page['content']['Profile Category']:
2624
 *   Profile categories keyed by their human-readable names.
2625
 * - $page['content']['Profile Category']['profile_machine_name']:
2626
 *   Profile fields keyed by their machine-readable names.
2627
 * - $page['content']['user_picture']:
2628
 *   User's rendered picture.
2629
 * - $page['content']['summary']:
2630
 *   Contains the default "History" profile data for a user.
2631
 * - $page['content']['#account']:
2632
 *   The user account of the profile being viewed.
2633
 *
2634
 * To theme user profiles, copy modules/user/user-profile.tpl.php
2635
 * to your theme directory, and edit it as instructed in that file's comments.
2636
 *
2637
 * @param $account
2638
 *   A user object.
2639
 * @param $view_mode
2640
 *   View mode, e.g. 'full'.
2641
 * @param $langcode
2642
 *   (optional) A language code to use for rendering. Defaults to the global
2643
 *   content language of the current request.
2644
 *
2645
 * @return
2646
 *   An array as expected by drupal_render().
2647
 */
2648
function user_view($account, $view_mode = 'full', $langcode = NULL) {
2649
  if (!isset($langcode)) {
2650
    $langcode = $GLOBALS['language_content']->language;
2651
  }
2652

    
2653
  // Retrieve all profile fields and attach to $account->content.
2654
  user_build_content($account, $view_mode, $langcode);
2655

    
2656
  $build = $account->content;
2657
  // We don't need duplicate rendering info in account->content.
2658
  unset($account->content);
2659

    
2660
  $build += array(
2661
    '#theme' => 'user_profile',
2662
    '#account' => $account,
2663
    '#view_mode' => $view_mode,
2664
    '#language' => $langcode,
2665
  );
2666

    
2667
  // Allow modules to modify the structured user.
2668
  $type = 'user';
2669
  drupal_alter(array('user_view', 'entity_view'), $build, $type);
2670

    
2671
  return $build;
2672
}
2673

    
2674
/**
2675
 * Builds a structured array representing the profile content.
2676
 *
2677
 * @param $account
2678
 *   A user object.
2679
 * @param $view_mode
2680
 *   View mode, e.g. 'full'.
2681
 * @param $langcode
2682
 *   (optional) A language code to use for rendering. Defaults to the global
2683
 *   content language of the current request.
2684
 */
2685
function user_build_content($account, $view_mode = 'full', $langcode = NULL) {
2686
  if (!isset($langcode)) {
2687
    $langcode = $GLOBALS['language_content']->language;
2688
  }
2689

    
2690
  // Remove previously built content, if exists.
2691
  $account->content = array();
2692

    
2693
  // Allow modules to change the view mode.
2694
  $view_mode = key(entity_view_mode_prepare('user', array($account->uid => $account), $view_mode, $langcode));
2695

    
2696
  // Build fields content.
2697
  field_attach_prepare_view('user', array($account->uid => $account), $view_mode, $langcode);
2698
  entity_prepare_view('user', array($account->uid => $account), $langcode);
2699
  $account->content += field_attach_view('user', $account, $view_mode, $langcode);
2700

    
2701
  // Populate $account->content with a render() array.
2702
  module_invoke_all('user_view', $account, $view_mode, $langcode);
2703
  module_invoke_all('entity_view', $account, 'user', $view_mode, $langcode);
2704

    
2705
  // Make sure the current view mode is stored if no module has already
2706
  // populated the related key.
2707
  $account->content += array('#view_mode' => $view_mode);
2708
}
2709

    
2710
/**
2711
 * Implements hook_mail().
2712
 */
2713
function user_mail($key, &$message, $params) {
2714
  $language = $message['language'];
2715
  $variables = array('user' => $params['account']);
2716
  $message['subject'] .= _user_mail_text($key . '_subject', $language, $variables);
2717
  $message['body'][] = _user_mail_text($key . '_body', $language, $variables);
2718
}
2719

    
2720
/**
2721
 * Returns a mail string for a variable name.
2722
 *
2723
 * Used by user_mail() and the settings forms to retrieve strings.
2724
 */
2725
function _user_mail_text($key, $language = NULL, $variables = array(), $replace = TRUE) {
2726
  $langcode = isset($language) ? $language->language : NULL;
2727

    
2728
  if ($admin_setting = variable_get('user_mail_' . $key, FALSE)) {
2729
    // An admin setting overrides the default string.
2730
    $text = $admin_setting;
2731
  }
2732
  else {
2733
    // No override, return default string.
2734
    switch ($key) {
2735
      case 'register_no_approval_required_subject':
2736
        $text = t('Account details for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2737
        break;
2738
      case 'register_no_approval_required_body':
2739
        $text = t("[user:name],
2740

    
2741
Thank you for registering at [site:name]. You may now log in by clicking this link or copying and pasting it to your browser:
2742

    
2743
[user:one-time-login-url]
2744

    
2745
This link can only be used once to log in and will lead you to a page where you can set your password.
2746

    
2747
After setting your password, you will be able to log in at [site:login-url] in the future using:
2748

    
2749
username: [user:name]
2750
password: Your password
2751

    
2752
--  [site:name] team", array(), array('langcode' => $langcode));
2753
        break;
2754

    
2755
      case 'register_admin_created_subject':
2756
        $text = t('An administrator created an account for you at [site:name]', array(), array('langcode' => $langcode));
2757
        break;
2758
      case 'register_admin_created_body':
2759
        $text = t("[user:name],
2760

    
2761
A site administrator at [site:name] has created an account for you. You may now log in by clicking this link or copying and pasting it to your browser:
2762

    
2763
[user:one-time-login-url]
2764

    
2765
This link can only be used once to log in and will lead you to a page where you can set your password.
2766

    
2767
After setting your password, you will be able to log in at [site:login-url] in the future using:
2768

    
2769
username: [user:name]
2770
password: Your password
2771

    
2772
--  [site:name] team", array(), array('langcode' => $langcode));
2773
        break;
2774

    
2775
      case 'register_pending_approval_subject':
2776
      case 'register_pending_approval_admin_subject':
2777
        $text = t('Account details for [user:name] at [site:name] (pending admin approval)', array(), array('langcode' => $langcode));
2778
        break;
2779
      case 'register_pending_approval_body':
2780
        $text = t("[user:name],
2781

    
2782
Thank you for registering at [site:name]. Your application for an account is currently pending approval. Once it has been approved, you will receive another e-mail containing information about how to log in, set your password, and other details.
2783

    
2784

    
2785
--  [site:name] team", array(), array('langcode' => $langcode));
2786
        break;
2787
      case 'register_pending_approval_admin_body':
2788
        $text = t("[user:name] has applied for an account.
2789

    
2790
[user:edit-url]", array(), array('langcode' => $langcode));
2791
        break;
2792

    
2793
      case 'password_reset_subject':
2794
        $text = t('Replacement login information for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2795
        break;
2796
      case 'password_reset_body':
2797
        $text = t("[user:name],
2798

    
2799
A request to reset the password for your account has been made at [site:name].
2800

    
2801
You may now log in by clicking this link or copying and pasting it to your browser:
2802

    
2803
[user:one-time-login-url]
2804

    
2805
This link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used.
2806

    
2807
--  [site:name] team", array(), array('langcode' => $langcode));
2808
        break;
2809

    
2810
      case 'status_activated_subject':
2811
        $text = t('Account details for [user:name] at [site:name] (approved)', array(), array('langcode' => $langcode));
2812
        break;
2813
      case 'status_activated_body':
2814
        $text = t("[user:name],
2815

    
2816
Your account at [site:name] has been activated.
2817

    
2818
You may now log in by clicking this link or copying and pasting it into your browser:
2819

    
2820
[user:one-time-login-url]
2821

    
2822
This link can only be used once to log in and will lead you to a page where you can set your password.
2823

    
2824
After setting your password, you will be able to log in at [site:login-url] in the future using:
2825

    
2826
username: [user:name]
2827
password: Your password
2828

    
2829
--  [site:name] team", array(), array('langcode' => $langcode));
2830
        break;
2831

    
2832
      case 'status_blocked_subject':
2833
        $text = t('Account details for [user:name] at [site:name] (blocked)', array(), array('langcode' => $langcode));
2834
        break;
2835
      case 'status_blocked_body':
2836
        $text = t("[user:name],
2837

    
2838
Your account on [site:name] has been blocked.
2839

    
2840
--  [site:name] team", array(), array('langcode' => $langcode));
2841
        break;
2842

    
2843
      case 'cancel_confirm_subject':
2844
        $text = t('Account cancellation request for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2845
        break;
2846
      case 'cancel_confirm_body':
2847
        $text = t("[user:name],
2848

    
2849
A request to cancel your account has been made at [site:name].
2850

    
2851
You may now cancel your account on [site:url-brief] by clicking this link or copying and pasting it into your browser:
2852

    
2853
[user:cancel-url]
2854

    
2855
NOTE: The cancellation of your account is not reversible.
2856

    
2857
This link expires in one day and nothing will happen if it is not used.
2858

    
2859
--  [site:name] team", array(), array('langcode' => $langcode));
2860
        break;
2861

    
2862
      case 'status_canceled_subject':
2863
        $text = t('Account details for [user:name] at [site:name] (canceled)', array(), array('langcode' => $langcode));
2864
        break;
2865
      case 'status_canceled_body':
2866
        $text = t("[user:name],
2867

    
2868
Your account on [site:name] has been canceled.
2869

    
2870
--  [site:name] team", array(), array('langcode' => $langcode));
2871
        break;
2872
    }
2873
  }
2874

    
2875
  if ($replace) {
2876
    // We do not sanitize the token replacement, since the output of this
2877
    // replacement is intended for an e-mail message, not a web browser.
2878
    return token_replace($text, $variables, array('language' => $language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
2879
  }
2880

    
2881
  return $text;
2882
}
2883

    
2884
/**
2885
 * Token callback to add unsafe tokens for user mails.
2886
 *
2887
 * This function is used by the token_replace() call at the end of
2888
 * _user_mail_text() to set up some additional tokens that can be
2889
 * used in email messages generated by user_mail().
2890
 *
2891
 * @param $replacements
2892
 *   An associative array variable containing mappings from token names to
2893
 *   values (for use with strtr()).
2894
 * @param $data
2895
 *   An associative array of token replacement values. If the 'user' element
2896
 *   exists, it must contain a user account object with the following
2897
 *   properties:
2898
 *   - login: The UNIX timestamp of the user's last login.
2899
 *   - pass: The hashed account login password.
2900
 * @param $options
2901
 *   Unused parameter required by the token_replace() function.
2902
 */
2903
function user_mail_tokens(&$replacements, $data, $options) {
2904
  if (isset($data['user'])) {
2905
    $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user']);
2906
    $replacements['[user:cancel-url]'] = user_cancel_url($data['user']);
2907
  }
2908
}
2909

    
2910
/*** Administrative features ***********************************************/
2911

    
2912
/**
2913
 * Retrieve an array of roles matching specified conditions.
2914
 *
2915
 * @param $membersonly
2916
 *   Set this to TRUE to exclude the 'anonymous' role.
2917
 * @param $permission
2918
 *   A string containing a permission. If set, only roles containing that
2919
 *   permission are returned.
2920
 *
2921
 * @return
2922
 *   An associative array with the role id as the key and the role name as
2923
 *   value.
2924
 */
2925
function user_roles($membersonly = FALSE, $permission = NULL) {
2926
  $query = db_select('role', 'r');
2927
  $query->addTag('translatable');
2928
  $query->fields('r', array('rid', 'name'));
2929
  $query->orderBy('weight');
2930
  $query->orderBy('name');
2931
  if (!empty($permission)) {
2932
    $query->innerJoin('role_permission', 'p', 'r.rid = p.rid');
2933
    $query->condition('p.permission', $permission);
2934
  }
2935
  $result = $query->execute();
2936

    
2937
  $roles = array();
2938
  foreach ($result as $role) {
2939
    switch ($role->rid) {
2940
      // We only translate the built in role names
2941
      case DRUPAL_ANONYMOUS_RID:
2942
        if (!$membersonly) {
2943
          $roles[$role->rid] = t($role->name);
2944
        }
2945
        break;
2946
      case DRUPAL_AUTHENTICATED_RID:
2947
        $roles[$role->rid] = t($role->name);
2948
        break;
2949
      default:
2950
        $roles[$role->rid] = $role->name;
2951
    }
2952
  }
2953

    
2954
  return $roles;
2955
}
2956

    
2957
/**
2958
 * Fetches a user role by role ID.
2959
 *
2960
 * @param $rid
2961
 *   An integer representing the role ID.
2962
 *
2963
 * @return
2964
 *   A fully-loaded role object if a role with the given ID exists, or FALSE
2965
 *   otherwise.
2966
 *
2967
 * @see user_role_load_by_name()
2968
 */
2969
function user_role_load($rid) {
2970
  return db_select('role', 'r')
2971
    ->fields('r')
2972
    ->condition('rid', $rid)
2973
    ->execute()
2974
    ->fetchObject();
2975
}
2976

    
2977
/**
2978
 * Fetches a user role by role name.
2979
 *
2980
 * @param $role_name
2981
 *   A string representing the role name.
2982
 *
2983
 * @return
2984
 *   A fully-loaded role object if a role with the given name exists, or FALSE
2985
 *   otherwise.
2986
 *
2987
 * @see user_role_load()
2988
 */
2989
function user_role_load_by_name($role_name) {
2990
  return db_select('role', 'r')
2991
    ->fields('r')
2992
    ->condition('name', $role_name)
2993
    ->execute()
2994
    ->fetchObject();
2995
}
2996

    
2997
/**
2998
 * Save a user role to the database.
2999
 *
3000
 * @param $role
3001
 *   A role object to modify or add. If $role->rid is not specified, a new
3002
 *   role will be created.
3003
 * @return
3004
 *   Status constant indicating if role was created or updated.
3005
 *   Failure to write the user role record will return FALSE. Otherwise.
3006
 *   SAVED_NEW or SAVED_UPDATED is returned depending on the operation
3007
 *   performed.
3008
 */
3009
function user_role_save($role) {
3010
  if ($role->name) {
3011
    // Prevent leading and trailing spaces in role names.
3012
    $role->name = trim($role->name);
3013
  }
3014
  if (!isset($role->weight)) {
3015
    // Set a role weight to make this new role last.
3016
    $query = db_select('role');
3017
    $query->addExpression('MAX(weight)');
3018
    $role->weight = $query->execute()->fetchField() + 1;
3019
  }
3020

    
3021
  // Let modules modify the user role before it is saved to the database.
3022
  module_invoke_all('user_role_presave', $role);
3023

    
3024
  if (!empty($role->rid) && $role->name) {
3025
    $status = drupal_write_record('role', $role, 'rid');
3026
    module_invoke_all('user_role_update', $role);
3027
  }
3028
  else {
3029
    $status = drupal_write_record('role', $role);
3030
    module_invoke_all('user_role_insert', $role);
3031
  }
3032

    
3033
  // Clear the user access cache.
3034
  drupal_static_reset('user_access');
3035
  drupal_static_reset('user_role_permissions');
3036

    
3037
  return $status;
3038
}
3039

    
3040
/**
3041
 * Delete a user role from database.
3042
 *
3043
 * @param $role
3044
 *   A string with the role name, or an integer with the role ID.
3045
 */
3046
function user_role_delete($role) {
3047
  if (is_int($role)) {
3048
    $role = user_role_load($role);
3049
  }
3050
  else {
3051
    $role = user_role_load_by_name($role);
3052
  }
3053

    
3054
  // If this is the administrator role, delete the user_admin_role variable.
3055
  if ($role->rid == variable_get('user_admin_role')) {
3056
    variable_del('user_admin_role');
3057
  }
3058

    
3059
  db_delete('role')
3060
    ->condition('rid', $role->rid)
3061
    ->execute();
3062
  db_delete('role_permission')
3063
    ->condition('rid', $role->rid)
3064
    ->execute();
3065
  // Update the users who have this role set:
3066
  db_delete('users_roles')
3067
    ->condition('rid', $role->rid)
3068
    ->execute();
3069

    
3070
  module_invoke_all('user_role_delete', $role);
3071

    
3072
  // Clear the user access cache.
3073
  drupal_static_reset('user_access');
3074
  drupal_static_reset('user_role_permissions');
3075
}
3076

    
3077
/**
3078
 * Menu access callback for user role editing.
3079
 */
3080
function user_role_edit_access($role) {
3081
  // Prevent the system-defined roles from being altered or removed.
3082
  if ($role->rid == DRUPAL_ANONYMOUS_RID || $role->rid == DRUPAL_AUTHENTICATED_RID) {
3083
    return FALSE;
3084
  }
3085

    
3086
  return user_access('administer permissions');
3087
}
3088

    
3089
/**
3090
 * Determine the modules that permissions belong to.
3091
 *
3092
 * @return
3093
 *   An associative array in the format $permission => $module.
3094
 */
3095
function user_permission_get_modules() {
3096
  $permissions = array();
3097
  foreach (module_implements('permission') as $module) {
3098
    $perms = module_invoke($module, 'permission');
3099
    foreach ($perms as $key => $value) {
3100
      $permissions[$key] = $module;
3101
    }
3102
  }
3103
  return $permissions;
3104
}
3105

    
3106
/**
3107
 * Change permissions for a user role.
3108
 *
3109
 * This function may be used to grant and revoke multiple permissions at once.
3110
 * For example, when a form exposes checkboxes to configure permissions for a
3111
 * role, the form submit handler may directly pass the submitted values for the
3112
 * checkboxes form element to this function.
3113
 *
3114
 * @param $rid
3115
 *   The ID of a user role to alter.
3116
 * @param $permissions
3117
 *   An associative array, where the key holds the permission name and the value
3118
 *   determines whether to grant or revoke that permission. Any value that
3119
 *   evaluates to TRUE will cause the permission to be granted. Any value that
3120
 *   evaluates to FALSE will cause the permission to be revoked.
3121
 *   @code
3122
 *     array(
3123
 *       'administer nodes' => 0,                // Revoke 'administer nodes'
3124
 *       'administer blocks' => FALSE,           // Revoke 'administer blocks'
3125
 *       'access user profiles' => 1,            // Grant 'access user profiles'
3126
 *       'access content' => TRUE,               // Grant 'access content'
3127
 *       'access comments' => 'access comments', // Grant 'access comments'
3128
 *     )
3129
 *   @endcode
3130
 *   Existing permissions are not changed, unless specified in $permissions.
3131
 *
3132
 * @see user_role_grant_permissions()
3133
 * @see user_role_revoke_permissions()
3134
 */
3135
function user_role_change_permissions($rid, array $permissions = array()) {
3136
  // Grant new permissions for the role.
3137
  $grant = array_filter($permissions);
3138
  if (!empty($grant)) {
3139
    user_role_grant_permissions($rid, array_keys($grant));
3140
  }
3141
  // Revoke permissions for the role.
3142
  $revoke = array_diff_assoc($permissions, $grant);
3143
  if (!empty($revoke)) {
3144
    user_role_revoke_permissions($rid, array_keys($revoke));
3145
  }
3146
}
3147

    
3148
/**
3149
 * Grant permissions to a user role.
3150
 *
3151
 * @param $rid
3152
 *   The ID of a user role to alter.
3153
 * @param $permissions
3154
 *   A list of permission names to grant.
3155
 *
3156
 * @see user_role_change_permissions()
3157
 * @see user_role_revoke_permissions()
3158
 */
3159
function user_role_grant_permissions($rid, array $permissions = array()) {
3160
  $modules = user_permission_get_modules();
3161
  // Grant new permissions for the role.
3162
  foreach ($permissions as $name) {
3163
    db_merge('role_permission')
3164
      ->key(array(
3165
        'rid' => $rid,
3166
        'permission' => $name,
3167
      ))
3168
      ->fields(array(
3169
        'module' => $modules[$name],
3170
      ))
3171
      ->execute();
3172
  }
3173

    
3174
  // Clear the user access cache.
3175
  drupal_static_reset('user_access');
3176
  drupal_static_reset('user_role_permissions');
3177
}
3178

    
3179
/**
3180
 * Revoke permissions from a user role.
3181
 *
3182
 * @param $rid
3183
 *   The ID of a user role to alter.
3184
 * @param $permissions
3185
 *   A list of permission names to revoke.
3186
 *
3187
 * @see user_role_change_permissions()
3188
 * @see user_role_grant_permissions()
3189
 */
3190
function user_role_revoke_permissions($rid, array $permissions = array()) {
3191
  // Revoke permissions for the role.
3192
  db_delete('role_permission')
3193
    ->condition('rid', $rid)
3194
    ->condition('permission', $permissions, 'IN')
3195
    ->execute();
3196

    
3197
  // Clear the user access cache.
3198
  drupal_static_reset('user_access');
3199
  drupal_static_reset('user_role_permissions');
3200
}
3201

    
3202
/**
3203
 * Implements hook_user_operations().
3204
 */
3205
function user_user_operations($form = array(), $form_state = array()) {
3206
  $operations = array(
3207
    'unblock' => array(
3208
      'label' => t('Unblock the selected users'),
3209
      'callback' => 'user_user_operations_unblock',
3210
    ),
3211
    'block' => array(
3212
      'label' => t('Block the selected users'),
3213
      'callback' => 'user_user_operations_block',
3214
    ),
3215
    'cancel' => array(
3216
      'label' => t('Cancel the selected user accounts'),
3217
    ),
3218
  );
3219

    
3220
  if (user_access('administer permissions')) {
3221
    $roles = user_roles(TRUE);
3222
    unset($roles[DRUPAL_AUTHENTICATED_RID]);  // Can't edit authenticated role.
3223

    
3224
    $add_roles = array();
3225
    foreach ($roles as $key => $value) {
3226
      $add_roles['add_role-' . $key] = $value;
3227
    }
3228

    
3229
    $remove_roles = array();
3230
    foreach ($roles as $key => $value) {
3231
      $remove_roles['remove_role-' . $key] = $value;
3232
    }
3233

    
3234
    if (count($roles)) {
3235
      $role_operations = array(
3236
        t('Add a role to the selected users') => array(
3237
          'label' => $add_roles,
3238
        ),
3239
        t('Remove a role from the selected users') => array(
3240
          'label' => $remove_roles,
3241
        ),
3242
      );
3243

    
3244
      $operations += $role_operations;
3245
    }
3246
  }
3247

    
3248
  // If the form has been posted, we need to insert the proper data for
3249
  // role editing if necessary.
3250
  if (!empty($form_state['submitted'])) {
3251
    $operation_rid = explode('-', $form_state['values']['operation']);
3252
    $operation = $operation_rid[0];
3253
    if ($operation == 'add_role' || $operation == 'remove_role') {
3254
      $rid = $operation_rid[1];
3255
      if (user_access('administer permissions')) {
3256
        $operations[$form_state['values']['operation']] = array(
3257
          'callback' => 'user_multiple_role_edit',
3258
          'callback arguments' => array($operation, $rid),
3259
        );
3260
      }
3261
      else {
3262
        watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING);
3263
        return;
3264
      }
3265
    }
3266
  }
3267

    
3268
  return $operations;
3269
}
3270

    
3271
/**
3272
 * Callback function for admin mass unblocking users.
3273
 */
3274
function user_user_operations_unblock($accounts) {
3275
  $accounts = user_load_multiple($accounts);
3276
  foreach ($accounts as $account) {
3277
    // Skip unblocking user if they are already unblocked.
3278
    if ($account !== FALSE && $account->status == 0) {
3279
      user_save($account, array('status' => 1));
3280
    }
3281
  }
3282
}
3283

    
3284
/**
3285
 * Callback function for admin mass blocking users.
3286
 */
3287
function user_user_operations_block($accounts) {
3288
  $accounts = user_load_multiple($accounts);
3289
  foreach ($accounts as $account) {
3290
    // Skip blocking user if they are already blocked.
3291
    if ($account !== FALSE && $account->status == 1) {
3292
      // For efficiency manually save the original account before applying any
3293
      // changes.
3294
      $account->original = clone $account;
3295
      user_save($account, array('status' => 0));
3296
    }
3297
  }
3298
}
3299

    
3300
/**
3301
 * Callback function for admin mass adding/deleting a user role.
3302
 */
3303
function user_multiple_role_edit($accounts, $operation, $rid) {
3304
  // The role name is not necessary as user_save() will reload the user
3305
  // object, but some modules' hook_user() may look at this first.
3306
  $role_name = db_query('SELECT name FROM {role} WHERE rid = :rid', array(':rid' => $rid))->fetchField();
3307

    
3308
  switch ($operation) {
3309
    case 'add_role':
3310
      $accounts = user_load_multiple($accounts);
3311
      foreach ($accounts as $account) {
3312
        // Skip adding the role to the user if they already have it.
3313
        if ($account !== FALSE && !isset($account->roles[$rid])) {
3314
          $roles = $account->roles + array($rid => $role_name);
3315
          // For efficiency manually save the original account before applying
3316
          // any changes.
3317
          $account->original = clone $account;
3318
          user_save($account, array('roles' => $roles));
3319
        }
3320
      }
3321
      break;
3322
    case 'remove_role':
3323
      $accounts = user_load_multiple($accounts);
3324
      foreach ($accounts as $account) {
3325
        // Skip removing the role from the user if they already don't have it.
3326
        if ($account !== FALSE && isset($account->roles[$rid])) {
3327
          $roles = array_diff($account->roles, array($rid => $role_name));
3328
          // For efficiency manually save the original account before applying
3329
          // any changes.
3330
          $account->original = clone $account;
3331
          user_save($account, array('roles' => $roles));
3332
        }
3333
      }
3334
      break;
3335
  }
3336
}
3337

    
3338
function user_multiple_cancel_confirm($form, &$form_state) {
3339
  $edit = $form_state['input'];
3340

    
3341
  $form['accounts'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
3342
  $accounts = user_load_multiple(array_keys(array_filter($edit['accounts'])));
3343
  foreach ($accounts as $uid => $account) {
3344
    // Prevent user 1 from being canceled.
3345
    if ($uid <= 1) {
3346
      continue;
3347
    }
3348
    $form['accounts'][$uid] = array(
3349
      '#type' => 'hidden',
3350
      '#value' => $uid,
3351
      '#prefix' => '<li>',
3352
      '#suffix' => check_plain($account->name) . "</li>\n",
3353
    );
3354
  }
3355

    
3356
  // Output a notice that user 1 cannot be canceled.
3357
  if (isset($accounts[1])) {
3358
    $redirect = (count($accounts) == 1);
3359
    $message = t('The user account %name cannot be cancelled.', array('%name' => $accounts[1]->name));
3360
    drupal_set_message($message, $redirect ? 'error' : 'warning');
3361
    // If only user 1 was selected, redirect to the overview.
3362
    if ($redirect) {
3363
      drupal_goto('admin/people');
3364
    }
3365
  }
3366

    
3367
  $form['operation'] = array('#type' => 'hidden', '#value' => 'cancel');
3368

    
3369
  module_load_include('inc', 'user', 'user.pages');
3370
  $form['user_cancel_method'] = array(
3371
    '#type' => 'item',
3372
    '#title' => t('When cancelling these accounts'),
3373
  );
3374
  $form['user_cancel_method'] += user_cancel_methods();
3375
  // Remove method descriptions.
3376
  foreach (element_children($form['user_cancel_method']) as $element) {
3377
    unset($form['user_cancel_method'][$element]['#description']);
3378
  }
3379

    
3380
  // Allow to send the account cancellation confirmation mail.
3381
  $form['user_cancel_confirm'] = array(
3382
    '#type' => 'checkbox',
3383
    '#title' => t('Require e-mail confirmation to cancel account.'),
3384
    '#default_value' => FALSE,
3385
    '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'),
3386
  );
3387
  // Also allow to send account canceled notification mail, if enabled.
3388
  $form['user_cancel_notify'] = array(
3389
    '#type' => 'checkbox',
3390
    '#title' => t('Notify user when account is canceled.'),
3391
    '#default_value' => FALSE,
3392
    '#access' => variable_get('user_mail_status_canceled_notify', FALSE),
3393
    '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'),
3394
  );
3395

    
3396
  return confirm_form($form,
3397
                      t('Are you sure you want to cancel these user accounts?'),
3398
                      'admin/people', t('This action cannot be undone.'),
3399
                      t('Cancel accounts'), t('Cancel'));
3400
}
3401

    
3402
/**
3403
 * Submit handler for mass-account cancellation form.
3404
 *
3405
 * @see user_multiple_cancel_confirm()
3406
 * @see user_cancel_confirm_form_submit()
3407
 */
3408
function user_multiple_cancel_confirm_submit($form, &$form_state) {
3409
  global $user;
3410

    
3411
  if ($form_state['values']['confirm']) {
3412
    foreach ($form_state['values']['accounts'] as $uid => $value) {
3413
      // Prevent programmatic form submissions from cancelling user 1.
3414
      if ($uid <= 1) {
3415
        continue;
3416
      }
3417
      // Prevent user administrators from deleting themselves without confirmation.
3418
      if ($uid == $user->uid) {
3419
        $admin_form_state = $form_state;
3420
        unset($admin_form_state['values']['user_cancel_confirm']);
3421
        $admin_form_state['values']['_account'] = $user;
3422
        user_cancel_confirm_form_submit(array(), $admin_form_state);
3423
      }
3424
      else {
3425
        user_cancel($form_state['values'], $uid, $form_state['values']['user_cancel_method']);
3426
      }
3427
    }
3428
  }
3429
  $form_state['redirect'] = 'admin/people';
3430
}
3431

    
3432
/**
3433
 * Retrieve a list of all user setting/information categories and sort them by weight.
3434
 */
3435
function _user_categories() {
3436
  $categories = module_invoke_all('user_categories');
3437
  usort($categories, '_user_sort');
3438

    
3439
  return $categories;
3440
}
3441

    
3442
function _user_sort($a, $b) {
3443
  $a = (array) $a + array('weight' => 0, 'title' => '');
3444
  $b = (array) $b + array('weight' => 0, 'title' => '');
3445
  return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1));
3446
}
3447

    
3448
/**
3449
 * List user administration filters that can be applied.
3450
 */
3451
function user_filters() {
3452
  // Regular filters
3453
  $filters = array();
3454
  $roles = user_roles(TRUE);
3455
  unset($roles[DRUPAL_AUTHENTICATED_RID]); // Don't list authorized role.
3456
  if (count($roles)) {
3457
    $filters['role'] = array(
3458
      'title' => t('role'),
3459
      'field' => 'ur.rid',
3460
      'options' => array(
3461
        '[any]' => t('any'),
3462
      ) + $roles,
3463
    );
3464
  }
3465

    
3466
  $options = array();
3467
  foreach (module_implements('permission') as $module) {
3468
    $function = $module . '_permission';
3469
    if ($permissions = $function()) {
3470
      asort($permissions);
3471
      foreach ($permissions as $permission => $description) {
3472
        $options[t('@module module', array('@module' => $module))][$permission] = t($permission);
3473
      }
3474
    }
3475
  }
3476
  ksort($options);
3477
  $filters['permission'] = array(
3478
    'title' => t('permission'),
3479
    'options' => array(
3480
      '[any]' => t('any'),
3481
    ) + $options,
3482
  );
3483

    
3484
  $filters['status'] = array(
3485
    'title' => t('status'),
3486
    'field' => 'u.status',
3487
    'options' => array(
3488
      '[any]' => t('any'),
3489
      1 => t('active'),
3490
      0 => t('blocked'),
3491
    ),
3492
  );
3493
  return $filters;
3494
}
3495

    
3496
/**
3497
 * Extends a query object for user administration filters based on session.
3498
 *
3499
 * @param $query
3500
 *   Query object that should be filtered.
3501
 */
3502
function user_build_filter_query(SelectQuery $query) {
3503
  $filters = user_filters();
3504
  // Extend Query with filter conditions.
3505
  foreach (isset($_SESSION['user_overview_filter']) ? $_SESSION['user_overview_filter'] : array() as $filter) {
3506
    list($key, $value) = $filter;
3507
    // This checks to see if this permission filter is an enabled permission for
3508
    // the authenticated role. If so, then all users would be listed, and we can
3509
    // skip adding it to the filter query.
3510
    if ($key == 'permission') {
3511
      $account = new stdClass();
3512
      $account->uid = 'user_filter';
3513
      $account->roles = array(DRUPAL_AUTHENTICATED_RID => 1);
3514
      if (user_access($value, $account)) {
3515
        continue;
3516
      }
3517
      $users_roles_alias = $query->join('users_roles', 'ur', '%alias.uid = u.uid');
3518
      $permission_alias = $query->join('role_permission', 'p', $users_roles_alias . '.rid = %alias.rid');
3519
      $query->condition($permission_alias . '.permission', $value);
3520
    }
3521
    elseif ($key == 'role') {
3522
      $users_roles_alias = $query->join('users_roles', 'ur', '%alias.uid = u.uid');
3523
      $query->condition($users_roles_alias . '.rid' , $value);
3524
    }
3525
    else {
3526
      $query->condition($filters[$key]['field'], $value);
3527
    }
3528
  }
3529
}
3530

    
3531
/**
3532
 * Implements hook_comment_view().
3533
 */
3534
function user_comment_view($comment) {
3535
  if (variable_get('user_signatures', 0) && !empty($comment->signature)) {
3536
    // @todo This alters and replaces the original object value, so a
3537
    //   hypothetical process of loading, viewing, and saving will hijack the
3538
    //   stored data. Consider renaming to $comment->signature_safe or similar
3539
    //   here and elsewhere in Drupal 8.
3540
    $comment->signature = check_markup($comment->signature, $comment->signature_format, '', TRUE);
3541
  }
3542
  else {
3543
    $comment->signature = '';
3544
  }
3545
}
3546

    
3547
/**
3548
 * Returns HTML for a user signature.
3549
 *
3550
 * @param $variables
3551
 *   An associative array containing:
3552
 *   - signature: The user's signature.
3553
 *
3554
 * @ingroup themeable
3555
 */
3556
function theme_user_signature($variables) {
3557
  $signature = $variables['signature'];
3558
  $output = '';
3559

    
3560
  if ($signature) {
3561
    $output .= '<div class="clear">';
3562
    $output .= '<div>—</div>';
3563
    $output .= $signature;
3564
    $output .= '</div>';
3565
  }
3566

    
3567
  return $output;
3568
}
3569

    
3570
/**
3571
 * Get the language object preferred by the user. This user preference can
3572
 * be set on the user account editing page, and is only available if there
3573
 * are more than one languages enabled on the site. If the user did not
3574
 * choose a preferred language, or is the anonymous user, the $default
3575
 * value, or if it is not set, the site default language will be returned.
3576
 *
3577
 * @param $account
3578
 *   User account to look up language for.
3579
 * @param $default
3580
 *   Optional default language object to return if the account
3581
 *   has no valid language.
3582
 */
3583
function user_preferred_language($account, $default = NULL) {
3584
  $language_list = language_list();
3585
  if (!empty($account->language) && isset($language_list[$account->language])) {
3586
    return $language_list[$account->language];
3587
  }
3588
  else {
3589
    return $default ? $default : language_default();
3590
  }
3591
}
3592

    
3593
/**
3594
 * Conditionally create and send a notification email when a certain
3595
 * operation happens on the given user account.
3596
 *
3597
 * @see user_mail_tokens()
3598
 * @see drupal_mail()
3599
 *
3600
 * @param $op
3601
 *   The operation being performed on the account. Possible values:
3602
 *   - 'register_admin_created': Welcome message for user created by the admin.
3603
 *   - 'register_no_approval_required': Welcome message when user
3604
 *     self-registers.
3605
 *   - 'register_pending_approval': Welcome message, user pending admin
3606
 *     approval.
3607
 *   - 'password_reset': Password recovery request.
3608
 *   - 'status_activated': Account activated.
3609
 *   - 'status_blocked': Account blocked.
3610
 *   - 'cancel_confirm': Account cancellation request.
3611
 *   - 'status_canceled': Account canceled.
3612
 *
3613
 * @param $account
3614
 *   The user object of the account being notified. Must contain at
3615
 *   least the fields 'uid', 'name', and 'mail'.
3616
 * @param $language
3617
 *   Optional language to use for the notification, overriding account language.
3618
 *
3619
 * @return
3620
 *   The return value from drupal_mail_system()->mail(), if ends up being
3621
 *   called.
3622
 */
3623
function _user_mail_notify($op, $account, $language = NULL) {
3624
  // By default, we always notify except for canceled and blocked.
3625
  $default_notify = ($op != 'status_canceled' && $op != 'status_blocked');
3626
  $notify = variable_get('user_mail_' . $op . '_notify', $default_notify);
3627
  if ($notify) {
3628
    $params['account'] = $account;
3629
    $language = $language ? $language : user_preferred_language($account);
3630
    $mail = drupal_mail('user', $op, $account->mail, $language, $params);
3631
    if ($op == 'register_pending_approval') {
3632
      // If a user registered requiring admin approval, notify the admin, too.
3633
      // We use the site default language for this.
3634
      drupal_mail('user', 'register_pending_approval_admin', variable_get('site_mail', ini_get('sendmail_from')), language_default(), $params);
3635
    }
3636
  }
3637
  return empty($mail) ? NULL : $mail['result'];
3638
}
3639

    
3640
/**
3641
 * Form element process handler for client-side password validation.
3642
 *
3643
 * This #process handler is automatically invoked for 'password_confirm' form
3644
 * elements to add the JavaScript and string translations for dynamic password
3645
 * validation.
3646
 *
3647
 * @see system_element_info()
3648
 */
3649
function user_form_process_password_confirm($element) {
3650
  global $user;
3651

    
3652
  $js_settings = array(
3653
    'password' => array(
3654
      'strengthTitle' => t('Password strength:'),
3655
      'hasWeaknesses' => t('To make your password stronger:'),
3656
      'tooShort' => t('Make it at least 6 characters'),
3657
      'addLowerCase' => t('Add lowercase letters'),
3658
      'addUpperCase' => t('Add uppercase letters'),
3659
      'addNumbers' => t('Add numbers'),
3660
      'addPunctuation' => t('Add punctuation'),
3661
      'sameAsUsername' => t('Make it different from your username'),
3662
      'confirmSuccess' => t('yes'),
3663
      'confirmFailure' => t('no'),
3664
      'weak' => t('Weak'),
3665
      'fair' => t('Fair'),
3666
      'good' => t('Good'),
3667
      'strong' => t('Strong'),
3668
      'confirmTitle' => t('Passwords match:'),
3669
      'username' => (isset($user->name) ? $user->name : ''),
3670
    ),
3671
  );
3672

    
3673
  $element['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.js';
3674
  $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting');
3675

    
3676
  return $element;
3677
}
3678

    
3679
/**
3680
 * Implements hook_node_load().
3681
 */
3682
function user_node_load($nodes, $types) {
3683
  // Build an array of all uids for node authors, keyed by nid.
3684
  $uids = array();
3685
  foreach ($nodes as $nid => $node) {
3686
    $uids[$nid] = $node->uid;
3687
  }
3688

    
3689
  // Fetch name, picture, and data for these users.
3690
  $user_fields = db_query("SELECT uid, name, picture, data FROM {users} WHERE uid IN (:uids)", array(':uids' => $uids))->fetchAllAssoc('uid');
3691

    
3692
  // Add these values back into the node objects.
3693
  foreach ($uids as $nid => $uid) {
3694
    $nodes[$nid]->name = $user_fields[$uid]->name;
3695
    $nodes[$nid]->picture = $user_fields[$uid]->picture;
3696
    $nodes[$nid]->data = $user_fields[$uid]->data;
3697
  }
3698
}
3699

    
3700
/**
3701
 * Implements hook_image_style_delete().
3702
 */
3703
function user_image_style_delete($style) {
3704
  // If a style is deleted, update the variables.
3705
  // Administrators choose a replacement style when deleting.
3706
  user_image_style_save($style);
3707
}
3708

    
3709
/**
3710
 * Implements hook_image_style_save().
3711
 */
3712
function user_image_style_save($style) {
3713
  // If a style is renamed, update the variables that use it.
3714
  if (isset($style['old_name']) && $style['old_name'] == variable_get('user_picture_style', '')) {
3715
    variable_set('user_picture_style', $style['name']);
3716
  }
3717
}
3718

    
3719
/**
3720
 * Implements hook_action_info().
3721
 */
3722
function user_action_info() {
3723
  return array(
3724
    'user_block_user_action' => array(
3725
      'label' => t('Block current user'),
3726
      'type' => 'user',
3727
      'configurable' => FALSE,
3728
      'triggers' => array('any'),
3729
    ),
3730
  );
3731
}
3732

    
3733
/**
3734
 * Blocks a specific user or the current user, if one is not specified.
3735
 *
3736
 * @param $entity
3737
 *   (optional) An entity object; if it is provided and it has a uid property,
3738
 *   the user with that ID is blocked.
3739
 * @param $context
3740
 *   (optional) An associative array; if no user ID is found in $entity, the
3741
 *   'uid' element of this array determines the user to block.
3742
 *
3743
 * @ingroup actions
3744
 */
3745
function user_block_user_action(&$entity, $context = array()) {
3746
  // First priority: If there is a $entity->uid, block that user.
3747
  // This is most likely a user object or the author if a node or comment.
3748
  if (isset($entity->uid)) {
3749
    $uid = $entity->uid;
3750
  }
3751
  elseif (isset($context['uid'])) {
3752
    $uid = $context['uid'];
3753
  }
3754
  // If neither of those are valid, then block the current user.
3755
  else {
3756
    $uid = $GLOBALS['user']->uid;
3757
  }
3758
  $account = user_load($uid);
3759
  $account = user_save($account, array('status' => 0));
3760
  watchdog('action', 'Blocked user %name.', array('%name' => $account->name));
3761
}
3762

    
3763
/**
3764
 * Implements hook_form_FORM_ID_alter().
3765
 *
3766
 * Add a checkbox for the 'user_register_form' instance settings on the 'Edit
3767
 * field instance' form.
3768
 */
3769
function user_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
3770
  $instance = $form['#instance'];
3771

    
3772
  if ($instance['entity_type'] == 'user' && !$form['#field']['locked']) {
3773
    $form['instance']['settings']['user_register_form'] = array(
3774
      '#type' => 'checkbox',
3775
      '#title' => t('Display on user registration form.'),
3776
      '#description' => t("This is compulsory for 'required' fields."),
3777
      // Field instances created in D7 beta releases before the setting was
3778
      // introduced might be set as 'required' and 'not shown on user_register
3779
      // form'. We make sure the checkbox comes as 'checked' for those.
3780
      '#default_value' => $instance['settings']['user_register_form'] || $instance['required'],
3781
      // Display just below the 'required' checkbox.
3782
      '#weight' => $form['instance']['required']['#weight'] + .1,
3783
      // Disabled when the 'required' checkbox is checked.
3784
      '#states' => array(
3785
        'enabled' => array('input[name="instance[required]"]' => array('checked' => FALSE)),
3786
      ),
3787
      // Checked when the 'required' checkbox is checked. This is done through
3788
      // a custom behavior, since the #states system would also synchronize on
3789
      // uncheck.
3790
      '#attached' => array(
3791
        'js' => array(drupal_get_path('module', 'user') . '/user.js'),
3792
      ),
3793
    );
3794

    
3795
    array_unshift($form['#submit'], 'user_form_field_ui_field_edit_form_submit');
3796
  }
3797
}
3798

    
3799
/**
3800
 * Additional submit handler for the 'Edit field instance' form.
3801
 *
3802
 * Make sure the 'user_register_form' setting is set for required fields.
3803
 */
3804
function user_form_field_ui_field_edit_form_submit($form, &$form_state) {
3805
  $instance = $form_state['values']['instance'];
3806

    
3807
  if (!empty($instance['required'])) {
3808
    form_set_value($form['instance']['settings']['user_register_form'], 1, $form_state);
3809
  }
3810
}
3811

    
3812
/**
3813
 * Form builder; the user registration form.
3814
 *
3815
 * @ingroup forms
3816
 * @see user_account_form()
3817
 * @see user_account_form_validate()
3818
 * @see user_register_submit()
3819
 */
3820
function user_register_form($form, &$form_state) {
3821
  global $user;
3822

    
3823
  $admin = user_access('administer users');
3824

    
3825
  // Pass access information to the submit handler. Running an access check
3826
  // inside the submit function interferes with form processing and breaks
3827
  // hook_form_alter().
3828
  $form['administer_users'] = array(
3829
    '#type' => 'value',
3830
    '#value' => $admin,
3831
  );
3832

    
3833
  // If we aren't admin but already logged on, go to the user page instead.
3834
  if (!$admin && $user->uid) {
3835
    drupal_goto('user/' . $user->uid);
3836
  }
3837

    
3838
  $form['#user'] = drupal_anonymous_user();
3839
  $form['#user_category'] = 'register';
3840

    
3841
  $form['#attached']['library'][] = array('system', 'jquery.cookie');
3842
  $form['#attributes']['class'][] = 'user-info-from-cookie';
3843

    
3844
  // Start with the default user account fields.
3845
  user_account_form($form, $form_state);
3846

    
3847
  // Attach field widgets, and hide the ones where the 'user_register_form'
3848
  // setting is not on.
3849
  $langcode = entity_language('user', $form['#user']);
3850
  field_attach_form('user', $form['#user'], $form, $form_state, $langcode);
3851
  foreach (field_info_instances('user', 'user') as $field_name => $instance) {
3852
    if (empty($instance['settings']['user_register_form'])) {
3853
      $form[$field_name]['#access'] = FALSE;
3854
    }
3855
  }
3856

    
3857
  if ($admin) {
3858
    // Redirect back to page which initiated the create request;
3859
    // usually admin/people/create.
3860
    $form_state['redirect'] = $_GET['q'];
3861
  }
3862

    
3863
  $form['actions'] = array('#type' => 'actions');
3864
  $form['actions']['submit'] = array(
3865
    '#type' => 'submit',
3866
    '#value' => t('Create new account'),
3867
  );
3868

    
3869
  $form['#validate'][] = 'user_register_validate';
3870
  // Add the final user registration form submit handler.
3871
  $form['#submit'][] = 'user_register_submit';
3872

    
3873
  return $form;
3874
}
3875

    
3876
/**
3877
 * Validation function for the user registration form.
3878
 */
3879
function user_register_validate($form, &$form_state) {
3880
  entity_form_field_validate('user', $form, $form_state);
3881
}
3882

    
3883
/**
3884
 * Submit handler for the user registration form.
3885
 *
3886
 * This function is shared by the installation form and the normal registration form,
3887
 * which is why it can't be in the user.pages.inc file.
3888
 *
3889
 * @see user_register_form()
3890
 */
3891
function user_register_submit($form, &$form_state) {
3892
  $admin = $form_state['values']['administer_users'];
3893

    
3894
  if (!variable_get('user_email_verification', TRUE) || $admin) {
3895
    $pass = $form_state['values']['pass'];
3896
  }
3897
  else {
3898
    $pass = user_password();
3899
  }
3900
  $notify = !empty($form_state['values']['notify']);
3901

    
3902
  // Remove unneeded values.
3903
  form_state_values_clean($form_state);
3904

    
3905
  $form_state['values']['pass'] = $pass;
3906
  $form_state['values']['init'] = $form_state['values']['mail'];
3907

    
3908
  $account = $form['#user'];
3909

    
3910
  entity_form_submit_build_entity('user', $account, $form, $form_state);
3911

    
3912
  // Populate $edit with the properties of $account, which have been edited on
3913
  // this form by taking over all values, which appear in the form values too.
3914
  $edit = array_intersect_key((array) $account, $form_state['values']);
3915
  $account = user_save($account, $edit);
3916

    
3917
  // Terminate if an error occurred during user_save().
3918
  if (!$account) {
3919
    drupal_set_message(t("Error saving user account."), 'error');
3920
    $form_state['redirect'] = '';
3921
    return;
3922
  }
3923
  $form_state['user'] = $account;
3924
  $form_state['values']['uid'] = $account->uid;
3925

    
3926
  watchdog('user', 'New user: %name (%email).', array('%name' => $form_state['values']['name'], '%email' => $form_state['values']['mail']), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit'));
3927

    
3928
  // Add plain text password into user account to generate mail tokens.
3929
  $account->password = $pass;
3930

    
3931
  // New administrative account without notification.
3932
  $uri = entity_uri('user', $account);
3933
  if ($admin && !$notify) {
3934
    drupal_set_message(t('Created a new user account for <a href="@url">%name</a>. No e-mail has been sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name)));
3935
  }
3936
  // No e-mail verification required; log in user immediately.
3937
  elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) {
3938
    _user_mail_notify('register_no_approval_required', $account);
3939
    $form_state['uid'] = $account->uid;
3940
    user_login_submit(array(), $form_state);
3941
    drupal_set_message(t('Registration successful. You are now logged in.'));
3942
    $form_state['redirect'] = '';
3943
  }
3944
  // No administrator approval required.
3945
  elseif ($account->status || $notify) {
3946
    $op = $notify ? 'register_admin_created' : 'register_no_approval_required';
3947
    _user_mail_notify($op, $account);
3948
    if ($notify) {
3949
      drupal_set_message(t('A welcome message with further instructions has been e-mailed to the new user <a href="@url">%name</a>.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name)));
3950
    }
3951
    else {
3952
      drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.'));
3953
      $form_state['redirect'] = '';
3954
    }
3955
  }
3956
  // Administrator approval required.
3957
  else {
3958
    _user_mail_notify('register_pending_approval', $account);
3959
    drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.<br />In the meantime, a welcome message with further instructions has been sent to your e-mail address.'));
3960
    $form_state['redirect'] = '';
3961
  }
3962
}
3963

    
3964
/**
3965
 * Implements hook_modules_installed().
3966
 */
3967
function user_modules_installed($modules) {
3968
  // Assign all available permissions to the administrator role.
3969
  $rid = variable_get('user_admin_role', 0);
3970
  if ($rid) {
3971
    $permissions = array();
3972
    foreach ($modules as $module) {
3973
      if ($module_permissions = module_invoke($module, 'permission')) {
3974
        $permissions = array_merge($permissions, array_keys($module_permissions));
3975
      }
3976
    }
3977
    if (!empty($permissions)) {
3978
      user_role_grant_permissions($rid, $permissions);
3979
    }
3980
  }
3981
}
3982

    
3983
/**
3984
 * Implements hook_modules_uninstalled().
3985
 */
3986
function user_modules_uninstalled($modules) {
3987
   db_delete('role_permission')
3988
     ->condition('module', $modules, 'IN')
3989
     ->execute();
3990
}
3991

    
3992
/**
3993
 * Helper function to rewrite the destination to avoid redirecting to login page after login.
3994
 *
3995
 * Third-party authentication modules may use this function to determine the
3996
 * proper destination after a user has been properly logged in.
3997
 */
3998
function user_login_destination() {
3999
  $destination = drupal_get_destination();
4000
  if ($destination['destination'] == 'user/login') {
4001
    $destination['destination'] = 'user';
4002
  }
4003
  return $destination;
4004
}
4005

    
4006
/**
4007
 * Saves visitor information as a cookie so it can be reused.
4008
 *
4009
 * @param $values
4010
 *   An array of key/value pairs to be saved into a cookie.
4011
 */
4012
function user_cookie_save(array $values) {
4013
  foreach ($values as $field => $value) {
4014
    // Set cookie for 365 days.
4015
    setrawcookie('Drupal.visitor.' . $field, rawurlencode($value), REQUEST_TIME + 31536000, '/');
4016
  }
4017
}
4018

    
4019
/**
4020
 * Delete a visitor information cookie.
4021
 *
4022
 * @param $cookie_name
4023
 *   A cookie name such as 'homepage'.
4024
 */
4025
function user_cookie_delete($cookie_name) {
4026
  setrawcookie('Drupal.visitor.' . $cookie_name, '', REQUEST_TIME - 3600, '/');
4027
}
4028

    
4029
/**
4030
 * Implements hook_rdf_mapping().
4031
 */
4032
function user_rdf_mapping() {
4033
  return array(
4034
    array(
4035
      'type' => 'user',
4036
      'bundle' => RDF_DEFAULT_BUNDLE,
4037
      'mapping' => array(
4038
        'rdftype' => array('sioc:UserAccount'),
4039
        'name' => array(
4040
          'predicates' => array('foaf:name'),
4041
        ),
4042
        'homepage' => array(
4043
          'predicates' => array('foaf:page'),
4044
          'type' => 'rel',
4045
        ),
4046
      ),
4047
    ),
4048
  );
4049
}
4050

    
4051
/**
4052
 * Implements hook_file_download_access().
4053
 */
4054
function user_file_download_access($field, $entity_type, $entity) {
4055
  if ($entity_type == 'user') {
4056
    return user_view_access($entity);
4057
  }
4058
}
4059

    
4060
/**
4061
 * Implements hook_system_info_alter().
4062
 *
4063
 * Drupal 7 ships with two methods to add additional fields to users: Profile
4064
 * module, a legacy module dating back from 2002, and Field API integration
4065
 * with users. While Field API support for users currently provides less end
4066
 * user features, the inefficient data storage mechanism of Profile module, as
4067
 * well as its lack of consistency with the rest of the entity / field based
4068
 * systems in Drupal 7, make this a sub-optimal solution to those who were not
4069
 * using it in previous releases of Drupal.
4070
 *
4071
 * To prevent new Drupal 7 sites from installing Profile module, and
4072
 * unwittingly ending up with two completely different and incompatible methods
4073
 * of extending users, only make the Profile module available if the profile_*
4074
 * tables are present.
4075
 *
4076
 * @todo: Remove in D8, pending upgrade path.
4077
 */
4078
function user_system_info_alter(&$info, $file, $type) {
4079
  if ($type == 'module' && $file->name == 'profile' && db_table_exists('profile_field')) {
4080
    $info['hidden'] = FALSE;
4081
  }
4082
}