Projet

Général

Profil

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

root / drupal7 / modules / user / user.module @ f7a2490e

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
 * Implement 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
      $account->picture = empty($account->picture->fid) ? 0 : $account->picture->fid;
505

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

    
516
      // Reload user roles if provided.
517
      if ($account->roles != $account->original->roles) {
518
        db_delete('users_roles')
519
          ->condition('uid', $account->uid)
520
          ->execute();
521

    
522
        $query = db_insert('users_roles')->fields(array('uid', 'rid'));
523
        foreach (array_keys($account->roles) as $rid) {
524
          if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
525
            $query->values(array(
526
              'uid' => $account->uid,
527
              'rid' => $rid,
528
            ));
529
          }
530
        }
531
        $query->execute();
532
      }
533

    
534
      // Delete a blocked user's sessions to kick them if they are online.
535
      if ($account->original->status != $account->status && $account->status == 0) {
536
        drupal_session_destroy_uid($account->uid);
537
      }
538

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

    
548
      // Save Field data.
549
      field_attach_update('user', $account);
550

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

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

    
584
      // Make sure $account is properly initialized.
585
      $account->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
586

    
587
      field_attach_insert('user', $account);
588
      $edit = (array) $account;
589
      user_module_invoke('insert', $edit, $account, $category);
590
      module_invoke_all('entity_insert', $account, 'user');
591

    
592
      // Save user roles.
593
      if (count($account->roles) > 1) {
594
        $query = db_insert('users_roles')->fields(array('uid', 'rid'));
595
        foreach (array_keys($account->roles) as $rid) {
596
          if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
597
            $query->values(array(
598
              'uid' => $account->uid,
599
              'rid' => $rid,
600
            ));
601
          }
602
        }
603
        $query->execute();
604
      }
605
    }
606
    // Clear internal properties.
607
    unset($account->is_new);
608
    unset($account->original);
609
    // Clear the static loading cache.
610
    entity_get_controller('user')->resetCache(array($account->uid));
611

    
612
    return $account;
613
  }
614
  catch (Exception $e) {
615
    $transaction->rollback();
616
    watchdog_exception('user', $e);
617
    throw $e;
618
  }
619
}
620

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

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

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

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

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

    
712
  // Zero-based count of characters in the allowable list:
713
  $len = strlen($allowable_characters) - 1;
714

    
715
  // Declare the password as a blank string.
716
  $pass = '';
717

    
718
  // Loop the number of times specified by $length.
719
  for ($i = 0; $i < $length; $i++) {
720
    do {
721
      // Find a secure random number within the range needed.
722
      $index = ord(drupal_random_bytes(1));
723
    } while ($index > $len);
724

    
725
    // Each iteration, pick a random character from the
726
    // allowable string and append it to the password:
727
    $pass .= $allowable_characters[$index];
728
  }
729

    
730
  return $pass;
731
}
732

    
733
/**
734
 * Determine the permissions for one or more roles.
735
 *
736
 * @param $roles
737
 *   An array whose keys are the role IDs of interest, such as $user->roles.
738
 *
739
 * @return
740
 *   An array indexed by role ID. Each value is an array whose keys are the
741
 *   permission strings for the given role ID.
742
 */
743
function user_role_permissions($roles = array()) {
744
  $cache = &drupal_static(__FUNCTION__, array());
745

    
746
  $role_permissions = $fetch = array();
747

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

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

    
766
      foreach ($result as $row) {
767
        $cache[$row->rid][$row->permission] = TRUE;
768
      }
769
      foreach ($fetch as $rid) {
770
        // For every rid, we know we at least assigned an empty array.
771
        $role_permissions[$rid] = $cache[$rid];
772
      }
773
    }
774
  }
775

    
776
  return $role_permissions;
777
}
778

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

    
797
  if (!isset($account)) {
798
    $account = $user;
799
  }
800

    
801
  // User #1 has all privileges:
802
  if ($account->uid == 1) {
803
    return TRUE;
804
  }
805

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

    
817
    $perms = array();
818
    foreach ($role_permissions as $one_role) {
819
      $perms += $one_role;
820
    }
821
    $perm[$account->uid] = $perms;
822
  }
823

    
824
  return isset($perm[$account->uid][$string]);
825
}
826

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

    
845
/**
846
 * Implements hook_permission().
847
 */
848
function user_permission() {
849
  return array(
850
    'administer permissions' =>  array(
851
      'title' => t('Administer permissions'),
852
      'restrict access' => TRUE,
853
    ),
854
    'administer users' => array(
855
      'title' => t('Administer users'),
856
      'restrict access' => TRUE,
857
    ),
858
    'access user profiles' => array(
859
      'title' => t('View user profiles'),
860
    ),
861
    'change own username' => array(
862
      'title' => t('Change own username'),
863
    ),
864
    'cancel account' => array(
865
      'title' => t('Cancel own user account'),
866
      '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'))),
867
    ),
868
    'select account cancellation method' => array(
869
      'title' => t('Select method for cancelling own account'),
870
      'restrict access' => TRUE,
871
    ),
872
  );
873
}
874

    
875
/**
876
 * Implements hook_file_download().
877
 *
878
 * Ensure that user pictures (avatars) are always downloadable.
879
 */
880
function user_file_download($uri) {
881
  if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
882
    $info = image_get_info($uri);
883
    return array('Content-Type' => $info['mime_type']);
884
  }
885
}
886

    
887
/**
888
 * Implements hook_file_move().
889
 */
890
function user_file_move($file, $source) {
891
  // If a user's picture is replaced with a new one, update the record in
892
  // the users table.
893
  if (isset($file->fid) && isset($source->fid) && $file->fid != $source->fid) {
894
    db_update('users')
895
      ->fields(array(
896
        'picture' => $file->fid,
897
      ))
898
      ->condition('picture', $source->fid)
899
      ->execute();
900
  }
901
}
902

    
903
/**
904
 * Implements hook_file_delete().
905
 */
906
function user_file_delete($file) {
907
  // Remove any references to the file.
908
  db_update('users')
909
    ->fields(array('picture' => 0))
910
    ->condition('picture', $file->fid)
911
    ->execute();
912
}
913

    
914
/**
915
 * Implements hook_search_info().
916
 */
917
function user_search_info() {
918
  return array(
919
    'title' => 'Users',
920
  );
921
}
922

    
923
/**
924
 * Implements hook_search_access().
925
 */
926
function user_search_access() {
927
  return user_access('access user profiles');
928
}
929

    
930
/**
931
 * Implements hook_search_execute().
932
 */
933
function user_search_execute($keys = NULL, $conditions = NULL) {
934
  $find = array();
935
  // Replace wildcards with MySQL/PostgreSQL wildcards.
936
  $keys = preg_replace('!\*+!', '%', $keys);
937
  $query = db_select('users')->extend('PagerDefault');
938
  $query->fields('users', array('uid'));
939
  if (user_access('administer users')) {
940
    // Administrators can also search in the otherwise private email field,
941
    // and they don't need to be restricted to only active users.
942
    $query->fields('users', array('mail'));
943
    $query->condition(db_or()->
944
      condition('name', '%' . db_like($keys) . '%', 'LIKE')->
945
      condition('mail', '%' . db_like($keys) . '%', 'LIKE'));
946
  }
947
  else {
948
    // Regular users can only search via usernames, and we do not show them
949
    // blocked accounts.
950
    $query->condition('name', '%' . db_like($keys) . '%', 'LIKE')
951
      ->condition('status', 1);
952
  }
953
  $uids = $query
954
    ->limit(15)
955
    ->execute()
956
    ->fetchCol();
957
  $accounts = user_load_multiple($uids);
958

    
959
  $results = array();
960
  foreach ($accounts as $account) {
961
    $result = array(
962
      'title' => format_username($account),
963
      'link' => url('user/' . $account->uid, array('absolute' => TRUE)),
964
    );
965
    if (user_access('administer users')) {
966
      $result['title'] .= ' (' . $account->mail . ')';
967
    }
968
    $results[] = $result;
969
  }
970

    
971
  return $results;
972
}
973

    
974
/**
975
 * Implements hook_element_info().
976
 */
977
function user_element_info() {
978
  $types['user_profile_category'] = array(
979
    '#theme_wrappers' => array('user_profile_category'),
980
  );
981
  $types['user_profile_item'] = array(
982
    '#theme' => 'user_profile_item',
983
  );
984
  return $types;
985
}
986

    
987
/**
988
 * Implements hook_user_view().
989
 */
990
function user_user_view($account) {
991
  $account->content['user_picture'] = array(
992
    '#markup' => theme('user_picture', array('account' => $account)),
993
    '#weight' => -10,
994
  );
995
  if (!isset($account->content['summary'])) {
996
    $account->content['summary'] = array();
997
  }
998
  $account->content['summary'] += array(
999
    '#type' => 'user_profile_category',
1000
    '#attributes' => array('class' => array('user-member')),
1001
    '#weight' => 5,
1002
    '#title' => t('History'),
1003
  );
1004
  $account->content['summary']['member_for'] = array(
1005
    '#type' => 'user_profile_item',
1006
    '#title' => t('Member for'),
1007
    '#markup' => format_interval(REQUEST_TIME - $account->created),
1008
  );
1009
}
1010

    
1011
/**
1012
 * Helper function to add default user account fields to user registration and edit form.
1013
 *
1014
 * @see user_account_form_validate()
1015
 * @see user_validate_current_pass()
1016
 * @see user_validate_picture()
1017
 * @see user_validate_mail()
1018
 */
1019
function user_account_form(&$form, &$form_state) {
1020
  global $user;
1021

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

    
1025
  $admin = user_access('administer users');
1026

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

    
1029
  // Account information.
1030
  $form['account'] = array(
1031
    '#type'   => 'container',
1032
    '#weight' => -10,
1033
  );
1034
  // Only show name field on registration form or user can change own username.
1035
  $form['account']['name'] = array(
1036
    '#type' => 'textfield',
1037
    '#title' => t('Username'),
1038
    '#maxlength' => USERNAME_MAX_LENGTH,
1039
    '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'),
1040
    '#required' => TRUE,
1041
    '#attributes' => array('class' => array('username')),
1042
    '#default_value' => (!$register ? $account->name : ''),
1043
    '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin),
1044
    '#weight' => -10,
1045
  );
1046

    
1047
  $form['account']['mail'] = array(
1048
    '#type' => 'textfield',
1049
    '#title' => t('E-mail address'),
1050
    '#maxlength' => EMAIL_MAX_LENGTH,
1051
    '#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.'),
1052
    '#required' => TRUE,
1053
    '#default_value' => (!$register ? $account->mail : ''),
1054
  );
1055

    
1056
  // Display password field only for existing users or when user is allowed to
1057
  // assign a password during registration.
1058
  if (!$register) {
1059
    $form['account']['pass'] = array(
1060
      '#type' => 'password_confirm',
1061
      '#size' => 25,
1062
      '#description' => t('To change the current user password, enter the new password in both fields.'),
1063
    );
1064
    // To skip the current password field, the user must have logged in via a
1065
    // one-time link and have the token in the URL.
1066
    $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]);
1067
    $protected_values = array();
1068
    $current_pass_description = '';
1069
    // The user may only change their own password without their current
1070
    // password if they logged in via a one-time login link.
1071
    if (!$pass_reset) {
1072
      $protected_values['mail'] = $form['account']['mail']['#title'];
1073
      $protected_values['pass'] = t('Password');
1074
      $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
1075
      $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));
1076
    }
1077
    // The user must enter their current password to change to a new one.
1078
    if ($user->uid == $account->uid) {
1079
      $form['account']['current_pass_required_values'] = array(
1080
        '#type' => 'value',
1081
        '#value' => $protected_values,
1082
      );
1083
      $form['account']['current_pass'] = array(
1084
        '#type' => 'password',
1085
        '#title' => t('Current password'),
1086
        '#size' => 25,
1087
        '#access' => !empty($protected_values),
1088
        '#description' => $current_pass_description,
1089
        '#weight' => -5,
1090
        // Do not let web browsers remember this password, since we are trying
1091
        // to confirm that the person submitting the form actually knows the
1092
        // current one.
1093
        '#attributes' => array('autocomplete' => 'off'),
1094
      );
1095
      $form['#validate'][] = 'user_validate_current_pass';
1096
    }
1097
  }
1098
  elseif (!variable_get('user_email_verification', TRUE) || $admin) {
1099
    $form['account']['pass'] = array(
1100
      '#type' => 'password_confirm',
1101
      '#size' => 25,
1102
      '#description' => t('Provide a password for the new account in both fields.'),
1103
      '#required' => TRUE,
1104
    );
1105
  }
1106

    
1107
  if ($admin) {
1108
    $status = isset($account->status) ? $account->status : 1;
1109
  }
1110
  else {
1111
    $status = $register ? variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS : $account->status;
1112
  }
1113
  $form['account']['status'] = array(
1114
    '#type' => 'radios',
1115
    '#title' => t('Status'),
1116
    '#default_value' => $status,
1117
    '#options' => array(t('Blocked'), t('Active')),
1118
    '#access' => $admin,
1119
  );
1120

    
1121
  $roles = array_map('check_plain', user_roles(TRUE));
1122
  // The disabled checkbox subelement for the 'authenticated user' role
1123
  // must be generated separately and added to the checkboxes element,
1124
  // because of a limitation in Form API not supporting a single disabled
1125
  // checkbox within a set of checkboxes.
1126
  // @todo This should be solved more elegantly. See issue #119038.
1127
  $checkbox_authenticated = array(
1128
    '#type' => 'checkbox',
1129
    '#title' => $roles[DRUPAL_AUTHENTICATED_RID],
1130
    '#default_value' => TRUE,
1131
    '#disabled' => TRUE,
1132
  );
1133
  unset($roles[DRUPAL_AUTHENTICATED_RID]);
1134
  $form['account']['roles'] = array(
1135
    '#type' => 'checkboxes',
1136
    '#title' => t('Roles'),
1137
    '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()),
1138
    '#options' => $roles,
1139
    '#access' => $roles && user_access('administer permissions'),
1140
    DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
1141
  );
1142

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

    
1149
  // Signature.
1150
  $form['signature_settings'] = array(
1151
    '#type' => 'fieldset',
1152
    '#title' => t('Signature settings'),
1153
    '#weight' => 1,
1154
    '#access' => (!$register && variable_get('user_signatures', 0)),
1155
  );
1156

    
1157
  $form['signature_settings']['signature'] = array(
1158
    '#type' => 'text_format',
1159
    '#title' => t('Signature'),
1160
    '#default_value' => isset($account->signature) ? $account->signature : '',
1161
    '#description' => t('Your signature will be publicly displayed at the end of your comments.'),
1162
    '#format' => isset($account->signature_format) ? $account->signature_format : NULL,
1163
  );
1164

    
1165
  // Picture/avatar.
1166
  $form['picture'] = array(
1167
    '#type' => 'fieldset',
1168
    '#title' => t('Picture'),
1169
    '#weight' => 1,
1170
    '#access' => (!$register && variable_get('user_pictures', 0)),
1171
  );
