Projet

Général

Profil

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

root / drupal7 / modules / user / user.module @ 01dfd3b5

1
<?php
2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
237
  return $return;
238
}
239

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

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

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

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

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

    
317
    // Add any additional roles from the database.
318
    $result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users)));
319
    foreach ($result as $record) {
320
      $queried_users[$record->uid]->roles[$record->rid] = $record->name;
321
    }
322

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
551
      // Save Field data.
552
      field_attach_update('user', $account);
553

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

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

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

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

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

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

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

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

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

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

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

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

    
718
  // Declare the password as a blank string.
719
  $pass = '';
720

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

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

    
733
  return $pass;
734
}
735

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

    
750
  $role_permissions = $fetch = array();
751

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

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

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

    
780
  return $role_permissions;
781
}
782

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

    
801
  if (!isset($account)) {
802
    $account = $user;
803
  }
804

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

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

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

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

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

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

    
866
  return isset($account->roles[$rid]);
867
}
868

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

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

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

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

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

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

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

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

    
997
  return $results;
998
}
999

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

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

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

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

    
1051
  $admin = user_access('administer users');
1052

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

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

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

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

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

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

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

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

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

    
1194
  // Picture/avatar.
1195
  $form['picture'] = array(
1196
    '#type' => 'fieldset',
1197
    '#title' => t('Picture'),
1198
    '#weight' => 1,
1199
    '#access' => (!$register && variable_get('user_pictures', 0)),
1200
  );
1201
  $form['picture']['picture'] = array(
1202
    '#type' => 'value',
1203
    '#value' => isset($account->picture) ? $account->picture : NULL,
1204
  );
1205
  $form['picture']['picture_current'] = array(
1206
    '#markup' => theme('user_picture', array('account' => $account)),
1207
  );
1208
  $form['picture']['picture_delete'] = array(
1209
    '#type' => 'checkbox',
1210
    '#title' => t('Delete picture'),
1211
    '#access' => !empty($account->picture->fid),
1212
    '#description' => t('Check this box to delete your current picture.'),
1213
  );
1214
  $form['picture']['picture_upload'] = array(
1215
    '#type' => 'file',
1216
    '#title' => t('Upload picture'),
1217
    '#size' => 48,
1218
    '#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', '')),
1219
  );
1220
  $form['#validate'][] = 'user_validate_picture';
1221
}
1222

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

    
1247
/**
1248
 * Form validation handler for user_account_form().
1249
 *
1250
 * @see user_account_form()
1251
 */
1252
function user_account_form_validate($form, &$form_state) {
1253
  if ($form['#user_category'] == 'account' || $form['#user_category'] == 'register') {
1254
    $account = $form['#user'];
1255
    // Validate new or changing username.
1256
    if (isset($form_state['values']['name'])) {
1257
      if ($error = user_validate_name($form_state['values']['name'])) {
1258
        form_set_error('name', $error);
1259
      }
1260
      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()) {
1261
        form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name'])));
1262
      }
1263
    }
1264

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

    
1270
    // Validate the e-mail address, and check if it is taken by an existing user.
1271
    if ($error = user_validate_mail($form_state['values']['mail'])) {
1272
      form_set_error('mail', $error);
1273
    }
1274
    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()) {
1275
      // Format error message dependent on whether the user is logged in or not.
1276
      if ($GLOBALS['user']->uid) {
1277
        form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $form_state['values']['mail'])));
1278
      }
1279
      else {
1280
        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'))));
1281
      }
1282
    }
1283

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

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

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

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

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

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

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

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

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

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

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

    
1386
  return $blocks;
1387
}
1388

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

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

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

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

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

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

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

    
1437
  $block = array();
1438

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

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

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

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

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

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

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

    
1471
        // Display a list of currently online users.
1472
        $max_users = variable_get('user_block_max_list_count', 10);
1473
        if ($authenticated_count && $max_users) {
1474
          $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();
1475
          $output .= theme('user_list', array('users' => $items));
1476
        }
1477

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1757
  $items['admin/people/create'] = array(
1758
    'title' => 'Add user',
1759
    'page callback' => 'user_admin',
1760
    'page arguments' => array('create'),
1761
    'access arguments' => array('administer users'),
1762
    'type' => MENU_LOCAL_ACTION,
1763
    'file' => 'user.admin.inc',
1764
  );
1765

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2129
  return $form;
2130
}
2131

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

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

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

    
2196
      // Don't allow login if the limit for this user has been reached.
2197
      // Default is to allow 5 failed attempts every 6 hours.
2198
      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)) {
2199
        $form_state['flood_control_triggered'] = 'user';
2200
        return;
2201
      }
2202
    }
2203
    // We are not limited by flood control, so try to authenticate.
2204
    // Set $form_state['uid'] as a flag for user_login_final_validate().
2205
    $form_state['uid'] = user_authenticate($form_state['values']['name'], $password);
2206
  }
2207
}
2208

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

    
2225
    if (isset($form_state['flood_control_triggered'])) {
2226
      if ($form_state['flood_control_triggered'] == 'user') {
2227
        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'))));
2228
        module_invoke_all('user_flood_control', ip_address(), $form_state['values']['name']);
2229
      }
2230
      else {
2231
        // We did not find a uid, so the limit is IP-based.
2232
        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'))));
2233
        module_invoke_all('user_flood_control', ip_address());
2234
      }
2235
      // We cannot call drupal_access_denied() here as that can result in an
2236
      // infinite loop if the login form is rendered on the 403 page (e.g. in a
2237
      // block). So add the 403 header and allow form processing to finish.
2238
      drupal_add_http_header('Status', '403 Forbidden');
2239
    }
2240
    else {
2241
      // Use $form_state['input']['name'] here to guarantee that we send
2242
      // exactly what the user typed in. $form_state['values']['name'] may have
2243
      // been modified by validation handlers that ran earlier than this one.
2244
      $query = isset($form_state['input']['name']) ? array('name' => $form_state['input']['name']) : array();
2245
      form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password', array('query' => $query)))));
2246
      watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
2247
    }
2248
  }
2249
  elseif (isset($form_state['flood_control_user_identifier'])) {
2250
    // Clear past failures for this user so as not to block a user who might
2251
    // log in and out more than once in an hour.
2252
    flood_clear_event('failed_login_attempt_user', $form_state['flood_control_user_identifier']);
2253
  }
2254
}
2255

    
2256
/**
2257
 * Implements hook_user_flood_control().
2258
 */
2259
function user_user_flood_control($ip, $username = FALSE) {
2260
  if (variable_get('log_user_flood_control', TRUE)) {
2261
    if (!empty($username)) {
2262
      watchdog('user', 'Flood control blocked login attempt for %user from %ip.', array(
2263
        '%user' => $username,
2264
        '%ip' => $ip
2265
      ));
2266
    }
2267
    else {
2268
      watchdog('user', 'Flood control blocked login attempt from %ip.', array('%ip' => $ip));
2269
    }
2270
  }
2271
}
2272

    
2273
/**
2274
 * Try to validate the user's login credentials locally.
2275
 *
2276
 * @param $name
2277
 *   User name to authenticate.
2278
 * @param $password
2279
 *   A plain-text password, such as trimmed text from form values.
2280
 * @return
2281
 *   The user's uid on success, or FALSE on failure to authenticate.
2282
 */
2283
function user_authenticate($name, $password) {
2284
  $uid = FALSE;
2285
  if (!empty($name) && strlen(trim($password)) > 0) {
2286
    $account = user_load_by_name($name);
2287
    if ($account) {
2288
      // Allow alternate password hashing schemes.
2289
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
2290
      if (user_check_password($password, $account)) {
2291
        // Successful authentication.
2292
        $uid = $account->uid;
2293

    
2294
        // Update user to new password scheme if needed.
2295
        if (user_needs_new_hash($account)) {
2296
          user_save($account, array('pass' => $password));
2297
        }
2298
      }
2299
    }
2300
  }
2301
  return $uid;
2302
}
2303

    
2304
/**
2305
 * Finalize the login process. Must be called when logging in a user.
2306
 *
2307
 * The function records a watchdog message about the new session, saves the
2308
 * login timestamp, calls hook_user_login(), and generates a new session.
2309
 *
2310
 * @param array $edit
2311
 *   The array of form values submitted by the user.
2312
 *
2313
 * @see hook_user_login()
2314
 */
2315
function user_login_finalize(&$edit = array()) {
2316
  global $user;
2317
  watchdog('user', 'Session opened for %name.', array('%name' => $user->name));
2318
  // Update the user table timestamp noting user has logged in.
2319
  // This is also used to invalidate one-time login links.
2320
  $user->login = REQUEST_TIME;
2321
  db_update('users')
2322
    ->fields(array('login' => $user->login))
2323
    ->condition('uid', $user->uid)
2324
    ->execute();
2325

    
2326
  // Regenerate the session ID to prevent against session fixation attacks.
2327
  // This is called before hook_user in case one of those functions fails
2328
  // or incorrectly does a redirect which would leave the old session in place.
2329
  drupal_session_regenerate();
2330

    
2331
  user_module_invoke('login', $edit, $user);
2332
}
2333

    
2334
/**
2335
 * Submit handler for the login form. Load $user object and perform standard login
2336
 * tasks. The user is then redirected to the My Account page. Setting the
2337
 * destination in the query string overrides the redirect.
2338
 */
