Issue #1797330 by xjm, Lars Toomre, dcam, lazysoundsystem: Remove t() from test asser...
[project/drupal.git] / modules / user / user.module
1 <?php
2
3 /**
4 * @file
5 * Enables the user registration and login system.
6 */
7
8 /**
9 * Maximum length of username text field.
10 */
11 define('USERNAME_MAX_LENGTH', 60);
12
13 /**
14 * Maximum length of user e-mail text field.
15 */
16 define('EMAIL_MAX_LENGTH', 254);
17
18 /**
19 * Only administrators can create user accounts.
20 */
21 define('USER_REGISTER_ADMINISTRATORS_ONLY', 0);
22
23 /**
24 * Visitors can create their own accounts.
25 */
26 define('USER_REGISTER_VISITORS', 1);
27
28 /**
29 * Visitors can create accounts, but they don't become active without
30 * administrative approval.
31 */
32 define('USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL', 2);
33
34 /**
35 * Implement hook_help().
36 */
37 function user_help($path, $arg) {
38 global $user;
39
40 switch ($path) {
41 case 'admin/help#user':
42 $output = '';
43 $output .= '<h3>' . t('About') . '</h3>';
44 $output .= '<p>' . t('The User module allows users to register, log in, and log out. It also allows users with proper permissions to manage user roles (used to classify users) and permissions associated with those roles. For more information, see the online handbook entry for <a href="@user">User module</a>.', array('@user' => 'http://drupal.org/documentation/modules/user')) . '</p>';
45 $output .= '<h3>' . t('Uses') . '</h3>';
46 $output .= '<dl>';
47 $output .= '<dt>' . t('Creating and managing users') . '</dt>';
48 $output .= '<dd>' . t('The User module allows users with the appropriate <a href="@permissions">permissions</a> to create user accounts through the <a href="@people">People administration page</a>, where they can also assign users to one or more roles, and block or delete user accounts. If allowed, users without accounts (anonymous users) can create their own accounts on the <a href="@register">Create new account</a> page.', array('@permissions' => url('admin/people/permissions', array('fragment' => 'module-user')), '@people' => url('admin/people'), '@register' => url('user/register'))) . '</dd>';
49 $output .= '<dt>' . t('User roles and permissions') . '</dt>';
50 $output .= '<dd>' . t('<em>Roles</em> are used to group and classify users; each user can be assigned one or more roles. By default there are two roles: <em>anonymous user</em> (users that are not logged in) and <em>authenticated user</em> (users that are registered and logged in). Depending on choices you made when you installed Drupal, the installation process may have defined more roles, and you can create additional custom roles on the <a href="@roles">Roles page</a>. After creating roles, you can set permissions for each role on the <a href="@permissions_user">Permissions page</a>. Granting a permission allows users who have been assigned a particular role to perform an action on the site, such as viewing a particular type of content, editing or creating content, administering settings for a particular module, or using a particular function of the site (such as search).', array('@permissions_user' => url('admin/people/permissions'), '@roles' => url('admin/people/permissions/roles'))) . '</dd>';
51 $output .= '<dt>' . t('Account settings') . '</dt>';
52 $output .= '<dd>' . t('The <a href="@accounts">Account settings page</a> allows you to manage settings for the displayed name of the anonymous user role, personal contact forms, user registration, and account cancellation. On this page you can also manage settings for account personalization (including signatures and user pictures), and adapt the text for the e-mail messages that are sent automatically during the user registration process.', array('@accounts' => url('admin/config/people/accounts'))) . '</dd>';
53 $output .= '</dl>';
54 return $output;
55 case 'admin/people/create':
56 return '<p>' . t("This web page allows administrators to register new users. Users' e-mail addresses and usernames must be unique.") . '</p>';
57 case 'admin/people/permissions':
58 return '<p>' . t('Permissions let you control what users can do and see on your site. You can define a specific set of permissions for each role. (See the <a href="@role">Roles</a> page to create a role). Two important roles to consider are Authenticated Users and Administrators. Any permissions granted to the Authenticated Users role will be given to any user who can log into your site. You can make any role the Administrator role for the site, meaning this will be granted all new permissions automatically. You can do this on the <a href="@settings">User Settings</a> page. You should be careful to ensure that only trusted users are given this access and level of control of your site.', array('@role' => url('admin/people/permissions/roles'), '@settings' => url('admin/config/people/accounts'))) . '</p>';
59 case 'admin/people/permissions/roles':
60 $output = '<p>' . t('Roles allow you to fine tune the security and administration of Drupal. A role defines a group of users that have certain privileges as defined on the <a href="@permissions">permissions page</a>. Examples of roles include: anonymous user, authenticated user, moderator, administrator and so on. In this area you will define the names and order of the roles on your site. It is recommended to order your roles from least permissive (anonymous user) to most permissive (administrator). To delete a role choose "edit role".', array('@permissions' => url('admin/people/permissions'))) . '</p>';
61 $output .= '<p>' . t('By default, Drupal comes with two user roles:') . '</p>';
62 $output .= '<ul>';
63 $output .= '<li>' . t("Anonymous user: this role is used for users that don't have a user account or that are not authenticated.") . '</li>';
64 $output .= '<li>' . t('Authenticated user: this role is automatically granted to all logged in users.') . '</li>';
65 $output .= '</ul>';
66 return $output;
67 case 'admin/config/people/accounts/fields':
68 return '<p>' . t('This form lets administrators add, edit, and arrange fields for storing user data.') . '</p>';
69 case 'admin/config/people/accounts/display':
70 return '<p>' . t('This form lets administrators configure how fields should be displayed when rendering a user profile page.') . '</p>';
71 case 'admin/people/search':
72 return '<p>' . t('Enter a simple pattern ("*" may be used as a wildcard match) to search for a username or e-mail address. For example, one may search for "br" and Drupal might return "brian", "brad", and "brenda@example.com".') . '</p>';
73 }
74 }
75
76 /**
77 * Invokes a user hook in every module.
78 *
79 * We cannot use module_invoke() for this, because the arguments need to
80 * be passed by reference.
81 *
82 * @param $type
83 * A text string that controls which user hook to invoke. Valid choices are:
84 * - cancel: Invokes hook_user_cancel().
85 * - insert: Invokes hook_user_insert().
86 * - login: Invokes hook_user_login().
87 * - presave: Invokes hook_user_presave().
88 * - update: Invokes hook_user_update().
89 * @param $edit
90 * An associative array variable containing form values to be passed
91 * as the first parameter of the hook function.
92 * @param $account
93 * The user account object to be passed as the second parameter of the hook
94 * function.
95 * @param $category
96 * The category of user information being acted upon.
97 */
98 function user_module_invoke($type, &$edit, $account, $category = NULL) {
99 foreach (module_implements('user_' . $type) as $module) {
100 $function = $module . '_user_' . $type;
101 $function($edit, $account, $category);
102 }
103 }
104
105 /**
106 * Implements hook_theme().
107 */
108 function user_theme() {
109 return array(
110 'user_picture' => array(
111 'variables' => array('account' => NULL),
112 'template' => 'user-picture',
113 ),
114 'user_profile' => array(
115 'render element' => 'elements',
116 'template' => 'user-profile',
117 'file' => 'user.pages.inc',
118 ),
119 'user_profile_category' => array(
120 'render element' => 'element',
121 'template' => 'user-profile-category',
122 'file' => 'user.pages.inc',
123 ),
124 'user_profile_item' => array(
125 'render element' => 'element',
126 'template' => 'user-profile-item',
127 'file' => 'user.pages.inc',
128 ),
129 'user_list' => array(
130 'variables' => array('users' => NULL, 'title' => NULL),
131 ),
132 'user_admin_permissions' => array(
133 'render element' => 'form',
134 'file' => 'user.admin.inc',
135 ),
136 'user_admin_roles' => array(
137 'render element' => 'form',
138 'file' => 'user.admin.inc',
139 ),
140 'user_permission_description' => array(
141 'variables' => array('permission_item' => NULL, 'hide' => NULL),
142 'file' => 'user.admin.inc',
143 ),
144 'user_signature' => array(
145 'variables' => array('signature' => NULL),
146 ),
147 );
148 }
149
150 /**
151 * Implements hook_entity_info().
152 */
153 function user_entity_info() {
154 $return = array(
155 'user' => array(
156 'label' => t('User'),
157 'controller class' => 'UserController',
158 'base table' => 'users',
159 'uri callback' => 'user_uri',
160 'label callback' => 'format_username',
161 'fieldable' => TRUE,
162 // $user->language is only the preferred user language for the user
163 // interface textual elements. As it is not necessarily related to the
164 // language assigned to fields, we do not define it as the entity language
165 // key.
166 'entity keys' => array(
167 'id' => 'uid',
168 ),
169 'bundles' => array(
170 'user' => array(
171 'label' => t('User'),
172 'admin' => array(
173 'path' => 'admin/config/people/accounts',
174 'access arguments' => array('administer users'),
175 ),
176 ),
177 ),
178 'view modes' => array(
179 'full' => array(
180 'label' => t('User account'),
181 'custom settings' => FALSE,
182 ),
183 ),
184 ),
185 );
186 return $return;
187 }
188
189 /**
190 * Implements callback_entity_info_uri().
191 */
192 function user_uri($user) {
193 return array(
194 'path' => 'user/' . $user->uid,
195 );
196 }
197
198 /**
199 * Implements hook_field_info_alter().
200 */
201 function user_field_info_alter(&$info) {
202 // Add the 'user_register_form' instance setting to all field types.
203 foreach ($info as $field_type => &$field_type_info) {
204 $field_type_info += array('instance_settings' => array());
205 $field_type_info['instance_settings'] += array(
206 'user_register_form' => FALSE,
207 );
208 }
209 }
210
211 /**
212 * Implements hook_field_extra_fields().
213 */
214 function user_field_extra_fields() {
215 $return['user']['user'] = array(
216 'form' => array(
217 'account' => array(
218 'label' => t('User name and password'),
219 'description' => t('User module account form elements.'),
220 'weight' => -10,
221 ),
222 'timezone' => array(
223 'label' => t('Timezone'),
224 'description' => t('User module timezone form element.'),
225 'weight' => 6,
226 ),
227 ),
228 'display' => array(
229 'summary' => array(
230 'label' => t('History'),
231 'description' => t('User module history view element.'),
232 'weight' => 5,
233 ),
234 ),
235 );
236
237 return $return;
238 }
239
240 /**
241 * Fetches a user object based on an external authentication source.
242 *
243 * @param string $authname
244 * The external authentication username.
245 *
246 * @return
247 * A fully-loaded user object if the user is found or FALSE if not found.
248 */
249 function user_external_load($authname) {
250 $uid = db_query("SELECT uid FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchField();
251
252 if ($uid) {
253 return user_load($uid);
254 }
255 else {
256 return FALSE;
257 }
258 }
259
260 /**
261 * Load multiple users based on certain conditions.
262 *
263 * This function should be used whenever you need to load more than one user
264 * from the database. Users are loaded into memory and will not require
265 * database access if loaded again during the same page request.
266 *
267 * @param $uids
268 * An array of user IDs.
269 * @param $conditions
270 * (deprecated) An associative array of conditions on the {users}
271 * table, where the keys are the database fields and the values are the
272 * values those fields must have. Instead, it is preferable to use
273 * EntityFieldQuery to retrieve a list of entity IDs loadable by
274 * this function.
275 * @param $reset
276 * A boolean indicating that the internal cache should be reset. Use this if
277 * loading a user object which has been altered during the page request.
278 *
279 * @return
280 * An array of user objects, indexed by uid.
281 *
282 * @see entity_load()
283 * @see user_load()
284 * @see user_load_by_mail()
285 * @see user_load_by_name()
286 * @see EntityFieldQuery
287 *
288 * @todo Remove $conditions in Drupal 8.
289 */
290 function user_load_multiple($uids = array(), $conditions = array(), $reset = FALSE) {
291 return entity_load('user', $uids, $conditions, $reset);
292 }
293
294 /**
295 * Controller class for users.
296 *
297 * This extends the DrupalDefaultEntityController class, adding required
298 * special handling for user objects.
299 */
300 class UserController extends DrupalDefaultEntityController {
301
302 function attachLoad(&$queried_users, $revision_id = FALSE) {
303 // Build an array of user picture IDs so that these can be fetched later.
304 $picture_fids = array();
305 foreach ($queried_users as $key => $record) {
306 $picture_fids[] = $record->picture;
307 $queried_users[$key]->data = unserialize($record->data);
308 $queried_users[$key]->roles = array();
309 if ($record->uid) {
310 $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
311 }
312 else {
313 $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
314 }
315 }
316
317 // Add any additional roles from the database.
318 $result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users)));
319 foreach ($result as $record) {
320 $queried_users[$record->uid]->roles[$record->rid] = $record->name;
321 }
322
323 // Add the full file objects for user pictures if enabled.
324 if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) {
325 $pictures = file_load_multiple($picture_fids);
326 foreach ($queried_users as $account) {
327 if (!empty($account->picture) && isset($pictures[$account->picture])) {
328 $account->picture = $pictures[$account->picture];
329 }
330 else {
331 $account->picture = NULL;
332 }
333 }
334 }
335 // Call the default attachLoad() method. This will add fields and call
336 // hook_user_load().
337 parent::attachLoad($queried_users, $revision_id);
338 }
339 }
340
341 /**
342 * Loads a user object.
343 *
344 * Drupal has a global $user object, which represents the currently-logged-in
345 * user. So to avoid confusion and to avoid clobbering the global $user object,
346 * it is a good idea to assign the result of this function to a different local
347 * variable, generally $account. If you actually do want to act as the user you
348 * are loading, it is essential to call drupal_save_session(FALSE); first.
349 * See
350 * @link http://drupal.org/node/218104 Safely impersonating another user @endlink
351 * for more information.
352 *
353 * @param $uid
354 * Integer specifying the user ID to load.
355 * @param $reset
356 * TRUE to reset the internal cache and load from the database; FALSE
357 * (default) to load from the internal cache, if set.
358 *
359 * @return
360 * A fully-loaded user object upon successful user load, or FALSE if the user
361 * cannot be loaded.
362 *
363 * @see user_load_multiple()
364 */
365 function user_load($uid, $reset = FALSE) {
366 $users = user_load_multiple(array($uid), array(), $reset);
367 return reset($users);
368 }
369
370 /**
371 * Fetch a user object by email address.
372 *
373 * @param $mail
374 * String with the account's e-mail address.
375 * @return
376 * A fully-loaded $user object upon successful user load or FALSE if user
377 * cannot be loaded.
378 *
379 * @see user_load_multiple()
380 */
381 function user_load_by_mail($mail) {
382 $users = user_load_multiple(array(), array('mail' => $mail));
383 return reset($users);
384 }
385
386 /**
387 * Fetch a user object by account name.
388 *
389 * @param $name
390 * String with the account's user name.
391 * @return
392 * A fully-loaded $user object upon successful user load or FALSE if user
393 * cannot be loaded.
394 *
395 * @see user_load_multiple()
396 */
397 function user_load_by_name($name) {
398 $users = user_load_multiple(array(), array('name' => $name));
399 return reset($users);
400 }
401
402 /**
403 * Save changes to a user account or add a new user.
404 *
405 * @param $account
406 * (optional) The user object to modify or add. If you want to modify
407 * an existing user account, you will need to ensure that (a) $account
408 * is an object, and (b) you have set $account->uid to the numeric
409 * user ID of the user account you wish to modify. If you
410 * want to create a new user account, you can set $account->is_new to
411 * TRUE or omit the $account->uid field.
412 * @param $edit
413 * An array of fields and values to save. For example array('name'
414 * => 'My name'). Key / value pairs added to the $edit['data'] will be
415 * serialized and saved in the {users.data} column.
416 * @param $category
417 * (optional) The category for storing profile information in.
418 *
419 * @return
420 * A fully-loaded $user object upon successful save or FALSE if the save failed.
421 *
422 * @todo D8: Drop $edit and fix user_save() to be consistent with others.
423 */
424 function user_save($account, $edit = array(), $category = 'account') {
425 $transaction = db_transaction();
426 try {
427 if (!empty($edit['pass'])) {
428 // Allow alternate password hashing schemes.
429 require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
430 $edit['pass'] = user_hash_password(trim($edit['pass']));
431 // Abort if the hashing failed and returned FALSE.
432 if (!$edit['pass']) {
433 return FALSE;
434 }
435 }
436 else {
437 // Avoid overwriting an existing password with a blank password.
438 unset($edit['pass']);
439 }
440 if (isset($edit['mail'])) {
441 $edit['mail'] = trim($edit['mail']);
442 }
443
444 // Load the stored entity, if any.
445 if (!empty($account->uid) && !isset($account->original)) {
446 $account->original = entity_load_unchanged('user', $account->uid);
447 }
448
449 if (empty($account)) {
450 $account = new stdClass();
451 }
452 if (!isset($account->is_new)) {
453 $account->is_new = empty($account->uid);
454 }
455 // Prepopulate $edit['data'] with the current value of $account->data.
456 // Modules can add to or remove from this array in hook_user_presave().
457 if (!empty($account->data)) {
458 $edit['data'] = !empty($edit['data']) ? array_merge($account->data, $edit['data']) : $account->data;
459 }
460
461 // Invoke hook_user_presave() for all modules.
462 user_module_invoke('presave', $edit, $account, $category);
463
464 // Invoke presave operations of Field Attach API and Entity API. Those APIs
465 // require a fully-fledged and updated entity object. Therefore, we need to
466 // copy any new property values of $edit into it.
467 foreach ($edit as $key => $value) {
468 $account->$key = $value;
469 }
470 field_attach_presave('user', $account);
471 module_invoke_all('entity_presave', $account, 'user');
472
473 if (is_object($account) && !$account->is_new) {
474 // Process picture uploads.
475 if (!empty($account->picture->fid) && (!isset($account->original->picture->fid) || $account->picture->fid != $account->original->picture->fid)) {
476 $picture = $account->picture;
477 // If the picture is a temporary file move it to its final location and
478 // make it permanent.
479 if (!$picture->status) {
480 $info = image_get_info($picture->uri);
481 $picture_directory = file_default_scheme() . '://' . variable_get('user_picture_path', 'pictures');
482
483 // Prepare the pictures directory.
484 file_prepare_directory($picture_directory, FILE_CREATE_DIRECTORY);
485 $destination = file_stream_wrapper_uri_normalize($picture_directory . '/picture-' . $account->uid . '-' . REQUEST_TIME . '.' . $info['extension']);
486
487 // Move the temporary file into the final location.
488 if ($picture = file_move($picture, $destination, FILE_EXISTS_RENAME)) {
489 $picture->status = FILE_STATUS_PERMANENT;
490 $account->picture = file_save($picture);
491 file_usage_add($picture, 'user', 'user', $account->uid);
492 }
493 }
494 // Delete the previous picture if it was deleted or replaced.
495 if (!empty($account->original->picture->fid)) {
496 file_usage_delete($account->original->picture, 'user', 'user', $account->uid);
497 file_delete($account->original->picture);
498 }
499 }
500 elseif (isset($edit['picture_delete']) && $edit['picture_delete']) {
501 file_usage_delete($account->original->picture, 'user', 'user', $account->uid);
502 file_delete($account->original->picture);
503 }
504 $account->picture = empty($account->picture->fid) ? 0 : $account->picture->fid;
505
506 // Do not allow 'uid' to be changed.
507 $account->uid = $account->original->uid;
508 // Save changes to the user table.
509 $success = drupal_write_record('users', $account, 'uid');
510 if ($success === FALSE) {
511 // The query failed - better to abort the save than risk further
512 // data loss.
513 return FALSE;
514 }
515
516 // Reload user roles if provided.
517 if ($account->roles != $account->original->roles) {
518 db_delete('users_roles')
519 ->condition('uid', $account->uid)
520 ->execute();
521
522 $query = db_insert('users_roles')->fields(array('uid', 'rid'));
523 foreach (array_keys($account->roles) as $rid) {
524 if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
525 $query->values(array(
526 'uid' => $account->uid,
527 'rid' => $rid,
528 ));
529 }
530 }
531 $query->execute();
532 }
533
534 // Delete a blocked user's sessions to kick them if they are online.
535 if ($account->original->status != $account->status && $account->status == 0) {
536 drupal_session_destroy_uid($account->uid);
537 }
538
539 // If the password changed, delete all open sessions and recreate
540 // the current one.
541 if ($account->pass != $account->original->pass) {
542 drupal_session_destroy_uid($account->uid);
543 if ($account->uid == $GLOBALS['user']->uid) {
544 drupal_session_regenerate();
545 }
546 }
547
548 // Save Field data.
549 field_attach_update('user', $account);
550
551 // Send emails after we have the new user object.
552 if ($account->status != $account->original->status) {
553 // The user's status is changing; conditionally send notification email.
554 $op = $account->status == 1 ? 'status_activated' : 'status_blocked';
555 _user_mail_notify($op, $account);
556 }
557
558 // Update $edit with any interim changes to $account.
559 foreach ($account as $key => $value) {
560 if (!property_exists($account->original, $key) || $value !== $account->original->$key) {
561 $edit[$key] = $value;
562 }
563 }
564 user_module_invoke('update', $edit, $account, $category);
565 module_invoke_all('entity_update', $account, 'user');
566 }
567 else {
568 // Allow 'uid' to be set by the caller. There is no danger of writing an
569 // existing user as drupal_write_record will do an INSERT.
570 if (empty($account->uid)) {
571 $account->uid = db_next_id(db_query('SELECT MAX(uid) FROM {users}')->fetchField());
572 }
573 // Allow 'created' to be set by the caller.
574 if (!isset($account->created)) {
575 $account->created = REQUEST_TIME;
576 }
577 $success = drupal_write_record('users', $account);
578 if ($success === FALSE) {
579 // On a failed INSERT some other existing user's uid may be returned.
580 // We must abort to avoid overwriting their account.
581 return FALSE;
582 }
583
584 // Make sure $account is properly initialized.
585 $account->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
586
587 field_attach_insert('user', $account);
588 $edit = (array) $account;
589 user_module_invoke('insert', $edit, $account, $category);
590 module_invoke_all('entity_insert', $account, 'user');
591
592 // Save user roles.
593 if (count($account->roles) > 1) {
594 $query = db_insert('users_roles')->fields(array('uid', 'rid'));
595 foreach (array_keys($account->roles) as $rid) {
596 if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
597 $query->values(array(
598 'uid' => $account->uid,
599 'rid' => $rid,
600 ));
601 }
602 }
603 $query->execute();
604 }
605 }
606 // Clear internal properties.
607 unset($account->is_new);
608 unset($account->original);
609 // Clear the static loading cache.
610 entity_get_controller('user')->resetCache(array($account->uid));
611
612 return $account;
613 }
614 catch (Exception $e) {
615 $transaction->rollback();
616 watchdog_exception('user', $e);
617 throw $e;
618 }
619 }
620
621 /**
622 * Verify the syntax of the given name.
623 */
624 function user_validate_name($name) {
625 if (!$name) {
626 return t('You must enter a username.');
627 }
628 if (substr($name, 0, 1) == ' ') {
629 return t('The username cannot begin with a space.');
630 }
631 if (substr($name, -1) == ' ') {
632 return t('The username cannot end with a space.');
633 }
634 if (strpos($name, ' ') !== FALSE) {
635 return t('The username cannot contain multiple spaces in a row.');
636 }
637 if (preg_match('/[^\x{80}-\x{F7} a-z0-9@_.\'-]/i', $name)) {
638 return t('The username contains an illegal character.');
639 }
640 if (preg_match('/[\x{80}-\x{A0}' . // Non-printable ISO-8859-1 + NBSP
641 '\x{AD}' . // Soft-hyphen
642 '\x{2000}-\x{200F}' . // Various space characters
643 '\x{2028}-\x{202F}' . // Bidirectional text overrides
644 '\x{205F}-\x{206F}' . // Various text hinting characters
645 '\x{FEFF}' . // Byte order mark
646 '\x{FF01}-\x{FF60}' . // Full-width latin
647 '\x{FFF9}-\x{FFFD}' . // Replacement characters
648 '\x{0}-\x{1F}]/u', // NULL byte and control characters
649 $name)) {
650 return t('The username contains an illegal character.');
651 }
652 if (drupal_strlen($name) > USERNAME_MAX_LENGTH) {
653 return t('The username %name is too long: it must be %max characters or less.', array('%name' => $name, '%max' => USERNAME_MAX_LENGTH));
654 }
655 }
656
657 /**
658 * Validates a user's email address.
659 *
660 * Checks that a user's email address exists and follows all standard
661 * validation rules. Returns error messages when the address is invalid.
662 *
663 * @param $mail
664 * A user's email address.
665 *
666 * @return
667 * If the address is invalid, a human-readable error message is returned.
668 * If the address is valid, nothing is returned.
669 */
670 function user_validate_mail($mail) {
671 if (!$mail) {
672 return t('You must enter an e-mail address.');
673 }
674 if (!valid_email_address($mail)) {
675 return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
676 }
677 }
678
679 /**
680 * Validates an image uploaded by a user.
681 *
682 * @see user_account_form()
683 */
684 function user_validate_picture(&$form, &$form_state) {
685 // If required, validate the uploaded picture.
686 $validators = array(
687 'file_validate_is_image' => array(),
688 'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')),
689 'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),
690 );
691
692 // Save the file as a temporary file.
693 $file = file_save_upload('picture_upload', $validators);
694 if ($file === FALSE) {
695 form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures'))));
696 }
697 elseif ($file !== NULL) {
698 $form_state['values']['picture_upload'] = $file;
699 }
700 }
701
702 /**
703 * Generate a random alphanumeric password.
704 */
705 function user_password($length = 10) {
706 // This variable contains the list of allowable characters for the
707 // password. Note that the number 0 and the letter 'O' have been
708 // removed to avoid confusion between the two. The same is true
709 // of 'I', 1, and 'l'.
710 $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
711
712 // Zero-based count of characters in the allowable list:
713 $len = strlen($allowable_characters) - 1;
714
715 // Declare the password as a blank string.
716 $pass = '';
717
718 // Loop the number of times specified by $length.
719 for ($i = 0; $i < $length; $i++) {
720
721 // Each iteration, pick a random character from the
722 // allowable string and append it to the password:
723 $pass .= $allowable_characters[mt_rand(0, $len)];
724 }
725
726 return $pass;
727 }
728
729 /**
730 * Determine the permissions for one or more roles.
731 *
732 * @param $roles
733 * An array whose keys are the role IDs of interest, such as $user->roles.
734 *
735 * @return
736 * An array indexed by role ID. Each value is an array whose keys are the
737 * permission strings for the given role ID.
738 */
739 function user_role_permissions($roles = array()) {
740 $cache = &drupal_static(__FUNCTION__, array());
741
742 $role_permissions = $fetch = array();
743
744 if ($roles) {
745 foreach ($roles as $rid => $name) {
746 if (isset($cache[$rid])) {
747 $role_permissions[$rid] = $cache[$rid];
748 }
749 else {
750 // Add this rid to the list of those needing to be fetched.
751 $fetch[] = $rid;
752 // Prepare in case no permissions are returned.
753 $cache[$rid] = array();
754 }
755 }
756
757 if ($fetch) {
758 // Get from the database permissions that were not in the static variable.
759 // Only role IDs with at least one permission assigned will return rows.
760 $result = db_query("SELECT rid, permission FROM {role_permission} WHERE rid IN (:fetch)", array(':fetch' => $fetch));
761
762 foreach ($result as $row) {
763 $cache[$row->rid][$row->permission] = TRUE;
764 }
765 foreach ($fetch as $rid) {
766 // For every rid, we know we at least assigned an empty array.
767 $role_permissions[$rid] = $cache[$rid];
768 }
769 }
770 }
771
772 return $role_permissions;
773 }
774
775 /**
776 * Determine whether the user has a given privilege.
777 *
778 * @param $string
779 * The permission, such as "administer nodes", being checked for.
780 * @param $account
781 * (optional) The account to check, if not given use currently logged in user.
782 *
783 * @return
784 * Boolean TRUE if the current user has the requested permission.
785 *
786 * All permission checks in Drupal should go through this function. This
787 * way, we guarantee consistent behavior, and ensure that the superuser
788 * can perform all actions.
789 */
790 function user_access($string, $account = NULL) {
791 global $user;
792
793 if (!isset($account)) {
794 $account = $user;
795 }
796
797 // User #1 has all privileges:
798 if ($account->uid == 1) {
799 return TRUE;
800 }
801
802 // To reduce the number of SQL queries, we cache the user's permissions
803 // in a static variable.
804 // Use the advanced drupal_static() pattern, since this is called very often.
805 static $drupal_static_fast;
806 if (!isset($drupal_static_fast)) {
807 $drupal_static_fast['perm'] = &drupal_static(__FUNCTION__);
808 }
809 $perm = &$drupal_static_fast['perm'];
810 if (!isset($perm[$account->uid])) {
811 $role_permissions = user_role_permissions($account->roles);
812
813 $perms = array();
814 foreach ($role_permissions as $one_role) {
815 $perms += $one_role;
816 }
817 $perm[$account->uid] = $perms;
818 }
819
820 return isset($perm[$account->uid][$string]);
821 }
822
823 /**
824 * Checks for usernames blocked by user administration.
825 *
826 * @param $name
827 * A string containing a name of the user.
828 *
829 * @return
830 * Object with property 'name' (the user name), if the user is blocked;
831 * FALSE if the user is not blocked.
832 */
833 function user_is_blocked($name) {
834 return db_select('users')
835 ->fields('users', array('name'))
836 ->condition('name', db_like($name), 'LIKE')
837 ->condition('status', 0)
838 ->execute()->fetchObject();
839 }
840
841 /**
842 * Implements hook_permission().
843 */
844 function user_permission() {
845 return array(
846 'administer permissions' => array(
847 'title' => t('Administer permissions'),
848 'restrict access' => TRUE,
849 ),
850 'administer users' => array(
851 'title' => t('Administer users'),
852 'restrict access' => TRUE,
853 ),
854 'access user profiles' => array(
855 'title' => t('View user profiles'),
856 ),
857 'change own username' => array(
858 'title' => t('Change own username'),
859 ),
860 'cancel account' => array(
861 'title' => t('Cancel own user account'),
862 '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'))),
863 ),
864 'select account cancellation method' => array(
865 'title' => t('Select method for cancelling own account'),
866 'restrict access' => TRUE,
867 ),
868 );
869 }
870
871 /**
872 * Implements hook_file_download().
873 *
874 * Ensure that user pictures (avatars) are always downloadable.
875 */
876 function user_file_download($uri) {
877 if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
878 $info = image_get_info($uri);
879 return array('Content-Type' => $info['mime_type']);
880 }
881 }
882
883 /**
884 * Implements hook_file_move().
885 */
886 function user_file_move($file, $source) {
887 // If a user's picture is replaced with a new one, update the record in
888 // the users table.
889 if (isset($file->fid) && isset($source->fid) && $file->fid != $source->fid) {
890 db_update('users')
891 ->fields(array(
892 'picture' => $file->fid,
893 ))
894 ->condition('picture', $source->fid)
895 ->execute();
896 }
897 }
898
899 /**
900 * Implements hook_file_delete().
901 */
902 function user_file_delete($file) {
903 // Remove any references to the file.
904 db_update('users')
905 ->fields(array('picture' => 0))
906 ->condition('picture', $file->fid)
907 ->execute();
908 }
909
910 /**
911 * Implements hook_search_info().
912 */
913 function user_search_info() {
914 return array(
915 'title' => 'Users',
916 );
917 }
918
919 /**
920 * Implements hook_search_access().
921 */
922 function user_search_access() {
923 return user_access('access user profiles');
924 }
925
926 /**
927 * Implements hook_search_execute().
928 */
929 function user_search_execute($keys = NULL, $conditions = NULL) {
930 $find = array();
931 // Replace wildcards with MySQL/PostgreSQL wildcards.
932 $keys = preg_replace('!\*+!', '%', $keys);
933 $query = db_select('users')->extend('PagerDefault');
934 $query->fields('users', array('uid'));
935 if (user_access('administer users')) {
936 // Administrators can also search in the otherwise private email field,
937 // and they don't need to be restricted to only active users.
938 $query->fields('users', array('mail'));
939 $query->condition(db_or()->
940 condition('name', '%' . db_like($keys) . '%', 'LIKE')->
941 condition('mail', '%' . db_like($keys) . '%', 'LIKE'));
942 }
943 else {
944 // Regular users can only search via usernames, and we do not show them
945 // blocked accounts.
946 $query->condition('name', '%' . db_like($keys) . '%', 'LIKE')
947 ->condition('status', 1);
948 }
949 $uids = $query
950 ->limit(15)
951 ->execute()
952 ->fetchCol();
953 $accounts = user_load_multiple($uids);
954
955 $results = array();
956 foreach ($accounts as $account) {
957 $result = array(
958 'title' => format_username($account),
959 'link' => url('user/' . $account->uid, array('absolute' => TRUE)),
960 );
961 if (user_access('administer users')) {
962 $result['title'] .= ' (' . $account->mail . ')';
963 }
964 $results[] = $result;
965 }
966
967 return $results;
968 }
969
970 /**
971 * Implements hook_element_info().
972 */
973 function user_element_info() {
974 $types['user_profile_category'] = array(
975 '#theme_wrappers' => array('user_profile_category'),
976 );
977 $types['user_profile_item'] = array(
978 '#theme' => 'user_profile_item',
979 );
980 return $types;
981 }
982
983 /**
984 * Implements hook_user_view().
985 */
986 function user_user_view($account) {
987 $account->content['user_picture'] = array(
988 '#markup' => theme('user_picture', array('account' => $account)),
989 '#weight' => -10,
990 );
991 if (!isset($account->content['summary'])) {
992 $account->content['summary'] = array();
993 }
994 $account->content['summary'] += array(
995 '#type' => 'user_profile_category',
996 '#attributes' => array('class' => array('user-member')),
997 '#weight' => 5,
998 '#title' => t('History'),
999 );
1000 $account->content['summary']['member_for'] = array(
1001 '#type' => 'user_profile_item',
1002 '#title' => t('Member for'),
1003 '#markup' => format_interval(REQUEST_TIME - $account->created),
1004 );
1005 }
1006
1007 /**
1008 * Helper function to add default user account fields to user registration and edit form.
1009 *
1010 * @see user_account_form_validate()
1011 * @see user_validate_current_pass()
1012 * @see user_validate_picture()
1013 * @see user_validate_mail()
1014 */
1015 function user_account_form(&$form, &$form_state) {
1016 global $user;
1017
1018 $account = $form['#user'];
1019 $register = ($form['#user']->uid > 0 ? FALSE : TRUE);
1020
1021 $admin = user_access('administer users');
1022
1023 $form['#validate'][] = 'user_account_form_validate';
1024
1025 // Account information.
1026 $form['account'] = array(
1027 '#type' => 'container',
1028 '#weight' => -10,
1029 );
1030 // Only show name field on registration form or user can change own username.
1031 $form['account']['name'] = array(
1032 '#type' => 'textfield',
1033 '#title' => t('Username'),
1034 '#maxlength' => USERNAME_MAX_LENGTH,
1035 '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'),
1036 '#required' => TRUE,
1037 '#attributes' => array('class' => array('username')),
1038 '#default_value' => (!$register ? $account->name : ''),
1039 '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin),
1040 '#weight' => -10,
1041 );
1042
1043 $form['account']['mail'] = array(
1044 '#type' => 'textfield',
1045 '#title' => t('E-mail address'),
1046 '#maxlength' => EMAIL_MAX_LENGTH,
1047 '#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.'),
1048 '#required' => TRUE,
1049 '#default_value' => (!$register ? $account->mail : ''),
1050 );
1051
1052 // Display password field only for existing users or when user is allowed to
1053 // assign a password during registration.
1054 if (!$register) {
1055 $form['account']['pass'] = array(
1056 '#type' => 'password_confirm',
1057 '#size' => 25,
1058 '#description' => t('To change the current user password, enter the new password in both fields.'),
1059 );
1060 // To skip the current password field, the user must have logged in via a
1061 // one-time link and have the token in the URL.
1062 $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]);
1063 $protected_values = array();
1064 $current_pass_description = '';
1065 // The user may only change their own password without their current
1066 // password if they logged in via a one-time login link.
1067 if (!$pass_reset) {
1068 $protected_values['mail'] = $form['account']['mail']['#title'];
1069 $protected_values['pass'] = t('Password');
1070 $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
1071 $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));
1072 }
1073 // The user must enter their current password to change to a new one.
1074 if ($user->uid == $account->uid) {
1075 $form['account']['current_pass_required_values'] = array(
1076 '#type' => 'value',
1077 '#value' => $protected_values,
1078 );
1079 $form['account']['current_pass'] = array(
1080 '#type' => 'password',
1081 '#title' => t('Current password'),
1082 '#size' => 25,
1083 '#access' => !empty($protected_values),
1084 '#description' => $current_pass_description,
1085 '#weight' => -5,
1086 // Do not let web browsers remember this password, since we are trying
1087 // to confirm that the person submitting the form actually knows the
1088 // current one.
1089 '#attributes' => array('autocomplete' => 'off'),
1090 );
1091 $form['#validate'][] = 'user_validate_current_pass';
1092 }
1093 }
1094 elseif (!variable_get('user_email_verification', TRUE) || $admin) {
1095 $form['account']['pass'] = array(
1096 '#type' => 'password_confirm',
1097 '#size' => 25,
1098 '#description' => t('Provide a password for the new account in both fields.'),
1099 '#required' => TRUE,
1100 );
1101 }
1102
1103 if ($admin) {
1104 $status = isset($account->status) ? $account->status : 1;
1105 }
1106 else {
1107 $status = $register ? variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS : $account->status;
1108 }
1109 $form['account']['status'] = array(
1110 '#type' => 'radios',
1111 '#title' => t('Status'),
1112 '#default_value' => $status,
1113 '#options' => array(t('Blocked'), t('Active')),
1114 '#access' => $admin,
1115 );
1116
1117 $roles = array_map('check_plain', user_roles(TRUE));
1118 // The disabled checkbox subelement for the 'authenticated user' role
1119 // must be generated separately and added to the checkboxes element,
1120 // because of a limitation in Form API not supporting a single disabled
1121 // checkbox within a set of checkboxes.
1122 // @todo This should be solved more elegantly. See issue #119038.
1123 $checkbox_authenticated = array(
1124 '#type' => 'checkbox',
1125 '#title' => $roles[DRUPAL_AUTHENTICATED_RID],
1126 '#default_value' => TRUE,
1127 '#disabled' => TRUE,
1128 );
1129 unset($roles[DRUPAL_AUTHENTICATED_RID]);
1130 $form['account']['roles'] = array(
1131 '#type' => 'checkboxes',
1132 '#title' => t('Roles'),
1133 '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()),
1134 '#options' => $roles,
1135 '#access' => $roles && user_access('administer permissions'),
1136 DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
1137 );
1138
1139 $form['account']['notify'] = array(
1140 '#type' => 'checkbox',
1141 '#title' => t('Notify user of new account'),
1142 '#access' => $register && $admin,
1143 );
1144
1145 // Signature.
1146 $form['signature_settings'] = array(
1147 '#type' => 'fieldset',
1148 '#title' => t('Signature settings'),
1149 '#weight' => 1,
1150 '#access' => (!$register && variable_get('user_signatures', 0)),
1151 );
1152
1153 $form['signature_settings']['signature'] = array(
1154 '#type' => 'text_format',
1155 '#title' => t('Signature'),
1156 '#default_value' => isset($account->signature) ? $account->signature : '',
1157 '#description' => t('Your signature will be publicly displayed at the end of your comments.'),
1158 '#format' => isset($account->signature_format) ? $account->signature_format : NULL,
1159 );
1160
1161 // Picture/avatar.
1162 $form['picture'] = array(
1163 '#type' => 'fieldset',
1164 '#title' => t('Picture'),
1165 '#weight' => 1,
1166 '#access' => (!$register && variable_get('user_pictures', 0)),
1167 );
1168 $form['picture']['picture'] = array(
1169 '#type' => 'value',
1170 '#value' => isset($account->picture) ? $account->picture : NULL,
1171 );
1172 $form['picture']['picture_current'] = array(
1173 '#markup' => theme('user_picture', array('account' => $account)),
1174 );
1175 $form['picture']['picture_delete'] = array(
1176 '#type' => 'checkbox',
1177 '#title' => t('Delete picture'),
1178 '#access' => !empty($account->picture->fid),
1179 '#description' => t('Check this box to delete your current picture.'),
1180 );
1181 $form['picture']['picture_upload'] = array(
1182 '#type' => 'file',
1183 '#title' => t('Upload picture'),
1184 '#size' => 48,
1185 '#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', '')),
1186 );
1187 $form['#validate'][] = 'user_validate_picture';
1188 }
1189
1190 /**
1191 * Form validation handler for the current password on the user_account_form().
1192 *
1193 * @see user_account_form()
1194 */
1195 function user_validate_current_pass(&$form, &$form_state) {
1196 $account = $form['#user'];
1197 foreach ($form_state['values']['current_pass_required_values'] as $key => $name) {
1198 // This validation only works for required textfields (like mail) or
1199 // form values like password_confirm that have their own validation
1200 // that prevent them from being empty if they are changed.
1201 if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) {
1202 require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
1203 $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account);
1204 if ($current_pass_failed) {
1205 form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name)));
1206 form_set_error($key);
1207 }
1208 // We only need to check the password once.
1209 break;
1210 }
1211 }
1212 }
1213
1214 /**
1215 * Form validation handler for user_account_form().
1216 *
1217 * @see user_account_form()
1218 */
1219 function user_account_form_validate($form, &$form_state) {
1220 if ($form['#user_category'] == 'account' || $form['#user_category'] == 'register') {
1221 $account = $form['#user'];
1222 // Validate new or changing username.
1223 if (isset($form_state['values']['name'])) {
1224 if ($error = user_validate_name($form_state['values']['name'])) {
1225 form_set_error('name', $error);
1226 }
1227 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()) {
1228 form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name'])));
1229 }
1230 }
1231
1232 // Trim whitespace from mail, to prevent confusing 'e-mail not valid'
1233 // warnings often caused by cutting and pasting.
1234 $mail = trim($form_state['values']['mail']);
1235 form_set_value($form['account']['mail'], $mail, $form_state);
1236
1237 // Validate the e-mail address, and check if it is taken by an existing user.
1238 if ($error = user_validate_mail($form_state['values']['mail'])) {
1239 form_set_error('mail', $error);
1240 }
1241 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()) {
1242 // Format error message dependent on whether the user is logged in or not.
1243 if ($GLOBALS['user']->uid) {
1244 form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $form_state['values']['mail'])));
1245 }
1246 else {
1247 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'))));
1248 }
1249 }
1250
1251 // Make sure the signature isn't longer than the size of the database field.
1252 // Signatures are disabled by default, so make sure it exists first.
1253 if (isset($form_state['values']['signature'])) {
1254 // Move text format for user signature into 'signature_format'.
1255 $form_state['values']['signature_format'] = $form_state['values']['signature']['format'];
1256 // Move text value for user signature into 'signature'.
1257 $form_state['values']['signature'] = $form_state['values']['signature']['value'];
1258
1259 $user_schema = drupal_get_schema('users');
1260 if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) {
1261 form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length'])));
1262 }
1263 }
1264 }
1265 }
1266
1267 /**
1268 * Implements hook_user_presave().
1269 */
1270 function user_user_presave(&$edit, $account, $category) {
1271 if ($category == 'account' || $category == 'register') {
1272 if (!empty($edit['picture_upload'])) {
1273 $edit['picture'] = $edit['picture_upload'];
1274 }
1275 // Delete picture if requested, and if no replacement picture was given.
1276 elseif (!empty($edit['picture_delete'])) {
1277 $edit['picture'] = NULL;
1278 }
1279 // Prepare user roles.
1280 if (isset($edit['roles'])) {
1281 $edit['roles'] = array_filter($edit['roles']);
1282 }
1283 }
1284
1285 // Move account cancellation information into $user->data.
1286 foreach (array('user_cancel_method', 'user_cancel_notify') as $key) {
1287 if (isset($edit[$key])) {
1288 $edit['data'][$key] = $edit[$key];
1289 }
1290 }
1291 }
1292
1293 /**
1294 * Implements hook_user_categories().
1295 */
1296 function user_user_categories() {
1297 return array(array(
1298 'name' => 'account',
1299 'title' => t('Account settings'),
1300 'weight' => 1,
1301 ));
1302 }
1303
1304 function user_login_block($form) {
1305 $form['#action'] = url(current_path(), array('query' => drupal_get_destination(), 'external' => FALSE));
1306 $form['#id'] = 'user-login-form';
1307 $form['#validate'] = user_login_default_validators();
1308 $form['#submit'][] = 'user_login_submit';
1309 $form['name'] = array('#type' => 'textfield',
1310 '#title' => t('Username'),
1311 '#maxlength' => USERNAME_MAX_LENGTH,
1312 '#size' => 15,
1313 '#required' => TRUE,
1314 );
1315 $form['pass'] = array('#type' => 'password',
1316 '#title' => t('Password'),
1317 '#size' => 15,
1318 '#required' => TRUE,
1319 );
1320 $form['actions'] = array('#type' => 'actions');
1321 $form['actions']['submit'] = array('#type' => 'submit',
1322 '#value' => t('Log in'),
1323 );
1324 $items = array();
1325 if (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)) {
1326 $items[] = l(t('Create new account'), 'user/register', array('attributes' => array('title' => t('Create a new user account.'))));
1327 }
1328 $items[] = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
1329 $form['links'] = array('#markup' => theme('item_list', array('items' => $items)));
1330 return $form;
1331 }
1332
1333 /**
1334 * Implements hook_block_info().
1335 */
1336 function user_block_info() {
1337 global $user;
1338
1339 $blocks['login']['info'] = t('User login');
1340 // Not worth caching.
1341 $blocks['login']['cache'] = DRUPAL_NO_CACHE;
1342
1343 $blocks['new']['info'] = t('Who\'s new');
1344 $blocks['new']['properties']['administrative'] = TRUE;
1345
1346 // Too dynamic to cache.
1347 $blocks['online']['info'] = t('Who\'s online');
1348 $blocks['online']['cache'] = DRUPAL_NO_CACHE;
1349 $blocks['online']['properties']['administrative'] = TRUE;
1350
1351 return $blocks;
1352 }
1353
1354 /**
1355 * Implements hook_block_configure().
1356 */
1357 function user_block_configure($delta = '') {
1358 global $user;
1359
1360 switch ($delta) {
1361 case 'new':
1362 $form['user_block_whois_new_count'] = array(
1363 '#type' => 'select',
1364 '#title' => t('Number of users to display'),
1365 '#default_value' => variable_get('user_block_whois_new_count', 5),
1366 '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
1367 );
1368 return $form;
1369
1370 case 'online':
1371 $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
1372 $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.'));
1373 $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.'));
1374 return $form;
1375 }
1376 }
1377
1378 /**
1379 * Implements hook_block_save().
1380 */
1381 function user_block_save($delta = '', $edit = array()) {
1382 global $user;
1383
1384 switch ($delta) {
1385 case 'new':
1386 variable_set('user_block_whois_new_count', $edit['user_block_whois_new_count']);
1387 break;
1388
1389 case 'online':
1390 variable_set('user_block_seconds_online', $edit['user_block_seconds_online']);
1391 variable_set('user_block_max_list_count', $edit['user_block_max_list_count']);
1392 break;
1393 }
1394 }
1395
1396 /**
1397 * Implements hook_block_view().
1398 */
1399 function user_block_view($delta = '') {
1400 global $user;
1401
1402 $block = array();
1403
1404 switch ($delta) {
1405 case 'login':
1406 // For usability's sake, avoid showing two login forms on one page.
1407 if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
1408
1409 $block['subject'] = t('User login');
1410 $block['content'] = drupal_get_form('user_login_block');
1411 }
1412 return $block;
1413
1414 case 'new':
1415 if (user_access('access content')) {
1416 // Retrieve a list of new users who have subsequently accessed the site successfully.
1417 $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();
1418 $output = theme('user_list', array('users' => $items));
1419
1420 $block['subject'] = t('Who\'s new');
1421 $block['content'] = $output;
1422 }
1423 return $block;
1424
1425 case 'online':
1426 if (user_access('access content')) {
1427 // Count users active within the defined period.
1428 $interval = REQUEST_TIME - variable_get('user_block_seconds_online', 900);
1429
1430 // Perform database queries to gather online user lists. We use s.timestamp
1431 // rather than u.access because it is much faster.
1432 $authenticated_count = db_query("SELECT COUNT(DISTINCT s.uid) FROM {sessions} s WHERE s.timestamp >= :timestamp AND s.uid > 0", array(':timestamp' => $interval))->fetchField();
1433
1434 $output = '<p>' . format_plural($authenticated_count, 'There is currently 1 user online.', 'There are currently @count users online.') . '</p>';
1435
1436 // Display a list of currently online users.
1437 $max_users = variable_get('user_block_max_list_count', 10);
1438 if ($authenticated_count && $max_users) {
1439 $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();
1440 $output .= theme('user_list', array('users' => $items));
1441 }
1442
1443 $block['subject'] = t('Who\'s online');
1444 $block['content'] = $output;
1445 }
1446 return $block;
1447 }
1448 }
1449
1450 /**
1451 * Process variables for user-picture.tpl.php.
1452 *
1453 * The $variables array contains the following arguments:
1454 * - $account: A user, node or comment object with 'name', 'uid' and 'picture'
1455 * fields.
1456 *
1457 * @see user-picture.tpl.php
1458 */
1459 function template_preprocess_user_picture(&$variables) {
1460 $variables['user_picture'] = '';
1461 if (variable_get('user_pictures', 0)) {
1462 $account = $variables['account'];
1463 if (!empty($account->picture)) {
1464 // @TODO: Ideally this function would only be passed file objects, but
1465 // since there's a lot of legacy code that JOINs the {users} table to
1466 // {node} or {comments} and passes the results into this function if we
1467 // a numeric value in the picture field we'll assume it's a file id
1468 // and load it for them. Once we've got user_load_multiple() and
1469 // comment_load_multiple() functions the user module will be able to load
1470 // the picture files in mass during the object's load process.
1471 if (is_numeric($account->picture)) {
1472 $account->picture = file_load($account->picture);
1473 }
1474 if (!empty($account->picture->uri)) {
1475 $filepath = $account->picture->uri;
1476 }
1477 }
1478 elseif (variable_get('user_picture_default', '')) {
1479 $filepath = variable_get('user_picture_default', '');
1480 }
1481 if (isset($filepath)) {
1482 $alt = t("@user's picture", array('@user' => format_username($account)));
1483 // If the image does not have a valid Drupal scheme (for eg. HTTP),
1484 // don't load image styles.
1485 if (module_exists('image') && file_valid_uri($filepath) && $style = variable_get('user_picture_style', '')) {
1486 $variables['user_picture'] = theme('image_style', array('style_name' => $style, 'path' => $filepath, 'alt' => $alt, 'title' => $alt));
1487 }
1488 else {
1489 $variables['user_picture'] = theme('image', array('path' => $filepath, 'alt' => $alt, 'title' => $alt));
1490 }
1491 if (!empty($account->uid) && user_access('access user profiles')) {
1492 $attributes = array('attributes' => array('title' => t('View user profile.')), 'html' => TRUE);
1493 $variables['user_picture'] = l($variables['user_picture'], "user/$account->uid", $attributes);
1494 }
1495 }
1496 }
1497 }
1498
1499 /**
1500 * Returns HTML for a list of users.
1501 *
1502 * @param $variables
1503 * An associative array containing:
1504 * - users: An array with user objects. Should contain at least the name and
1505 * uid.
1506 * - title: (optional) Title to pass on to theme_item_list().
1507 *
1508 * @ingroup themeable
1509 */
1510 function theme_user_list($variables) {
1511 $users = $variables['users'];
1512 $title = $variables['title'];
1513 $items = array();
1514
1515 if (!empty($users)) {
1516 foreach ($users as $user) {
1517 $items[] = theme('username', array('account' => $user));
1518 }
1519 }
1520 return theme('item_list', array('items' => $items, 'title' => $title));
1521 }
1522
1523 /**
1524 * Determines if the current user is anonymous.
1525 *
1526 * @return bool
1527 * TRUE if the user is anonymous, FALSE if the user is authenticated.
1528 */
1529 function user_is_anonymous() {
1530 // Menu administrators can see items for anonymous when administering.
1531 return !$GLOBALS['user']->uid || !empty($GLOBALS['menu_admin']);
1532 }
1533
1534 /**
1535 * Determines if the current user is logged in.
1536 *
1537 * @return bool
1538 * TRUE if the user is logged in, FALSE if the user is anonymous.
1539 */
1540 function user_is_logged_in() {
1541 return (bool) $GLOBALS['user']->uid;
1542 }
1543
1544 /**
1545 * Determines if the current user has access to the user registration page.
1546 *
1547 * @return bool
1548 * TRUE if the user is not already logged in and can register for an account.
1549 */
1550 function user_register_access() {
1551 return user_is_anonymous() && variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
1552 }
1553
1554 /**
1555 * User view access callback.
1556 *
1557 * @param $account
1558 * Can either be a full user object or a $uid.
1559 */
1560 function user_view_access($account) {
1561 $uid = is_object($account) ? $account->uid : (int) $account;
1562
1563 // Never allow access to view the anonymous user account.
1564 if ($uid) {
1565 // Admins can view all, users can view own profiles at all times.
1566 if ($GLOBALS['user']->uid == $uid || user_access('administer users')) {
1567 return TRUE;
1568 }
1569 elseif (user_access('access user profiles')) {
1570 // At this point, load the complete account object.
1571 if (!is_object($account)) {
1572 $account = user_load($uid);
1573 }
1574 return (is_object($account) && $account->status);
1575 }
1576 }
1577 return FALSE;
1578 }
1579
1580 /**
1581 * Access callback for user account editing.
1582 */
1583 function user_edit_access($account) {
1584 return (($GLOBALS['user']->uid == $account->uid) || user_access('administer users')) && $account->uid > 0;
1585 }
1586
1587 /**
1588 * Menu access callback; limit access to account cancellation pages.
1589 *
1590 * Limit access to users with the 'cancel account' permission or administrative
1591 * users, and prevent the anonymous user from cancelling the account.
1592 */
1593 function user_cancel_access($account) {
1594 return ((($GLOBALS['user']->uid == $account->uid) && user_access('cancel account')) || user_access('administer users')) && $account->uid > 0;
1595 }
1596
1597 /**
1598 * Implements hook_menu().
1599 */
1600 function user_menu() {
1601 $items['user/autocomplete'] = array(
1602 'title' => 'User autocomplete',
1603 'page callback' => 'user_autocomplete',
1604 'access callback' => 'user_access',
1605 'access arguments' => array('access user profiles'),
1606 'type' => MENU_CALLBACK,
1607 'file' => 'user.pages.inc',
1608 );
1609
1610 // Registration and login pages.
1611 $items['user'] = array(
1612 'title' => 'User account',
1613 'title callback' => 'user_menu_title',
1614 'page callback' => 'user_page',
1615 'access callback' => TRUE,
1616 'file' => 'user.pages.inc',
1617 'weight' => -10,
1618 'menu_name' => 'user-menu',
1619 );
1620
1621 $items['user/login'] = array(
1622 'title' => 'Log in',
1623 'access callback' => 'user_is_anonymous',
1624 'type' => MENU_DEFAULT_LOCAL_TASK,
1625 );
1626
1627 $items['user/register'] = array(
1628 'title' => 'Create new account',
1629 'page callback' => 'drupal_get_form',
1630 'page arguments' => array('user_register_form'),
1631 'access callback' => 'user_register_access',
1632 'type' => MENU_LOCAL_TASK,
1633 );
1634
1635 $items['user/password'] = array(
1636 'title' => 'Request new password',
1637 'page callback' => 'drupal_get_form',
1638 'page arguments' => array('user_pass'),
1639 'access callback' => TRUE,
1640 'type' => MENU_LOCAL_TASK,
1641 'file' => 'user.pages.inc',
1642 );
1643 $items['user/reset/%/%/%'] = array(
1644 'title' => 'Reset password',
1645 'page callback' => 'drupal_get_form',
1646 'page arguments' => array('user_pass_reset', 2, 3, 4),
1647 'access callback' => TRUE,
1648 'type' => MENU_CALLBACK,
1649 'file' => 'user.pages.inc',
1650 );
1651
1652 $items['user/logout'] = array(
1653 'title' => 'Log out',
1654 'access callback' => 'user_is_logged_in',
1655 'page callback' => 'user_logout',
1656 'weight' => 10,
1657 'menu_name' => 'user-menu',
1658 'file' => 'user.pages.inc',
1659 );
1660
1661 // User listing pages.
1662 $items['admin/people'] = array(
1663 'title' => 'People',
1664 'description' => 'Manage user accounts, roles, and permissions.',
1665 'page callback' => 'user_admin',
1666 'page arguments' => array('list'),
1667 'access arguments' => array('administer users'),
1668 'position' => 'left',
1669 'weight' => -4,
1670 'file' => 'user.admin.inc',
1671 );
1672 $items['admin/people/people'] = array(
1673 'title' => 'List',
1674 'description' => 'Find and manage people interacting with your site.',
1675 'access arguments' => array('administer users'),
1676 'type' => MENU_DEFAULT_LOCAL_TASK,
1677 'weight' => -10,
1678 'file' => 'user.admin.inc',
1679 );
1680
1681 // Permissions and role forms.
1682 $items['admin/people/permissions'] = array(
1683 'title' => 'Permissions',
1684 'description' => 'Determine access to features by selecting permissions for roles.',
1685 'page callback' => 'drupal_get_form',
1686 'page arguments' => array('user_admin_permissions'),
1687 'access arguments' => array('administer permissions'),
1688 'file' => 'user.admin.inc',
1689 'type' => MENU_LOCAL_TASK,
1690 );
1691 $items['admin/people/permissions/list'] = array(
1692 'title' => 'Permissions',
1693 'description' => 'Determine access to features by selecting permissions for roles.',
1694 'type' => MENU_DEFAULT_LOCAL_TASK,
1695 'weight' => -8,
1696 );
1697 $items['admin/people/permissions/roles'] = array(
1698 'title' => 'Roles',
1699 'description' => 'List, edit, or add user roles.',
1700 'page callback' => 'drupal_get_form',
1701 'page arguments' => array('user_admin_roles'),
1702 'access arguments' => array('administer permissions'),
1703 'file' => 'user.admin.inc',
1704 'type' => MENU_LOCAL_TASK,
1705 'weight' => -5,
1706 );
1707 $items['admin/people/permissions/roles/edit/%user_role'] = array(
1708 'title' => 'Edit role',
1709 'page arguments' => array('user_admin_role', 5),
1710 'access callback' => 'user_role_edit_access',
1711 'access arguments' => array(5),
1712 );
1713 $items['admin/people/permissions/roles/delete/%user_role'] = array(
1714 'title' => 'Delete role',
1715 'page callback' => 'drupal_get_form',
1716 'page arguments' => array('user_admin_role_delete_confirm', 5),
1717 'access callback' => 'user_role_edit_access',
1718 'access arguments' => array(5),
1719 'file' => 'user.admin.inc',
1720 );
1721
1722 $items['admin/people/create'] = array(
1723 'title' => 'Add user',
1724 'page arguments' => array('create'),
1725 'access arguments' => array('administer users'),
1726 'type' => MENU_LOCAL_ACTION,
1727 );
1728
1729 // Administration pages.
1730 $items['admin/config/people'] = array(
1731 'title' => 'People',
1732 'description' => 'Configure user accounts.',
1733 'position' => 'left',
1734 'weight' => -20,
1735 'page callback' => 'system_admin_menu_block_page',
1736 'access arguments' => array('access administration pages'),
1737 'file' => 'system.admin.inc',
1738 'file path' => drupal_get_path('module', 'system'),
1739 );
1740 $items['admin/config/people/accounts'] = array(
1741 'title' => 'Account settings',
1742 'description' => 'Configure default behavior of users, including registration requirements, e-mails, fields, and user pictures.',
1743 'page callback' => 'drupal_get_form',
1744 'page arguments' => array('user_admin_settings'),
1745 'access arguments' => array('administer users'),
1746 'file' => 'user.admin.inc',
1747 'weight' => -10,
1748 );
1749 $items['admin/config/people/accounts/settings'] = array(
1750 'title' => 'Settings',
1751 'type' => MENU_DEFAULT_LOCAL_TASK,
1752 'weight' => -10,
1753 );
1754
1755 $items['user/%user'] = array(
1756 'title' => 'My account',
1757 'title callback' => 'user_page_title',
1758 'title arguments' => array(1),
1759 'page callback' => 'user_view_page',
1760 'page arguments' => array(1),
1761 'access callback' => 'user_view_access',
1762 'access arguments' => array(1),
1763 // By assigning a different menu name, this item (and all registered child
1764 // paths) are no longer considered as children of 'user'. When accessing the
1765 // user account pages, the preferred menu link that is used to build the
1766 // active trail (breadcrumb) will be found in this menu (unless there is
1767 // more specific link), so the link to 'user' will not be in the breadcrumb.
1768 'menu_name' => 'navigation',
1769 );
1770
1771 $items['user/%user/view'] = array(
1772 'title' => 'View',
1773 'type' => MENU_DEFAULT_LOCAL_TASK,
1774 'weight' => -10,
1775 );
1776
1777 $items['user/%user/cancel'] = array(
1778 'title' => 'Cancel account',
1779 'page callback' => 'drupal_get_form',
1780 'page arguments' => array('user_cancel_confirm_form', 1),
1781 'access callback' => 'user_cancel_access',
1782 'access arguments' => array(1),
1783 'file' => 'user.pages.inc',
1784 );
1785
1786 $items['user/%user/cancel/confirm/%/%'] = array(
1787 'title' => 'Confirm account cancellation',
1788 'page callback' => 'user_cancel_confirm',
1789 'page arguments' => array(1, 4, 5),
1790 'access callback' => 'user_cancel_access',
1791 'access arguments' => array(1),
1792 'file' => 'user.pages.inc',
1793 );
1794
1795 $items['user/%user/edit'] = array(
1796 'title' => 'Edit',
1797 'page callback' => 'drupal_get_form',
1798 'page arguments' => array('user_profile_form', 1),
1799 'access callback' => 'user_edit_access',
1800 'access arguments' => array(1),
1801 'type' => MENU_LOCAL_TASK,
1802 'file' => 'user.pages.inc',
1803 );
1804
1805 $items['user/%user_category/edit/account'] = array(
1806 'title' => 'Account',
1807 'type' => MENU_DEFAULT_LOCAL_TASK,
1808 'load arguments' => array('%map', '%index'),
1809 );
1810
1811 if (($categories = _user_categories()) && (count($categories) > 1)) {
1812 foreach ($categories as $key => $category) {
1813 // 'account' is already handled by the MENU_DEFAULT_LOCAL_TASK.
1814 if ($category['name'] != 'account') {
1815 $items['user/%user_category/edit/' . $category['name']] = array(
1816 'title callback' => 'check_plain',
1817 'title arguments' => array($category['title']),
1818 'page callback' => 'drupal_get_form',
1819 'page arguments' => array('user_profile_form', 1, 3),
1820 'access callback' => isset($category['access callback']) ? $category['access callback'] : 'user_edit_access',
1821 'access arguments' => isset($category['access arguments']) ? $category['access arguments'] : array(1),
1822 'type' => MENU_LOCAL_TASK,
1823 'weight' => $category['weight'],
1824 'load arguments' => array('%map', '%index'),
1825 'tab_parent' => 'user/%/edit',
1826 'file' => 'user.pages.inc',
1827 );
1828 }
1829 }
1830 }
1831 return $items;
1832 }
1833
1834 /**
1835 * Implements hook_menu_site_status_alter().
1836 */
1837 function user_menu_site_status_alter(&$menu_site_status, $path) {
1838 if ($menu_site_status == MENU_SITE_OFFLINE) {
1839 // If the site is offline, log out unprivileged users.
1840 if (user_is_logged_in() && !user_access('access site in maintenance mode')) {
1841 module_load_include('pages.inc', 'user', 'user');
1842 user_logout();
1843 }
1844
1845 if (user_is_anonymous()) {
1846 switch ($path) {
1847 case 'user':
1848 // Forward anonymous user to login page.
1849 drupal_goto('user/login');
1850 case 'user/login':
1851 case 'user/password':
1852 // Disable offline mode.
1853 $menu_site_status = MENU_SITE_ONLINE;
1854 break;
1855 default:
1856 if (strpos($path, 'user/reset/') === 0) {
1857 // Disable offline mode.
1858 $menu_site_status = MENU_SITE_ONLINE;
1859 }
1860 break;
1861 }
1862 }
1863 }
1864 if (user_is_logged_in()) {
1865 if ($path == 'user/login') {
1866 // If user is logged in, redirect to 'user' instead of giving 403.
1867 drupal_goto('user');
1868 }
1869 if ($path == 'user/register') {
1870 // Authenticated user should be redirected to user edit page.
1871 drupal_goto('user/' . $GLOBALS['user']->uid . '/edit');
1872 }
1873 }
1874 }
1875
1876 /**
1877 * Implements hook_menu_link_alter().
1878 */
1879 function user_menu_link_alter(&$link) {
1880 // The path 'user' must be accessible for anonymous users, but only visible
1881 // for authenticated users. Authenticated users should see "My account", but
1882 // anonymous users should not see it at all. Therefore, invoke
1883 // user_translated_menu_link_alter() to conditionally hide the link.
1884 if ($link['link_path'] == 'user' && $link['module'] == 'system') {
1885 $link['options']['alter'] = TRUE;
1886 }
1887
1888 // Force the Logout link to appear on the top-level of 'user-menu' menu by
1889 // default (i.e., unless it has been customized).
1890 if ($link['link_path'] == 'user/logout' && $link['module'] == 'system' && empty($link['customized'])) {
1891 $link['plid'] = 0;
1892 }
1893 }
1894
1895 /**
1896 * Implements hook_translated_menu_link_alter().
1897 */
1898 function user_translated_menu_link_alter(&$link) {
1899 // Hide the "User account" link for anonymous users.
1900 if ($link['link_path'] == 'user' && $link['module'] == 'system' && !$GLOBALS['user']->uid) {
1901 $link['hidden'] = 1;
1902 }
1903 }
1904
1905 /**
1906 * Implements hook_admin_paths().
1907 */
1908 function user_admin_paths() {
1909 $paths = array(
1910 'user/*/cancel' => TRUE,
1911 'user/*/edit' => TRUE,
1912 'user/*/edit/*' => TRUE,
1913 );
1914 return $paths;
1915 }
1916
1917 /**
1918 * Returns $arg or the user ID of the current user if $arg is '%' or empty.
1919 *
1920 * Deprecated. Use %user_uid_optional instead.
1921 *
1922 * @todo D8: Remove.
1923 */
1924 function user_uid_only_optional_to_arg($arg) {
1925 return user_uid_optional_to_arg($arg);
1926 }
1927
1928 /**
1929 * Load either a specified or the current user account.
1930 *
1931 * @param $uid
1932 * An optional user ID of the user to load. If not provided, the current
1933 * user's ID will be used.
1934 * @return
1935 * A fully-loaded $user object upon successful user load, FALSE if user
1936 * cannot be loaded.
1937 *
1938 * @see user_load()
1939 * @todo rethink the naming of this in Drupal 8.
1940 */
1941 function user_uid_optional_load($uid = NULL) {
1942 if (!isset($uid)) {
1943 $uid = $GLOBALS['user']->uid;
1944 }
1945 return user_load($uid);
1946 }
1947
1948 /**
1949 * Return a user object after checking if any profile category in the path exists.
1950 */
1951 function user_category_load($uid, &$map, $index) {
1952 static $user_categories, $accounts;
1953
1954 // Cache $account - this load function will get called for each profile tab.
1955 if (!isset($accounts[$uid])) {
1956 $accounts[$uid] = user_load($uid);
1957 }
1958 $valid = TRUE;
1959 if ($account = $accounts[$uid]) {
1960 // Since the path is like user/%/edit/category_name, the category name will
1961 // be at a position 2 beyond the index corresponding to the % wildcard.
1962 $category_index = $index + 2;
1963 // Valid categories may contain slashes, and hence need to be imploded.
1964 $category_path = implode('/', array_slice($map, $category_index));
1965 if ($category_path) {
1966 // Check that the requested category exists.
1967 $valid = FALSE;
1968 if (!isset($user_categories)) {
1969 $user_categories = _user_categories();
1970 }
1971 foreach ($user_categories as $category) {
1972 if ($category['name'] == $category_path) {
1973 $valid = TRUE;
1974 // Truncate the map array in case the category name had slashes.
1975 $map = array_slice($map, 0, $category_index);
1976 // Assign the imploded category name to the last map element.
1977 $map[$category_index] = $category_path;
1978 break;
1979 }
1980 }
1981 }
1982 }
1983 return $valid ? $account : FALSE;
1984 }
1985
1986 /**
1987 * Returns $arg or the user ID of the current user if $arg is '%' or empty.
1988 *
1989 * @todo rethink the naming of this in Drupal 8.
1990 */
1991 function user_uid_optional_to_arg($arg) {
1992 // Give back the current user uid when called from eg. tracker, aka.
1993 // with an empty arg. Also use the current user uid when called from
1994 // the menu with a % for the current account link.
1995 return empty($arg) || $arg == '%' ? $GLOBALS['user']->uid : $arg;
1996 }
1997
1998 /**
1999 * Menu item title callback for the 'user' path.
2000 *
2001 * Anonymous users should see "User account", but authenticated users are
2002 * expected to see "My account".
2003 */
2004 function user_menu_title() {
2005 return user_is_logged_in() ? t('My account') : t('User account');
2006 }
2007
2008 /**
2009 * Menu item title callback - use the user name.
2010 */
2011 function user_page_title($account) {
2012 return is_object($account) ? format_username($account) : '';
2013 }
2014
2015 /**
2016 * Discover which external authentication module(s) authenticated a username.
2017 *
2018 * @param $authname
2019 * A username used by an external authentication module.
2020 * @return
2021 * An associative array with module as key and username as value.
2022 */
2023 function user_get_authmaps($authname = NULL) {
2024 $authmaps = db_query("SELECT module, authname FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchAllKeyed();
2025 return count($authmaps) ? $authmaps : 0;
2026 }
2027
2028 /**
2029 * Save mappings of which external authentication module(s) authenticated
2030 * a user. Maps external usernames to user ids in the users table.
2031 *
2032 * @param $account
2033 * A user object.
2034 * @param $authmaps
2035 * An associative array with a compound key and the username as the value.
2036 * The key is made up of 'authname_' plus the name of the external authentication
2037 * module.
2038 * @see user_external_login_register()
2039 */
2040 function user_set_authmaps($account, $authmaps) {
2041 foreach ($authmaps as $key => $value) {
2042 $module = explode('_', $key, 2);
2043 if ($value) {
2044 db_merge('authmap')
2045 ->key(array(
2046 'uid' => $account->uid,
2047 'module' => $module[1],
2048 ))
2049 ->fields(array('authname' => $value))
2050 ->execute();
2051 }
2052 else {
2053 db_delete('authmap')
2054 ->condition('uid', $account->uid)
2055 ->condition('module', $module[1])
2056 ->execute();
2057 }
2058 }
2059 }
2060
2061 /**
2062 * Form builder; the main user login form.
2063 *
2064 * @ingroup forms
2065 */
2066 function user_login($form, &$form_state) {
2067 global $user;
2068
2069 // If we are already logged on, go to the user page instead.
2070 if ($user->uid) {
2071 drupal_goto('user/' . $user->uid);
2072 }
2073
2074 // Display login form:
2075 $form['name'] = array('#type' => 'textfield',
2076 '#title' => t('Username'),
2077 '#size' => 60,
2078 '#maxlength' => USERNAME_MAX_LENGTH,
2079 '#required' => TRUE,
2080 );
2081
2082 $form['name']['#description'] = t('Enter your @s username.', array('@s' => variable_get('site_name', 'Drupal')));
2083 $form['pass'] = array('#type' => 'password',
2084 '#title' => t('Password'),
2085 '#description' => t('Enter the password that accompanies your username.'),
2086 '#required' => TRUE,
2087 );
2088 $form['#validate'] = user_login_default_validators();
2089 $form['actions'] = array('#type' => 'actions');
2090 $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
2091
2092 return $form;
2093 }
2094
2095 /**
2096 * Set up a series for validators which check for blocked users,
2097 * then authenticate against local database, then return an error if
2098 * authentication fails. Distributed authentication modules are welcome
2099 * to use hook_form_alter() to change this series in order to
2100 * authenticate against their user database instead of the local users
2101 * table. If a distributed authentication module is successful, it
2102 * should set $form_state['uid'] to a user ID.
2103 *
2104 * We use three validators instead of one since external authentication
2105 * modules usually only need to alter the second validator.
2106 *
2107 * @see user_login_name_validate()
2108 * @see user_login_authenticate_validate()
2109 * @see user_login_final_validate()
2110 * @return array
2111 * A simple list of validate functions.
2112 */
2113 function user_login_default_validators() {
2114 return array('user_login_name_validate', 'user_login_authenticate_validate', 'user_login_final_validate');
2115 }
2116
2117 /**
2118 * A FAPI validate handler. Sets an error if supplied username has been blocked.
2119 */
2120 function user_login_name_validate($form, &$form_state) {
2121 if (isset($form_state['values']['name']) && user_is_blocked($form_state['values']['name'])) {
2122 // Blocked in user administration.
2123 form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
2124 }
2125 }
2126
2127 /**
2128 * A validate handler on the login form. Check supplied username/password
2129 * against local users table. If successful, $form_state['uid']
2130 * is set to the matching user ID.
2131 */
2132 function user_login_authenticate_validate($form, &$form_state) {
2133 $password = trim($form_state['values']['pass']);
2134 if (!empty($form_state['values']['name']) && !empty($password)) {
2135 // Do not allow any login from the current user's IP if the limit has been
2136 // reached. Default is 50 failed attempts allowed in one hour. This is
2137 // independent of the per-user limit to catch attempts from one IP to log
2138 // in to many different user accounts. We have a reasonably high limit
2139 // since there may be only one apparent IP for all users at an institution.
2140 if (!flood_is_allowed('failed_login_attempt_ip', variable_get('user_failed_login_ip_limit', 50), variable_get('user_failed_login_ip_window', 3600))) {
2141 $form_state['flood_control_triggered'] = 'ip';
2142 return;
2143 }
2144 $account = db_query("SELECT * FROM {users} WHERE name = :name AND status = 1", array(':name' => $form_state['values']['name']))->fetchObject();
2145 if ($account) {
2146 if (variable_get('user_failed_login_identifier_uid_only', FALSE)) {
2147 // Register flood events based on the uid only, so they apply for any
2148 // IP address. This is the most secure option.
2149 $identifier = $account->uid;
2150 }
2151 else {
2152 // The default identifier is a combination of uid and IP address. This
2153 // is less secure but more resistant to denial-of-service attacks that
2154 // could lock out all users with public user names.
2155 $identifier = $account->uid . '-' . ip_address();
2156 }
2157 $form_state['flood_control_user_identifier'] = $identifier;
2158
2159 // Don't allow login if the limit for this user has been reached.
2160 // Default is to allow 5 failed attempts every 6 hours.
2161 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)) {
2162 $form_state['flood_control_triggered'] = 'user';
2163 return;
2164 }
2165 }
2166 // We are not limited by flood control, so try to authenticate.
2167 // Set $form_state['uid'] as a flag for user_login_final_validate().
2168 $form_state['uid'] = user_authenticate($form_state['values']['name'], $password);
2169 }
2170 }
2171
2172 /**
2173 * The final validation handler on the login form.
2174 *
2175 * Sets a form error if user has not been authenticated, or if too many
2176 * logins have been attempted. This validation function should always
2177 * be the last one.
2178 */
2179 function user_login_final_validate($form, &$form_state) {
2180 if (empty($form_state['uid'])) {
2181 // Always register an IP-based failed login event.
2182 flood_register_event('failed_login_attempt_ip', variable_get('user_failed_login_ip_window', 3600));
2183 // Register a per-user failed login event.
2184 if (isset($form_state['flood_control_user_identifier'])) {
2185 flood_register_event('failed_login_attempt_user', variable_get('user_failed_login_user_window', 21600), $form_state['flood_control_user_identifier']);
2186 }
2187
2188 if (isset($form_state['flood_control_triggered'])) {
2189 if ($form_state['flood_control_triggered'] == 'user') {
2190 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'))));
2191 }
2192 else {
2193 // We did not find a uid, so the limit is IP-based.
2194 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'))));
2195 }
2196 }
2197 else {
2198 form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password'))));
2199 watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
2200 }
2201 }
2202 elseif (isset($form_state['flood_control_user_identifier'])) {
2203 // Clear past failures for this user so as not to block a user who might
2204 // log in and out more than once in an hour.
2205 flood_clear_event('failed_login_attempt_user', $form_state['flood_control_user_identifier']);
2206 }
2207 }
2208
2209 /**
2210 * Try to validate the user's login credentials locally.
2211 *
2212 * @param $name
2213 * User name to authenticate.
2214 * @param $password
2215 * A plain-text password, such as trimmed text from form values.
2216 * @return
2217 * The user's uid on success, or FALSE on failure to authenticate.
2218 */
2219 function user_authenticate($name, $password) {
2220 $uid = FALSE;
2221 if (!empty($name) && !empty($password)) {
2222 $account = user_load_by_name($name);
2223 if ($account) {
2224 // Allow alternate password hashing schemes.
2225 require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
2226 if (user_check_password($password, $account)) {
2227 // Successful authentication.
2228 $uid = $account->uid;
2229
2230 // Update user to new password scheme if needed.
2231 if (user_needs_new_hash($account)) {
2232 user_save($account, array('pass' => $password));
2233 }
2234 }
2235 }
2236 }
2237 return $uid;
2238 }
2239
2240 /**
2241 * Finalize the login process. Must be called when logging in a user.
2242 *
2243 * The function records a watchdog message about the new session, saves the
2244 * login timestamp, calls hook_user_login(), and generates a new session.
2245 *
2246 * @param array $edit
2247 * The array of form values submitted by the user.
2248 *
2249 * @see hook_user_login()
2250 */
2251 function user_login_finalize(&$edit = array()) {
2252 global $user;
2253 watchdog('user', 'Session opened for %name.', array('%name' => $user->name));
2254 // Update the user table timestamp noting user has logged in.
2255 // This is also used to invalidate one-time login links.
2256 $user->login = REQUEST_TIME;
2257 db_update('users')
2258 ->fields(array('login' => $user->login))
2259 ->condition('uid', $user->uid)
2260 ->execute();
2261
2262 // Regenerate the session ID to prevent against session fixation attacks.
2263 // This is called before hook_user in case one of those functions fails
2264 // or incorrectly does a redirect which would leave the old session in place.
2265 drupal_session_regenerate();
2266
2267 user_module_invoke('login', $edit, $user);
2268 }
2269
2270 /**
2271 * Submit handler for the login form. Load $user object and perform standard login
2272 * tasks. The user is then redirected to the My Account page. Setting the
2273 * destination in the query string overrides the redirect.
2274 */
2275 function user_login_submit($form, &$form_state) {
2276 global $user;
2277 $user = user_load($form_state['uid']);
2278 $form_state['redirect'] = 'user/' . $user->uid;
2279
2280 user_login_finalize($form_state);
2281 }
2282
2283 /**
2284 * Helper function for authentication modules. Either logs in or registers
2285 * the current user, based on username. Either way, the global $user object is
2286 * populated and login tasks are performed.
2287 */
2288 function user_external_login_register($name, $module) {
2289 $account = user_external_load($name);
2290 if (!$account) {
2291 // Register this new user.
2292 $userinfo = array(
2293 'name' => $name,
2294 'pass' => user_password(),
2295 'init' => $name,
2296 'status' => 1,
2297 'access' => REQUEST_TIME
2298 );
2299 $account = user_save(drupal_anonymous_user(), $userinfo);
2300 // Terminate if an error occurred during user_save().
2301 if (!$account) {
2302 drupal_set_message(t("Error saving user account."), 'error');
2303 return;
2304 }
2305 user_set_authmaps($account, array("authname_$module" => $name));
2306 }
2307
2308 // Log user in.
2309 $form_state['uid'] = $account->uid;
2310 user_login_submit(array(), $form_state);
2311 }
2312
2313 /**
2314 * Generates a unique URL for a user to login and reset their password.
2315 *
2316 * @param object $account
2317 * An object containing the user account.
2318 *
2319 * @return
2320 * A unique URL that provides a one-time log in for the user, from which
2321 * they can change their password.
2322 */
2323 function user_pass_reset_url($account) {
2324 $timestamp = REQUEST_TIME;
2325 return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
2326 }
2327
2328 /**
2329 * Generates a URL to confirm an account cancellation request.
2330 *
2331 * @param object $account
2332 * The user account object, which must contain at least the following
2333 * properties:
2334 * - uid: The user uid number.
2335 * - pass: The hashed user password string.
2336 * - login: The user login name.
2337 *
2338 * @return
2339 * A unique URL that may be used to confirm the cancellation of the user
2340 * account.
2341 *
2342 * @see user_mail_tokens()
2343 * @see user_cancel_confirm()
2344 */
2345 function user_cancel_url($account) {
2346 $timestamp = REQUEST_TIME;
2347 return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
2348 }
2349
2350 /**
2351 * Creates a unique hash value for use in time-dependent per-user URLs.
2352 *
2353 * This hash is normally used to build a unique and secure URL that is sent to
2354 * the user by email for purposes such as resetting the user's password. In
2355 * order to validate the URL, the same hash can be generated again, from the
2356 * same information, and compared to the hash value from the URL. The URL
2357 * normally contains both the time stamp and the numeric user ID. The login
2358 * name and hashed password are retrieved from the database as necessary. For a
2359 * usage example, see user_cancel_url() and user_cancel_confirm().
2360 *
2361 * @param $password
2362 * The hashed user account password value.
2363 * @param $timestamp
2364 * A unix timestamp.
2365 * @param $login
2366 * The user account login name.
2367 *
2368 * @return
2369 * A string that is safe for use in URLs and SQL statements.
2370 */
2371 function user_pass_rehash($password, $timestamp, $login) {
2372 return drupal_hmac_base64($timestamp . $login, drupal_get_hash_salt() . $password);
2373 }
2374
2375 /**
2376 * Cancel a user account.
2377 *
2378 * Since the user cancellation process needs to be run in a batch, either
2379 * Form API will invoke it, or batch_process() needs to be invoked after calling
2380 * this function and should define the path to redirect to.
2381 *
2382 * @param $edit
2383 * An array of submitted form values.
2384 * @param $uid
2385 * The user ID of the user account to cancel.
2386 * @param $method
2387 * The account cancellation method to use.
2388 *
2389 * @see _user_cancel()
2390 */
2391 function user_cancel($edit, $uid, $method) {
2392 global $user;
2393
2394 $account = user_load($uid);
2395
2396 if (!$account) {
2397 drupal_set_message(t('The user account %id does not exist.', array('%id' => $uid)), 'error');
2398 watchdog('user', 'Attempted to cancel non-existing user account: %id.', array('%id' => $uid), WATCHDOG_ERROR);
2399 return;
2400 }
2401
2402 // Initialize batch (to set title).
2403 $batch = array(
2404 'title' => t('Cancelling account'),
2405 'operations' => array(),
2406 );
2407 batch_set($batch);
2408
2409 // Modules use hook_user_delete() to respond to deletion.
2410 if ($method != 'user_cancel_delete') {
2411 // Allow modules to add further sets to this batch.
2412 module_invoke_all('user_cancel', $edit, $account, $method);
2413 }
2414
2415 // Finish the batch and actually cancel the account.
2416 $batch = array(
2417 'title' => t('Cancelling user account'),
2418 'operations' => array(
2419 array('_user_cancel', array($edit, $account, $method)),
2420 ),
2421 );
2422 batch_set($batch);
2423
2424 // Batch processing is either handled via Form API or has to be invoked
2425 // manually.
2426 }
2427
2428 /**
2429 * Last batch processing step for cancelling a user account.
2430 *
2431 * Since batch and session API require a valid user account, the actual
2432 * cancellation of a user account needs to happen last.
2433 *
2434 * @see user_cancel()
2435 */
2436 function _user_cancel($edit, $account, $method) {
2437 global $user;
2438
2439 switch ($method) {
2440 case 'user_cancel_block':
2441 case 'user_cancel_block_unpublish':
2442 default:
2443 // Send account blocked notification if option was checked.
2444 if (!empty($edit['user_cancel_notify'])) {
2445 _user_mail_notify('status_blocked', $account);
2446 }
2447 user_save($account, array('status' => 0));
2448 drupal_set_message(t('%name has been disabled.', array('%name' => $account->name)));
2449 watchdog('user', 'Blocked user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
2450 break;
2451
2452 case 'user_cancel_reassign':
2453 case 'user_cancel_delete':
2454 // Send account canceled notification if option was checked.
2455 if (!empty($edit['user_cancel_notify'])) {
2456 _user_mail_notify('status_canceled', $account);
2457 }
2458 user_delete($account->uid);
2459 drupal_set_message(t('%name has been deleted.', array('%name' => $account->name)));
2460 watchdog('user', 'Deleted user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
2461 break;
2462 }
2463
2464 // After cancelling account, ensure that user is logged out.
2465 if ($account->uid == $user->uid) {
2466 // Destroy the current session, and reset $user to the anonymous user.
2467 session_destroy();
2468 }
2469
2470 // Clear the cache for anonymous users.
2471 cache_clear_all();
2472 }
2473
2474 /**
2475 * Delete a user.
2476 *
2477 * @param $uid
2478 * A user ID.
2479 */
2480 function user_delete($uid) {
2481 user_delete_multiple(array($uid));
2482 }
2483
2484 /**
2485 * Delete multiple user accounts.
2486 *
2487 * @param $uids
2488 * An array of user IDs.
2489 */
2490 function user_delete_multiple(array $uids) {
2491 if (!empty($uids)) {
2492 $accounts = user_load_multiple($uids, array());
2493
2494 $transaction = db_transaction();
2495 try {
2496 foreach ($accounts as $uid => $account) {
2497 module_invoke_all('user_delete', $account);
2498 module_invoke_all('entity_delete', $account, 'user');
2499 field_attach_delete('user', $account);
2500 drupal_session_destroy_uid($account->uid);
2501 }
2502
2503 db_delete('users')
2504 ->condition('uid', $uids, 'IN')
2505 ->execute();
2506 db_delete('users_roles')
2507 ->condition('uid', $uids, 'IN')
2508 ->execute();
2509 db_delete('authmap')
2510 ->condition('uid', $uids, 'IN')
2511 ->execute();
2512 }
2513 catch (Exception $e) {
2514 $transaction->rollback();
2515 watchdog_exception('user', $e);
2516 throw $e;
2517 }
2518 entity_get_controller('user')->resetCache();
2519 }
2520 }
2521
2522 /**
2523 * Page callback wrapper for user_view().
2524 */
2525 function user_view_page($account) {
2526 // An administrator may try to view a non-existent account,
2527 // so we give them a 404 (versus a 403 for non-admins).
2528 return is_object($account) ? user_view($account) : MENU_NOT_FOUND;
2529 }
2530
2531 /**
2532 * Generate an array for rendering the given user.
2533 *
2534 * When viewing a user profile, the $page array contains:
2535 *
2536 * - $page['content']['Profile Category']:
2537 * Profile categories keyed by their human-readable names.
2538 * - $page['content']['Profile Category']['profile_machine_name']:
2539 * Profile fields keyed by their machine-readable names.
2540 * - $page['content']['user_picture']:
2541 * User's rendered picture.
2542 * - $page['content']['summary']:
2543 * Contains the default "History" profile data for a user.
2544 * - $page['content']['#account']:
2545 * The user account of the profile being viewed.
2546 *
2547 * To theme user profiles, copy modules/user/user-profile.tpl.php
2548 * to your theme directory, and edit it as instructed in that file's comments.
2549 *
2550 * @param $account
2551 * A user object.
2552 * @param $view_mode
2553 * View mode, e.g. 'full'.
2554 * @param $langcode
2555 * (optional) A language code to use for rendering. Defaults to the global
2556 * content language of the current request.
2557 *
2558 * @return
2559 * An array as expected by drupal_render().
2560 */
2561 function user_view($account, $view_mode = 'full', $langcode = NULL) {
2562 if (!isset($langcode)) {
2563 $langcode = $GLOBALS['language_content']->language;
2564 }
2565
2566 // Retrieve all profile fields and attach to $account->content.
2567 user_build_content($account, $view_mode, $langcode);
2568
2569 $build = $account->content;
2570 // We don't need duplicate rendering info in account->content.
2571 unset($account->content);
2572
2573 $build += array(
2574 '#theme' => 'user_profile',
2575 '#account' => $account,
2576 '#view_mode' => $view_mode,
2577 '#language' => $langcode,
2578 );
2579
2580 // Allow modules to modify the structured user.
2581 $type = 'user';
2582 drupal_alter(array('user_view', 'entity_view'), $build, $type);
2583
2584 return $build;
2585 }
2586
2587 /**
2588 * Builds a structured array representing the profile content.
2589 *
2590 * @param $account
2591 * A user object.
2592 * @param $view_mode
2593 * View mode, e.g. 'full'.
2594 * @param $langcode
2595 * (optional) A language code to use for rendering. Defaults to the global
2596 * content language of the current request.
2597 */
2598 function user_build_content($account, $view_mode = 'full', $langcode = NULL) {
2599 if (!isset($langcode)) {
2600 $langcode = $GLOBALS['language_content']->language;
2601 }
2602
2603 // Remove previously built content, if exists.
2604 $account->content = array();
2605
2606 // Allow modules to change the view mode.
2607 $context = array(
2608 'entity_type' => 'user',
2609 'entity' => $account,
2610 'langcode' => $langcode,
2611 );
2612 drupal_alter('entity_view_mode', $view_mode, $context);
2613
2614 // Build fields content.
2615 field_attach_prepare_view('user', array($account->uid => $account), $view_mode, $langcode);
2616 entity_prepare_view('user', array($account->uid => $account), $langcode);
2617 $account->content += field_attach_view('user', $account, $view_mode, $langcode);
2618
2619 // Populate $account->content with a render() array.
2620 module_invoke_all('user_view', $account, $view_mode, $langcode);
2621 module_invoke_all('entity_view', $account, 'user', $view_mode, $langcode);
2622
2623 // Make sure the current view mode is stored if no module has already
2624 // populated the related key.
2625 $account->content += array('#view_mode' => $view_mode);
2626 }
2627
2628 /**
2629 * Implements hook_mail().
2630 */
2631 function user_mail($key, &$message, $params) {
2632 $language = $message['language'];
2633 $variables = array('user' => $params['account']);
2634 $message['subject'] .= _user_mail_text($key . '_subject', $language, $variables);
2635 $message['body'][] = _user_mail_text($key . '_body', $language, $variables);
2636 }
2637
2638 /**
2639 * Returns a mail string for a variable name.
2640 *
2641 * Used by user_mail() and the settings forms to retrieve strings.
2642 */
2643 function _user_mail_text($key, $language = NULL, $variables = array(), $replace = TRUE) {
2644 $langcode = isset($language) ? $language->language : NULL;
2645
2646 if ($admin_setting = variable_get('user_mail_' . $key, FALSE)) {
2647 // An admin setting overrides the default string.
2648 $text = $admin_setting;
2649 }
2650 else {
2651 // No override, return default string.
2652 switch ($key) {
2653 case 'register_no_approval_required_subject':
2654 $text = t('Account details for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2655 break;
2656 case 'register_no_approval_required_body':
2657 $text = t("[user:name],
2658
2659 Thank you for registering at [site:name]. You may now log in by clicking this link or copying and pasting it to your browser:
2660
2661 [user:one-time-login-url]
2662
2663 This link can only be used once to log in and will lead you to a page where you can set your password.
2664
2665 After setting your password, you will be able to log in at [site:login-url] in the future using:
2666
2667 username: [user:name]
2668 password: Your password
2669
2670 -- [site:name] team", array(), array('langcode' => $langcode));
2671 break;
2672
2673 case 'register_admin_created_subject':
2674 $text = t('An administrator created an account for you at [site:name]', array(), array('langcode' => $langcode));
2675 break;
2676 case 'register_admin_created_body':
2677 $text = t("[user:name],
2678
2679 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:
2680
2681 [user:one-time-login-url]
2682
2683 This link can only be used once to log in and will lead you to a page where you can set your password.
2684
2685 After setting your password, you will be able to log in at [site:login-url] in the future using:
2686
2687 username: [user:name]
2688 password: Your password
2689
2690 -- [site:name] team", array(), array('langcode' => $langcode));
2691 break;
2692
2693 case 'register_pending_approval_subject':
2694 case 'register_pending_approval_admin_subject':
2695 $text = t('Account details for [user:name] at [site:name] (pending admin approval)', array(), array('langcode' => $langcode));
2696 break;
2697 case 'register_pending_approval_body':
2698 $text = t("[user:name],
2699
2700 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.
2701
2702
2703 -- [site:name] team", array(), array('langcode' => $langcode));
2704 break;
2705 case 'register_pending_approval_admin_body':
2706 $text = t("[user:name] has applied for an account.
2707
2708 [user:edit-url]", array(), array('langcode' => $langcode));
2709 break;
2710
2711 case 'password_reset_subject':
2712 $text = t('Replacement login information for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2713 break;
2714 case 'password_reset_body':
2715 $text = t("[user:name],
2716
2717 A request to reset the password for your account has been made at [site:name].
2718
2719 You may now log in by clicking this link or copying and pasting it to your browser:
2720
2721 [user:one-time-login-url]
2722
2723 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.
2724
2725 -- [site:name] team", array(), array('langcode' => $langcode));
2726 break;
2727
2728 case 'status_activated_subject':
2729 $text = t('Account details for [user:name] at [site:name] (approved)', array(), array('langcode' => $langcode));
2730 break;
2731 case 'status_activated_body':
2732 $text = t("[user:name],
2733
2734 Your account at [site:name] has been activated.
2735
2736 You may now log in by clicking this link or copying and pasting it into your browser:
2737
2738 [user:one-time-login-url]
2739
2740 This link can only be used once to log in and will lead you to a page where you can set your password.
2741
2742 After setting your password, you will be able to log in at [site:login-url] in the future using:
2743
2744 username: [user:name]
2745 password: Your password
2746
2747 -- [site:name] team", array(), array('langcode' => $langcode));
2748 break;
2749
2750 case 'status_blocked_subject':
2751 $text = t('Account details for [user:name] at [site:name] (blocked)', array(), array('langcode' => $langcode));
2752 break;
2753 case 'status_blocked_body':
2754 $text = t("[user:name],
2755
2756 Your account on [site:name] has been blocked.
2757
2758 -- [site:name] team", array(), array('langcode' => $langcode));
2759 break;
2760
2761 case 'cancel_confirm_subject':
2762 $text = t('Account cancellation request for [user:name] at [site:name]', array(), array('langcode' => $langcode));
2763 break;
2764 case 'cancel_confirm_body':
2765 $text = t("[user:name],
2766
2767 A request to cancel your account has been made at [site:name].
2768
2769 You may now cancel your account on [site:url-brief] by clicking this link or copying and pasting it into your browser:
2770
2771 [user:cancel-url]
2772
2773 NOTE: The cancellation of your account is not reversible.
2774
2775 This link expires in one day and nothing will happen if it is not used.
2776
2777 -- [site:name] team", array(), array('langcode' => $langcode));
2778 break;
2779
2780 case 'status_canceled_subject':
2781 $text = t('Account details for [user:name] at [site:name] (canceled)', array(), array('langcode' => $langcode));
2782 break;
2783 case 'status_canceled_body':
2784 $text = t("[user:name],
2785
2786 Your account on [site:name] has been canceled.
2787
2788 -- [site:name] team", array(), array('langcode' => $langcode));
2789 break;
2790 }
2791 }
2792
2793 if ($replace) {
2794 // We do not sanitize the token replacement, since the output of this
2795 // replacement is intended for an e-mail message, not a web browser.
2796 return token_replace($text, $variables, array('language' => $language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
2797 }
2798
2799 return $text;
2800 }
2801
2802 /**
2803 * Token callback to add unsafe tokens for user mails.
2804 *
2805 * This function is used by the token_replace() call at the end of
2806 * _user_mail_text() to set up some additional tokens that can be
2807 * used in email messages generated by user_mail().
2808 *
2809 * @param $replacements
2810 * An associative array variable containing mappings from token names to
2811 * values (for use with strtr()).
2812 * @param $data
2813 * An associative array of token replacement values. If the 'user' element
2814 * exists, it must contain a user account object with the following
2815 * properties:
2816 * - login: The account login name.
2817 * - pass: The hashed account login password.
2818 * @param $options
2819 * Unused parameter required by the token_replace() function.
2820 */
2821 function user_mail_tokens(&$replacements, $data, $options) {
2822 if (isset($data['user'])) {
2823 $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user']);
2824 $replacements['[user:cancel-url]'] = user_cancel_url($data['user']);
2825 }
2826 }
2827
2828 /*** Administrative features ***********************************************/
2829
2830 /**
2831 * Retrieve an array of roles matching specified conditions.
2832 *
2833 * @param $membersonly
2834 * Set this to TRUE to exclude the 'anonymous' role.
2835 * @param $permission
2836 * A string containing a permission. If set, only roles containing that
2837 * permission are returned.
2838 *
2839 * @return
2840 * An associative array with the role id as the key and the role name as
2841 * value.
2842 */
2843 function user_roles($membersonly = FALSE, $permission = NULL) {
2844 $query = db_select('role', 'r');
2845 $query->addTag('translatable');
2846 $query->fields('r', array('rid', 'name'));
2847 $query->orderBy('weight');
2848 $query->orderBy('name');
2849 if (!empty($permission)) {
2850 $query->innerJoin('role_permission', 'p', 'r.rid = p.rid');
2851 $query->condition('p.permission', $permission);
2852 }
2853 $result = $query->execute();
2854
2855 $roles = array();
2856 foreach ($result as $role) {
2857 switch ($role->rid) {
2858 // We only translate the built in role names
2859 case DRUPAL_ANONYMOUS_RID:
2860 if (!$membersonly) {
2861 $roles[$role->rid] = t($role->name);
2862 }
2863 break;
2864 case DRUPAL_AUTHENTICATED_RID:
2865 $roles[$role->rid] = t($role->name);
2866 break;
2867 default:
2868 $roles[$role->rid] = $role->name;
2869 }
2870 }
2871
2872 return $roles;
2873 }
2874
2875 /**
2876 * Fetches a user role by role ID.
2877 *
2878 * @param $rid
2879 * An integer representing the role ID.
2880 *
2881 * @return
2882 * A fully-loaded role object if a role with the given ID exists, or FALSE
2883 * otherwise.
2884 *
2885 * @see user_role_load_by_name()
2886 */
2887 function user_role_load($rid) {
2888 return db_select('role', 'r')
2889 ->fields('r')
2890 ->condition('rid', $rid)
2891 ->execute()
2892 ->fetchObject();
2893 }
2894
2895 /**
2896 * Fetches a user role by role name.
2897 *
2898 * @param $role_name
2899 * A string representing the role name.
2900 *
2901 * @return
2902 * A fully-loaded role object if a role with the given name exists, or FALSE
2903 * otherwise.
2904 *
2905 * @see user_role_load()
2906 */
2907 function user_role_load_by_name($role_name) {
2908 return db_select('role', 'r')
2909 ->fields('r')
2910 ->condition('name', $role_name)
2911 ->execute()
2912 ->fetchObject();
2913 }
2914
2915 /**
2916 * Save a user role to the database.
2917 *
2918 * @param $role
2919 * A role object to modify or add. If $role->rid is not specified, a new
2920 * role will be created.
2921 * @return
2922 * Status constant indicating if role was created or updated.
2923 * Failure to write the user role record will return FALSE. Otherwise.
2924 * SAVED_NEW or SAVED_UPDATED is returned depending on the operation
2925 * performed.
2926 */
2927 function user_role_save($role) {
2928 if ($role->name) {
2929 // Prevent leading and trailing spaces in role names.
2930 $role->name = trim($role->name);
2931 }
2932 if (!isset($role->weight)) {
2933 // Set a role weight to make this new role last.
2934 $query = db_select('role');
2935 $query->addExpression('MAX(weight)');
2936 $role->weight = $query->execute()->fetchField() + 1;
2937 }
2938
2939 // Let modules modify the user role before it is saved to the database.
2940 module_invoke_all('user_role_presave', $role);
2941
2942 if (!empty($role->rid) && $role->name) {
2943 $status = drupal_write_record('role', $role, 'rid');
2944 module_invoke_all('user_role_update', $role);
2945 }
2946 else {
2947 $status = drupal_write_record('role', $role);
2948 module_invoke_all('user_role_insert', $role);
2949 }
2950
2951 // Clear the user access cache.
2952 drupal_static_reset('user_access');
2953 drupal_static_reset('user_role_permissions');
2954
2955 return $status;
2956 }
2957
2958 /**
2959 * Delete a user role from database.
2960 *
2961 * @param $role
2962 * A string with the role name, or an integer with the role ID.
2963 */
2964 function user_role_delete($role) {
2965 if (is_int($role)) {
2966 $role = user_role_load($role);
2967 }
2968 else {
2969 $role = user_role_load_by_name($role);
2970 }
2971
2972 db_delete('role')
2973 ->condition('rid', $role->rid)
2974 ->execute();
2975 db_delete('role_permission')
2976 ->condition('rid', $role->rid)
2977 ->execute();
2978 // Update the users who have this role set:
2979 db_delete('users_roles')
2980 ->condition('rid', $role->rid)
2981 ->execute();
2982
2983 module_invoke_all('user_role_delete', $role);
2984
2985 // Clear the user access cache.
2986 drupal_static_reset('user_access');
2987 drupal_static_reset('user_role_permissions');
2988 }
2989
2990 /**
2991 * Menu access callback for user role editing.
2992 */
2993 function user_role_edit_access($role) {
2994 // Prevent the system-defined roles from being altered or removed.
2995 if ($role->rid == DRUPAL_ANONYMOUS_RID || $role->rid == DRUPAL_AUTHENTICATED_RID) {
2996 return FALSE;
2997 }
2998
2999 return user_access('administer permissions');
3000 }
3001
3002 /**
3003 * Determine the modules that permissions belong to.
3004 *
3005 * @return
3006 * An associative array in the format $permission => $module.
3007 */
3008 function user_permission_get_modules() {
3009 $permissions = array();
3010 foreach (module_implements('permission') as $module) {
3011 $perms = module_invoke($module, 'permission');
3012 foreach ($perms as $key => $value) {
3013 $permissions[$key] = $module;
3014 }
3015 }
3016 return $permissions;
3017 }
3018
3019 /**
3020 * Change permissions for a user role.
3021 *
3022 * This function may be used to grant and revoke multiple permissions at once.
3023 * For example, when a form exposes checkboxes to configure permissions for a
3024 * role, the form submit handler may directly pass the submitted values for the
3025 * checkboxes form element to this function.
3026 *
3027 * @param $rid
3028 * The ID of a user role to alter.
3029 * @param $permissions
3030 * An associative array, where the key holds the permission name and the value
3031 * determines whether to grant or revoke that permission. Any value that
3032 * evaluates to TRUE will cause the permission to be granted. Any value that
3033 * evaluates to FALSE will cause the permission to be revoked.
3034 * @code
3035 * array(
3036 * 'administer nodes' => 0, // Revoke 'administer nodes'
3037 * 'administer blocks' => FALSE, // Revoke 'administer blocks'
3038 * 'access user profiles' => 1, // Grant 'access user profiles'
3039 * 'access content' => TRUE, // Grant 'access content'
3040 * 'access comments' => 'access comments', // Grant 'access comments'
3041 * )
3042 * @endcode
3043 * Existing permissions are not changed, unless specified in $permissions.
3044 *
3045 * @see user_role_grant_permissions()
3046 * @see user_role_revoke_permissions()
3047 */
3048 function user_role_change_permissions($rid, array $permissions = array()) {
3049 // Grant new permissions for the role.
3050 $grant = array_filter($permissions);
3051 if (!empty($grant)) {
3052 user_role_grant_permissions($rid, array_keys($grant));
3053 }
3054 // Revoke permissions for the role.
3055 $revoke = array_diff_assoc($permissions, $grant);
3056 if (!empty($revoke)) {
3057 user_role_revoke_permissions($rid, array_keys($revoke));
3058 }
3059 }
3060
3061 /**
3062 * Grant permissions to a user role.
3063 *
3064 * @param $rid
3065 * The ID of a user role to alter.
3066 * @param $permissions
3067 * A list of permission names to grant.
3068 *
3069 * @see user_role_change_permissions()
3070 * @see user_role_revoke_permissions()
3071 */
3072 function user_role_grant_permissions($rid, array $permissions = array()) {
3073 $modules = user_permission_get_modules();
3074 // Grant new permissions for the role.
3075 foreach ($permissions as $name) {
3076 db_merge('role_permission')
3077 ->key(array(
3078 'rid' => $rid,
3079 'permission' => $name,
3080 ))
3081 ->fields(array(
3082 'module' => $modules[$name],
3083 ))
3084 ->execute();
3085 }
3086
3087 // Clear the user access cache.
3088 drupal_static_reset('user_access');
3089 drupal_static_reset('user_role_permissions');
3090 }
3091
3092 /**
3093 * Revoke permissions from a user role.
3094 *
3095 * @param $rid
3096 * The ID of a user role to alter.
3097 * @param $permissions
3098 * A list of permission names to revoke.
3099 *
3100 * @see user_role_change_permissions()
3101 * @see user_role_grant_permissions()
3102 */
3103 function user_role_revoke_permissions($rid, array $permissions = array()) {
3104 // Revoke permissions for the role.
3105 db_delete('role_permission')
3106 ->condition('rid', $rid)
3107 ->condition('permission', $permissions, 'IN')
3108 ->execute();
3109
3110 // Clear the user access cache.
3111 drupal_static_reset('user_access');
3112 drupal_static_reset('user_role_permissions');
3113 }
3114
3115 /**
3116 * Implements hook_user_operations().
3117 */
3118 function user_user_operations($form = array(), $form_state = array()) {
3119 $operations = array(
3120 'unblock' => array(
3121 'label' => t('Unblock the selected users'),
3122 'callback' => 'user_user_operations_unblock',
3123 ),
3124 'block' => array(
3125 'label' => t('Block the selected users'),
3126 'callback' => 'user_user_operations_block',
3127 ),
3128 'cancel' => array(
3129 'label' => t('Cancel the selected user accounts'),
3130 ),
3131 );
3132
3133 if (user_access('administer permissions')) {
3134 $roles = user_roles(TRUE);
3135 unset($roles[DRUPAL_AUTHENTICATED_RID]); // Can't edit authenticated role.
3136
3137 $add_roles = array();
3138 foreach ($roles as $key => $value) {
3139 $add_roles['add_role-' . $key] = $value;
3140 }
3141
3142 $remove_roles = array();
3143 foreach ($roles as $key => $value) {
3144 $remove_roles['remove_role-' . $key] = $value;
3145 }
3146
3147 if (count($roles)) {
3148 $role_operations = array(
3149 t('Add a role to the selected users') => array(
3150 'label' => $add_roles,
3151 ),
3152 t('Remove a role from the selected users') => array(
3153 'label' => $remove_roles,
3154 ),
3155 );
3156
3157 $operations += $role_operations;
3158 }
3159 }
3160
3161 // If the form has been posted, we need to insert the proper data for
3162 // role editing if necessary.
3163 if (!empty($form_state['submitted'])) {
3164 $operation_rid = explode('-', $form_state['values']['operation']);
3165 $operation = $operation_rid[0];
3166 if ($operation == 'add_role' || $operation == 'remove_role') {
3167 $rid = $operation_rid[1];
3168 if (user_access('administer permissions')) {
3169 $operations[$form_state['values']['operation']] = array(
3170 'callback' => 'user_multiple_role_edit',
3171 'callback arguments' => array($operation, $rid),
3172 );
3173 }
3174 else {
3175 watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING);
3176 return;
3177 }
3178 }
3179 }
3180
3181 return $operations;
3182 }
3183
3184 /**
3185 * Callback function for admin mass unblocking users.
3186 */
3187 function user_user_operations_unblock($accounts) {
3188 $accounts = user_load_multiple($accounts);
3189 foreach ($accounts as $account) {
3190 // Skip unblocking user if they are already unblocked.
3191 if ($account !== FALSE && $account->status == 0) {
3192 user_save($account, array('status' => 1));
3193 }
3194 }
3195 }
3196
3197 /**
3198 * Callback function for admin mass blocking users.
3199 */
3200 function user_user_operations_block($accounts) {
3201 $accounts = user_load_multiple($accounts);
3202 foreach ($accounts as $account) {
3203 // Skip blocking user if they are already blocked.
3204 if ($account !== FALSE && $account->status == 1) {
3205 // For efficiency manually save the original account before applying any
3206 // changes.
3207 $account->original = clone $account;
3208 user_save($account, array('status' => 0));
3209 }
3210 }
3211 }
3212
3213 /**
3214 * Callback function for admin mass adding/deleting a user role.
3215 */
3216 function user_multiple_role_edit($accounts, $operation, $rid) {
3217 // The role name is not necessary as user_save() will reload the user
3218 // object, but some modules' hook_user() may look at this first.
3219 $role_name = db_query('SELECT name FROM {role} WHERE rid = :rid', array(':rid' => $rid))->fetchField();
3220
3221 switch ($operation) {
3222 case 'add_role':
3223 $accounts = user_load_multiple($accounts);
3224 foreach ($accounts as $account) {
3225 // Skip adding the role to the user if they already have it.
3226 if ($account !== FALSE && !isset($account->roles[$rid])) {
3227 $roles = $account->roles + array($rid => $role_name);
3228 // For efficiency manually save the original account before applying
3229 // any changes.
3230 $account->original = clone $account;
3231 user_save($account, array('roles' => $roles));
3232 }
3233 }
3234 break;
3235 case 'remove_role':
3236 $accounts = user_load_multiple($accounts);
3237 foreach ($accounts as $account) {
3238 // Skip removing the role from the user if they already don't have it.
3239 if ($account !== FALSE && isset($account->roles[$rid])) {
3240 $roles = array_diff($account->roles, array($rid => $role_name));
3241 // For efficiency manually save the original account before applying
3242 // any changes.
3243 $account->original = clone $account;
3244 user_save($account, array('roles' => $roles));
3245 }
3246 }
3247 break;
3248 }
3249 }
3250
3251 function user_multiple_cancel_confirm($form, &$form_state) {
3252 $edit = $form_state['input'];
3253
3254 $form['accounts'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
3255 $accounts = user_load_multiple(array_keys(array_filter($edit['accounts'])));
3256 foreach ($accounts as $uid => $account) {
3257 // Prevent user 1 from being canceled.
3258 if ($uid <= 1) {
3259 continue;
3260 }
3261 $form['accounts'][$uid] = array(
3262 '#type' => 'hidden',
3263 '#value' => $uid,
3264 '#prefix' => '<li>',
3265 '#suffix' => check_plain($account->name) . "</li>\n",
3266 );
3267 }
3268
3269 // Output a notice that user 1 cannot be canceled.
3270 if (isset($accounts[1])) {
3271 $redirect = (count($accounts) == 1);
3272 $message = t('The user account %name cannot be cancelled.', array('%name' => $accounts[1]->name));
3273 drupal_set_message($message, $redirect ? 'error' : 'warning');
3274 // If only user 1 was selected, redirect to the overview.
3275 if ($redirect) {
3276 drupal_goto('admin/people');
3277 }
3278 }
3279
3280 $form['operation'] = array('#type' => 'hidden', '#value' => 'cancel');
3281
3282 module_load_include('inc', 'user', 'user.pages');
3283 $form['user_cancel_method'] = array(
3284 '#type' => 'item',
3285 '#title' => t('When cancelling these accounts'),
3286 );
3287 $form['user_cancel_method'] += user_cancel_methods();
3288 // Remove method descriptions.
3289 foreach (element_children($form['user_cancel_method']) as $element) {
3290 unset($form['user_cancel_method'][$element]['#description']);
3291 }
3292
3293 // Allow to send the account cancellation confirmation mail.
3294 $form['user_cancel_confirm'] = array(
3295 '#type' => 'checkbox',
3296 '#title' => t('Require e-mail confirmation to cancel account.'),
3297 '#default_value' => FALSE,
3298 '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'),
3299 );
3300 // Also allow to send account canceled notification mail, if enabled.
3301 $form['user_cancel_notify'] = array(
3302 '#type' => 'checkbox',
3303 '#title' => t('Notify user when account is canceled.'),
3304 '#default_value' => FALSE,
3305 '#access' => variable_get('user_mail_status_canceled_notify', FALSE),
3306 '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'),
3307 );
3308
3309 return confirm_form($form,
3310 t('Are you sure you want to cancel these user accounts?'),
3311 'admin/people', t('This action cannot be undone.'),
3312 t('Cancel accounts'), t('Cancel'));
3313 }
3314
3315 /**
3316 * Submit handler for mass-account cancellation form.
3317 *
3318 * @see user_multiple_cancel_confirm()
3319 * @see user_cancel_confirm_form_submit()
3320 */
3321 function user_multiple_cancel_confirm_submit($form, &$form_state) {
3322 global $user;
3323
3324 if ($form_state['values']['confirm']) {
3325 foreach ($form_state['values']['accounts'] as $uid => $value) {
3326 // Prevent programmatic form submissions from cancelling user 1.
3327 if ($uid <= 1) {
3328 continue;
3329 }
3330 // Prevent user administrators from deleting themselves without confirmation.
3331 if ($uid == $user->uid) {
3332 $admin_form_state = $form_state;
3333 unset($admin_form_state['values']['user_cancel_confirm']);
3334 $admin_form_state['values']['_account'] = $user;
3335 user_cancel_confirm_form_submit(array(), $admin_form_state);
3336 }
3337 else {
3338 user_cancel($form_state['values'], $uid, $form_state['values']['user_cancel_method']);
3339 }
3340 }
3341 }
3342 $form_state['redirect'] = 'admin/people';
3343 }
3344
3345 /**
3346 * Retrieve a list of all user setting/information categories and sort them by weight.
3347 */
3348 function _user_categories() {
3349 $categories = module_invoke_all('user_categories');
3350 usort($categories, '_user_sort');
3351
3352 return $categories;
3353 }
3354
3355 function _user_sort($a, $b) {
3356 $a = (array) $a + array('weight' => 0, 'title' => '');
3357 $b = (array) $b + array('weight' => 0, 'title' => '');
3358 return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1));
3359 }
3360
3361 /**
3362 * List user administration filters that can be applied.
3363 */
3364 function user_filters() {
3365 // Regular filters
3366 $filters = array();
3367 $roles = user_roles(TRUE);
3368 unset($roles[DRUPAL_AUTHENTICATED_RID]); // Don't list authorized role.
3369 if (count($roles)) {
3370 $filters['role'] = array(
3371 'title' => t('role'),
3372 'field' => 'ur.rid',
3373 'options' => array(
3374 '[any]' => t('any'),
3375 ) + $roles,
3376 );
3377 }
3378
3379 $options = array();
3380 foreach (module_implements('permission') as $module) {
3381 $function = $module . '_permission';
3382 if ($permissions = $function()) {
3383 asort($permissions);
3384 foreach ($permissions as $permission => $description) {
3385 $options[t('@module module', array('@module' => $module))][$permission] = t($permission);
3386 }
3387 }
3388 }
3389 ksort($options);
3390 $filters['permission'] = array(
3391 'title' => t('permission'),
3392 'options' => array(
3393 '[any]' => t('any'),
3394 ) + $options,
3395 );
3396
3397 $filters['status'] = array(
3398 'title' => t('status'),
3399 'field' => 'u.status',
3400 'options' => array(
3401 '[any]' => t('any'),
3402 1 => t('active'),
3403 0 => t('blocked'),
3404 ),
3405 );
3406 return $filters;
3407 }
3408
3409 /**
3410 * Extends a query object for user administration filters based on session.
3411 *
3412 * @param $query
3413 * Query object that should be filtered.
3414 */
3415 function user_build_filter_query(SelectQuery $query) {
3416 $filters = user_filters();
3417 // Extend Query with filter conditions.
3418 foreach (isset($_SESSION['user_overview_filter']) ? $_SESSION['user_overview_filter'] : array() as $filter) {
3419 list($key, $value) = $filter;
3420 // This checks to see if this permission filter is an enabled permission for
3421 // the authenticated role. If so, then all users would be listed, and we can
3422 // skip adding it to the filter query.
3423 if ($key == 'permission') {
3424 $account = new stdClass();
3425 $account->uid = 'user_filter';
3426 $account->roles = array(DRUPAL_AUTHENTICATED_RID => 1);
3427 if (user_access($value, $account)) {
3428 continue;
3429 }
3430 $users_roles_alias = $query->join('users_roles', 'ur', '%alias.uid = u.uid');
3431 $permission_alias = $query->join('role_permission', 'p', $users_roles_alias . '.rid = %alias.rid');
3432 $query->condition($permission_alias . '.permission', $value);
3433 }
3434 elseif ($key == 'role') {
3435 $users_roles_alias = $query->join('users_roles', 'ur', '%alias.uid = u.uid');
3436 $query->condition($users_roles_alias . '.rid' , $value);
3437 }
3438 else {
3439 $query->condition($filters[$key]['field'], $value);
3440 }
3441 }
3442 }
3443
3444 /**
3445 * Implements hook_comment_view().
3446 */
3447 function user_comment_view($comment) {
3448 if (variable_get('user_signatures', 0) && !empty($comment->signature)) {
3449 // @todo This alters and replaces the original object value, so a
3450 // hypothetical process of loading, viewing, and saving will hijack the
3451 // stored data. Consider renaming to $comment->signature_safe or similar
3452 // here and elsewhere in Drupal 8.
3453 $comment->signature = check_markup($comment->signature, $comment->signature_format, '', TRUE);
3454 }
3455 else {
3456 $comment->signature = '';
3457 }
3458 }
3459
3460 /**
3461 * Returns HTML for a user signature.
3462 *
3463 * @param $variables
3464 * An associative array containing:
3465 * - signature: The user's signature.
3466 *
3467 * @ingroup themeable
3468 */
3469 function theme_user_signature($variables) {
3470 $signature = $variables['signature'];
3471 $output = '';
3472
3473 if ($signature) {
3474 $output .= '<div class="clear">';
3475 $output .= '<div>—</div>';
3476 $output .= $signature;
3477 $output .= '</div>';
3478 }
3479
3480 return $output;
3481 }
3482
3483 /**
3484 * Get the language object preferred by the user. This user preference can
3485 * be set on the user account editing page, and is only available if there
3486 * are more than one languages enabled on the site. If the user did not
3487 * choose a preferred language, or is the anonymous user, the $default
3488 * value, or if it is not set, the site default language will be returned.
3489 *
3490 * @param $account
3491 * User account to look up language for.
3492 * @param $default
3493 * Optional default language object to return if the account
3494 * has no valid language.
3495 */
3496 function user_preferred_language($account, $default = NULL) {
3497 $language_list = language_list();
3498 if (!empty($account->language) && isset($language_list[$account->language])) {
3499 return $language_list[$account->language];
3500 }
3501 else {
3502 return $default ? $default : language_default();
3503 }
3504 }
3505
3506 /**
3507 * Conditionally create and send a notification email when a certain
3508 * operation happens on the given user account.
3509 *
3510 * @see user_mail_tokens()
3511 * @see drupal_mail()
3512 *
3513 * @param $op
3514 * The operation being performed on the account. Possible values:
3515 * - 'register_admin_created': Welcome message for user created by the admin.
3516 * - 'register_no_approval_required': Welcome message when user
3517 * self-registers.
3518 * - 'register_pending_approval': Welcome message, user pending admin
3519 * approval.
3520 * - 'password_reset': Password recovery request.
3521 * - 'status_activated': Account activated.
3522 * - 'status_blocked': Account blocked.
3523 * - 'cancel_confirm': Account cancellation request.
3524 * - 'status_canceled': Account canceled.
3525 *
3526 * @param $account
3527 * The user object of the account being notified. Must contain at
3528 * least the fields 'uid', 'name', and 'mail'.
3529 * @param $language
3530 * Optional language to use for the notification, overriding account language.
3531 *
3532 * @return
3533 * The return value from drupal_mail_system()->mail(), if ends up being
3534 * called.
3535 */
3536 function _user_mail_notify($op, $account, $language = NULL) {
3537 // By default, we always notify except for canceled and blocked.
3538 $default_notify = ($op != 'status_canceled' && $op != 'status_blocked');
3539 $notify = variable_get('user_mail_' . $op . '_notify', $default_notify);
3540 if ($notify) {
3541 $params['account'] = $account;
3542 $language = $language ? $language : user_preferred_language($account);
3543 $mail = drupal_mail('user',