1172
  $form['picture']['picture'] = array(
1173
    '#type' => 'value',
1174
    '#value' => isset($account->picture) ? $account->picture : NULL,
1175
  );
1176
  $form['picture']['picture_current'] = array(
1177
    '#markup' => theme('user_picture', array('account' => $account)),
1178
  );
1179
  $form['picture']['picture_delete'] = array(
1180
    '#type' => 'checkbox',
1181
    '#title' => t('Delete picture'),
1182
    '#access' => !empty($account->picture->fid),
1183
    '#description' => t('Check this box to delete your current picture.'),
1184
  );
1185
  $form['picture']['picture_upload'] = array(
1186
    '#type' => 'file',
1187
    '#title' => t('Upload picture'),
1188
    '#size' => 48,
1189
    '#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', '')),
1190
  );
1191
  $form['#validate'][] = 'user_validate_picture';
1192
}
1193

    
1194
/**
1195
 * Form validation handler for the current password on the user_account_form().
1196
 *
1197
 * @see user_account_form()
1198
 */
1199
function user_validate_current_pass(&$form, &$form_state) {
1200
  $account = $form['#user'];
1201
  foreach ($form_state['values']['current_pass_required_values'] as $key => $name) {
1202
    // This validation only works for required textfields (like mail) or
1203
    // form values like password_confirm that have their own validation
1204
    // that prevent them from being empty if they are changed.
1205
    if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) {
1206
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
1207
      $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account);
1208
      if ($current_pass_failed) {
1209
        form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name)));
1210
        form_set_error($key);
1211
      }
1212
      // We only need to check the password once.
1213
      break;
1214
    }
1215
  }
1216
}
1217

    
1218
/**
1219
 * Form validation handler for user_account_form().
1220
 *
1221
 * @see user_account_form()
1222
 */
1223
function user_account_form_validate($form, &$form_state) {
1224
  if ($form['#user_category'] == 'account' || $form['#user_category'] == 'register') {
1225
    $account = $form['#user'];
1226
    // Validate new or changing username.
1227
    if (isset($form_state['values']['name'])) {
1228
      if ($error = user_validate_name($form_state['values']['name'])) {
1229
        form_set_error('name', $error);
1230
      }
1231
      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()) {
1232
        form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name'])));
1233
      }
1234
    }
1235

    
1236
    // Trim whitespace from mail, to prevent confusing 'e-mail not valid'
1237
    // warnings often caused by cutting and pasting.
1238
    $mail = trim($form_state['values']['mail']);
1239
    form_set_value($form['account']['mail'], $mail, $form_state);
1240

    
1241
    // Validate the e-mail address, and check if it is taken by an existing user.
1242
    if ($error = user_validate_mail($form_state['values']['mail'])) {
1243
      form_set_error('mail', $error);
1244
    }
1245
    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()) {
1246
      // Format error message dependent on whether the user is logged in or not.
1247
      if ($GLOBALS['user']->uid) {
1248
        form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $form_state['values']['mail'])));
1249
      }
1250
      else {
1251
        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'))));
1252
      }
1253
    }
1254

    
1255
    // Make sure the signature isn't longer than the size of the database field.
1256
    // Signatures are disabled by default, so make sure it exists first.
1257
    if (isset($form_state['values']['signature'])) {
1258
      // Move text format for user signature into 'signature_format'.
1259
      $form_state['values']['signature_format'] = $form_state['values']['signature']['format'];
1260
      // Move text value for user signature into 'signature'.
1261
      $form_state['values']['signature'] = $form_state['values']['signature']['value'];
1262

    
1263
      $user_schema = drupal_get_schema('users');
1264
      if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) {
1265
        form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length'])));
1266
      }
1267
    }
1268
  }
1269
}
1270

    
1271
/**
1272
 * Implements hook_user_presave().
1273
 */
1274
function user_user_presave(&$edit, $account, $category) {
1275
  if ($category == 'account' || $category == 'register') {
1276
    if (!empty($edit['picture_upload'])) {
1277
      $edit['picture'] = $edit['picture_upload'];
1278
    }
1279
    // Delete picture if requested, and if no replacement picture was given.
1280
    elseif (!empty($edit['picture_delete'])) {
1281
      $edit['picture'] = NULL;
1282
    }
1283
    // Prepare user roles.
1284
    if (isset($edit['roles'])) {
1285
      $edit['roles'] = array_filter($edit['roles']);
1286
    }
1287
  }
1288

    
1289
  // Move account cancellation information into $user->data.
1290
  foreach (array('user_cancel_method', 'user_cancel_notify') as $key) {
1291
    if (isset($edit[$key])) {
1292
      $edit['data'][$key] = $edit[$key];
1293
    }
1294
  }
1295
}
1296

    
1297
/**
1298
 * Implements hook_user_categories().
1299
 */
1300
function user_user_categories() {
1301
  return array(array(
1302
    'name' => 'account',
1303
    'title' => t('Account settings'),
1304
    'weight' => 1,
1305
  ));
1306
}
1307

    
1308
function user_login_block($form) {
1309
  $form['#action'] = url(current_path(), array('query' => drupal_get_destination(), 'external' => FALSE));
1310
  $form['#id'] = 'user-login-form';
1311
  $form['#validate'] = user_login_default_validators();
1312
  $form['#submit'][] = 'user_login_submit';
1313
  $form['name'] = array('#type' => 'textfield',
1314
    '#title' => t('Username'),
1315
    '#maxlength' => USERNAME_MAX_LENGTH,
1316
    '#size' => 15,
1317
    '#required' => TRUE,
1318
  );
1319
  $form['pass'] = array('#type' => 'password',
1320
    '#title' => t('Password'),
1321
    '#size' => 15,
1322
    '#required' => TRUE,
1323
  );
1324
  $form['actions'] = array('#type' => 'actions');
1325
  $form['actions']['submit'] = array('#type' => 'submit',
1326
    '#value' => t('Log in'),
1327
  );
1328
  $items = array();
1329
  if (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)) {
1330
    $items[] = l(t('Create new account'), 'user/register', array('attributes' => array('title' => t('Create a new user account.'))));
1331
  }
1332
  $items[] = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
1333
  $form['links'] = array('#markup' => theme('item_list', array('items' => $items)));
1334
  return $form;
1335
}
1336

    
1337
/**
1338
 * Implements hook_block_info().
1339
 */
1340
function user_block_info() {
1341
  global $user;
1342

    
1343
  $blocks['login']['info'] = t('User login');
1344
  // Not worth caching.
1345
  $blocks['login']['cache'] = DRUPAL_NO_CACHE;
1346

    
1347
  $blocks['new']['info'] = t('Who\'s new');
1348
  $blocks['new']['properties']['administrative'] = TRUE;
1349

    
1350
  // Too dynamic to cache.
1351
  $blocks['online']['info'] = t('Who\'s online');
1352
  $blocks['online']['cache'] = DRUPAL_NO_CACHE;
1353
  $blocks['online']['properties']['administrative'] = TRUE;
1354

    
1355
  return $blocks;
1356
}
1357

    
1358
/**
1359
 * Implements hook_block_configure().
1360
 */
1361
function user_block_configure($delta = '') {
1362
  global $user;
1363

    
1364
  switch ($delta) {
1365
    case 'new':
1366
      $form['user_block_whois_new_count'] = array(
1367
        '#type' => 'select',
1368
        '#title' => t('Number of users to display'),
1369
        '#default_value' => variable_get('user_block_whois_new_count', 5),
1370
        '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
1371
      );
1372
      return $form;
1373

    
1374
    case 'online':
1375
      $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
1376
      $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.'));
1377
      $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.'));
1378
      return $form;
1379
  }
1380
}
1381

    
1382
/**
1383
 * Implements hook_block_save().
1384
 */
1385
function user_block_save($delta = '', $edit = array()) {
1386
  global $user;
1387

    
1388
  switch ($delta) {
1389
    case 'new':
1390
      variable_set('user_block_whois_new_count', $edit['user_block_whois_new_count']);
1391
      break;
1392

    
1393
    case 'online':
1394
      variable_set('user_block_seconds_online', $edit['user_block_seconds_online']);
1395
      variable_set('user_block_max_list_count', $edit['user_block_max_list_count']);
1396
      break;
1397
  }
1398
}
1399

    
1400
/**
1401
 * Implements hook_block_view().
1402
 */
1403
function user_block_view($delta = '') {
1404
  global $user;
1405

    
1406
  $block = array();
1407

    
1408
  switch ($delta) {
1409
    case 'login':
1410
      // For usability's sake, avoid showing two login forms on one page.
1411
      if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
1412

    
1413
        $block['subject'] = t('User login');
1414
        $block['content'] = drupal_get_form('user_login_block');
1415
      }
1416
      return $block;
1417

    
1418
    case 'new':
1419
      if (user_access('access content')) {
1420
        // Retrieve a list of new users who have subsequently accessed the site successfully.
1421
        $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();
1422
        $output = theme('user_list', array('users' => $items));
1423

    
1424
        $block['subject'] = t('Who\'s new');
1425
        $block['content'] = $output;
1426
      }
1427
      return $block;
1428

    
1429
    case 'online':
1430
      if (user_access('access content')) {
1431
        // Count users active within the defined period.
1432
        $interval = REQUEST_TIME - variable_get('user_block_seconds_online', 900);
1433

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

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

    
1440
        // Display a list of currently online users.
1441
        $max_users = variable_get('user_block_max_list_count', 10);
1442
        if ($authenticated_count && $max_users) {
1443
          $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();
1444
          $output .= theme('user_list', array('users' => $items));
1445
        }
1446

    
1447
        $block['subject'] = t('Who\'s online');
1448
        $block['content'] = $output;
1449
      }
1450
      return $block;
1451
  }
1452
}
1453

    
1454
/**
1455
 * Process variables for user-picture.tpl.php.
1456
 *
1457
 * The $variables array contains the following arguments:
1458
 * - $account: A user, node or comment object with 'name', 'uid' and 'picture'
1459
 *   fields.
1460
 *
1461
 * @see user-picture.tpl.php
1462
 */
1463
function template_preprocess_user_picture(&$variables) {
1464
  $variables['user_picture'] = '';
1465
  if (variable_get('user_pictures', 0)) {
1466
    $account = $variables['account'];
1467
    if (!empty($account->picture)) {
1468
      // @TODO: Ideally this function would only be passed file objects, but
1469
      // since there's a lot of legacy code that JOINs the {users} table to
1470
      // {node} or {comments} and passes the results into this function if we
1471
      // a numeric value in the picture field we'll assume it's a file id
1472
      // and load it for them. Once we've got user_load_multiple() and
1473
      // comment_load_multiple() functions the user module will be able to load
1474
      // the picture files in mass during the object's load process.
1475
      if (is_numeric($account->picture)) {
1476
        $account->picture = file_load($account->picture);
1477
      }
1478
      if (!empty($account->picture->uri)) {
1479
        $filepath = $account->picture->uri;
1480
      }
1481
    }
1482
    elseif (variable_get('user_picture_default', '')) {
1483
      $filepath = variable_get('user_picture_default', '');
1484
    }
1485
    if (isset($filepath)) {
1486
      $alt = t("@user's picture", array('@user' => format_username($account)));
1487
      // If the image does not have a valid Drupal scheme (for eg. HTTP),
1488
      // don't load image styles.
1489
      if (module_exists('image') && file_valid_uri($filepath) && $style = variable_get('user_picture_style', '')) {
1490
        $variables['user_picture'] = theme('image_style', array('style_name' => $style, 'path' => $filepath, 'alt' => $alt, 'title' => $alt));
1491
      }
1492
      else {
1493
        $variables['user_picture'] = theme('image', array('path' => $filepath, 'alt' => $alt, 'title' => $alt));
1494
      }
1495
      if (!empty($account->uid) && user_access('access user profiles')) {
1496
        $attributes = array('attributes' => array('title' => t('View user profile.')), 'html' => TRUE);
1497
        $variables['user_picture'] = l($variables['user_picture'], "user/$account->uid", $attributes);
1498
      }
1499
    }
1500
  }
1501
}
1502

    
1503
/**
1504
 * Returns HTML for a list of users.
1505
 *
1506
 * @param $variables
1507
 *   An associative array containing:
1508
 *   - users: An array with user objects. Should contain at least the name and
1509
 *     uid.
1510
 *   - title: (optional) Title to pass on to theme_item_list().
1511
 *
1512
 * @ingroup themeable
1513
 */
1514
function theme_user_list($variables) {
1515
  $users = $variables['users'];
1516
  $title = $variables['title'];
1517
  $items = array();
1518

    
1519
  if (!empty($users)) {
1520
    foreach ($users as $user) {
1521
      $items[] = theme('username', array('account' => $user));
1522
    }
1523
  }
1524
  return theme('item_list', array('items' => $items, 'title' => $title));
1525
}
1526

    
1527
/**
1528
 * Determines if the current user is anonymous.
1529
 *
1530
 * @return bool
1531
 *   TRUE if the user is anonymous, FALSE if the user is authenticated.
1532
 */
1533
function user_is_anonymous() {
1534
  // Menu administrators can see items for anonymous when administering.
1535
  return !$GLOBALS['user']->uid || !empty($GLOBALS['menu_admin']);
1536
}
1537

    
1538
/**
1539
 * Determines if the current user is logged in.
1540
 *
1541
 * @return bool
1542
 *   TRUE if the user is logged in, FALSE if the user is anonymous.
1543
 */
1544
function user_is_logged_in() {
1545
  return (bool) $GLOBALS['user']->uid;
1546
}
1547

    
1548
/**
1549
 * Determines if the current user has access to the user registration page.
1550
 *
1551
 * @return bool
1552
 *   TRUE if the user is not already logged in and can register for an account.
1553
 */
1554
function user_register_access() {
1555
  return user_is_anonymous() && variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
1556
}
1557

    
1558
/**
1559
 * User view access callback.
1560
 *
1561
 * @param $account
1562
 *   Can either be a full user object or a $uid.
1563
 */
1564
function user_view_access($account) {
1565
  $uid = is_object($account) ? $account->uid : (int) $account;
1566

    
1567
  // Never allow access to view the anonymous user account.
1568
  if ($uid) {
1569
    // Admins can view all, users can view own profiles at all times.
1570
    if ($GLOBALS['user']->uid == $uid || user_access('administer users')) {
1571
      return TRUE;
1572
    }
1573
    elseif (user_access('access user profiles')) {
1574
      // At this point, load the complete account object.
1575
      if (!is_object($account)) {
1576
        $account = user_load($uid);
1577
      }
1578
      return (is_object($account) && $account->status);
1579
    }
1580
  }
1581
  return FALSE;
1582
}
1583

    
1584
/**
1585
 * Access callback for user account editing.
1586
 */