2339
function user_login_submit($form, &$form_state) {
2340
  global $user;
2341
  $user = user_load($form_state['uid']);
2342
  $form_state['redirect'] = 'user/' . $user->uid;
2343

    
2344
  user_login_finalize($form_state);
2345
}
2346

    
2347
/**
2348
 * Helper function for authentication modules. Either logs in or registers
2349
 * the current user, based on username. Either way, the global $user object is
2350
 * populated and login tasks are performed.
2351
 */
2352
function user_external_login_register($name, $module) {
2353
  $account = user_external_load($name);
2354
  if (!$account) {
2355
    // Register this new user.
2356
    $userinfo = array(
2357
      'name' => $name,
2358
      'pass' => user_password(),
2359
      'init' => $name,
2360
      'status' => 1,
2361
      'access' => REQUEST_TIME
2362
    );
2363
    $account = user_save(drupal_anonymous_user(), $userinfo);
2364
    // Terminate if an error occurred during user_save().
2365
    if (!$account) {
2366
      drupal_set_message(t("Error saving user account."), 'error');
2367
      return;
2368
    }
2369
    user_set_authmaps($account, array("authname_$module" => $name));
2370
  }
2371

    
2372
  // Log user in.
2373
  $form_state['uid'] = $account->uid;
2374
  user_login_submit(array(), $form_state);
2375
}
2376

    
2377
/**
2378
 * Generates a unique URL for a user to login and reset their password.
2379
 *
2380
 * @param object $account
2381
 *   An object containing the user account, which must contain at least the
2382
 *   following properties:
2383
 *   - uid: The user ID number.
2384
 *   - login: The UNIX timestamp of the user's last login.
2385
 *
2386
 * @return
2387
 *   A unique URL that provides a one-time log in for the user, from which
2388
 *   they can change their password.
2389
 */
2390
function user_pass_reset_url($account) {
2391
  $timestamp = REQUEST_TIME;
2392
  return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
2393
}
2394

    
2395
/**
2396
 * Generates a URL to confirm an account cancellation request.
2397
 *
2398
 * @param object $account
2399
 *   The user account object, which must contain at least the following
2400
 *   properties:
2401
 *   - uid: The user ID number.
2402
 *   - pass: The hashed user password string.
2403
 *   - login: The UNIX timestamp of the user's last login.
2404
 *
2405
 * @return
2406
 *   A unique URL that may be used to confirm the cancellation of the user
2407
 *   account.
2408
 *
2409
 * @see user_mail_tokens()
2410
 * @see user_cancel_confirm()
2411
 */
2412
function user_cancel_url($account) {
2413
  $timestamp = REQUEST_TIME;
2414
  return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
2415
}
2416

    
2417
/**
2418
 * Creates a unique hash value for use in time-dependent per-user URLs.
2419
 *
2420
 * This hash is normally used to build a unique and secure URL that is sent to
2421
 * the user by email for purposes such as resetting the user's password. In
2422
 * order to validate the URL, the same hash can be generated again, from the
2423
 * same information, and compared to the hash value from the URL. The URL
2424
 * normally contains both the time stamp and the numeric user ID. The login
2425
 * timestamp and hashed password are retrieved from the database as necessary.
2426
 * For a usage example, see user_cancel_url() and user_cancel_confirm().
2427
 *
2428
 * @param string $password
2429
 *   The hashed user account password value.
2430
 * @param int $timestamp
2431
 *   A UNIX timestamp, typically REQUEST_TIME.
2432
 * @param int $login
2433
 *   The UNIX timestamp of the user's last login.
2434
 * @param int $uid
2435
 *   The user ID of the user account.
2436
 *
2437
 * @return
2438
 *   A string that is safe for use in URLs and SQL statements.
2439
 */
2440
function user_pass_rehash($password, $timestamp, $login, $uid) {
2441
  // Backwards compatibility: Try to determine a $uid if one was not passed.
2442
  // (Since $uid is a required parameter to this function, a PHP warning will
2443
  // be generated if it's not provided, which is an indication that the calling
2444
  // code should be updated. But the code below will try to generate a correct
2445
  // hash in the meantime.)
2446
  if (!isset($uid)) {
2447
    $uids = db_query_range('SELECT uid FROM {users} WHERE pass = :password AND login = :login AND uid > 0', 0, 2, array(':password' => $password, ':login' => $login))->fetchCol();
2448
    // If exactly one user account matches the provided password and login
2449
    // timestamp, proceed with that $uid.
2450
    if (count($uids) == 1) {
2451
      $uid = reset($uids);
2452
    }
2453
    // Otherwise there is no safe hash to return, so return a random string
2454
    // that will never be treated as a valid token.
2455
    else {
2456
      return drupal_random_key();
2457
    }
2458
  }
2459

    
2460
  return drupal_hmac_base64($timestamp . $login . $uid, drupal_get_hash_salt() . $password);
2461
}
2462

    
2463
/**
2464
 * Cancel a user account.
2465
 *
2466
 * Since the user cancellation process needs to be run in a batch, either
2467
 * Form API will invoke it, or batch_process() needs to be invoked after calling
2468
 * this function and should define the path to redirect to.
2469
 *
2470
 * @param $edit
2471
 *   An array of submitted form values.
2472
 * @param $uid
2473
 *   The user ID of the user account to cancel.
2474
 * @param $method
2475
 *   The account cancellation method to use.
2476
 *
2477
 * @see _user_cancel()
2478
 */
2479
function user_cancel($edit, $uid, $method) {
2480
  global $user;
2481

    
2482
  $account = user_load($uid);
2483

    
2484
  if (!$account) {
2485
    drupal_set_message(t('The user account %id does not exist.', array('%id' => $uid)), 'error');
2486
    watchdog('user', 'Attempted to cancel non-existing user account: %id.', array('%id' => $uid), WATCHDOG_ERROR);
2487
    return;
2488
  }
2489

    
2490
  // Initialize batch (to set title).
2491
  $batch = array(
2492
    'title' => t('Cancelling account'),
2493
    'operations' => array(),
2494
  );
2495
  batch_set($batch);
2496

    
2497
  // Modules use hook_user_delete() to respond to deletion.
2498
  if ($method != 'user_cancel_delete') {
2499
    // Allow modules to add further sets to this batch.
2500
    module_invoke_all('user_cancel', $edit, $account, $method);
2501
  }
2502

    
2503
  // Finish the batch and actually cancel the account.
2504
  $batch = array(
2505
    'title' => t('Cancelling user account'),
2506
    'operations' => array(
2507
      array('_user_cancel', array($edit, $account, $method)),
2508
    ),
2509
  );
2510

    
2511
  // After cancelling account, ensure that user is logged out.
2512
  if ($account->uid == $user->uid) {
2513
    // Batch API stores data in the session, so use the finished operation to
2514
    // manipulate the current user's session id.
2515
    $batch['finished'] = '_user_cancel_session_regenerate';
2516
  }
2517

    
2518
  batch_set($batch);
2519

    
2520
  // Batch processing is either handled via Form API or has to be invoked
2521
  // manually.
2522
}
2523

    
2524
/**
2525
 * Implements callback_batch_operation().
2526
 *
2527
 * Last step for cancelling a user account.
2528
 *
2529
 * Since batch and session API require a valid user account, the actual
2530
 * cancellation of a user account needs to happen last.
2531
 *
2532
 * @see user_cancel()
2533
 */