1587
function user_edit_access($account) {
1588
  return (($GLOBALS['user']->uid == $account->uid) || user_access('administer users')) && $account->uid > 0;
1589
}
1590

    
1591
/**
1592
 * Menu access callback; limit access to account cancellation pages.
1593
 *
1594
 * Limit access to users with the 'cancel account' permission or administrative
1595
 * users, and prevent the anonymous user from cancelling the account.
1596
 */
1597
function user_cancel_access($account) {
1598
  return ((($GLOBALS['user']->uid == $account->uid) && user_access('cancel account')) || user_access('administer users')) && $account->uid > 0;
1599
}
1600

    
1601
/**
1602
 * Implements hook_menu().
1603
 */
1604
function user_menu() {
1605
  $items['user/autocomplete'] = array(
1606
    'title' => 'User autocomplete',
1607
    'page callback' => 'user_autocomplete',
1608
    'access callback' => 'user_access',
1609
    'access arguments' => array('access user profiles'),
1610
    'type' => MENU_CALLBACK,
1611
    'file' => 'user.pages.inc',
1612
  );
1613

    
1614
  // Registration and login pages.
1615
  $items['user'] = array(
1616
    'title' => 'User account',
1617
    'title callback' => 'user_menu_title',
1618
    'page callback' => 'user_page',
1619
    'access callback' => TRUE,
1620
    'file' => 'user.pages.inc',
1621
    'weight' => -10,
1622
    'menu_name' => 'user-menu',
1623
  );
1624

    
1625
  $items['user/login'] = array(
1626
    'title' => 'Log in',
1627
    'access callback' => 'user_is_anonymous',
1628
    'type' => MENU_DEFAULT_LOCAL_TASK,
1629
  );
1630

    
1631
  $items['user/register'] = array(
1632
    'title' => 'Create new account',
1633
    'page callback' => 'drupal_get_form',
1634
    'page arguments' => array('user_register_form'),
1635
    'access callback' => 'user_register_access',
1636
    'type' => MENU_LOCAL_TASK,
1637
  );
1638

    
1639
  $items['user/password'] = array(
1640
    'title' => 'Request new password',
1641
    'page callback' => 'drupal_get_form',
1642
    'page arguments' => array('user_pass'),
1643
    'access callback' => TRUE,
1644
    'type' => MENU_LOCAL_TASK,
1645
    'file' => 'user.pages.inc',
1646
  );
1647
  $items['user/reset/%/%/%'] = array(
1648
    'title' => 'Reset password',
1649
    'page callback' => 'drupal_get_form',
1650
    'page arguments' => array('user_pass_reset', 2, 3, 4),
1651
    'access callback' => TRUE,
1652
    'type' => MENU_CALLBACK,
1653
    'file' => 'user.pages.inc',
1654
  );
1655

    
1656
  $items['user/logout'] = array(
1657
    'title' => 'Log out',
1658
    'access callback' => 'user_is_logged_in',
1659
    'page callback' => 'user_logout',
1660
    'weight' => 10,
1661
    'menu_name' => 'user-menu',
1662
    'file' => 'user.pages.inc',
1663
  );
1664

    
1665
  // User listing pages.
1666
  $items['admin/people'] = array(
1667
    'title' => 'People',
1668
    'description' => 'Manage user accounts, roles, and permissions.',
1669
    'page callback' => 'user_admin',
1670
    'page arguments' => array('list'),
1671
    'access arguments' => array('administer users'),
1672
    'position' => 'left',
1673
    'weight' => -4,
1674
    'file' => 'user.admin.inc',
1675
  );
1676
  $items['admin/people/people'] = array(
1677
    'title' => 'List',
1678
    'description' => 'Find and manage people interacting with your site.',
1679
    'access arguments' => array('administer users'),
1680
    'type' => MENU_DEFAULT_LOCAL_TASK,
1681
    'weight' => -10,
1682
    'file' => 'user.admin.inc',
1683
  );
1684

    
1685
  // Permissions and role forms.
1686
  $items['admin/people/permissions'] = array(
1687
    'title' => 'Permissions',
1688
    'description' => 'Determine access to features by selecting permissions for roles.',
1689
    'page callback' => 'drupal_get_form',
1690
    'page arguments' => array('user_admin_permissions'),
1691
    'access arguments' => array('administer permissions'),
1692
    'file' => 'user.admin.inc',
1693
    'type' => MENU_LOCAL_TASK,
1694
  );
1695
  $items['admin/people/permissions/list'] = array(
1696
    'title' => 'Permissions',
1697
    'description' => 'Determine access to features by selecting permissions for roles.',
1698
    'type' => MENU_DEFAULT_LOCAL_TASK,
1699
    'weight' => -8,
1700
  );
1701
  $items['admin/people/permissions/roles'] = array(
1702
    'title' => 'Roles',
1703
    'description' => 'List, edit, or add user roles.',
1704
    'page callback' => 'drupal_get_form',
1705
    'page arguments' => array('user_admin_roles'),
1706
    'access arguments' => array('administer permissions'),
1707
    'file' => 'user.admin.inc',
1708
    'type' => MENU_LOCAL_TASK,
1709
    'weight' => -5,
1710
  );
1711
  $items['admin/people/permissions/roles/edit/%user_role'] = array(
1712
    'title' => 'Edit role',
1713
    'page arguments' => array('user_admin_role', 5),
1714
    'access callback' => 'user_role_edit_access',
1715
    'access arguments' => array(5),
1716
  );
1717
  $items['admin/people/permissions/roles/delete/%user_role'] = array(
1718
    'title' => 'Delete role',
1719
    'page callback' => 'drupal_get_form',
1720
    'page arguments' => array('user_admin_role_delete_confirm', 5),
1721
    'access callback' => 'user_role_edit_access',
1722
    'access arguments' => array(5),
1723
    'file' => 'user.admin.inc',
1724
  );
1725

    
1726
  $items['admin/people/create'] = array(
1727
    'title' => 'Add user',
1728
    'page arguments' => array('create'),
1729
    'access arguments' => array('administer users'),
1730
    'type' => MENU_LOCAL_ACTION,
1731
  );
1732

    
1733
  // Administration pages.
1734
  $items['admin/config/people'] = array(
1735
    'title' => 'People',
1736
    'description' => 'Configure user accounts.',
1737
    'position' => 'left',
1738
    'weight' => -20,
1739
    'page callback' => 'system_admin_menu_block_page',
1740
    'access arguments' => array('access administration pages'),
1741
    'file' => 'system.admin.inc',
1742
    'file path' => drupal_get_path('module', 'system'),
1743
  );
1744
  $items['admin/config/people/accounts'] = array(
1745
    'title' => 'Account settings',
1746
    'description' => 'Configure default behavior of users, including registration requirements, e-mails, fields, and user pictures.',
1747
    'page callback' => 'drupal_get_form',
1748
    'page arguments' => array('user_admin_settings'),
1749
    'access arguments' => array('administer users'),
1750
    'file' => 'user.admin.inc',
1751
    'weight' => -10,
1752
  );
1753
  $items['admin/config/people/accounts/settings'] = array(
1754
    'title' => 'Settings',
1755
    'type' => MENU_DEFAULT_LOCAL_TASK,
1756
    'weight' => -10,
1757
  );
1758

    
1759
  $items['user/%user'] = array(
1760
    'title' => 'My account',
1761
    'title callback' => 'user_page_title',
1762
    'title arguments' => array(1),
1763
    'page callback' => 'user_view_page',
1764
    'page arguments' => array(1),
1765
    'access callback' => 'user_view_access',
1766
    'access arguments' => array(1),
1767
    // By assigning a different menu name, this item (and all registered child
1768
    // paths) are no longer considered as children of 'user'. When accessing the
1769
    // user account pages, the preferred menu link that is used to build the
1770
    // active trail (breadcrumb) will be found in this menu (unless there is
1771
    // more specific link), so the link to 'user' will not be in the breadcrumb.
1772
    'menu_name' => 'navigation',
1773
  );
1774

    
1775
  $items['user/%user/view'] = array(
1776
    'title' => 'View',
1777
    'type' => MENU_DEFAULT_LOCAL_TASK,
1778
    'weight' => -10,
1779
  );
1780

    
1781
  $items['user/%user/cancel'] = array(
1782
    'title' => 'Cancel account',
1783
    'page callback' => 'drupal_get_form',
1784
    'page arguments' => array('user_cancel_confirm_form', 1),
1785
    'access callback' => 'user_cancel_access',
1786
    'access arguments' => array(1),
1787
    'file' => 'user.pages.inc',
1788
  );
1789

    
1790
  $items['user/%user/cancel/confirm/%/%'] = array(
1791
    'title' => 'Confirm account cancellation',
1792
    'page callback' => 'user_cancel_confirm',
1793
    'page arguments' => array(1, 4, 5),
1794
    'access callback' => 'user_cancel_access',
1795
    'access arguments' => array(1),
1796
    'file' => 'user.pages.inc',
1797
  );
1798

    
1799
  $items['user/%user/edit'] = array(
1800
    'title' => 'Edit',
1801
    'page callback' => 'drupal_get_form',
1802
    'page arguments' => array('user_profile_form', 1),
1803
    'access callback' => 'user_edit_access',
1804
    'access arguments' => array(1),
1805
    'type' => MENU_LOCAL_TASK,
1806
    'file' => 'user.pages.inc',
1807
  );
1808

    
1809
  $items['user/%user_category/edit/account'] = array(
1810
    'title' => 'Account',
1811
    'type' => MENU_DEFAULT_LOCAL_TASK,
1812
    'load arguments' => array('%map', '%index'),
1813
  );
1814

    
1815
  if (($categories = _user_categories()) && (count($categories) > 1)) {
1816
    foreach ($categories as $key => $category) {
1817
      // 'account' is already handled by the MENU_DEFAULT_LOCAL_TASK.
1818
      if ($category['name'] != 'account') {
1819
        $items['user/%user_category/edit/' . $category['name']] = array(
1820
          'title callback' => 'check_plain',
1821
          'title arguments' => array($category['title']),
1822
          'page callback' => 'drupal_get_form',
1823
          'page arguments' => array('user_profile_form', 1, 3),
1824
          'access callback' => isset($category['access callback']) ? $category['access callback'] : 'user_edit_access',
1825
          'access arguments' => isset($category['access arguments']) ? $category['access arguments'] : array(1),
1826
          'type' => MENU_LOCAL_TASK,
1827
          'weight' => $category['weight'],
1828
          'load arguments' => array('%map', '%index'),
1829
          'tab_parent' => 'user/%/edit',
1830
          'file' => 'user.pages.inc',
1831
        );
1832
      }
1833
    }
1834
  }
1835
  return $items;
1836
}
1837

    
1838
/**
1839
 * Implements hook_menu_site_status_alter().
1840
 */
1841
function user_menu_site_status_alter(&$menu_site_status, $path) {
1842
  if ($menu_site_status == MENU_SITE_OFFLINE) {
1843
    // If the site is offline, log out unprivileged users.
1844
    if (user_is_logged_in() && !user_access('access site in maintenance mode')) {
1845
      module_load_include('pages.inc', 'user', 'user');
1846
      user_logout();
1847
    }
1848

    
1849
    if (user_is_anonymous()) {
1850
      switch ($path) {
1851
        case 'user':
1852
          // Forward anonymous user to login page.
1853
          drupal_goto('user/login');
1854
        case 'user/login':
1855
        case 'user/password':
1856
          // Disable offline mode.
1857
          $menu_site_status = MENU_SITE_ONLINE;
1858
          break;
1859
        default:
1860
          if (strpos($path, 'user/reset/') === 0) {
1861
            // Disable offline mode.
1862
            $menu_site_status = MENU_SITE_ONLINE;
1863
          }
1864
          break;
1865
      }
1866
    }
1867
  }
1868
  if (user_is_logged_in()) {
1869
    if ($path == 'user/login') {
1870
      // If user is logged in, redirect to 'user' instead of giving 403.
1871
      drupal_goto('user');
1872
    }
1873
    if ($path == 'user/register') {
1874
      // Authenticated user should be redirected to user edit page.
1875
      drupal_goto('user/' . $GLOBALS['user']->uid . '/edit');
1876
    }
1877
  }
1878
}
1879

    
1880
/**
1881
 * Implements hook_menu_link_alter().
1882
 */
1883
function user_menu_link_alter(&$link) {
1884
  // The path 'user' must be accessible for anonymous users, but only visible
1885
  // for authenticated users. Authenticated users should see "My account", but
1886
  // anonymous users should not see it at all. Therefore, invoke
1887
  // user_translated_menu_link_alter() to conditionally hide the link.
1888
  if ($link['link_path'] == 'user' && $link['module'] == 'system') {
1889
    $link['options']['alter'] = TRUE;
1890
  }
1891

    
1892
  // Force the Logout link to appear on the top-level of 'user-menu' menu by
1893
  // default (i.e., unless it has been customized).
1894
  if ($link['link_path'] == 'user/logout' && $link['module'] == 'system' && empty($link['customized'])) {
1895
    $link['plid'] = 0;
1896
  }
1897
}
1898

    
1899
/**
1900
 * Implements hook_translated_menu_link_alter().
1901
 */
1902
function user_translated_menu_link_alter(&$link) {
1903
  // Hide the "User account" link for anonymous users.
1904
  if ($link['link_path'] == 'user' && $link['module'] == 'system' && !$GLOBALS['user']->uid) {
1905
    $link['hidden'] = 1;
1906
  }
1907
}
1908

    
1909
/**
1910
 * Implements hook_admin_paths().
1911
 */
1912
function user_admin_paths() {
1913
  $paths = array(
1914
    'user/*/cancel' => TRUE,
1915
    'user/*/edit' => TRUE,
1916
    'user/*/edit/*' => TRUE,
1917
  );
1918
  return $paths;
1919
}
1920

    
1921
/**
1922
 * Returns $arg or the user ID of the current user if $arg is '%' or empty.
1923
 *
1924
 * Deprecated. Use %user_uid_optional instead.
1925
 *
1926
 * @todo D8: Remove.
1927
 */
1928
function user_uid_only_optional_to_arg($arg) {
1929
  return user_uid_optional_to_arg($arg);
1930
}
1931

    
1932
/**
1933
 * Load either a specified or the current user account.
1934
 *
1935
 * @param $uid
1936
 *   An optional user ID of the user to load. If not provided, the current
1937
 *   user's ID will be used.
1938
 * @return
1939
 *   A fully-loaded $user object upon successful user load, FALSE if user
1940
 *   cannot be loaded.
1941
 *
1942
 * @see user_load()
1943
 * @todo rethink the naming of this in Drupal 8.
1944
 */
1945
function user_uid_optional_load($uid = NULL) {
1946
  if (!isset($uid)) {
1947
    $uid = $GLOBALS['user']->uid;
1948
  }
1949
  return user_load($uid);
1950
}
1951

    
1952
/**
1953
 * Return a user object after checking if any profile category in the path exists.
1954
 */
1955
function user_category_load($uid, &$map, $index) {
1956
  static $user_categories, $accounts;
1957

    
1958
  // Cache $account - this load function will get called for each profile tab.
1959
  if (!isset($accounts[$uid])) {
1960
    $accounts[$uid] = user_load($uid);
1961
  }
1962
  $valid = TRUE;
1963
  if ($account = $accounts[$uid]) {
1964
    // Since the path is like user/%/edit/category_name, the category name will
1965
    // be at a position 2 beyond the index corresponding to the % wildcard.
1966
    $category_index = $index + 2;
1967
    // Valid categories may contain slashes, and hence need to be imploded.
1968
    $category_path = implode('/', array_slice($map, $category_index));
1969
    if ($category_path) {
1970
      // Check that the requested category exists.
1971
      $valid = FALSE;
1972
      if (!isset($user_categories)) {
1973
        $user_categories = _user_categories();
1974
      }
1975
      foreach ($user_categories as $category) {
1976
        if ($category['name'] == $category_path) {
1977
          $valid = TRUE;
1978
          // Truncate the map array in case the category name had slashes.
1979
          $map = array_slice($map, 0, $category_index);
1980
          // Assign the imploded category name to the last map element.
1981
          $map[$category_index] = $category_path;
1982
          break;
1983
        }
1984
      }
1985
    }
1986
  }
1987
  return $valid ? $account : FALSE;
1988
}
1989

    
1990
/**
1991
 * Returns $arg or the user ID of the current user if $arg is '%' or empty.
1992
 *
1993
 * @todo rethink the naming of this in Drupal 8.
1994
 */
1995
function user_uid_optional_to_arg($arg) {
1996
  // Give back the current user uid when called from eg. tracker, aka.
1997
  // with an empty arg. Also use the current user uid when called from
1998
  // the menu with a % for the current account link.
1999
  return empty($arg) || $arg == '%' ? $GLOBALS['user']->uid : $arg;
2000
}
2001

    
2002
/**
2003
 * Menu item title callback for the 'user' path.
2004
 *
2005
 * Anonymous users should see "User account", but authenticated users are
2006
 * expected to see "My account".
2007
 */
2008
function user_menu_title() {
2009
  return user_is_logged_in() ? t('My account') : t('User account');
2010
}
2011

    
2012
/**
2013
 * Menu item title callback - use the user name.
2014
 */
2015
function user_page_title($account) {
2016
  return is_object($account) ? format_username($account) : '';
2017
}
2018

    
2019
/**
2020
 * Discover which external authentication module(s) authenticated a username.
2021
 *
2022
 * @param $authname
2023
 *   A username used by an external authentication module.
2024
 * @return
2025
 *   An associative array with module as key and username as value.
2026
 */
2027
function user_get_authmaps($authname = NULL) {
2028
  $authmaps = db_query("SELECT module, authname FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchAllKeyed();
2029
  return count($authmaps) ? $authmaps : 0;
2030
}
2031

    
2032
/**
2033
 * Save mappings of which external authentication module(s) authenticated
2034
 * a user. Maps external usernames to user ids in the users table.
2035
 *
2036
 * @param $account
2037
 *   A user object.
2038
 * @param $authmaps
2039
 *   An associative array with a compound key and the username as the value.
2040
 *   The key is made up of 'authname_' plus the name of the external authentication
2041
 *   module.
2042
 * @see user_external_login_register()
2043
 */
2044
function user_set_authmaps($account, $authmaps) {
2045
  foreach ($authmaps as $key => $value) {
2046
    $module = explode('_', $key, 2);
2047
    if ($value) {
2048
      db_merge('authmap')
2049
        ->key(array(
2050
          'uid' => $account->uid,
2051
          'module' => $module[1],
2052
        ))
2053
        ->fields(array('authname' => $value))
2054
        ->execute();
2055
    }
2056
    else {
2057
      db_delete('authmap')
2058
        ->condition('uid', $account->uid)
2059
        ->condition('module', $module[1])
2060
        ->execute();
2061
    }
2062
  }
2063
}
2064

    
2065
/**
2066
 * Form builder; the main user login form.
2067
 *
2068
 * @ingroup forms
2069
 */
2070
function user_login($form, &$form_state) {
2071
  global $user;
2072

    
2073
  // If we are already logged on, go to the user page instead.
2074
  if ($user->uid) {
2075
    drupal_goto('user/' . $user->uid);
2076
  }
2077

    
2078
  // Display login form:
2079
  $form['name'] = array('#type' => 'textfield',
2080
    '#title' => t('Username'),
2081
    '#size' => 60,
2082
    '#maxlength' => USERNAME_MAX_LENGTH,
2083
    '#required' => TRUE,
2084
  );
2085

    
2086
  $form['name']['#description'] = t('Enter your @s username.', array('@s' => variable_get('site_name', 'Drupal')));
2087
  $form['pass'] = array('#type' => 'password',
2088
    '#title' => t('Password'),
2089
    '#description' => t('Enter the password that accompanies your username.'),
2090
    '#required' => TRUE,
2091
  );
2092
  $form['#validate'] = user_login_default_validators();
2093
  $form['actions'] = array('#type' => 'actions');
2094
  $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
2095

    
2096
  return $form;
2097
}
2098

    
2099
/**
2100
 * Set up a series for validators which check for blocked users,
2101
 * then authenticate against local database, then return an error if
2102
 * authentication fails. Distributed authentication modules are welcome
2103
 * to use hook_form_alter() to change this series in order to
2104
 * authenticate against their user database instead of the local users
2105
 * table. If a distributed authentication module is successful, it
2106
 * should set $form_state['uid'] to a user ID.
2107
 *
2108
 * We use three validators instead of one since external authentication
2109
 * modules usually only need to alter the second validator.
2110
 *
2111
 * @see user_login_name_validate()
2112
 * @see user_login_authenticate_validate()
2113
 * @see user_login_final_validate()
2114
 * @return array
2115
 *   A simple list of validate functions.
2116
 */
2117
function user_login_default_validators() {
2118
  return array('user_login_name_validate', 'user_login_authenticate_validate', 'user_login_final_validate');
2119
}
2120

    
2121
/**
2122
 * A FAPI validate handler. Sets an error if supplied username has been blocked.
2123
 */
2124
function user_login_name_validate($form, &$form_state) {
2125
  if (!empty($form_state['values']['name']) && user_is_blocked($form_state['values']['name'])) {
2126
    // Blocked in user administration.
2127
    form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
2128
  }
2129
}
2130

    
2131
/**
2132
 * A validate handler on the login form. Check supplied username/password
2133
 * against local users table. If successful, $form_state['uid']
2134
 * is set to the matching user ID.
2135
 */
2136
function user_login_authenticate_validate($form, &$form_state) {
2137
  $password = trim($form_state['values']['pass']);
2138
  if (!empty($form_state['values']['name']) && !empty($password)) {
2139
    // Do not allow any login from the current user's IP if the limit has been
2140
    // reached. Default is 50 failed attempts allowed in one hour. This is
2141
    // independent of the per-user limit to catch attempts from one IP to log
2142
    // in to many different user accounts.  We have a reasonably high limit
2143
    // since there may be only one apparent IP for all users at an institution.
2144
    if (!flood_is_allowed('failed_login_attempt_ip', variable_get('user_failed_login_ip_limit', 50), variable_get('user_failed_login_ip_window', 3600))) {
2145
      $form_state['flood_control_triggered'] = 'ip';
2146
      return;
2147
    }
2148
    $account = db_query("SELECT * FROM {users} WHERE name = :name AND status = 1", array(':name' => $form_state['values']['name']))->fetchObject();
2149
    if ($account) {
2150
      if (variable_get('user_failed_login_identifier_uid_only', FALSE)) {
2151
        // Register flood events based on the uid only, so they apply for any
2152
        // IP address. This is the most secure option.
2153
        $identifier = $account->uid;
2154
      }
2155
      else {
2156
        // The default identifier is a combination of uid and IP address. This
2157
        // is less secure but more resistant to denial-of-service attacks that
2158
        // could lock out all users with public user names.
2159
        $identifier = $account->uid . '-' . ip_address();
2160
      }
2161
      $form_state['flood_control_user_identifier'] = $identifier;
2162

    
2163
      // Don't allow login if the limit for this user has been reached.
2164
      // Default is to allow 5 failed attempts every 6 hours.
2165
      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)) {
2166
        $form_state['flood_control_triggered'] = 'user';
2167
        return;
2168
      }
2169
    }
2170
    // We are not limited by flood control, so try to authenticate.
2171
    // Set $form_state['uid'] as a flag for user_login_final_validate().
2172
    $form_state['uid'] = user_authenticate($form_state['values']['name'], $password);
2173
  }
2174
}
2175

    
2176
/**
2177
 * The final validation handler on the login form.
2178
 *
2179
 * Sets a form error if user has not been authenticated, or if too many
2180
 * logins have been attempted. This validation function should always
2181
 * be the last one.
2182
 */
2183
function user_login_final_validate($form, &$form_state) {
2184
  if (empty($form_state['uid'])) {
2185
    // Always register an IP-based failed login event.
2186
    flood_register_event('failed_login_attempt_ip', variable_get('user_failed_login_ip_window', 3600));
2187
    // Register a per-user failed login event.
2188
    if (isset($form_state['flood_control_user_identifier'])) {
2189
      flood_register_event('failed_login_attempt_user', variable_get('user_failed_login_user_window', 21600), $form_state['flood_control_user_identifier']);
2190
    }
2191

    
2192
    if (isset($form_state['flood_control_triggered'])) {
2193
      if ($form_state['flood_control_triggered'] == 'user') {
2194
        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'))));
2195
      }
2196
      else {
2197
        // We did not find a uid, so the limit is IP-based.
2198
        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'))));
2199
      }
2200
    }
2201
    else {
2202
      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' => array('name' => $form_state['values']['name']))))));
2203
      watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
2204
    }
2205
  }
2206
  elseif (isset($form_state['flood_control_user_identifier'])) {
2207
    // Clear past failures for this user so as not to block a user who might
2208
    // log in and out more than once in an hour.
2209
    flood_clear_event('failed_login_attempt_user', $form_state['flood_control_user_identifier']);
2210
  }
2211
}
2212

    
2213
/**
2214
 * Try to validate the user's login credentials locally.
2215
 *
2216
 * @param $name
2217
 *   User name to authenticate.
2218
 * @param $password
2219
 *   A plain-text password, such as trimmed text from form values.
2220
 * @return
2221
 *   The user's uid on success, or FALSE on failure to authenticate.
2222
 */
2223
function user_authenticate($name, $password) {
2224
  $uid = FALSE;
2225
  if (!empty($name) && !empty($password)) {
2226
    $account = user_load_by_name($name);
2227
    if ($account) {
2228
      // Allow alternate password hashing schemes.
2229
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
2230
      if (user_check_password($password, $account)) {
2231
        // Successful authentication.
2232
        $uid = $account->uid;
2233

    
2234
        // Update user to new password scheme if needed.
2235
        if (user_needs_new_hash($account)) {
2236
          user_save($account, array('pass' => $password));
2237
        }
2238
      }
2239
    }
2240
  }
2241
  return $uid;
2242
}
2243

    
2244
/**
2245
 * Finalize the login process. Must be called when logging in a user.
2246
 *
2247
 * The function records a watchdog message about the new session, saves the
2248
 * login timestamp, calls hook_user_login(), and generates a new session.
2249
 *
2250
 * @param array $edit
2251
 *   The array of form values submitted by the user.
2252
 *
2253
 * @see hook_user_login()
2254
 */
2255
function user_login_finalize(&$edit = array()) {
2256
  global $user;
2257
  watchdog('user', 'Session opened for %name.', array('%name' => $user->name));
2258
  // Update the user table timestamp noting user has logged in.
2259
  // This is also used to invalidate one-time login links.
2260
  $user->login = REQUEST_TIME;
2261
  db_update('users')
2262
    ->fields(array('login' => $user->login))
2263
    ->condition('uid', $user->uid)
2264
    ->execute();
2265

    
2266
  // Regenerate the session ID to prevent against session fixation attacks.
2267
  // This is called before hook_user in case one of those functions fails
2268
  // or incorrectly does a redirect which would leave the old session in place.
2269
  drupal_session_regenerate();
2270

    
2271
  user_module_invoke('login', $edit, $user);
2272
}
2273

    
2274
/**
2275
 * Submit handler for the login form. Load $user object and perform standard login
2276
 * tasks. The user is then redirected to the My Account page. Setting the
2277
 * destination in the query string overrides the redirect.
2278
 */
2279
function user_login_submit($form, &$form_state) {
2280
  global $user;
2281
  $user = user_load($form_state['uid']);
2282
  $form_state['redirect'] = 'user/' . $user->uid;
2283

    
2284
  user_login_finalize($form_state);
2285
}
2286

    
2287
/**
2288
 * Helper function for authentication modules. Either logs in or registers
2289
 * the current user, based on username. Either way, the global $user object is
2290
 * populated and login tasks are performed.
2291
 */
2292
function user_external_login_register($name, $module) {
2293
  $account = user_external_load($name);
2294
  if (!$account) {
2295
    // Register this new user.
2296
    $userinfo = array(
2297
      'name' => $name,
2298
      'pass' => user_password(),
2299
      'init' => $name,
2300
      'status' => 1,
2301
      'access' => REQUEST_TIME
2302
    );
2303
    $account = user_save(drupal_anonymous_user(), $userinfo);
2304
    // Terminate if an error occurred during user_save().
2305
    if (!$account) {
2306
      drupal_set_message(t("Error saving user account."), 'error');
2307
      return;
2308
    }
2309
    user_set_authmaps($account, array("authname_$module" => $name));
2310
  }
2311

    
2312
  // Log user in.
2313
  $form_state['uid'] = $account->uid;
2314
  user_login_submit(array(), $form_state);
2315
}
2316

    
2317
/**
2318
 * Generates a unique URL for a user to login and reset their password.
2319
 *
2320
 * @param object $account
2321
 *   An object containing the user account, which must contain at least the
2322
 *   following properties:
2323
 *   - uid: The user ID number.
2324
 *   - login: The UNIX timestamp of the user's last login.
2325
 *
2326
 * @return
2327
 *   A unique URL that provides a one-time log in for the user, from which
2328
 *   they can change their password.
2329
 */
2330
function user_pass_reset_url($account) {
2331
  $timestamp = REQUEST_TIME;
2332
  return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
2333
}
2334

    
2335
/**
2336
 * Generates a URL to confirm an account cancellation request.
2337
 *
2338
 * @param object $account
2339
 *   The user account object, which must contain at least the following
2340
 *   properties:
2341
 *   - uid: The user ID number.
2342
 *   - pass: The hashed user password string.
2343
 *   - login: The UNIX timestamp of the user's last login.
2344
 *
2345
 * @return
2346
 *   A unique URL that may be used to confirm the cancellation of the user
2347
 *   account.
2348
 *
2349
 * @see user_mail_tokens()
2350
 * @see user_cancel_confirm()
2351
 */