2534
function _user_cancel($edit, $account, $method) {
2535
  global $user;
2536

    
2537
  switch ($method) {
2538
    case 'user_cancel_block':
2539
    case 'user_cancel_block_unpublish':
2540
    default:
2541
      // Send account blocked notification if option was checked.
2542
      if (!empty($edit['user_cancel_notify'])) {
2543
        _user_mail_notify('status_blocked', $account);
2544
      }
2545
      user_save($account, array('status' => 0));
2546
      drupal_set_message(t('%name has been disabled.', array('%name' => $account->name)));
2547
      watchdog('user', 'Blocked user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
2548
      break;
2549

    
2550
    case 'user_cancel_reassign':
2551
    case 'user_cancel_delete':
2552
      // Send account canceled notification if option was checked.
2553
      if (!empty($edit['user_cancel_notify'])) {
2554
        _user_mail_notify('status_canceled', $account);
2555
      }
2556
      user_delete($account->uid);
2557
      drupal_set_message(t('%name has been deleted.', array('%name' => $account->name)));
2558
      watchdog('user', 'Deleted user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
2559
      break;
2560
  }
2561

    
2562
  // After cancelling account, ensure that user is logged out. We can't destroy
2563
  // their session though, as we might have information in it, and we can't
2564
  // regenerate it because batch API uses the session ID, we will regenerate it
2565
  // in _user_cancel_session_regenerate().
2566
  if ($account->uid == $user->uid) {
2567
    $user = drupal_anonymous_user();
2568
  }
2569

    
2570
  // Clear the cache for anonymous users.
2571
  cache_clear_all();
2572
}
2573

    
2574
/**
2575
 * Implements callback_batch_finished().
2576
 *
2577
 * Finished batch processing callback for cancelling a user account.
2578
 *
2579
 * @see user_cancel()
2580
 */
2581
function _user_cancel_session_regenerate() {
2582
  // Regenerate the users session instead of calling session_destroy() as we
2583
  // want to preserve any messages that might have been set.
2584
  drupal_session_regenerate();
2585
}
2586

    
2587
/**
2588
 * Delete a user.
2589
 *
2590
 * @param $uid
2591
 *   A user ID.
2592
 */
2593
function user_delete($uid) {
2594
  user_delete_multiple(array($uid));
2595
}
2596

    
2597
/**
2598
 * Delete multiple user accounts.
2599
 *
2600
 * @param $uids
2601
 *   An array of user IDs.
2602
 */
2603
function user_delete_multiple(array $uids) {
2604
  if (!empty($uids)) {
2605
    $accounts = user_load_multiple($uids, array());
2606

    
2607
    $transaction = db_transaction();
2608
    try {
2609
      foreach ($accounts as $uid => $account) {
2610
        module_invoke_all('user_delete', $account);
2611
        module_invoke_all('entity_delete', $account, 'user');
2612
        field_attach_delete('user', $account);
2613
        drupal_session_destroy_uid($account->uid);
2614
      }
2615

    
2616
      db_delete('users')
2617
        ->condition('uid', $uids, 'IN')
2618
        ->execute();
2619
      db_delete('users_roles')
2620
        ->condition('uid', $uids, 'IN')
2621
        ->execute();
2622
      db_delete('authmap')
2623
        ->condition('uid', $uids, 'IN')
2624
        ->execute();
2625
    }
2626
    catch (Exception $e) {
2627
      $transaction->rollback();
2628
      watchdog_exception('user', $e);
2629
      throw $e;
2630
    }
2631
    entity_get_controller('user')->resetCache();
2632
  }
2633
}
2634

    
2635
/**
2636
 * Page callback wrapper for user_view().
2637
 */
2638
function user_view_page($account) {
2639
  // An administrator may try to view a non-existent account,
2640
  // so we give them a 404 (versus a 403 for non-admins).
2641
  return is_object($account) ? user_view($account) : MENU_NOT_FOUND;
2642
}
2643

    
2644
/**
2645
 * Generate an array for rendering the given user.
2646
 *
2647
 * When viewing a user profile, the $page array contains:
2648
 *
2649
 * - $page['content']['Profile Category']:
2650
 *   Profile categories keyed by their human-readable names.
2651
 * - $page['content']['Profile Category']['profile_machine_name']:
2652
 *   Profile fields keyed by their machine-readable names.
2653
 * - $page['content']['user_picture']:
2654
 *   User's rendered picture.
2655
 * - $page['content']['summary']:
2656
 *   Contains the default "History" profile data for a user.
2657
 * - $page['content']['#account']:
2658
 *   The user account of the profile being viewed.
2659
 *
2660
 * To theme user profiles, copy modules/user/user-profile.tpl.php
2661
 * to your theme directory, and edit it as instructed in that file's comments.
2662
 *
2663
 * @param $account
2664
 *   A user object.
2665
 * @param $view_mode
2666
 *   View mode, e.g. 'full'.
2667
 * @param $langcode
2668
 *   (optional) A language code to use for rendering. Defaults to the global
2669
 *   content language of the current request.
2670
 *
2671
 * @return
2672
 *   An array as expected by drupal_render().
2673
 */
2674
function user_view($account, $view_mode = 'full', $langcode = NULL) {
2675
  if (!isset($langcode)) {
2676
    $langcode = $GLOBALS['language_content']->language;
2677
  }
2678

    
2679
  // Retrieve all profile fields and attach to $account->content.
2680
  user_build_content($account, $view_mode, $langcode);
2681

    
2682
  $build = $account->content;
2683
  // We don't need duplicate rendering info in account->content.
2684
  unset($account->content);
2685

    
2686
  $build += array(
2687
    '#theme' => 'user_profile',
2688
    '#account' => $account,
2689
    '#view_mode' => $view_mode,
2690
    '#language' => $langcode,
2691
  );
2692

    
2693
  // Allow modules to modify the structured user.
2694
  $type = 'user';
2695
  drupal_alter(array('user_view', 'entity_view'), $build, $type);
2696

    
2697
  return $build;
2698
}
2699

    
2700
/**
2701
 * Builds a structured array representing the profile content.
2702
 *
2703
 * @param $account
2704
 *   A user object.
2705
 * @param $view_mode
2706
 *   View mode, e.g. 'full'.
2707
 * @param $langcode
2708
 *   (optional) A language code to use for rendering. Defaults to the global
2709
 *   content language of the current request.
2710
 */
2711
function user_build_content($account, $view_mode = 'full', $langcode = NULL) {
2712
  if (!isset($langcode)) {
2713
    $langcode = $GLOBALS['language_content']->language;
2714
  }
2715

    
2716
  // Remove previously built content, if exists.
2717
  $account->content = array();
2718

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

    
2722
  // Build fields content.
2723
  field_attach_prepare_view('user', array($account->uid => $account), $view_mode, $langcode);
2724
  entity_prepare_view('user', array($account->uid => $account), $langcode);
2725
  $account->content += field_attach_view('user', $account, $view_mode, $langcode);
2726

    
2727
  // Populate $account->content with a render() array.
2728
  module_invoke_all('user_view', $account, $view_mode, $langcode);
2729
  module_invoke_all('entity_view', $account, 'user', $view_mode, $langcode);
2730

    
2731
  // Make sure the current view mode is stored if no module has already
2732
  // populated the related key.
2733
  $account->content += array('#view_mode' => $view_mode);
2734
}
2735

    
2736
/**
2737
 * Implements hook_mail().
2738
 */
2739
function user_mail($key, &$message, $params) {
2740
  $language = $message['language'];
2741
  $variables = array('user' => $params['account']);
2742
  $message['subject'] .= _user_mail_text($key . '_subject', $language, $variables);
2743
  $message['body'][] = _user_mail_text($key . '_body', $language, $variables);
2744
}
2745

    
2746
/**
2747
 * Returns a mail string for a variable name.
2748
 *
2749
 * Used by user_mail() and the settings forms to retrieve strings.
2750
 */
2751
function _user_mail_text($key, $language = NULL, $variables = array(), $replace = TRUE) {
2752
  $langcode = isset($language) ? $language->language : NULL;
2753

    
2754
  if ($admin_setting = variable_get('user_mail_' . $key, FALSE)) {
2755
    // An admin setting overrides the default string.
2756
    $text = $admin_setting;
2757
  }
2758
  else {
2759
    // No override, return default string.
2760
    switch ($key) {
2761
      case 'register_no_approval_required_subject':
2762
        $text = t('Account details for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2763
        break;
2764
      case 'register_no_approval_required_body':
2765
        $text = t("[user:name],
2766

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

    
2769
[user:one-time-login-url]
2770

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

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

    
2775
username: [user:name]
2776
password: Your password
2777

    
2778
--  [site:name] team", array(), array('langcode' => $langcode));
2779
        break;
2780

    
2781
      case 'register_admin_created_subject':
2782
        $text = t('An administrator created an account for you at [site:name]', array(), array('langcode' => $langcode));
2783
        break;
2784
      case 'register_admin_created_body':
2785
        $text = t("[user:name],
2786

    
2787
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:
2788

    
2789
[user:one-time-login-url]
2790

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

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

    
2795
username: [user:name]
2796
password: Your password
2797

    
2798
--  [site:name] team", array(), array('langcode' => $langcode));
2799
        break;
2800

    
2801
      case 'register_pending_approval_subject':
2802
      case 'register_pending_approval_admin_subject':
2803
        $text = t('Account details for [user:name] at [site:name] (pending admin approval)', array(), array('langcode' => $langcode));
2804
        break;
2805
      case 'register_pending_approval_body':
2806
        $text = t("[user:name],
2807

    
2808
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.
2809

    
2810

    
2811
--  [site:name] team", array(), array('langcode' => $langcode));
2812
        break;
2813
      case 'register_pending_approval_admin_body':
2814
        $text = t("[user:name] has applied for an account.
2815

    
2816
[user:edit-url]", array(), array('langcode' => $langcode));
2817
        break;
2818

    
2819
      case 'password_reset_subject':
2820
        $text = t('Replacement login information for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2821
        break;
2822
      case 'password_reset_body':
2823
        $text = t("[user:name],
2824

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

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

    
2829
[user:one-time-login-url]
2830

    
2831
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.
2832

    
2833
--  [site:name] team", array(), array('langcode' => $langcode));
2834
        break;
2835

    
2836
      case 'status_activated_subject':
2837
        $text = t('Account details for [user:name] at [site:name] (approved)', array(), array('langcode' => $langcode));
2838
        break;
2839
      case 'status_activated_body':
2840
        $text = t("[user:name],
2841

    
2842
Your account at [site:name] has been activated.
2843

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

    
2846
[user:one-time-login-url]
2847

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

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

    
2852
username: [user:name]
2853
password: Your password
2854

    
2855
--  [site:name] team", array(), array('langcode' => $langcode));
2856
        break;
2857

    
2858
      case 'status_blocked_subject':
2859
        $text = t('Account details for [user:name] at [site:name] (blocked)', array(), array('langcode' => $langcode));
2860
        break;
2861
      case 'status_blocked_body':
2862
        $text = t("[user:name],
2863

    
2864
Your account on [site:name] has been blocked.
2865

    
2866
--  [site:name] team", array(), array('langcode' => $langcode));
2867
        break;
2868

    
2869
      case 'cancel_confirm_subject':
2870
        $text = t('Account cancellation request for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2871
        break;
2872
      case 'cancel_confirm_body':
2873
        $text = t("[user:name],
2874

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

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

    
2879
[user:cancel-url]
2880

    
2881
NOTE: The cancellation of your account is not reversible.
2882

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

    
2885
--  [site:name] team", array(), array('langcode' => $langcode));
2886
        break;
2887

    
2888
      case 'status_canceled_subject':
2889
        $text = t('Account details for [user:name] at [site:name] (canceled)', array(), array('langcode' => $langcode));
2890
        break;
2891
      case 'status_canceled_body':
2892
        $text = t("[user:name],
2893

    
2894
Your account on [site:name] has been canceled.
2895

    
2896
--  [site:name] team", array(), array('langcode' => $langcode));
2897
        break;
2898
    }
2899
  }
2900

    
2901
  if ($replace) {
2902
    // We do not sanitize the token replacement, since the output of this
2903
    // replacement is intended for an e-mail message, not a web browser.
2904
    return token_replace($text, $variables, array('language' => $language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
2905
  }
2906

    
2907
  return $text;
2908
}
2909

    
2910
/**
2911
 * Token callback to add unsafe tokens for user mails.
2912
 *
2913
 * This function is used by the token_replace() call at the end of
2914
 * _user_mail_text() to set up some additional tokens that can be
2915
 * used in email messages generated by user_mail().
2916
 *
2917
 * @param $replacements
2918
 *   An associative array variable containing mappings from token names to
2919
 *   values (for use with strtr()).
2920
 * @param $data
2921
 *   An associative array of token replacement values. If the 'user' element
2922
 *   exists, it must contain a user account object with the following
2923
 *   properties:
2924
 *   - login: The UNIX timestamp of the user's last login.
2925
 *   - pass: The hashed account login password.
2926
 * @param $options
2927
 *   Unused parameter required by the token_replace() function.
2928
 */
2929
function user_mail_tokens(&$replacements, $data, $options) {
2930
  if (isset($data['user'])) {
2931
    $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user']);
2932
    $replacements['[user:cancel-url]'] = user_cancel_url($data['user']);
2933
  }
2934
}
2935

    
2936
/*** Administrative features ***********************************************/
2937

    
2938
/**
2939
 * Retrieve an array of roles matching specified conditions.
2940
 *
2941
 * @param $membersonly
2942
 *   Set this to TRUE to exclude the 'anonymous' role.
2943
 * @param $permission
2944
 *   A string containing a permission. If set, only roles containing that
2945
 *   permission are returned.
2946
 *
2947
 * @return
2948
 *   An associative array with the role id as the key and the role name as
2949
 *   value.
2950
 */
2951
function user_roles($membersonly = FALSE, $permission = NULL) {
2952
  $query = db_select('role', 'r');
2953
  $query->addTag('translatable');
2954
  $query->fields('r', array('rid', 'name'));
2955
  $query->orderBy('weight');
2956
  $query->orderBy('name');
2957
  if (!empty($permission)) {
2958
    $query->innerJoin('role_permission', 'p', 'r.rid = p.rid');
2959
    $query->condition('p.permission', $permission);
2960
  }
2961
  $result = $query->execute();
2962

    
2963
  $roles = array();
2964
  foreach ($result as $role) {
2965
    switch ($role->rid) {
2966
      // We only translate the built in role names
2967
      case DRUPAL_ANONYMOUS_RID:
2968
        if (!$membersonly) {
2969
          $roles[$role->rid] = t($role->name);
2970
        }
2971
        break;
2972
      case DRUPAL_AUTHENTICATED_RID:
2973
        $roles[$role->rid] = t($role->name);
2974
        break;
2975
      default:
2976
        $roles[$role->rid] = $role->name;
2977
    }
2978
  }
2979

    
2980
  return $roles;
2981
}
2982

    
2983
/**
2984
 * Fetches a user role by role ID.
2985
 *
2986
 * @param $rid
2987
 *   An integer representing the role ID.
2988
 *
2989
 * @return
2990
 *   A fully-loaded role object if a role with the given ID exists, or FALSE
2991
 *   otherwise.
2992
 *
2993
 * @see user_role_load_by_name()
2994
 */
2995
function user_role_load($rid) {
2996
  return db_select('role', 'r')
2997
    ->fields('r')
2998
    ->condition('rid', $rid)
2999
    ->execute()
3000
    ->fetchObject();
3001
}
3002

    
3003
/**
3004
 * Fetches a user role by role name.
3005
 *
3006
 * @param $role_name
3007
 *   A string representing the role name.
3008
 *
3009
 * @return
3010
 *   A fully-loaded role object if a role with the given name exists, or FALSE
3011
 *   otherwise.
3012
 *
3013
 * @see user_role_load()
3014
 */
3015
function user_role_load_by_name($role_name) {
3016
  return db_select('role', 'r')
3017
    ->fields('r')
3018
    ->condition('name', $role_name)
3019
    ->execute()
3020
    ->fetchObject();
3021
}
3022

    
3023
/**
3024
 * Save a user role to the database.
3025
 *
3026
 * @param $role
3027
 *   A role object to modify or add. If $role->rid is not specified, a new
3028
 *   role will be created.
3029
 * @return
3030
 *   Status constant indicating if role was created or updated.
3031
 *   Failure to write the user role record will return FALSE. Otherwise.
3032
 *   SAVED_NEW or SAVED_UPDATED is returned depending on the operation
3033
 *   performed.
3034
 */
3035
function user_role_save($role) {
3036
  if ($role->name) {
3037
    // Prevent leading and trailing spaces in role names.
3038
    $role->name = trim($role->name);
3039
  }
3040
  if (!isset($role->weight)) {
3041
    // Set a role weight to make this new role last.
3042
    $query = db_select('role');
3043
    $query->addExpression('MAX(weight)');
3044
    $role->weight = $query->execute()->fetchField() + 1;
3045
  }
3046

    
3047
  // Let modules modify the user role before it is saved to the database.
3048
  module_invoke_all('user_role_presave', $role);
3049

    
3050
  if (!empty($role->rid) && $role->name) {
3051
    $status = drupal_write_record('role', $role, 'rid');
3052
    module_invoke_all('user_role_update', $role);
3053
  }
3054
  else {
3055
    $status = drupal_write_record('role', $role);
3056
    module_invoke_all('user_role_insert', $role);
3057
  }
3058

    
3059
  // Clear the user access cache.
3060
  drupal_static_reset('user_access');
3061
  drupal_static_reset('user_role_permissions');
3062

    
3063
  return $status;
3064
}
3065

    
3066
/**
3067
 * Delete a user role from database.
3068
 *
3069
 * @param $role
3070
 *   A string with the role name, or an integer with the role ID.
3071
 */
3072
function user_role_delete($role) {
3073
  if (is_int($role)) {
3074
    $role = user_role_load($role);
3075
  }
3076
  else {
3077
    $role = user_role_load_by_name($role);
3078
  }
3079

    
3080
  // If this is the administrator role, delete the user_admin_role variable.
3081
  if ($role->rid == variable_get('user_admin_role')) {
3082
    variable_del('user_admin_role');
3083
  }
3084

    
3085
  db_delete('role')
3086
    ->condition('rid', $role->rid)
3087
    ->execute();
3088
  db_delete('role_permission')
3089
    ->condition('rid', $role->rid)
3090
    ->execute();
3091
  // Update the users who have this role set:
3092
  db_delete('users_roles')
3093
    ->condition('rid', $role->rid)
3094
    ->execute();
3095

    
3096
  module_invoke_all('user_role_delete', $role);
3097

    
3098
  // Clear the user access cache.
3099
  drupal_static_reset('user_access');
3100
  drupal_static_reset('user_role_permissions');
3101
}
3102

    
3103
/**
3104
 * Menu access callback for user role editing.
3105
 */
3106
function user_role_edit_access($role) {
3107
  // Prevent the system-defined roles from being altered or removed.
3108
  if ($role->rid == DRUPAL_ANONYMOUS_RID || $role->rid == DRUPAL_AUTHENTICATED_RID) {
3109
    return FALSE;
3110
  }
3111

    
3112
  return user_access('administer permissions');
3113
}
3114

    
3115
/**
3116
 * Determine the modules that permissions belong to.
3117
 *
3118
 * @return
3119
 *   An associative array in the format $permission => $module.
3120
 */
3121
function user_permission_get_modules() {
3122
  $permissions = array();
3123
  foreach (module_implements('permission') as $module) {
3124
    $perms = module_invoke($module, 'permission');
3125
    foreach ($perms as $key => $value) {
3126
      $permissions[$key] = $module;
3127
    }
3128
  }
3129
  return $permissions;
3130
}
3131

    
3132
/**
3133
 * Change permissions for a user role.
3134
 *
3135
 * This function may be used to grant and revoke multiple permissions at once.
3136
 * For example, when a form exposes checkboxes to configure permissions for a
3137
 * role, the form submit handler may directly pass the submitted values for the
3138
 * checkboxes form element to this function.
3139
 *
3140
 * @param $rid
3141
 *   The ID of a user role to alter.
3142
 * @param $permissions
3143
 *   An associative array, where the key holds the permission name and the value
3144
 *   determines whether to grant or revoke that permission. Any value that
3145
 *   evaluates to TRUE will cause the permission to be granted. Any value that
3146
 *   evaluates to FALSE will cause the permission to be revoked.
3147
 *   @code
3148
 *     array(
3149
 *       'administer nodes' => 0,                // Revoke 'administer nodes'
3150
 *       'administer blocks' => FALSE,           // Revoke 'administer blocks'
3151
 *       'access user profiles' => 1,            // Grant 'access user profiles'
3152
 *       'access content' => TRUE,               // Grant 'access content'
3153
 *       'access comments' => 'access comments', // Grant 'access comments'
3154
 *     )
3155
 *   @endcode
3156
 *   Existing permissions are not changed, unless specified in $permissions.
3157
 *
3158
 * @see user_role_grant_permissions()
3159
 * @see user_role_revoke_permissions()
3160
 */
3161
function user_role_change_permissions($rid, array $permissions = array()) {
3162
  // Grant new permissions for the role.
3163
  $grant = array_filter($permissions);
3164
  if (!empty($grant)) {
3165
    user_role_grant_permissions($rid, array_keys($grant));
3166
  }
3167
  // Revoke permissions for the role.
3168
  $revoke = array_diff_assoc($permissions, $grant);
3169
  if (!empty($revoke)) {
3170
    user_role_revoke_permissions($rid, array_keys($revoke));
3171
  }
3172
}
3173

    
3174
/**
3175
 * Grant permissions to a user role.
3176
 *
3177
 * @param $rid
3178
 *   The ID of a user role to alter.
3179
 * @param $permissions
3180
 *   A list of permission names to grant.
3181
 *
3182
 * @see user_role_change_permissions()
3183
 * @see user_role_revoke_permissions()
3184
 */
3185
function user_role_grant_permissions($rid, array $permissions = array()) {
3186
  $modules = user_permission_get_modules();
3187
  // Grant new permissions for the role.
3188
  foreach ($permissions as $name) {
3189
    db_merge('role_permission')
3190
      ->key(array(
3191
        'rid' => $rid,
3192
        'permission' => $name,
3193
      ))
3194
      ->fields(array(
3195
        'module' => $modules[$name],
3196
      ))
3197
      ->execute();
3198
  }
3199

    
3200
  // Clear the user access cache.
3201
  drupal_static_reset('user_access');
3202
  drupal_static_reset('user_role_permissions');
3203
}
3204

    
3205
/**
3206
 * Revoke permissions from a user role.
3207
 *
3208
 * @param $rid
3209
 *   The ID of a user role to alter.
3210
 * @param $permissions
3211
 *   A list of permission names to revoke.
3212
 *
3213
 * @see user_role_change_permissions()
3214
 * @see user_role_grant_permissions()
3215
 */
3216
function user_role_revoke_permissions($rid, array $permissions = array()) {
3217
  // Revoke permissions for the role.
3218
  db_delete('role_permission')
3219
    ->condition('rid', $rid)
3220
    ->condition('permission', $permissions, 'IN')
3221
    ->execute();
3222

    
3223
  // Clear the user access cache.
3224
  drupal_static_reset('user_access');
3225
  drupal_static_reset('user_role_permissions');
3226
}
3227

    
3228
/**
3229
 * Implements hook_user_operations().
3230
 */
3231
function user_user_operations($form = array(), $form_state = array()) {
3232
  $operations = array(
3233
    'unblock' => array(
3234
      'label' => t('Unblock the selected users'),
3235
      'callback' => 'user_user_operations_unblock',
3236
    ),
3237
    'block' => array(
3238
      'label' => t('Block the selected users'),
3239
      'callback' => 'user_user_operations_block',
3240
    ),
3241
    'cancel' => array(
3242
      'label' => t('Cancel the selected user accounts'),
3243
    ),
3244
  );
3245

    
3246
  if (user_access('administer permissions')) {
3247
    $roles = user_roles(TRUE);
3248
    unset($roles[DRUPAL_AUTHENTICATED_RID]);  // Can't edit authenticated role.
3249

    
3250
    $add_roles = array();
3251
    foreach ($roles as $key => $value) {
3252
      $add_roles['add_role-' . $key] = $value;
3253
    }
3254

    
3255
    $remove_roles = array();
3256
    foreach ($roles as $key => $value) {
3257
      $remove_roles['remove_role-' . $key] = $value;
3258
    }
3259

    
3260
    if (count($roles)) {
3261
      $role_operations = array(
3262
        t('Add a role to the selected users') => array(
3263
          'label' => $add_roles,
3264
        ),
3265
        t('Remove a role from the selected users') => array(
3266
          'label' => $remove_roles,
3267
        ),
3268
      );
3269

    
3270
      $operations += $role_operations;
3271
    }
3272
  }
3273

    
3274
  // If the form has been posted, we need to insert the proper data for
3275
  // role editing if necessary.
3276
  if (!empty($form_state['submitted'])) {
3277
    $operation_rid = explode('-', $form_state['values']['operation']);
3278
    $operation = $operation_rid[0];
3279
    if ($operation == 'add_role' || $operation == 'remove_role') {
3280
      $rid = $operation_rid[1];
3281
      if (user_access('administer permissions')) {
3282
        $operations[$form_state['values']['operation']] = array(
3283
          'callback' => 'user_multiple_role_edit',
3284
          'callback arguments' => array($operation, $rid),
3285
        );
3286
      }
3287
      else {
3288
        watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING);
3289
        return;
3290
      }
3291
    }
3292
  }
3293

    
3294
  return $operations;
3295
}
3296

    
3297
/**
3298
 * Callback function for admin mass unblocking users.
3299
 */
3300
function user_user_operations_unblock($accounts) {
3301
  $accounts = user_load_multiple($accounts);
3302
  foreach ($accounts as $account) {
3303
    // Skip unblocking user if they are already unblocked.
3304
    if ($account !== FALSE && $account->status == 0) {
3305
      user_save($account, array('status' => 1));
3306
    }
3307
  }
3308
}
3309

    
3310
/**
3311
 * Callback function for admin mass blocking users.
3312
 */
3313
function user_user_operations_block($accounts) {
3314
  $accounts = user_load_multiple($accounts);
3315
  foreach ($accounts as $account) {
3316
    // Skip blocking user if they are already blocked.
3317
    if ($account !== FALSE && $account->status == 1) {
3318
      // For efficiency manually save the original account before applying any
3319
      // changes.
3320
      $account->original = clone $account;
3321
      user_save($account, array('status' => 0));
3322
    }
3323
  }
3324
}
3325

    
3326
/**
3327
 * Callback function for admin mass adding/deleting a user role.
3328
 */
3329
function user_multiple_role_edit($accounts, $operation, $rid) {
3330
  // The role name is not necessary as user_save() will reload the user
3331
  // object, but some modules' hook_user() may look at this first.
3332
  $role_name = db_query('SELECT name FROM {role} WHERE rid = :rid', array(':rid' => $rid))->fetchField();
3333

    
3334
  switch ($operation) {
3335
    case 'add_role':
3336
      $accounts = user_load_multiple($accounts);
3337
      foreach ($accounts as $account) {
3338
        // Skip adding the role to the user if they already have it.
3339
        if ($account !== FALSE && !isset($account->roles[$rid])) {
3340
          $roles = $account->roles + array($rid => $role_name);
3341
          // For efficiency manually save the original account before applying
3342
          // any changes.
3343
          $account->original = clone $account;
3344
          user_save($account, array('roles' => $roles));
3345
        }
3346
      }
3347
      break;
3348
    case 'remove_role':
3349
      $accounts = user_load_multiple($accounts);
3350
      foreach ($accounts as $account) {
3351
        // Skip removing the role from the user if they already don't have it.
3352
        if ($account !== FALSE && isset($account->roles[$rid])) {
3353
          $roles = array_diff($account->roles, array($rid => $role_name));
3354
          // For efficiency manually save the original account before applying
3355
          // any changes.
3356
          $account->original = clone $account;
3357
          user_save($account, array('roles' => $roles));
3358
        }
3359
      }
3360
      break;
3361
  }
3362
}
3363

    
3364
function user_multiple_cancel_confirm($form, &$form_state) {
3365
  $edit = $form_state['input'];
3366

    
3367
  $form['accounts'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
3368
  $accounts = user_load_multiple(array_keys(array_filter($edit['accounts'])));
3369
  foreach ($accounts as $uid => $account) {
3370
    // Prevent user 1 from being canceled.
3371
    if ($uid <= 1) {
3372
      continue;
3373
    }
3374
    $form['accounts'][$uid] = array(
3375
      '#type' => 'hidden',
3376
      '#value' => $uid,
3377
      '#prefix' => '<li>',
3378
      '#suffix' => check_plain($account->name) . "</li>\n",
3379
    );
3380
  }
3381

    
3382
  // Output a notice that user 1 cannot be canceled.
3383
  if (isset($accounts[1])) {
3384
    $redirect = (count($accounts) == 1);
3385
    $message = t('The user account %name cannot be cancelled.', array('%name' => $accounts[1]->name));
3386
    drupal_set_message($message, $redirect ? 'error' : 'warning');
3387
    // If only user 1 was selected, redirect to the overview.
3388
    if ($redirect) {
3389
      drupal_goto('admin/people');
3390
    }
3391
  }
3392

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

    
3395
  module_load_include('inc', 'user', 'user.pages');
3396
  $form['user_cancel_method'] = array(
3397
    '#type' => 'item',
3398
    '#title' => t('When cancelling these accounts'),
3399
  );
3400
  $form['user_cancel_method'] += user_cancel_methods();
3401
  // Remove method descriptions.
3402
  foreach (element_children($form['user_cancel_method']) as $element) {
3403
    unset($form['user_cancel_method'][$element]['#description']);
3404
  }
3405

    
3406
  // Allow to send the account cancellation confirmation mail.
3407
  $form['user_cancel_confirm'] = array(
3408
    '#type' => 'checkbox',
3409
    '#title' => t('Require e-mail confirmation to cancel account.'),
3410
    '#default_value' => FALSE,
3411
    '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'),
3412
  );
3413
  // Also allow to send account canceled notification mail, if enabled.
3414
  $form['user_cancel_notify'] = array(
3415
    '#type' => 'checkbox',
3416
    '#title' => t('Notify user when account is canceled.'),
3417
    '#default_value' => FALSE,
3418
    '#access' => variable_get('user_mail_status_canceled_notify', FALSE),
3419
    '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'),
3420
  );
3421

    
3422
  return confirm_form($form,
3423
                      t('Are you sure you want to cancel these user accounts?'),
3424
                      'admin/people', t('This action cannot be undone.'),
3425
                      t('Cancel accounts'), t('Cancel'));
3426
}
3427

    
3428
/**
3429
 * Submit handler for mass-account cancellation form.
3430
 *
3431
 * @see user_multiple_cancel_confirm()
3432
 * @see user_cancel_confirm_form_submit()
3433
 */
3434
function user_multiple_cancel_confirm_submit($form, &$form_state) {
3435
  global $user;
3436

    
3437
  if ($form_state['values']['confirm']) {
3438
    foreach ($form_state['values']['accounts'] as $uid => $value) {
3439
      // Prevent programmatic form submissions from cancelling user 1.
3440
      if ($uid <= 1) {
3441
        continue;
3442
      }
3443
      // Prevent user administrators from deleting themselves without confirmation.
3444
      if ($uid == $user->uid) {
3445
        $admin_form_state = $form_state;
3446
        unset($admin_form_state['values']['user_cancel_confirm']);
3447
        $admin_form_state['values']['_account'] = $user;
3448
        user_cancel_confirm_form_submit(array(), $admin_form_state);
3449
      }
3450
      else {
3451
        user_cancel($form_state['values'], $uid, $form_state['values']['user_cancel_method']);
3452
      }
3453
    }
3454
  }
3455
  $form_state['redirect'] = 'admin/people';
3456
}
3457

    
3458
/**
3459
 * Retrieve a list of all user setting/information categories and sort them by weight.
3460
 */
3461
function _user_categories() {
3462
  $categories = module_invoke_all('user_categories');
3463
  usort($categories, '_user_sort');
3464

    
3465
  return $categories;
3466
}
3467

    
3468
function _user_sort($a, $b) {
3469
  $a = (array) $a + array('weight' => 0, 'title' => '');
3470
  $b = (array) $b + array('weight' => 0, 'title' => '');
3471
  return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1));
3472
}
3473

    
3474
/**
3475
 * List user administration filters that can be applied.
3476
 */
3477
function user_filters() {
3478
  // Regular filters
3479
  $filters = array();
3480
  $roles = user_roles(TRUE);
3481
  unset($roles[DRUPAL_AUTHENTICATED_RID]); // Don't list authorized role.
3482
  if (count($roles)) {
3483
    $filters['role'] = array(
3484
      'title' => t('role'),
3485
      'field' => 'ur.rid',
3486
      'options' => array(
3487
        '[any]' => t('any'),
3488
      ) + $roles,
3489
    );
3490
  }
3491

    
3492
  $options = array();
3493
  foreach (module_implements('permission') as $module) {
3494
    $function = $module . '_permission';
3495
    if ($permissions = $function()) {
3496
      asort($permissions);
3497
      foreach ($permissions as $permission => $description) {
3498
        $options[t('@module module', array('@module' => $module))][$permission] = t($permission);
3499
      }
3500
    }
3501
  }
3502
  ksort($options);
3503
  $filters['permission'] = array(
3504
    'title' => t('permission'),
3505
    'options' => array(
3506
      '[any]' => t('any'),
3507
    ) + $options,
3508
  );
3509

    
3510
  $filters['status'] = array(
3511
    'title' => t('status'),
3512
    'field' => 'u.status',
3513
    'options' => array(
3514
      '[any]' => t('any'),
3515
      1 => t('active'),
3516
      0 => t('blocked'),
3517
    ),
3518
  );
3519
  return $filters;
3520
}
3521

    
3522
/**
3523
 * Extends a query object for user administration filters based on session.
3524
 *
3525
 * @param $query
3526
 *   Query object that should be filtered.
3527
 */
3528
function user_build_filter_query(SelectQuery $query) {
3529
  $filters = user_filters();
3530
  // Extend Query with filter conditions.
3531
  foreach (isset($_SESSION['user_overview_filter']) ? $_SESSION['user_overview_filter'] : array() as $filter) {
3532
    list($key, $value) = $filter;
3533
    // This checks to see if this permission filter is an enabled permission for
3534
    // the authenticated role. If so, then all users would be listed, and we can
3535
    // skip adding it to the filter query.
3536
    if ($key == 'permission') {
3537
      $account = new stdClass();
3538
      $account->uid = 'user_filter';
3539
      $account->roles = array(DRUPAL_AUTHENTICATED_RID => 1);
3540
      if (user_access($value, $account)) {
3541
        continue;
3542
      }
3543
      $users_roles_alias = $query->join('users_roles', 'ur', '%alias.uid = u.uid');
3544
      $permission_alias = $query->join('role_permission', 'p', $users_roles_alias . '.rid = %alias.rid');
3545
      $query->condition($permission_alias . '.permission', $value);
3546
    }
3547
    elseif ($key == 'role') {
3548
      $users_roles_alias = $query->join('users_roles', 'ur', '%alias.uid = u.uid');
3549
      $query->condition($users_roles_alias . '.rid' , $value);
3550
    }
3551
    else {
3552
      $query->condition($filters[$key]['field'], $value);
3553
    }
3554
  }
3555
}
3556

    
3557
/**
3558
 * Implements hook_comment_view().
3559
 */
3560
function user_comment_view($comment) {
3561
  if (variable_get('user_signatures', 0) && !empty($comment->signature)) {
3562
    // @todo This alters and replaces the original object value, so a
3563
    //   hypothetical process of loading, viewing, and saving will hijack the
3564
    //   stored data. Consider renaming to $comment->signature_safe or similar
3565
    //   here and elsewhere in Drupal 8.
3566
    $comment->signature = check_markup($comment->signature, $comment->signature_format, '', TRUE);
3567
  }
3568
  else {
3569
    $comment->signature = '';
3570
  }
3571
}
3572

    
3573
/**
3574
 * Returns HTML for a user signature.
3575
 *
3576
 * @param $variables
3577
 *   An associative array containing:
3578
 *   - signature: The user's signature.
3579
 *
3580
 * @ingroup themeable
3581
 */
3582
function theme_user_signature($variables) {
3583
  $signature = $variables['signature'];
3584
  $output = '';
3585

    
3586
  if ($signature) {
3587
    $output .= '<div class="clear">';
3588
    $output .= '<div>—</div>';
3589
    $output .= $signature;
3590
    $output .= '</div>';
3591
  }
3592

    
3593
  return $output;
3594
}
3595

    
3596
/**
3597
 * Get the language object preferred by the user. This user preference can
3598
 * be set on the user account editing page, and is only available if there
3599
 * are more than one languages enabled on the site. If the user did not
3600
 * choose a preferred language, or is the anonymous user, the $default
3601
 * value, or if it is not set, the site default language will be returned.
3602
 *
3603
 * @param $account
3604
 *   User account to look up language for.
3605
 * @param $default
3606
 *   Optional default language object to return if the account
3607
 *   has no valid language.
3608
 */
3609
function user_preferred_language($account, $default = NULL) {
3610
  $language_list = language_list();
3611
  if (!empty($account->language) && isset($language_list[$account->language])) {
3612
    return $language_list[$account->language];
3613
  }
3614
  else {
3615
    return $default ? $default : language_default();
3616
  }
3617
}
3618

    
3619
/**
3620
 * Conditionally create and send a notification email when a certain
3621
 * operation happens on the given user account.
3622
 *
3623
 * @see user_mail_tokens()
3624
 * @see drupal_mail()
3625
 *
3626
 * @param $op
3627
 *   The operation being performed on the account. Possible values:
3628
 *   - 'register_admin_created': Welcome message for user created by the admin.
3629
 *   - 'register_no_approval_required': Welcome message when user
3630
 *     self-registers.
3631
 *   - 'register_pending_approval': Welcome message, user pending admin
3632
 *     approval.
3633
 *   - 'password_reset': Password recovery request.
3634
 *   - 'status_activated': Account activated.
3635
 *   - 'status_blocked': Account blocked.
3636
 *   - 'cancel_confirm': Account cancellation request.
3637
 *   - 'status_canceled': Account canceled.
3638
 *
3639
 * @param $account
3640
 *   The user object of the account being notified. Must contain at
3641
 *   least the fields 'uid', 'name', and 'mail'.
3642
 * @param $language
3643
 *   Optional language to use for the notification, overriding account language.
3644
 *
3645
 * @return
3646
 *   The return value from drupal_mail_system()->mail(), if ends up being
3647
 *   called.
3648
 */
3649
function _user_mail_notify($op, $account, $language = NULL) {
3650
  // By default, we always notify except for canceled and blocked.
3651
  $default_notify = ($op != 'status_canceled' && $op != 'status_blocked');
3652
  $notify = variable_get('user_mail_' . $op . '_notify', $default_notify);
3653
  if ($notify) {
3654
    $params['account'] = $account;
3655
    $language = $language ? $language : user_preferred_language($account);
3656
    $mail = drupal_mail('user', $op, $account->mail, $language, $params);
3657
    if ($op == 'register_pending_approval') {
3658
      // If a user registered requiring admin approval, notify the admin, too.
3659
      // We use the site default language for this.
3660
      drupal_mail('user', 'register_pending_approval_admin', variable_get('site_mail', ini_get('sendmail_from')), language_default(), $params);
3661
    }
3662
  }
3663
  return empty($mail) ? NULL : $mail['result'];
3664
}
3665

    
3666
/**
3667
 * Form element process handler for client-side password validation.
3668
 *
3669
 * This #process handler is automatically invoked for 'password_confirm' form
3670
 * elements to add the JavaScript and string translations for dynamic password
3671
 * validation.
3672
 *
3673
 * @see system_element_info()
3674
 */
3675
function user_form_process_password_confirm($element) {
3676
  global $user;
3677

    
3678
  $js_settings = array(
3679
    'password' => array(
3680
      'strengthTitle' => t('Password strength:'),
3681
      'hasWeaknesses' => t('To make your password stronger:'),
3682
      'tooShort' => t('Make it at least 6 characters'),
3683
      'addLowerCase' => t('Add lowercase letters'),
3684
      'addUpperCase' => t('Add uppercase letters'),
3685
      'addNumbers' => t('Add numbers'),
3686
      'addPunctuation' => t('Add punctuation'),
3687
      'sameAsUsername' => t('Make it different from your username'),
3688
      'confirmSuccess' => t('yes'),
3689
      'confirmFailure' => t('no'),
3690
      'weak' => t('Weak'),
3691
      'fair' => t('Fair'),
3692
      'good' => t('Good'),
3693
      'strong' => t('Strong'),
3694
      'confirmTitle' => t('Passwords match:'),
3695
      'username' => (isset($user->name) ? $user->name : ''),
3696
    ),
3697
  );
3698

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

    
3702
  return $element;
3703
}
3704

    
3705
/**
3706
 * Implements hook_node_load().
3707
 */
3708
function user_node_load($nodes, $types) {
3709
  // Build an array of all uids for node authors, keyed by nid.
3710
  $uids = array();
3711
  foreach ($nodes as $nid => $node) {
3712
    $uids[$nid] = $node->uid;
3713
  }
3714

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

    
3718
  // Add these values back into the node objects.
3719
  foreach ($uids as $nid => $uid) {
3720
    $nodes[$nid]->name = $user_fields[$uid]->name;
3721
    $nodes[$nid]->picture = $user_fields[$uid]->picture;
3722
    $nodes[$nid]->data = $user_fields[$uid]->data;
3723
  }
3724
}
3725

    
3726
/**
3727
 * Implements hook_image_style_delete().
3728
 */
3729
function user_image_style_delete($style) {
3730
  // If a style is deleted, update the variables.
3731
  // Administrators choose a replacement style when deleting.
3732
  user_image_style_save($style);
3733
}
3734

    
3735
/**
3736
 * Implements hook_image_style_save().
3737
 */
3738
function user_image_style_save($style) {
3739
  // If a style is renamed, update the variables that use it.
3740
  if (isset($style['old_name']) && $style['old_name'] == variable_get('user_picture_style', '')) {
3741
    variable_set('user_picture_style', $style['name']);
3742
  }
3743
}
3744

    
3745
/**
3746
 * Implements hook_action_info().
3747
 */
3748
function user_action_info() {
3749
  return array(
3750
    'user_block_user_action' => array(
3751
      'label' => t('Block current user'),
3752
      'type' => 'user',
3753
      'configurable' => FALSE,
3754
      'triggers' => array('any'),
3755
    ),
3756
  );
3757
}
3758

    
3759
/**
3760
 * Blocks a specific user or the current user, if one is not specified.
3761
 *
3762
 * @param $entity
3763
 *   (optional) An entity object; if it is provided and it has a uid property,
3764
 *   the user with that ID is blocked.
3765
 * @param $context
3766
 *   (optional) An associative array; if no user ID is found in $entity, the
3767
 *   'uid' element of this array determines the user to block.
3768
 *
3769
 * @ingroup actions
3770
 */
3771
function user_block_user_action(&$entity, $context = array()) {
3772
  // First priority: If there is a $entity->uid, block that user.
3773
  // This is most likely a user object or the author if a node or comment.
3774
  if (isset($entity->uid)) {
3775
    $uid = $entity->uid;
3776
  }
3777
  elseif (isset($context['uid'])) {
3778
    $uid = $context['uid'];
3779
  }
3780
  // If neither of those are valid, then block the current user.
3781
  else {
3782
    $uid = $GLOBALS['user']->uid;
3783
  }
3784
  $account = user_load($uid);
3785
  $account = user_save($account, array('status' => 0));
3786
  watchdog('action', 'Blocked user %name.', array('%name' => $account->name));
3787
}
3788

    
3789
/**
3790
 * Implements hook_form_FORM_ID_alter().
3791
 *
3792
 * Add a checkbox for the 'user_register_form' instance settings on the 'Edit
3793
 * field instance' form.
3794
 */
3795
function user_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
3796
  $instance = $form['#instance'];
3797

    
3798
  if ($instance['entity_type'] == 'user' && !$form['#field']['locked']) {
3799
    $form['instance']['settings']['user_register_form'] = array(
3800
      '#type' => 'checkbox',
3801
      '#title' => t('Display on user registration form.'),
3802
      '#description' => t("This is compulsory for 'required' fields."),
3803
      // Field instances created in D7 beta releases before the setting was
3804
      // introduced might be set as 'required' and 'not shown on user_register
3805
      // form'. We make sure the checkbox comes as 'checked' for those.
3806
      '#default_value' => $instance['settings']['user_register_form'] || $instance['required'],
3807
      // Display just below the 'required' checkbox.
3808
      '#weight' => $form['instance']['required']['#weight'] + .1,
3809
      // Disabled when the 'required' checkbox is checked.
3810
      '#states' => array(
3811
        'enabled' => array('input[name="instance[required]"]' => array('checked' => FALSE)),
3812
      ),
3813
      // Checked when the 'required' checkbox is checked. This is done through
3814
      // a custom behavior, since the #states system would also synchronize on
3815
      // uncheck.
3816
      '#attached' => array(
3817
        'js' => array(drupal_get_path('module', 'user') . '/user.js'),
3818
      ),
3819
    );
3820

    
3821
    array_unshift($form['#submit'], 'user_form_field_ui_field_edit_form_submit');
3822
  }
3823
}
3824

    
3825
/**
3826
 * Additional submit handler for the 'Edit field instance' form.
3827
 *
3828
 * Make sure the 'user_register_form' setting is set for required fields.
3829
 */
3830
function user_form_field_ui_field_edit_form_submit($form, &$form_state) {
3831
  $instance = $form_state['values']['instance'];
3832

    
3833
  if (!empty($instance['required'])) {
3834
    form_set_value($form['instance']['settings']['user_register_form'], 1, $form_state);
3835
  }
3836
}
3837

    
3838
/**
3839
 * Form builder; the user registration form.
3840
 *
3841
 * @ingroup forms
3842
 * @see user_account_form()
3843
 * @see user_account_form_validate()
3844
 * @see user_register_submit()
3845
 */
3846
function user_register_form($form, &$form_state) {
3847
  global $user;
3848

    
3849
  $admin = user_access('administer users');
3850

    
3851
  // Pass access information to the submit handler. Running an access check
3852
  // inside the submit function interferes with form processing and breaks
3853
  // hook_form_alter().
3854
  $form['administer_users'] = array(
3855
    '#type' => 'value',
3856
    '#value' => $admin,
3857
  );
3858

    
3859
  // If we aren't admin but already logged on, go to the user page instead.
3860
  if (!$admin && $user->uid) {
3861
    drupal_goto('user/' . $user->uid);
3862
  }
3863

    
3864
  $form['#user'] = drupal_anonymous_user();
3865
  $form['#user_category'] = 'register';
3866

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

    
3870
  // Start with the default user account fields.
3871
  user_account_form($form, $form_state);
3872

    
3873
  // Attach field widgets, and hide the ones where the 'user_register_form'
3874
  // setting is not on.
3875
  $langcode = entity_language('user', $form['#user']);
3876
  field_attach_form('user', $form['#user'], $form, $form_state, $langcode);
3877
  foreach (field_info_instances('user', 'user') as $field_name => $instance) {
3878
    if (empty($instance['settings']['user_register_form'])) {
3879
      $form[$field_name]['#access'] = FALSE;
3880
    }
3881
  }
3882

    
3883
  if ($admin) {
3884
    // Redirect back to page which initiated the create request;
3885
    // usually admin/people/create.
3886
    $form_state['redirect'] = $_GET['q'];
3887
  }
3888

    
3889
  $form['actions'] = array('#type' => 'actions');
3890
  $form['actions']['submit'] = array(
3891
    '#type' => 'submit',
3892
    '#value' => t('Create new account'),
3893
  );
3894

    
3895
  $form['#validate'][] = 'user_register_validate';
3896
  // Add the final user registration form submit handler.
3897
  $form['#submit'][] = 'user_register_submit';
3898

    
3899
  return $form;
3900
}
3901

    
3902
/**
3903
 * Validation function for the user registration form.
3904
 */
3905
function user_register_validate($form, &$form_state) {
3906
  entity_form_field_validate('user', $form, $form_state);
3907
}
3908

    
3909
/**
3910
 * Submit handler for the user registration form.
3911
 *
3912
 * This function is shared by the installation form and the normal registration form,
3913
 * which is why it can't be in the user.pages.inc file.
3914
 *
3915
 * @see user_register_form()
3916
 */
3917
function user_register_submit($form, &$form_state) {
3918
  $admin = $form_state['values']['administer_users'];
3919

    
3920
  if (!variable_get('user_email_verification', TRUE) || $admin) {
3921
    $pass = $form_state['values']['pass'];
3922
  }
3923
  else {
3924
    $pass = user_password();
3925
  }
3926
  $notify = !empty($form_state['values']['notify']);
3927

    
3928
  // Remove unneeded values.
3929
  form_state_values_clean($form_state);
3930

    
3931
  $form_state['values']['pass'] = $pass;
3932
  $form_state['values']['init'] = $form_state['values']['mail'];
3933

    
3934
  $account = $form['#user'];
3935

    
3936
  entity_form_submit_build_entity('user', $account, $form, $form_state);
3937

    
3938
  // Populate $edit with the properties of $account, which have been edited on
3939
  // this form by taking over all values, which appear in the form values too.
3940
  $edit = array_intersect_key((array) $account, $form_state['values']);
3941
  $account = user_save($account, $edit);
3942

    
3943
  // Terminate if an error occurred during user_save().
3944
  if (!$account) {
3945
    drupal_set_message(t("Error saving user account."), 'error');
3946
    $form_state['redirect'] = '';
3947
    return;
3948
  }
3949
  $form_state['user'] = $account;
3950
  $form_state['values']['uid'] = $account->uid;
3951

    
3952
  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'));
3953

    
3954
  // Add plain text password into user account to generate mail tokens.
3955
  $account->password = $pass;
3956

    
3957
  // New administrative account without notification.
3958
  $uri = entity_uri('user', $account);
3959
  if ($admin && !$notify) {
3960
    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)));
3961
  }
3962
  // No e-mail verification required; log in user immediately.
3963
  elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) {
3964
    _user_mail_notify('register_no_approval_required', $account);
3965
    $form_state['uid'] = $account->uid;
3966
    user_login_submit(array(), $form_state);
3967
    drupal_set_message(t('Registration successful. You are now logged in.'));
3968
    $form_state['redirect'] = '';
3969
  }
3970
  // No administrator approval required.
3971
  elseif ($account->status || $notify) {
3972
    $op = $notify ? 'register_admin_created' : 'register_no_approval_required';
3973
    _user_mail_notify($op, $account);
3974
    if ($notify) {
3975
      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)));
3976
    }
3977
    else {
3978
      drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.'));
3979
      $form_state['redirect'] = '';
3980
    }
3981
  }
3982
  // Administrator approval required.
3983
  else {
3984
    _user_mail_notify('register_pending_approval', $account);
3985
    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.'));
3986
    $form_state['redirect'] = '';
3987
  }
3988
}
3989

    
3990
/**
3991
 * Implements hook_modules_installed().
3992
 */
3993
function user_modules_installed($modules) {
3994
  // Assign all available permissions to the administrator role.
3995
  $rid = variable_get('user_admin_role', 0);
3996
  if ($rid) {
3997
    $permissions = array();
3998
    foreach ($modules as $module) {
3999
      if ($module_permissions = module_invoke($module, 'permission')) {
4000
        $permissions = array_merge($permissions, array_keys($module_permissions));
4001
      }
4002
    }
4003
    if (!empty($permissions)) {
4004
      user_role_grant_permissions($rid, $permissions);
4005
    }
4006
  }
4007
}
4008

    
4009
/**
4010
 * Implements hook_modules_uninstalled().
4011
 */
4012
function user_modules_uninstalled($modules) {
4013
   db_delete('role_permission')
4014
     ->condition('module', $modules, 'IN')
4015
     ->execute();
4016
}
4017

    
4018
/**
4019
 * Helper function to rewrite the destination to avoid redirecting to login page after login.
4020
 *
4021
 * Third-party authentication modules may use this function to determine the
4022
 * proper destination after a user has been properly logged in.
4023
 */
4024
function user_login_destination() {
4025
  $destination = drupal_get_destination();
4026
  if ($destination['destination'] == 'user/login') {
4027
    $destination['destination'] = 'user';
4028
  }
4029
  return $destination;
4030
}
4031

    
4032
/**
4033
 * Saves visitor information as a cookie so it can be reused.
4034
 *
4035
 * @param $values
4036
 *   An array of key/value pairs to be saved into a cookie.
4037
 */
4038
function user_cookie_save(array $values) {
4039
  foreach ($values as $field => $value) {
4040
    // Set cookie for 365 days.
4041
    setrawcookie('Drupal.visitor.' . $field, rawurlencode($value), REQUEST_TIME + 31536000, '/');
4042
  }
4043
}
4044

    
4045
/**
4046
 * Delete a visitor information cookie.
4047
 *
4048
 * @param $cookie_name
4049
 *   A cookie name such as 'homepage'.
4050
 */
4051
function user_cookie_delete($cookie_name) {
4052
  setrawcookie('Drupal.visitor.' . $cookie_name, '', REQUEST_TIME - 3600, '/');
4053
}
4054

    
4055
/**
4056
 * Implements hook_rdf_mapping().
4057
 */
4058
function user_rdf_mapping() {
4059
  return array(
4060
    array(
4061
      'type' => 'user',
4062
      'bundle' => RDF_DEFAULT_BUNDLE,
4063
      'mapping' => array(
4064
        'rdftype' => array('sioc:UserAccount'),
4065
        'name' => array(
4066
          'predicates' => array('foaf:name'),
4067
        ),
4068
        'homepage' => array(
4069
          'predicates' => array('foaf:page'),
4070
          'type' => 'rel',
4071
        ),
4072
      ),
4073
    ),
4074
  );
4075
}
4076

    
4077
/**
4078
 * Implements hook_file_download_access().
4079
 */
4080
function user_file_download_access($field, $entity_type, $entity) {
4081
  if ($entity_type == 'user') {
4082
    return user_view_access($entity);
4083
  }
4084
}
4085

    
4086
/**
4087
 * Implements hook_system_info_alter().
4088
 *
4089
 * Drupal 7 ships with two methods to add additional fields to users: Profile
4090
 * module, a legacy module dating back from 2002, and Field API integration
4091
 * with users. While Field API support for users currently provides less end
4092
 * user features, the inefficient data storage mechanism of Profile module, as
4093
 * well as its lack of consistency with the rest of the entity / field based
4094
 * systems in Drupal 7, make this a sub-optimal solution to those who were not
4095
 * using it in previous releases of Drupal.
4096
 *
4097
 * To prevent new Drupal 7 sites from installing Profile module, and
4098
 * unwittingly ending up with two completely different and incompatible methods
4099
 * of extending users, only make the Profile module available if the profile_*
4100
 * tables are present.
4101
 *
4102
 * @todo: Remove in D8, pending upgrade path.
4103
 */
4104
function user_system_info_alter(&$info, $file, $type) {
4105
  if ($type == 'module' && $file->name == 'profile' && db_table_exists('profile_field')) {
4106
    $info['hidden'] = FALSE;
4107
  }
4108
}