2352
function user_cancel_url($account) {
2353
  $timestamp = REQUEST_TIME;
2354
  return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
2355
}
2356

    
2357
/**
2358
 * Creates a unique hash value for use in time-dependent per-user URLs.
2359
 *
2360
 * This hash is normally used to build a unique and secure URL that is sent to
2361
 * the user by email for purposes such as resetting the user's password. In
2362
 * order to validate the URL, the same hash can be generated again, from the
2363
 * same information, and compared to the hash value from the URL. The URL
2364
 * normally contains both the time stamp and the numeric user ID. The login
2365
 * timestamp and hashed password are retrieved from the database as necessary.
2366
 * For a usage example, see user_cancel_url() and user_cancel_confirm().
2367
 *
2368
 * @param string $password
2369
 *   The hashed user account password value.
2370
 * @param int $timestamp
2371
 *   A UNIX timestamp, typically REQUEST_TIME.
2372
 * @param int $login
2373
 *   The UNIX timestamp of the user's last login.
2374
 *
2375
 * @return
2376
 *   A string that is safe for use in URLs and SQL statements.
2377
 */
2378
function user_pass_rehash($password, $timestamp, $login) {
2379
  return drupal_hmac_base64($timestamp . $login, drupal_get_hash_salt() . $password);
2380
}
2381

    
2382
/**
2383
 * Cancel a user account.
2384
 *
2385
 * Since the user cancellation process needs to be run in a batch, either
2386
 * Form API will invoke it, or batch_process() needs to be invoked after calling
2387
 * this function and should define the path to redirect to.
2388
 *
2389
 * @param $edit
2390
 *   An array of submitted form values.
2391
 * @param $uid
2392
 *   The user ID of the user account to cancel.
2393
 * @param $method
2394
 *   The account cancellation method to use.
2395
 *
2396
 * @see _user_cancel()
2397
 */
2398
function user_cancel($edit, $uid, $method) {
2399
  global $user;
2400

    
2401
  $account = user_load($uid);
2402

    
2403
  if (!$account) {
2404
    drupal_set_message(t('The user account %id does not exist.', array('%id' => $uid)), 'error');
2405
    watchdog('user', 'Attempted to cancel non-existing user account: %id.', array('%id' => $uid), WATCHDOG_ERROR);
2406
    return;
2407
  }
2408

    
2409
  // Initialize batch (to set title).
2410
  $batch = array(
2411
    'title' => t('Cancelling account'),
2412
    'operations' => array(),
2413
  );
2414
  batch_set($batch);
2415

    
2416
  // Modules use hook_user_delete() to respond to deletion.
2417
  if ($method != 'user_cancel_delete') {
2418
    // Allow modules to add further sets to this batch.
2419
    module_invoke_all('user_cancel', $edit, $account, $method);
2420
  }
2421

    
2422
  // Finish the batch and actually cancel the account.
2423
  $batch = array(
2424
    'title' => t('Cancelling user account'),
2425
    'operations' => array(
2426
      array('_user_cancel', array($edit, $account, $method)),
2427
    ),
2428
  );
2429

    
2430
  // After cancelling account, ensure that user is logged out.
2431
  if ($account->uid == $user->uid) {
2432
    // Batch API stores data in the session, so use the finished operation to
2433
    // manipulate the current user's session id.
2434
    $batch['finished'] = '_user_cancel_session_regenerate';
2435
  }
2436

    
2437
  batch_set($batch);
2438

    
2439
  // Batch processing is either handled via Form API or has to be invoked
2440
  // manually.
2441
}
2442

    
2443
/**
2444
 * Last batch processing step for cancelling a user account.
2445
 *
2446
 * Since batch and session API require a valid user account, the actual
2447
 * cancellation of a user account needs to happen last.
2448
 *
2449
 * @see user_cancel()
2450
 */
2451
function _user_cancel($edit, $account, $method) {
2452
  global $user;
2453

    
2454
  switch ($method) {
2455
    case 'user_cancel_block':
2456
    case 'user_cancel_block_unpublish':
2457
    default:
2458
      // Send account blocked notification if option was checked.
2459
      if (!empty($edit['user_cancel_notify'])) {
2460
        _user_mail_notify('status_blocked', $account);
2461
      }
2462
      user_save($account, array('status' => 0));
2463
      drupal_set_message(t('%name has been disabled.', array('%name' => $account->name)));
2464
      watchdog('user', 'Blocked user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
2465
      break;
2466

    
2467
    case 'user_cancel_reassign':
2468
    case 'user_cancel_delete':
2469
      // Send account canceled notification if option was checked.
2470
      if (!empty($edit['user_cancel_notify'])) {
2471
        _user_mail_notify('status_canceled', $account);
2472
      }
2473
      user_delete($account->uid);
2474
      drupal_set_message(t('%name has been deleted.', array('%name' => $account->name)));
2475
      watchdog('user', 'Deleted user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
2476
      break;
2477
  }
2478

    
2479
  // After cancelling account, ensure that user is logged out. We can't destroy
2480
  // their session though, as we might have information in it, and we can't
2481
  // regenerate it because batch API uses the session ID, we will regenerate it
2482
  // in _user_cancel_session_regenerate().
2483
  if ($account->uid == $user->uid) {
2484
    $user = drupal_anonymous_user();
2485
  }
2486

    
2487
  // Clear the cache for anonymous users.
2488
  cache_clear_all();
2489
}
2490

    
2491
/**
2492
 * Finished batch processing callback for cancelling a user account.
2493
 *
2494
 * @see user_cancel()
2495
 */
2496
function _user_cancel_session_regenerate() {
2497
  // Regenerate the users session instead of calling session_destroy() as we
2498
  // want to preserve any messages that might have been set.
2499
  drupal_session_regenerate();
2500
}
2501

    
2502
/**
2503
 * Delete a user.
2504
 *
2505
 * @param $uid
2506
 *   A user ID.
2507
 */
2508
function user_delete($uid) {
2509
  user_delete_multiple(array($uid));
2510
}
2511

    
2512
/**
2513
 * Delete multiple user accounts.
2514
 *
2515
 * @param $uids
2516
 *   An array of user IDs.
2517
 */
2518
function user_delete_multiple(array $uids) {
2519
  if (!empty($uids)) {
2520
    $accounts = user_load_multiple($uids, array());
2521

    
2522
    $transaction = db_transaction();
2523
    try {
2524
      foreach ($accounts as $uid => $account) {
2525
        module_invoke_all('user_delete', $account);
2526
        module_invoke_all('entity_delete', $account, 'user');
2527
        field_attach_delete('user', $account);
2528
        drupal_session_destroy_uid($account->uid);
2529
      }
2530

    
2531
      db_delete('users')
2532
        ->condition('uid', $uids, 'IN')
2533
        ->execute();
2534
      db_delete('users_roles')
2535
        ->condition('uid', $uids, 'IN')
2536
        ->execute();
2537
      db_delete('authmap')
2538
        ->condition('uid', $uids, 'IN')
2539
        ->execute();
2540
    }
2541
    catch (Exception $e) {
2542
      $transaction->rollback();
2543
      watchdog_exception('user', $e);
2544
      throw $e;
2545
    }
2546
    entity_get_controller('user')->resetCache();
2547
  }
2548
}
2549

    
2550
/**
2551
 * Page callback wrapper for user_view().
2552
 */
2553
function user_view_page($account) {
2554
  // An administrator may try to view a non-existent account,
2555
  // so we give them a 404 (versus a 403 for non-admins).
2556
  return is_object($account) ? user_view($account) : MENU_NOT_FOUND;
2557
}
2558

    
2559
/**
2560
 * Generate an array for rendering the given user.
2561
 *
2562
 * When viewing a user profile, the $page array contains:
2563
 *
2564
 * - $page['content']['Profile Category']:
2565
 *   Profile categories keyed by their human-readable names.
2566
 * - $page['content']['Profile Category']['profile_machine_name']:
2567
 *   Profile fields keyed by their machine-readable names.
2568
 * - $page['content']['user_picture']:
2569
 *   User's rendered picture.
2570
 * - $page['content']['summary']:
2571
 *   Contains the default "History" profile data for a user.
2572
 * - $page['content']['#account']:
2573
 *   The user account of the profile being viewed.
2574
 *
2575
 * To theme user profiles, copy modules/user/user-profile.tpl.php
2576
 * to your theme directory, and edit it as instructed in that file's comments.
2577
 *
2578
 * @param $account
2579
 *   A user object.
2580
 * @param $view_mode
2581
 *   View mode, e.g. 'full'.
2582
 * @param $langcode
2583
 *   (optional) A language code to use for rendering. Defaults to the global
2584
 *   content language of the current request.
2585
 *
2586
 * @return
2587
 *   An array as expected by drupal_render().
2588
 */
2589
function user_view($account, $view_mode = 'full', $langcode = NULL) {
2590
  if (!isset($langcode)) {
2591
    $langcode = $GLOBALS['language_content']->language;
2592
  }
2593

    
2594
  // Retrieve all profile fields and attach to $account->content.
2595
  user_build_content($account, $view_mode, $langcode);
2596

    
2597
  $build = $account->content;
2598
  // We don't need duplicate rendering info in account->content.
2599
  unset($account->content);
2600

    
2601
  $build += array(
2602
    '#theme' => 'user_profile',
2603
    '#account' => $account,
2604
    '#view_mode' => $view_mode,
2605
    '#language' => $langcode,
2606
  );
2607

    
2608
  // Allow modules to modify the structured user.
2609
  $type = 'user';
2610
  drupal_alter(array('user_view', 'entity_view'), $build, $type);
2611

    
2612
  return $build;
2613
}
2614

    
2615
/**
2616
 * Builds a structured array representing the profile content.
2617
 *
2618
 * @param $account
2619
 *   A user object.
2620
 * @param $view_mode
2621
 *   View mode, e.g. 'full'.
2622
 * @param $langcode
2623
 *   (optional) A language code to use for rendering. Defaults to the global
2624
 *   content language of the current request.
2625
 */
2626
function user_build_content($account, $view_mode = 'full', $langcode = NULL) {
2627
  if (!isset($langcode)) {
2628
    $langcode = $GLOBALS['language_content']->language;
2629
  }
2630

    
2631
  // Remove previously built content, if exists.
2632
  $account->content = array();
2633

    
2634
  // Allow modules to change the view mode.
2635
  $context = array(
2636
    'entity_type' => 'user',
2637
    'entity' => $account,
2638
    'langcode' => $langcode,
2639
  );
2640
  drupal_alter('entity_view_mode', $view_mode, $context);
2641

    
2642
  // Build fields content.
2643
  field_attach_prepare_view('user', array($account->uid => $account), $view_mode, $langcode);
2644
  entity_prepare_view('user', array($account->uid => $account), $langcode);
2645
  $account->content += field_attach_view('user', $account, $view_mode, $langcode);
2646

    
2647
  // Populate $account->content with a render() array.
2648
  module_invoke_all('user_view', $account, $view_mode, $langcode);
2649
  module_invoke_all('entity_view', $account, 'user', $view_mode, $langcode);
2650

    
2651
  // Make sure the current view mode is stored if no module has already
2652
  // populated the related key.
2653
  $account->content += array('#view_mode' => $view_mode);
2654
}
2655

    
2656
/**
2657
 * Implements hook_mail().
2658
 */
2659
function user_mail($key, &$message, $params) {
2660
  $language = $message['language'];
2661
  $variables = array('user' => $params['account']);
2662
  $message['subject'] .= _user_mail_text($key . '_subject', $language, $variables);
2663
  $message['body'][] = _user_mail_text($key . '_body', $language, $variables);
2664
}
2665

    
2666
/**
2667
 * Returns a mail string for a variable name.
2668
 *
2669
 * Used by user_mail() and the settings forms to retrieve strings.
2670
 */
2671
function _user_mail_text($key, $language = NULL, $variables = array(), $replace = TRUE) {
2672
  $langcode = isset($language) ? $language->language : NULL;
2673

    
2674
  if ($admin_setting = variable_get('user_mail_' . $key, FALSE)) {
2675
    // An admin setting overrides the default string.
2676
    $text = $admin_setting;
2677
  }
2678
  else {
2679
    // No override, return default string.
2680
    switch ($key) {
2681
      case 'register_no_approval_required_subject':
2682
        $text = t('Account details for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2683
        break;
2684
      case 'register_no_approval_required_body':
2685
        $text = t("[user:name],
2686

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

    
2689
[user:one-time-login-url]
2690

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

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

    
2695
username: [user:name]
2696
password: Your password
2697

    
2698
--  [site:name] team", array(), array('langcode' => $langcode));
2699
        break;
2700

    
2701
      case 'register_admin_created_subject':
2702
        $text = t('An administrator created an account for you at [site:name]', array(), array('langcode' => $langcode));
2703
        break;
2704
      case 'register_admin_created_body':
2705
        $text = t("[user:name],
2706

    
2707
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:
2708

    
2709
[user:one-time-login-url]
2710

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

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

    
2715
username: [user:name]
2716
password: Your password
2717

    
2718
--  [site:name] team", array(), array('langcode' => $langcode));
2719
        break;
2720

    
2721
      case 'register_pending_approval_subject':
2722
      case 'register_pending_approval_admin_subject':
2723
        $text = t('Account details for [user:name] at [site:name] (pending admin approval)', array(), array('langcode' => $langcode));
2724
        break;
2725
      case 'register_pending_approval_body':
2726
        $text = t("[user:name],
2727

    
2728
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.
2729

    
2730

    
2731
--  [site:name] team", array(), array('langcode' => $langcode));
2732
        break;
2733
      case 'register_pending_approval_admin_body':
2734
        $text = t("[user:name] has applied for an account.
2735

    
2736
[user:edit-url]", array(), array('langcode' => $langcode));
2737
        break;
2738

    
2739
      case 'password_reset_subject':
2740
        $text = t('Replacement login information for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2741
        break;
2742
      case 'password_reset_body':
2743
        $text = t("[user:name],
2744

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

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

    
2749
[user:one-time-login-url]
2750

    
2751
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.
2752

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

    
2756
      case 'status_activated_subject':
2757
        $text = t('Account details for [user:name] at [site:name] (approved)', array(), array('langcode' => $langcode));
2758
        break;
2759
      case 'status_activated_body':
2760
        $text = t("[user:name],
2761

    
2762
Your account at [site:name] has been activated.
2763

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

    
2766
[user:one-time-login-url]
2767

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

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

    
2772
username: [user:name]
2773
password: Your password
2774

    
2775
--  [site:name] team", array(), array('langcode' => $langcode));
2776
        break;
2777

    
2778
      case 'status_blocked_subject':
2779
        $text = t('Account details for [user:name] at [site:name] (blocked)', array(), array('langcode' => $langcode));
2780
        break;
2781
      case 'status_blocked_body':
2782
        $text = t("[user:name],
2783

    
2784
Your account on [site:name] has been blocked.
2785

    
2786
--  [site:name] team", array(), array('langcode' => $langcode));
2787
        break;
2788

    
2789
      case 'cancel_confirm_subject':
2790
        $text = t('Account cancellation request for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2791
        break;
2792
      case 'cancel_confirm_body':
2793
        $text = t("[user:name],
2794

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

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

    
2799
[user:cancel-url]
2800

    
2801
NOTE: The cancellation of your account is not reversible.
2802

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

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

    
2808
      case 'status_canceled_subject':
2809
        $text = t('Account details for [user:name] at [site:name] (canceled)', array(), array('langcode' => $langcode));
2810
        break;
2811
      case 'status_canceled_body':
2812
        $text = t("[user:name],
2813

    
2814
Your account on [site:name] has been canceled.
2815

    
2816
--  [site:name] team", array(), array('langcode' => $langcode));
2817
        break;
2818
    }
2819
  }
2820

    
2821
  if ($replace) {
2822
    // We do not sanitize the token replacement, since the output of this
2823
    // replacement is intended for an e-mail message, not a web browser.
2824
    return token_replace($text, $variables, array('language' => $language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
2825
  }
2826

    
2827
  return $text;
2828
}
2829

    
2830
/**
2831
 * Token callback to add unsafe tokens for user mails.
2832
 *
2833
 * This function is used by the token_replace() call at the end of
2834
 * _user_mail_text() to set up some additional tokens that can be
2835
 * used in email messages generated by user_mail().
2836
 *
2837
 * @param $replacements
2838
 *   An associative array variable containing mappings from token names to
2839
 *   values (for use with strtr()).
2840
 * @param $data
2841
 *   An associative array of token replacement values. If the 'user' element
2842
 *   exists, it must contain a user account object with the following
2843
 *   properties:
2844
 *   - login: The UNIX timestamp of the user's last login.
2845
 *   - pass: The hashed account login password.
2846
 * @param $options
2847
 *   Unused parameter required by the token_replace() function.
2848
 */
2849
function user_mail_tokens(&$replacements, $data, $options) {
2850
  if (isset($data['user'])) {
2851
    $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user']);
2852
    $replacements['[user:cancel-url]'] = user_cancel_url($data['user']);
2853
  }
2854
}
2855

    
2856
/*** Administrative features ***********************************************/
2857

    
2858
/**
2859
 * Retrieve an array of roles matching specified conditions.
2860
 *
2861
 * @param $membersonly
2862
 *   Set this to TRUE to exclude the 'anonymous' role.
2863
 * @param $permission
2864
 *   A string containing a permission. If set, only roles containing that
2865
 *   permission are returned.
2866
 *
2867
 * @return
2868
 *   An associative array with the role id as the key and the role name as
2869
 *   value.
2870
 */
2871
function user_roles($membersonly = FALSE, $permission = NULL) {
2872
  $query = db_select('role', 'r');
2873
  $query->addTag('translatable');
2874
  $query->fields('r', array('rid', 'name'));
2875
  $query->orderBy('weight');
2876
  $query->orderBy('name');
2877
  if (!empty($permission)) {
2878
    $query->innerJoin('role_permission', 'p', 'r.rid = p.rid');
2879
    $query->condition('p.permission', $permission);
2880
  }
2881
  $result = $query->execute();
2882

    
2883
  $roles = array();
2884
  foreach ($result as $role) {
2885
    switch ($role->rid) {
2886
      // We only translate the built in role names
2887
      case DRUPAL_ANONYMOUS_RID:
2888
        if (!$membersonly) {
2889
          $roles[$role->rid] = t($role->name);
2890
        }
2891
        break;
2892
      case DRUPAL_AUTHENTICATED_RID:
2893
        $roles[$role->rid] = t($role->name);
2894
        break;
2895
      default:
2896
        $roles[$role->rid] = $role->name;
2897
    }
2898
  }
2899

    
2900
  return $roles;
2901
}
2902

    
2903
/**
2904
 * Fetches a user role by role ID.
2905
 *
2906
 * @param $rid
2907
 *   An integer representing the role ID.
2908
 *
2909
 * @return
2910
 *   A fully-loaded role object if a role with the given ID exists, or FALSE
2911
 *   otherwise.
2912
 *
2913
 * @see user_role_load_by_name()
2914
 */
2915
function user_role_load($rid) {
2916
  return db_select('role', 'r')
2917
    ->fields('r')
2918
    ->condition('rid', $rid)
2919
    ->execute()
2920
    ->fetchObject();
2921
}
2922

    
2923
/**
2924
 * Fetches a user role by role name.
2925
 *
2926
 * @param $role_name
2927
 *   A string representing the role name.
2928
 *
2929
 * @return
2930
 *   A fully-loaded role object if a role with the given name exists, or FALSE
2931
 *   otherwise.
2932
 *
2933
 * @see user_role_load()
2934
 */
2935
function user_role_load_by_name($role_name) {
2936
  return db_select('role', 'r')
2937
    ->fields('r')
2938
    ->condition('name', $role_name)
2939
    ->execute()
2940
    ->fetchObject();
2941
}
2942

    
2943
/**
2944
 * Save a user role to the database.
2945
 *
2946
 * @param $role
2947
 *   A role object to modify or add. If $role->rid is not specified, a new
2948
 *   role will be created.
2949
 * @return
2950
 *   Status constant indicating if role was created or updated.
2951
 *   Failure to write the user role record will return FALSE. Otherwise.
2952
 *   SAVED_NEW or SAVED_UPDATED is returned depending on the operation
2953
 *   performed.
2954
 */
2955
function user_role_save($role) {
2956
  if ($role->name) {
2957
    // Prevent leading and trailing spaces in role names.
2958
    $role->name = trim($role->name);
2959
  }
2960
  if (!isset($role->weight)) {
2961
    // Set a role weight to make this new role last.
2962
    $query = db_select('role');
2963
    $query->addExpression('MAX(weight)');
2964
    $role->weight = $query->execute()->fetchField() + 1;
2965
  }
2966

    
2967
  // Let modules modify the user role before it is saved to the database.
2968
  module_invoke_all('user_role_presave', $role);
2969

    
2970
  if (!empty($role->rid) && $role->name) {
2971
    $status = drupal_write_record('role', $role, 'rid');
2972
    module_invoke_all('user_role_update', $role);
2973
  }
2974
  else {
2975
    $status = drupal_write_record('role', $role);
2976
    module_invoke_all('user_role_insert', $role);
2977
  }
2978

    
2979
  // Clear the user access cache.
2980
  drupal_static_reset('user_access');
2981
  drupal_static_reset('user_role_permissions');
2982

    
2983
  return $status;
2984
}
2985

    
2986
/**
2987
 * Delete a user role from database.
2988
 *
2989
 * @param $role
2990
 *   A string with the role name, or an integer with the role ID.
2991
 */
2992
function user_role_delete($role) {
2993
  if (is_int($role)) {
2994
    $role = user_role_load($role);
2995
  }
2996
  else {
2997
    $role = user_role_load_by_name($role);
2998
  }
2999

    
3000
  db_delete('role')
3001
    ->condition('rid', $role->rid)
3002
    ->execute();
3003
  db_delete('role_permission')
3004
    ->condition('rid', $role->rid)
3005
    ->execute();
3006
  // Update the users who have this role set:
3007
  db_delete('users_roles')
3008
    ->condition('rid', $role->rid)
3009
    ->execute();
3010

    
3011
  module_invoke_all('user_role_delete', $role);
3012

    
3013
  // Clear the user access cache.
3014
  drupal_static_reset('user_access');
3015
  drupal_static_reset('user_role_permissions');
3016
}
3017

    
3018
/**
3019
 * Menu access callback for user role editing.
3020
 */
3021
function user_role_edit_access($role) {
3022
  // Prevent the system-defined roles from being altered or removed.
3023
  if ($role->rid == DRUPAL_ANONYMOUS_RID || $role->rid == DRUPAL_AUTHENTICATED_RID) {
3024
    return FALSE;
3025
  }
3026

    
3027
  return user_access('administer permissions');
3028
}
3029

    
3030
/**
3031
 * Determine the modules that permissions belong to.
3032
 *
3033
 * @return
3034
 *   An associative array in the format $permission => $module.
3035
 */
3036
function user_permission_get_modules() {
3037
  $permissions = array();
3038
  foreach (module_implements('permission') as $module) {
3039
    $perms = module_invoke($module, 'permission');
3040
    foreach ($perms as $key => $value) {
3041
      $permissions[$key] = $module;
3042
    }
3043
  }
3044
  return $permissions;
3045
}
3046

    
3047
/**
3048
 * Change permissions for a user role.
3049
 *
3050
 * This function may be used to grant and revoke multiple permissions at once.
3051
 * For example, when a form exposes checkboxes to configure permissions for a
3052
 * role, the form submit handler may directly pass the submitted values for the
3053
 * checkboxes form element to this function.
3054
 *
3055
 * @param $rid
3056
 *   The ID of a user role to alter.
3057
 * @param $permissions
3058
 *   An associative array, where the key holds the permission name and the value
3059
 *   determines whether to grant or revoke that permission. Any value that
3060
 *   evaluates to TRUE will cause the permission to be granted. Any value that
3061
 *   evaluates to FALSE will cause the permission to be revoked.
3062
 *   @code
3063
 *     array(
3064
 *       'administer nodes' => 0,                // Revoke 'administer nodes'
3065
 *       'administer blocks' => FALSE,           // Revoke 'administer blocks'
3066
 *       'access user profiles' => 1,            // Grant 'access user profiles'
3067
 *       'access content' => TRUE,               // Grant 'access content'
3068
 *       'access comments' => 'access comments', // Grant 'access comments'
3069
 *     )
3070
 *   @endcode
3071
 *   Existing permissions are not changed, unless specified in $permissions.
3072
 *
3073
 * @see user_role_grant_permissions()
3074
 * @see user_role_revoke_permissions()
3075
 */
3076
function user_role_change_permissions($rid, array $permissions = array()) {
3077
  // Grant new permissions for the role.
3078
  $grant = array_filter($permissions);
3079
  if (!empty($grant)) {
3080
    user_role_grant_permissions($rid, array_keys($grant));
3081
  }
3082
  // Revoke permissions for the role.
3083
  $revoke = array_diff_assoc($permissions, $grant);
3084
  if (!empty($revoke)) {
3085
    user_role_revoke_permissions($rid, array_keys($revoke));
3086
  }
3087
}
3088

    
3089
/**
3090
 * Grant permissions to a user role.
3091
 *
3092
 * @param $rid
3093
 *   The ID of a user role to alter.
3094
 * @param $permissions
3095
 *   A list of permission names to grant.
3096
 *
3097
 * @see user_role_change_permissions()
3098
 * @see user_role_revoke_permissions()
3099
 */
3100
function user_role_grant_permissions($rid, array $permissions = array()) {
3101
  $modules = user_permission_get_modules();
3102
  // Grant new permissions for the role.
3103
  foreach ($permissions as $name) {
3104
    db_merge('role_permission')
3105
      ->key(array(
3106
        'rid' => $rid,
3107
        'permission' => $name,
3108
      ))
3109
      ->fields(array(
3110
        'module' => $modules[$name],
3111
      ))
3112
      ->execute();
3113
  }
3114

    
3115
  // Clear the user access cache.
3116
  drupal_static_reset('user_access');
3117
  drupal_static_reset('user_role_permissions');
3118
}
3119

    
3120
/**
3121
 * Revoke permissions from a user role.
3122
 *
3123
 * @param $rid
3124
 *   The ID of a user role to alter.
3125
 * @param $permissions
3126
 *   A list of permission names to revoke.
3127
 *
3128
 * @see user_role_change_permissions()
3129
 * @see user_role_grant_permissions()
3130
 */
3131
function user_role_revoke_permissions($rid, array $permissions = array()) {
3132
  // Revoke permissions for the role.
3133
  db_delete('role_permission')
3134
    ->condition('rid', $rid)
3135
    ->condition('permission', $permissions, 'IN')
3136
    ->execute();
3137

    
3138
  // Clear the user access cache.
3139
  drupal_static_reset('user_access');
3140
  drupal_static_reset('user_role_permissions');
3141
}
3142

    
3143
/**
3144
 * Implements hook_user_operations().
3145
 */
3146
function user_user_operations($form = array(), $form_state = array()) {
3147
  $operations = array(
3148
    'unblock' => array(
3149
      'label' => t('Unblock the selected users'),
3150
      'callback' => 'user_user_operations_unblock',
3151
    ),
3152
    'block' => array(
3153
      'label' => t('Block the selected users'),
3154
      'callback' => 'user_user_operations_block',
3155
    ),
3156
    'cancel' => array(
3157
      'label' => t('Cancel the selected user accounts'),
3158
    ),
3159
  );
3160

    
3161
  if (user_access('administer permissions')) {
3162
    $roles = user_roles(TRUE);
3163
    unset($roles[DRUPAL_AUTHENTICATED_RID]);  // Can't edit authenticated role.
3164

    
3165
    $add_roles = array();
3166
    foreach ($roles as $key => $value) {
3167
      $add_roles['add_role-' . $key] = $value;
3168
    }
3169

    
3170
    $remove_roles = array();
3171
    foreach ($roles as $key => $value) {
3172
      $remove_roles['remove_role-' . $key] = $value;
3173
    }
3174

    
3175
    if (count($roles)) {
3176
      $role_operations = array(
3177
        t('Add a role to the selected users') => array(
3178
          'label' => $add_roles,
3179
        ),
3180
        t('Remove a role from the selected users') => array(
3181
          'label' => $remove_roles,
3182
        ),
3183
      );
3184

    
3185
      $operations += $role_operations;
3186
    }
3187
  }
3188

    
3189
  // If the form has been posted, we need to insert the proper data for
3190
  // role editing if necessary.
3191
  if (!empty($form_state['submitted'])) {
3192
    $operation_rid = explode('-', $form_state['values']['operation']);
3193
    $operation = $operation_rid[0];
3194
    if ($operation == 'add_role' || $operation == 'remove_role') {
3195
      $rid = $operation_rid[1];
3196
      if (user_access('administer permissions')) {
3197
        $operations[$form_state['values']['operation']] = array(
3198
          'callback' => 'user_multiple_role_edit',
3199
          'callback arguments' => array($operation, $rid),
3200
        );
3201
      }
3202
      else {
3203
        watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING);
3204
        return;
3205
      }
3206
    }
3207
  }
3208

    
3209
  return $operations;
3210
}
3211

    
3212
/**
3213
 * Callback function for admin mass unblocking users.
3214
 */
3215
function user_user_operations_unblock($accounts) {
3216
  $accounts = user_load_multiple($accounts);
3217
  foreach ($accounts as $account) {
3218
    // Skip unblocking user if they are already unblocked.
3219
    if ($account !== FALSE && $account->status == 0) {
3220
      user_save($account, array('status' => 1));
3221
    }
3222
  }
3223
}
3224

    
3225
/**
3226
 * Callback function for admin mass blocking users.
3227
 */
3228
function user_user_operations_block($accounts) {
3229
  $accounts = user_load_multiple($accounts);
3230
  foreach ($accounts as $account) {
3231
    // Skip blocking user if they are already blocked.
3232
    if ($account !== FALSE && $account->status == 1) {
3233
      // For efficiency manually save the original account before applying any
3234
      // changes.
3235
      $account->original = clone $account;
3236
      user_save($account, array('status' => 0));
3237
    }
3238
  }
3239
}
3240

    
3241
/**
3242
 * Callback function for admin mass adding/deleting a user role.
3243
 */
3244
function user_multiple_role_edit($accounts, $operation, $rid) {
3245
  // The role name is not necessary as user_save() will reload the user
3246
  // object, but some modules' hook_user() may look at this first.
3247
  $role_name = db_query('SELECT name FROM {role} WHERE rid = :rid', array(':rid' => $rid))->fetchField();
3248

    
3249
  switch ($operation) {
3250
    case 'add_role':
3251
      $accounts = user_load_multiple($accounts);
3252
      foreach ($accounts as $account) {
3253
        // Skip adding the role to the user if they already have it.
3254
        if ($account !== FALSE && !isset($account->roles[$rid])) {
3255
          $roles = $account->roles + array($rid => $role_name);
3256
          // For efficiency manually save the original account before applying
3257
          // any changes.
3258
          $account->original = clone $account;
3259
          user_save($account, array('roles' => $roles));
3260
        }
3261
      }
3262
      break;
3263
    case 'remove_role':
3264
      $accounts = user_load_multiple($accounts);
3265
      foreach ($accounts as $account) {
3266
        // Skip removing the role from the user if they already don't have it.
3267
        if ($account !== FALSE && isset($account->roles[$rid])) {
3268
          $roles = array_diff($account->roles, array($rid => $role_name));
3269
          // For efficiency manually save the original account before applying
3270
          // any changes.
3271
          $account->original = clone $account;
3272
          user_save($account, array('roles' => $roles));
3273
        }
3274
      }
3275
      break;
3276
  }
3277
}
3278

    
3279
function user_multiple_cancel_confirm($form, &$form_state) {
3280
  $edit = $form_state['input'];
3281

    
3282
  $form['accounts'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
3283
  $accounts = user_load_multiple(array_keys(array_filter($edit['accounts'])));
3284
  foreach ($accounts as $uid => $account) {
3285
    // Prevent user 1 from being canceled.
3286
    if ($uid <= 1) {
3287
      continue;
3288
    }
3289
    $form['accounts'][$uid] = array(
3290
      '#type' => 'hidden',
3291
      '#value' => $uid,
3292
      '#prefix' => '<li>',
3293
      '#suffix' => check_plain($account->name) . "</li>\n",
3294
    );
3295
  }
3296

    
3297
  // Output a notice that user 1 cannot be canceled.
3298
  if (isset($accounts[1])) {
3299
    $redirect = (count($accounts) == 1);
3300
    $message = t('The user account %name cannot be cancelled.', array('%name' => $accounts[1]->name));
3301
    drupal_set_message($message, $redirect ? 'error' : 'warning');
3302
    // If only user 1 was selected, redirect to the overview.
3303
    if ($redirect) {
3304
      drupal_goto('admin/people');
3305
    }
3306
  }
3307

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

    
3310
  module_load_include('inc', 'user', 'user.pages');
3311
  $form['user_cancel_method'] = array(
3312
    '#type' => 'item',
3313
    '#title' => t('When cancelling these accounts'),
3314
  );
3315
  $form['user_cancel_method'] += user_cancel_methods();
3316
  // Remove method descriptions.
3317
  foreach (element_children($form['user_cancel_method']) as $element) {
3318
    unset($form['user_cancel_method'][$element]['#description']);
3319
  }
3320

    
3321
  // Allow to send the account cancellation confirmation mail.
3322
  $form['user_cancel_confirm'] = array(
3323
    '#type' => 'checkbox',
3324
    '#title' => t('Require e-mail confirmation to cancel account.'),
3325
    '#default_value' => FALSE,
3326
    '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'),
3327
  );
3328
  // Also allow to send account canceled notification mail, if enabled.
3329
  $form['user_cancel_notify'] = array(
3330
    '#type' => 'checkbox',
3331
    '#title' => t('Notify user when account is canceled.'),
3332
    '#default_value' => FALSE,
3333
    '#access' => variable_get('user_mail_status_canceled_notify', FALSE),
3334
    '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'),
3335
  );
3336

    
3337
  return confirm_form($form,
3338
                      t('Are you sure you want to cancel these user accounts?'),
3339
                      'admin/people', t('This action cannot be undone.'),
3340
                      t('Cancel accounts'), t('Cancel'));
3341
}
3342

    
3343
/**
3344
 * Submit handler for mass-account cancellation form.
3345
 *
3346
 * @see user_multiple_cancel_confirm()
3347
 * @see user_cancel_confirm_form_submit()
3348
 */
3349
function user_multiple_cancel_confirm_submit($form, &$form_state) {
3350
  global $user;
3351

    
3352
  if ($form_state['values']['confirm']) {
3353
    foreach ($form_state['values']['accounts'] as $uid => $value) {
3354
      // Prevent programmatic form submissions from cancelling user 1.
3355
      if ($uid <= 1) {
3356
        continue;
3357
      }
3358
      // Prevent user administrators from deleting themselves without confirmation.
3359
      if ($uid == $user->uid) {
3360
        $admin_form_state = $form_state;
3361
        unset($admin_form_state['values']['user_cancel_confirm']);
3362
        $admin_form_state['values']['_account'] = $user;
3363
        user_cancel_confirm_form_submit(array(), $admin_form_state);
3364
      }
3365
      else {
3366
        user_cancel($form_state['values'], $uid, $form_state['values']['user_cancel_method']);
3367
      }
3368
    }
3369
  }
3370
  $form_state['redirect'] = 'admin/people';
3371
}
3372

    
3373
/**
3374
 * Retrieve a list of all user setting/information categories and sort them by weight.
3375
 */
3376
function _user_categories() {
3377
  $categories = module_invoke_all('user_categories');
3378
  usort($categories, '_user_sort');
3379

    
3380
  return $categories;
3381
}
3382

    
3383
function _user_sort($a, $b) {
3384
  $a = (array) $a + array('weight' => 0, 'title' => '');
3385
  $b = (array) $b + array('weight' => 0, 'title' => '');
3386
  return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1));
3387
}
3388

    
3389
/**
3390
 * List user administration filters that can be applied.
3391
 */
3392
function user_filters() {
3393
  // Regular filters
3394
  $filters = array();
3395
  $roles = user_roles(TRUE);
3396
  unset($roles[DRUPAL_AUTHENTICATED_RID]); // Don't list authorized role.
3397
  if (count($roles)) {
3398
    $filters['role'] = array(
3399
      'title' => t('role'),
3400
      'field' => 'ur.rid',
3401
      'options' => array(
3402
        '[any]' => t('any'),
3403
      ) + $roles,
3404
    );
3405
  }
3406

    
3407
  $options = array();
3408
  foreach (module_implements('permission') as $module) {
3409
    $function = $module . '_permission';
3410
    if ($permissions = $function()) {
3411
      asort($permissions);
3412
      foreach ($permissions as $permission => $description) {
3413
        $options[t('@module module', array('@module' => $module))][$permission] = t($permission);
3414
      }
3415
    }
3416
  }
3417
  ksort($options);
3418
  $filters['permission'] = array(
3419
    'title' => t('permission'),
3420
    'options' => array(
3421
      '[any]' => t('any'),
3422
    ) + $options,
3423
  );
3424

    
3425
  $filters['status'] = array(
3426
    'title' => t('status'),
3427
    'field' => 'u.status',
3428
    'options' => array(
3429
      '[any]' => t('any'),
3430
      1 => t('active'),
3431
      0 => t('blocked'),
3432
    ),
3433
  );
3434
  return $filters;
3435
}
3436

    
3437
/**
3438
 * Extends a query object for user administration filters based on session.
3439
 *
3440
 * @param $query
3441
 *   Query object that should be filtered.
3442
 */
3443
function user_build_filter_query(SelectQuery $query) {
3444
  $filters = user_filters();
3445
  // Extend Query with filter conditions.
3446
  foreach (isset($_SESSION['user_overview_filter']) ? $_SESSION['user_overview_filter'] : array() as $filter) {
3447
    list($key, $value) = $filter;
3448
    // This checks to see if this permission filter is an enabled permission for
3449
    // the authenticated role. If so, then all users would be listed, and we can
3450
    // skip adding it to the filter query.
3451
    if ($key == 'permission') {
3452
      $account = new stdClass();
3453
      $account->uid = 'user_filter';
3454
      $account->roles = array(DRUPAL_AUTHENTICATED_RID => 1);
3455
      if (user_access($value, $account)) {
3456
        continue;
3457
      }
3458
      $users_roles_alias = $query->join('users_roles', 'ur', '%alias.uid = u.uid');
3459
      $permission_alias = $query->join('role_permission', 'p', $users_roles_alias . '.rid = %alias.rid');
3460
      $query->condition($permission_alias . '.permission', $value);
3461
    }
3462
    elseif ($key == 'role') {
3463
      $users_roles_alias = $query->join('users_roles', 'ur', '%alias.uid = u.uid');
3464
      $query->condition($users_roles_alias . '.rid' , $value);
3465
    }
3466
    else {
3467
      $query->condition($filters[$key]['field'], $value);
3468
    }
3469
  }
3470
}
3471

    
3472
/**
3473
 * Implements hook_comment_view().
3474
 */
3475
function user_comment_view($comment) {
3476
  if (variable_get('user_signatures', 0) && !empty($comment->signature)) {
3477
    // @todo This alters and replaces the original object value, so a
3478
    //   hypothetical process of loading, viewing, and saving will hijack the
3479
    //   stored data. Consider renaming to $comment->signature_safe or similar
3480
    //   here and elsewhere in Drupal 8.
3481
    $comment->signature = check_markup($comment->signature, $comment->signature_format, '', TRUE);
3482
  }
3483
  else {
3484
    $comment->signature = '';
3485
  }
3486
}
3487

    
3488
/**
3489
 * Returns HTML for a user signature.
3490
 *
3491
 * @param $variables
3492
 *   An associative array containing:
3493
 *   - signature: The user's signature.
3494
 *
3495
 * @ingroup themeable
3496
 */
3497
function theme_user_signature($variables) {
3498
  $signature = $variables['signature'];
3499
  $output = '';
3500

    
3501
  if ($signature) {
3502
    $output .= '<div class="clear">';
3503
    $output .= '<div>—</div>';
3504
    $output .= $signature;
3505
    $output .= '</div>';
3506
  }
3507

    
3508
  return $output;
3509
}
3510

    
3511
/**
3512
 * Get the language object preferred by the user. This user preference can
3513
 * be set on the user account editing page, and is only available if there
3514
 * are more than one languages enabled on the site. If the user did not
3515
 * choose a preferred language, or is the anonymous user, the $default
3516
 * value, or if it is not set, the site default language will be returned.
3517
 *
3518
 * @param $account
3519
 *   User account to look up language for.
3520
 * @param $default
3521
 *   Optional default language object to return if the account
3522
 *   has no valid language.
3523
 */
3524
function user_preferred_language($account, $default = NULL) {
3525
  $language_list = language_list();
3526
  if (!empty($account->language) && isset($language_list[$account->language])) {
3527
    return $language_list[$account->language];
3528
  }
3529
  else {
3530
    return $default ? $default : language_default();
3531
  }
3532
}
3533

    
3534
/**
3535
 * Conditionally create and send a notification email when a certain
3536
 * operation happens on the given user account.
3537
 *
3538
 * @see user_mail_tokens()
3539
 * @see drupal_mail()
3540
 *
3541
 * @param $op
3542
 *   The operation being performed on the account. Possible values:
3543
 *   - 'register_admin_created': Welcome message for user created by the admin.
3544
 *   - 'register_no_approval_required': Welcome message when user
3545
 *     self-registers.
3546
 *   - 'register_pending_approval': Welcome message, user pending admin
3547
 *     approval.
3548
 *   - 'password_reset': Password recovery request.
3549
 *   - 'status_activated': Account activated.
3550
 *   - 'status_blocked': Account blocked.
3551
 *   - 'cancel_confirm': Account cancellation request.
3552
 *   - 'status_canceled': Account canceled.
3553
 *
3554
 * @param $account
3555
 *   The user object of the account being notified. Must contain at
3556
 *   least the fields 'uid', 'name', and 'mail'.
3557
 * @param $language
3558
 *   Optional language to use for the notification, overriding account language.
3559
 *
3560
 * @return
3561
 *   The return value from drupal_mail_system()->mail(), if ends up being
3562
 *   called.
3563
 */
3564
function _user_mail_notify($op, $account, $language = NULL) {
3565
  // By default, we always notify except for canceled and blocked.
3566
  $default_notify = ($op != 'status_canceled' && $op != 'status_blocked');
3567
  $notify = variable_get('user_mail_' . $op . '_notify', $default_notify);
3568
  if ($notify) {
3569
    $params['account'] = $account;
3570
    $language = $language ? $language : user_preferred_language($account);
3571
    $mail = drupal_mail('user', $op, $account->mail, $language, $params);
3572
    if ($op == 'register_pending_approval') {
3573
      // If a user registered requiring admin approval, notify the admin, too.
3574
      // We use the site default language for this.
3575
      drupal_mail('user', 'register_pending_approval_admin', variable_get('site_mail', ini_get('sendmail_from')), language_default(), $params);
3576
    }
3577
  }
3578
  return empty($mail) ? NULL : $mail['result'];
3579
}
3580

    
3581
/**
3582
 * Form element process handler for client-side password validation.
3583
 *
3584
 * This #process handler is automatically invoked for 'password_confirm' form
3585
 * elements to add the JavaScript and string translations for dynamic password
3586
 * validation.
3587
 *
3588
 * @see system_element_info()
3589
 */
3590
function user_form_process_password_confirm($element) {
3591
  global $user;
3592

    
3593
  $js_settings = array(
3594
    'password' => array(
3595
      'strengthTitle' => t('Password strength:'),
3596
      'hasWeaknesses' => t('To make your password stronger:'),
3597
      'tooShort' => t('Make it at least 6 characters'),
3598
      'addLowerCase' => t('Add lowercase letters'),
3599
      'addUpperCase' => t('Add uppercase letters'),
3600
      'addNumbers' => t('Add numbers'),
3601
      'addPunctuation' => t('Add punctuation'),
3602
      'sameAsUsername' => t('Make it different from your username'),
3603
      'confirmSuccess' => t('yes'),
3604
      'confirmFailure' => t('no'),
3605
      'weak' => t('Weak'),
3606
      'fair' => t('Fair'),
3607
      'good' => t('Good'),
3608
      'strong' => t('Strong'),
3609
      'confirmTitle' => t('Passwords match:'),
3610
      'username' => (isset($user->name) ? $user->name : ''),
3611
    ),
3612
  );
3613

    
3614
  $element['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.js';
3615
  // Ensure settings are only added once per page.
3616
  static $already_added = FALSE;
3617
  if (!$already_added) {
3618
    $already_added = TRUE;
3619
    $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting');
3620
  }
3621

    
3622
  return $element;
3623
}
3624

    
3625
/**
3626
 * Implements hook_node_load().
3627
 */
3628
function user_node_load($nodes, $types) {
3629
  // Build an array of all uids for node authors, keyed by nid.
3630
  $uids = array();
3631
  foreach ($nodes as $nid => $node) {
3632
    $uids[$nid] = $node->uid;
3633
  }
3634

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

    
3638
  // Add these values back into the node objects.
3639
  foreach ($uids as $nid => $uid) {
3640
    $nodes[$nid]->name = $user_fields[$uid]->name;
3641
    $nodes[$nid]->picture = $user_fields[$uid]->picture;
3642
    $nodes[$nid]->data = $user_fields[$uid]->data;
3643
  }
3644
}
3645

    
3646
/**
3647
 * Implements hook_image_style_delete().
3648
 */
3649
function user_image_style_delete($style) {
3650
  // If a style is deleted, update the variables.
3651
  // Administrators choose a replacement style when deleting.
3652
  user_image_style_save($style);
3653
}
3654

    
3655
/**
3656
 * Implements hook_image_style_save().
3657
 */
3658
function user_image_style_save($style) {
3659
  // If a style is renamed, update the variables that use it.
3660
  if (isset($style['old_name']) && $style['old_name'] == variable_get('user_picture_style', '')) {
3661
    variable_set('user_picture_style', $style['name']);
3662
  }
3663
}
3664

    
3665
/**
3666
 * Implements hook_action_info().
3667
 */
3668
function user_action_info() {
3669
  return array(
3670
    'user_block_user_action' => array(
3671
      'label' => t('Block current user'),
3672
      'type' => 'user',
3673
      'configurable' => FALSE,
3674
      'triggers' => array('any'),
3675
    ),
3676
  );
3677
}
3678

    
3679
/**
3680
 * Blocks a specific user or the current user, if one is not specified.
3681
 *
3682
 * @param $entity
3683
 *   (optional) An entity object; if it is provided and it has a uid property,
3684
 *   the user with that ID is blocked.
3685
 * @param $context
3686
 *   (optional) An associative array; if no user ID is found in $entity, the
3687
 *   'uid' element of this array determines the user to block.
3688
 *
3689
 * @ingroup actions
3690
 */
3691
function user_block_user_action(&$entity, $context = array()) {
3692
  // First priority: If there is a $entity->uid, block that user.
3693
  // This is most likely a user object or the author if a node or comment.
3694
  if (isset($entity->uid)) {
3695
    $uid = $entity->uid;
3696
  }
3697
  elseif (isset($context['uid'])) {
3698
    $uid = $context['uid'];
3699
  }
3700
  // If neither of those are valid, then block the current user.
3701
  else {
3702
    $uid = $GLOBALS['user']->uid;
3703
  }
3704
  $account = user_load($uid);
3705
  $account = user_save($account, array('status' => 0));
3706
  watchdog('action', 'Blocked user %name.', array('%name' => $account->name));
3707
}
3708

    
3709
/**
3710
 * Implements hook_form_FORM_ID_alter().
3711
 *
3712
 * Add a checkbox for the 'user_register_form' instance settings on the 'Edit
3713
 * field instance' form.
3714
 */
3715
function user_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
3716
  $instance = $form['#instance'];
3717

    
3718
  if ($instance['entity_type'] == 'user' && !$form['#field']['locked']) {
3719
    $form['instance']['settings']['user_register_form'] = array(
3720
      '#type' => 'checkbox',
3721
      '#title' => t('Display on user registration form.'),
3722
      '#description' => t("This is compulsory for 'required' fields."),
3723
      // Field instances created in D7 beta releases before the setting was
3724
      // introduced might be set as 'required' and 'not shown on user_register
3725
      // form'. We make sure the checkbox comes as 'checked' for those.
3726
      '#default_value' => $instance['settings']['user_register_form'] || $instance['required'],
3727
      // Display just below the 'required' checkbox.
3728
      '#weight' => $form['instance']['required']['#weight'] + .1,
3729
      // Disabled when the 'required' checkbox is checked.
3730
      '#states' => array(
3731
        'enabled' => array('input[name="instance[required]"]' => array('checked' => FALSE)),
3732
      ),
3733
      // Checked when the 'required' checkbox is checked. This is done through
3734
      // a custom behavior, since the #states system would also synchronize on
3735
      // uncheck.
3736
      '#attached' => array(
3737
        'js' => array(drupal_get_path('module', 'user') . '/user.js'),
3738
      ),
3739
    );
3740

    
3741
    array_unshift($form['#submit'], 'user_form_field_ui_field_edit_form_submit');
3742
  }
3743
}
3744

    
3745
/**
3746
 * Additional submit handler for the 'Edit field instance' form.
3747
 *
3748
 * Make sure the 'user_register_form' setting is set for required fields.
3749
 */
3750
function user_form_field_ui_field_edit_form_submit($form, &$form_state) {
3751
  $instance = $form_state['values']['instance'];
3752

    
3753
  if (!empty($instance['required'])) {
3754
    form_set_value($form['instance']['settings']['user_register_form'], 1, $form_state);
3755
  }
3756
}
3757

    
3758
/**
3759
 * Form builder; the user registration form.
3760
 *
3761
 * @ingroup forms
3762
 * @see user_account_form()
3763
 * @see user_account_form_validate()
3764
 * @see user_register_submit()
3765
 */
3766
function user_register_form($form, &$form_state) {
3767
  global $user;
3768

    
3769
  $admin = user_access('administer users');
3770

    
3771
  // Pass access information to the submit handler. Running an access check
3772
  // inside the submit function interferes with form processing and breaks
3773
  // hook_form_alter().
3774
  $form['administer_users'] = array(
3775
     '#type' => 'value',
3776
     '#value' => $admin,
3777
  );
3778

    
3779
  // If we aren't admin but already logged on, go to the user page instead.
3780
  if (!$admin && $user->uid) {
3781
    drupal_goto('user/' . $user->uid);
3782
  }
3783

    
3784
  $form['#user'] = drupal_anonymous_user();
3785
  $form['#user_category'] = 'register';
3786

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

    
3790
  // Start with the default user account fields.
3791
  user_account_form($form, $form_state);
3792

    
3793
  // Attach field widgets, and hide the ones where the 'user_register_form'
3794
  // setting is not on.
3795
  $langcode = entity_language('user', $form['#user']);
3796
  field_attach_form('user', $form['#user'], $form, $form_state, $langcode);
3797
  foreach (field_info_instances('user', 'user') as $field_name => $instance) {
3798
    if (empty($instance['settings']['user_register_form'])) {
3799
      $form[$field_name]['#access'] = FALSE;
3800
    }
3801
  }
3802

    
3803
  if ($admin) {
3804
    // Redirect back to page which initiated the create request;
3805
    // usually admin/people/create.
3806
    $form_state['redirect'] = $_GET['q'];
3807
  }
3808

    
3809
  $form['actions'] = array('#type' => 'actions');
3810
  $form['actions']['submit'] = array(
3811
    '#type' => 'submit',
3812
    '#value' => t('Create new account'),
3813
  );
3814

    
3815
  $form['#validate'][] = 'user_register_validate';
3816
  // Add the final user registration form submit handler.
3817
  $form['#submit'][] = 'user_register_submit';
3818

    
3819
  return $form;
3820
}
3821

    
3822
/**
3823
 * Validation function for the user registration form.
3824
 */
3825
function user_register_validate($form, &$form_state) {
3826
  entity_form_field_validate('user', $form, $form_state);
3827
}
3828

    
3829
/**
3830
 * Submit handler for the user registration form.
3831
 *
3832
 * This function is shared by the installation form and the normal registration form,
3833
 * which is why it can't be in the user.pages.inc file.
3834
 *
3835
 * @see user_register_form()
3836
 */
3837
function user_register_submit($form, &$form_state) {
3838
  $admin = $form_state['values']['administer_users'];
3839

    
3840
  if (!variable_get('user_email_verification', TRUE) || $admin) {
3841
    $pass = $form_state['values']['pass'];
3842
  }
3843
  else {
3844
    $pass = user_password();
3845
  }
3846
  $notify = !empty($form_state['values']['notify']);
3847

    
3848
  // Remove unneeded values.
3849
  form_state_values_clean($form_state);
3850

    
3851
  $form_state['values']['pass'] = $pass;
3852
  $form_state['values']['init'] = $form_state['values']['mail'];
3853

    
3854
  $account = $form['#user'];
3855

    
3856
  entity_form_submit_build_entity('user', $account, $form, $form_state);
3857

    
3858
  // Populate $edit with the properties of $account, which have been edited on
3859
  // this form by taking over all values, which appear in the form values too.
3860
  $edit = array_intersect_key((array) $account, $form_state['values']);
3861
  $account = user_save($account, $edit);
3862

    
3863
  // Terminate if an error occurred during user_save().
3864
  if (!$account) {
3865
    drupal_set_message(t("Error saving user account."), 'error');
3866
    $form_state['redirect'] = '';
3867
    return;
3868
  }
3869
  $form_state['user'] = $account;
3870
  $form_state['values']['uid'] = $account->uid;
3871

    
3872
  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'));
3873

    
3874
  // Add plain text password into user account to generate mail tokens.
3875
  $account->password = $pass;
3876

    
3877
  // New administrative account without notification.
3878
  $uri = entity_uri('user', $account);
3879
  if ($admin && !$notify) {
3880
    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)));
3881
  }
3882
  // No e-mail verification required; log in user immediately.
3883
  elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) {
3884
    _user_mail_notify('register_no_approval_required', $account);
3885
    $form_state['uid'] = $account->uid;
3886
    user_login_submit(array(), $form_state);
3887
    drupal_set_message(t('Registration successful. You are now logged in.'));
3888
    $form_state['redirect'] = '';
3889
  }
3890
  // No administrator approval required.
3891
  elseif ($account->status || $notify) {
3892
    $op = $notify ? 'register_admin_created' : 'register_no_approval_required';
3893
    _user_mail_notify($op, $account);
3894
    if ($notify) {
3895
      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)));
3896
    }
3897
    else {
3898
      drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.'));
3899
      $form_state['redirect'] = '';
3900
    }
3901
  }
3902
  // Administrator approval required.
3903
  else {
3904
    _user_mail_notify('register_pending_approval', $account);
3905
    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.'));
3906
    $form_state['redirect'] = '';
3907
  }
3908
}
3909

    
3910
/**
3911
 * Implements hook_modules_installed().
3912
 */
3913
function user_modules_installed($modules) {
3914
  // Assign all available permissions to the administrator role.
3915
  $rid = variable_get('user_admin_role', 0);
3916
  if ($rid) {
3917
    $permissions = array();
3918
    foreach ($modules as $module) {
3919
      if ($module_permissions = module_invoke($module, 'permission')) {
3920
        $permissions = array_merge($permissions, array_keys($module_permissions));
3921
      }
3922
    }
3923
    if (!empty($permissions)) {
3924
      user_role_grant_permissions($rid, $permissions);
3925
    }
3926
  }
3927
}
3928

    
3929
/**
3930
 * Implements hook_modules_uninstalled().
3931
 */
3932
function user_modules_uninstalled($modules) {
3933
   db_delete('role_permission')
3934
     ->condition('module', $modules, 'IN')
3935
     ->execute();
3936
}
3937

    
3938
/**
3939
 * Helper function to rewrite the destination to avoid redirecting to login page after login.
3940
 *
3941
 * Third-party authentication modules may use this function to determine the
3942
 * proper destination after a user has been properly logged in.
3943
 */
3944
function user_login_destination() {
3945
  $destination = drupal_get_destination();
3946
  if ($destination['destination'] == 'user/login') {
3947
    $destination['destination'] = 'user';
3948
  }
3949
  return $destination;
3950
}
3951

    
3952
/**
3953
 * Saves visitor information as a cookie so it can be reused.
3954
 *
3955
 * @param $values
3956
 *   An array of key/value pairs to be saved into a cookie.
3957
 */
3958
function user_cookie_save(array $values) {
3959
  foreach ($values as $field => $value) {
3960
    // Set cookie for 365 days.
3961
    setrawcookie('Drupal.visitor.' . $field, rawurlencode($value), REQUEST_TIME + 31536000, '/');
3962
  }
3963
}
3964

    
3965
/**
3966
 * Delete a visitor information cookie.
3967
 *
3968
 * @param $cookie_name
3969
 *   A cookie name such as 'homepage'.
3970
 */
3971
function user_cookie_delete($cookie_name) {
3972
  setrawcookie('Drupal.visitor.' . $cookie_name, '', REQUEST_TIME - 3600, '/');
3973
}
3974

    
3975
/**
3976
 * Implements hook_rdf_mapping().
3977
 */
3978
function user_rdf_mapping() {
3979
  return array(
3980
    array(
3981
      'type' => 'user',
3982
      'bundle' => RDF_DEFAULT_BUNDLE,
3983
      'mapping' => array(
3984
        'rdftype' => array('sioc:UserAccount'),
3985
        'name' => array(
3986
          'predicates' => array('foaf:name'),
3987
        ),
3988
        'homepage' => array(
3989
          'predicates' => array('foaf:page'),
3990
          'type' => 'rel',
3991
        ),
3992
      ),
3993
    ),
3994
  );
3995
}
3996

    
3997
/**
3998
 * Implements hook_file_download_access().
3999
 */
4000
function user_file_download_access($field, $entity_type, $entity) {
4001
  if ($entity_type == 'user') {
4002
    return user_view_access($entity);
4003
  }
4004
}
4005

    
4006
/**
4007
 * Implements hook_system_info_alter().
4008
 *
4009
 * Drupal 7 ships with two methods to add additional fields to users: Profile
4010
 * module, a legacy module dating back from 2002, and Field API integration
4011
 * with users. While Field API support for users currently provides less end
4012
 * user features, the inefficient data storage mechanism of Profile module, as
4013
 * well as its lack of consistency with the rest of the entity / field based
4014
 * systems in Drupal 7, make this a sub-optimal solution to those who were not
4015
 * using it in previous releases of Drupal.
4016
 *
4017
 * To prevent new Drupal 7 sites from installing Profile module, and
4018
 * unwittingly ending up with two completely different and incompatible methods
4019
 * of extending users, only make the Profile module available if the profile_*
4020
 * tables are present.
4021
 *
4022
 * @todo: Remove in D8, pending upgrade path.
4023
 */
4024
function user_system_info_alter(&$info, $file, $type) {
4025
  if ($type == 'module' && $file->name == 'profile' && db_table_exists('profile_field')) {
4026
    $info['hidden'] = FALSE;
4027
  }
4028
}