/[drupal]/drupal/modules/user/user.module
ViewVC logotype

Contents of /drupal/modules/user/user.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1075 - (show annotations) (download) (as text)
Sun Nov 1 21:26:44 2009 UTC (3 weeks, 6 days ago) by webchick
Branch: MAIN
Changes since 1.1074: +3 -3 lines
File MIME type: text/x-php
#192056 by effulgentsia, Dave Cohen, andypost, hswong3i, geodaniel, pwolanin, and dahacouk: Ensure user's raw login name is never output directly.
1 <?php
2 // $Id: user.module,v 1.1074 2009/10/31 18:04:01 dries Exp $
3
4 /**
5 * @file
6 * Enables the user registration and login system.
7 */
8
9 /**
10 * Maximum length of username text field.
11 */
12 define('USERNAME_MAX_LENGTH', 60);
13
14 /**
15 * Maximum length of user e-mail text field.
16 */
17 define('EMAIL_MAX_LENGTH', 64);
18
19
20 /**
21 * Invokes hook_user() in every module.
22 *
23 * We cannot use module_invoke() for this, because the arguments need to
24 * be passed by reference.
25 */
26 function user_module_invoke($type, &$edit, $account, $category = NULL) {
27 foreach (module_implements('user_' . $type) as $module) {
28 $function = $module . '_user_' . $type;
29 $function($edit, $account, $category);
30 }
31 }
32
33 /**
34 * Implement hook_theme().
35 */
36 function user_theme() {
37 return array(
38 'user_picture' => array(
39 'variables' => array('account' => NULL),
40 'template' => 'user-picture',
41 ),
42 'user_profile' => array(
43 'render element' => 'elements',
44 'template' => 'user-profile',
45 'file' => 'user.pages.inc',
46 ),
47 'user_profile_category' => array(
48 'render element' => 'element',
49 'template' => 'user-profile-category',
50 'file' => 'user.pages.inc',
51 ),
52 'user_profile_item' => array(
53 'render element' => 'element',
54 'template' => 'user-profile-item',
55 'file' => 'user.pages.inc',
56 ),
57 'user_list' => array(
58 'variables' => array('users' => NULL, 'title' => NULL),
59 ),
60 'user_admin_permissions' => array(
61 'render element' => 'form',
62 'file' => 'user.admin.inc',
63 ),
64 'user_admin_new_role' => array(
65 'render element' => 'form',
66 'file' => 'user.admin.inc',
67 ),
68 'user_filter_form' => array(
69 'render element' => 'form',
70 'file' => 'user.admin.inc',
71 ),
72 'user_filters' => array(
73 'render element' => 'form',
74 'file' => 'user.admin.inc',
75 ),
76 'user_signature' => array(
77 'variables' => array('signature' => NULL),
78 ),
79 );
80 }
81
82 /**
83 * Implement hook_entity_info().
84 */
85 function user_entity_info() {
86 $return = array(
87 'user' => array(
88 'label' => t('User'),
89 'controller class' => 'UserController',
90 'base table' => 'users',
91 'fieldable' => TRUE,
92 'object keys' => array(
93 'id' => 'uid',
94 ),
95 'bundles' => array(
96 'user' => array(
97 'label' => t('User'),
98 'admin' => array(
99 'path' => 'admin/config/people/accounts',
100 'access arguments' => array('administer users'),
101 ),
102 ),
103 ),
104 ),
105 );
106 return $return;
107 }
108
109 /**
110 * Implement hook_field_build_modes().
111 */
112 function user_field_build_modes($obj_type) {
113 $modes = array();
114 if ($obj_type == 'user') {
115 $modes = array(
116 'full' => t('User account'),
117 );
118 }
119 return $modes;
120 }
121
122 /**
123 * Implement hook_field_extra_fields().
124 */
125 function user_field_extra_fields($bundle) {
126 $extra = array();
127
128 if ($bundle == 'user') {
129 $extra['account'] = array(
130 'label' => 'User name and password',
131 'description' => t('User module account form elements'),
132 'weight' => -10,
133 );
134 $extra['timezone'] = array(
135 'label' => 'Timezone',
136 'description' => t('User module timezone form element.'),
137 'weight' => 6,
138 );
139 $extra['summary'] = array(
140 'label' => 'History',
141 'description' => t('User module history view element.'),
142 'weight' => 5,
143 );
144 }
145
146 return $extra;
147 }
148
149 function user_external_load($authname) {
150 $uid = db_query("SELECT uid FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchField();
151
152 if ($uid) {
153 return user_load($uid);
154 }
155 else {
156 return FALSE;
157 }
158 }
159
160 /**
161 * Load multiple users based on certain conditions.
162 *
163 * This function should be used whenever you need to load more than one user
164 * from the database. Users are loaded into memory and will not require
165 * database access if loaded again during the same page request.
166 *
167 * @param $uids
168 * An array of user IDs.
169 * @param $conditions
170 * An array of conditions to match against the {users} table. These
171 * should be supplied in the form array('field_name' => 'field_value').
172 * @param $reset
173 * A boolean indicating that the internal cache should be reset. Use this if
174 * loading a user object which has been altered during the page request.
175 * @return
176 * An array of user objects, indexed by uid.
177 *
178 * @see entity_load()
179 * @see user_load()
180 * @see user_load_by_mail()
181 * @see user_load_by_name()
182 */
183 function user_load_multiple($uids = array(), $conditions = array(), $reset = FALSE) {
184 return entity_load('user', $uids, $conditions, $reset);
185 }
186
187 /**
188 * Controller class for users.
189 *
190 * This extends the DrupalDefaultEntityController class, adding required
191 * special handling for user objects.
192 */
193 class UserController extends DrupalDefaultEntityController {
194 function attachLoad(&$queried_users) {
195 // Build an array of user picture IDs so that these can be fetched later.
196 $picture_fids = array();
197 foreach ($queried_users as $key => $record) {
198 $picture_fids[] = $record->picture;
199 $queried_users[$key] = drupal_unpack($record);
200 $queried_users[$key]->roles = array();
201 if ($record->uid) {
202 $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
203 }
204 else {
205 $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
206 }
207 }
208
209 // Add any additional roles from the database.
210 $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)));
211 foreach ($result as $record) {
212 $queried_users[$record->uid]->roles[$record->rid] = $record->name;
213 }
214
215 // Add the full file objects for user pictures if enabled.
216 if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) {
217 $pictures = file_load_multiple($picture_fids);
218 foreach ($queried_users as $account) {
219 if (!empty($account->picture) && isset($pictures[$account->picture])) {
220 $account->picture = $pictures[$account->picture];
221 }
222 else {
223 $account->picture = NULL;
224 }
225 }
226 }
227 // Call the default attachLoad() method. This will add fields and call
228 // hook_user_load().
229 parent::attachLoad($queried_users);
230 }
231 }
232
233 /**
234 * Fetch a user object.
235 *
236 * @param $uid
237 * Integer specifying the user id.
238 * @param $reset
239 * A boolean indicating that the internal cache should be reset.
240 * @return
241 * A fully-loaded $user object upon successful user load or FALSE if user
242 * cannot be loaded.
243 *
244 * @see user_load_multiple()
245 */
246 function user_load($uid, $reset = FALSE) {
247 $users = user_load_multiple(array($uid), array(), $reset);
248 return reset($users);
249 }
250
251 /**
252 * Fetch a user object by email address.
253 *
254 * @param $mail
255 * String with the account's e-mail address.
256 * @return
257 * A fully-loaded $user object upon successful user load or FALSE if user
258 * cannot be loaded.
259 *
260 * @see user_load_multiple()
261 */
262 function user_load_by_mail($mail) {
263 $users = user_load_multiple(array(), array('mail' => $mail));
264 return reset($users);
265 }
266
267 /**
268 * Fetch a user object by account name.
269 *
270 * @param $name
271 * String with the account's user name.
272 * @return
273 * A fully-loaded $user object upon successful user load or FALSE if user
274 * cannot be loaded.
275 *
276 * @see user_load_multiple()
277 */
278 function user_load_by_name($name) {
279 $users = user_load_multiple(array(), array('name' => $name));
280 return reset($users);
281 }
282
283 /**
284 * Save changes to a user account or add a new user.
285 *
286 * @param $account
287 * (optional) The user object to modify or add. If you want to modify
288 * an existing user account, you will need to ensure that (a) $account
289 * is an object, and (b) you have set $account->uid to the numeric
290 * user ID of the user account you wish to modify. If you
291 * want to create a new user account, you can set $account->is_new to
292 * TRUE or omit the $account->uid field.
293 * @param $edit
294 * An array of fields and values to save. For example array('name'
295 * => 'My name'). Keys that do not belong to columns in the user-related
296 * tables are added to the a serialized array in the 'data' column
297 * and will be loaded in the $user->data array by user_load().
298 * Setting a field to NULL deletes it from the data column, if you are
299 * modifying an existing user account.
300 * @param $category
301 * (optional) The category for storing profile information in.
302 *
303 * @return
304 * A fully-loaded $user object upon successful save or FALSE if the save failed.
305 */
306 function user_save($account, $edit = array(), $category = 'account') {
307 $table = drupal_get_schema('users');
308 $user_fields = $table['fields'];
309
310 if (!empty($edit['pass'])) {
311 // Allow alternate password hashing schemes.
312 require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
313 $edit['pass'] = user_hash_password(trim($edit['pass']));
314 // Abort if the hashing failed and returned FALSE.
315 if (!$edit['pass']) {
316 return FALSE;
317 }
318 }
319 else {
320 // Avoid overwriting an existing password with a blank password.
321 unset($edit['pass']);
322 }
323
324 // Get the fields form so we can recognize the fields in the $edit
325 // form that should not go into the serialized data array.
326 $field_form = array();
327 $field_form_state = array();
328 $edit = (object) $edit;
329 field_attach_form('user', $edit, $field_form, $field_form_state);
330
331 // Presave fields.
332 field_attach_presave('user', $edit);
333
334 $edit = (array) $edit;
335
336 if (!isset($account->is_new)) {
337 $account->is_new = empty($account->uid);
338 }
339
340 user_module_invoke('presave', $edit, $account, $category);
341
342 if (is_object($account) && !$account->is_new) {
343 $data = unserialize(db_query('SELECT data FROM {users} WHERE uid = :uid', array(':uid' => $account->uid))->fetchField());
344 // Consider users edited by an administrator as logged in, if they haven't
345 // already, so anonymous users can view the profile (if allowed).
346 if (empty($edit['access']) && empty($account->access) && user_access('administer users')) {
347 $edit['access'] = REQUEST_TIME;
348 }
349 foreach ($edit as $key => $value) {
350 // Form fields that don't pertain to the users, user_roles, or
351 // Field API are automatically serialized into the users.data
352 // column.
353 if (!in_array($key, array('roles', 'is_new')) && empty($user_fields[$key]) && empty($field_form[$key])) {
354 if ($value === NULL) {
355 unset($data[$key]);
356 }
357 else {
358 $data[$key] = $value;
359 }
360 }
361 }
362
363 // Process picture uploads.
364 if (!empty($edit['picture']->fid)) {
365 $picture = $edit['picture'];
366 // If the picture is a temporary file move it to its final location and
367 // make it permanent.
368 if (($picture->status & FILE_STATUS_PERMANENT) == 0) {
369 $info = image_get_info($picture->uri);
370 $picture_directory = variable_get('file_default_scheme', 'public') . '://' . variable_get('user_picture_path', 'pictures');
371
372 // Prepare the pictures directory.
373 file_prepare_directory($picture_directory, FILE_CREATE_DIRECTORY);
374 $destination = file_stream_wrapper_uri_normalize($picture_directory . '/picture-' . $account->uid . '.' . $info['extension']);
375
376 if ($picture = file_move($picture, $destination, FILE_EXISTS_REPLACE)) {
377 $picture->status |= FILE_STATUS_PERMANENT;
378 $edit['picture'] = file_save($picture);
379 }
380 }
381 }
382 $edit['picture'] = empty($edit['picture']->fid) ? 0 : $edit['picture']->fid;
383
384 $edit['data'] = $data;
385 // Do not allow 'uid' to be changed.
386 $edit['uid'] = $account->uid;
387 // Save changes to the user table.
388 $success = drupal_write_record('users', $edit, 'uid');
389 if ($success === FALSE) {
390 // The query failed - better to abort the save than risk further
391 // data loss.
392 return FALSE;
393 }
394
395 // If the picture changed or was unset, remove the old one. This step needs
396 // to occur after updating the {users} record so that user_file_references()
397 // doesn't report it in use and block the deletion.
398 if (!empty($account->picture->fid) && ($edit['picture'] != $account->picture->fid)) {
399 file_delete($account->picture);
400 }
401
402 // Reload user roles if provided.
403 if (isset($edit['roles']) && is_array($edit['roles'])) {
404 db_delete('users_roles')
405 ->condition('uid', $account->uid)
406 ->execute();
407
408 $query = db_insert('users_roles')->fields(array('uid', 'rid'));
409 foreach (array_keys($edit['roles']) as $rid) {
410 if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
411 $query->values(array(
412 'uid' => $account->uid,
413 'rid' => $rid,
414 ));
415 }
416 }
417 $query->execute();
418 }
419
420 // Delete a blocked user's sessions to kick them if they are online.
421 if (isset($edit['status']) && $edit['status'] == 0) {
422 drupal_session_destroy_uid($account->uid);
423 }
424
425 // If the password changed, delete all open sessions and recreate
426 // the current one.
427 if (!empty($edit['pass'])) {
428 drupal_session_destroy_uid($account->uid);
429 if ($account->uid == $GLOBALS['user']->uid) {
430 drupal_session_regenerate();
431 }
432 }
433
434 // Save Field data.
435 $object = (object) $edit;
436 field_attach_update('user', $object);
437
438 // Refresh user object.
439 $user = user_load($account->uid, TRUE);
440
441 // Send emails after we have the new user object.
442 if (isset($edit['status']) && $edit['status'] != $account->status) {
443 // The user's status is changing; conditionally send notification email.
444 $op = $edit['status'] == 1 ? 'status_activated' : 'status_blocked';
445 _user_mail_notify($op, $user);
446 }
447
448 user_module_invoke('update', $edit, $user, $category);
449 }
450 else {
451 // Allow 'uid' to be set by the caller. There is no danger of writing an
452 // existing user as drupal_write_record will do an INSERT.
453 if (empty($edit['uid'])) {
454 $edit['uid'] = db_next_id(db_query('SELECT MAX(uid) FROM {users}')->fetchField());
455 }
456 // Allow 'created' to be set by the caller.
457 if (!isset($edit['created'])) {
458 $edit['created'] = REQUEST_TIME;
459 }
460 // Consider users created by an administrator as already logged in, so
461 // anonymous users can view the profile (if allowed).
462 if (empty($edit['access']) && user_access('administer users')) {
463 $edit['access'] = REQUEST_TIME;
464 }
465
466 $edit['mail'] = trim($edit['mail']);
467 $success = drupal_write_record('users', $edit);
468 if ($success === FALSE) {
469 // On a failed INSERT some other existing user's uid may be returned.
470 // We must abort to avoid overwriting their account.
471 return FALSE;
472 }
473
474 // Build the initial user object.
475 $user = user_load($edit['uid'], TRUE);
476
477 $object = (object) $edit;
478 field_attach_insert('user', $object);
479
480 user_module_invoke('insert', $edit, $user, $category);
481
482 // Note, we wait with saving the data column to prevent module-handled
483 // fields from being saved there.
484 $data = array();
485 foreach ($edit as $key => $value) {
486 // Form fields that don't pertain to the users, user_roles, or
487 // Field API are automatically serialized into the user.data
488 // column.
489 if ((!in_array($key, array('roles', 'is_new'))) && (empty($user_fields[$key]) && empty($field_form[$key])) && ($value !== NULL)) {
490 $data[$key] = $value;
491 }
492 }
493 if (!empty($data)) {
494 $data_array = array('uid' => $user->uid, 'data' => $data);
495 drupal_write_record('users', $data_array, 'uid');
496 }
497
498 // Save user roles (delete just to be safe).
499 if (isset($edit['roles']) && is_array($edit['roles'])) {
500 db_delete('users_roles')
501 ->condition('uid', $edit['uid'])
502 ->execute();
503 $query = db_insert('users_roles')->fields(array('uid', 'rid'));
504 foreach (array_keys($edit['roles']) as $rid) {
505 if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
506 $query->values(array(
507 'uid' => $edit['uid'],
508 'rid' => $rid,
509 ));
510 }
511 }
512 $query->execute();
513 }
514
515 // Build the finished user object.
516 $user = user_load($edit['uid'], TRUE);
517 }
518
519 return $user;
520 }
521
522 /**
523 * Verify the syntax of the given name.
524 */
525 function user_validate_name($name) {
526 if (!$name) {
527 return t('You must enter a username.');
528 }
529 if (substr($name, 0, 1) == ' ') {
530 return t('The username cannot begin with a space.');
531 }
532 if (substr($name, -1) == ' ') {
533 return t('The username cannot end with a space.');
534 }
535 if (strpos($name, ' ') !== FALSE) {
536 return t('The username cannot contain multiple spaces in a row.');
537 }
538 if (preg_match('/[^\x{80}-\x{F7} a-z0-9@_.\'-]/i', $name)) {
539 return t('The username contains an illegal character.');
540 }
541 if (preg_match('/[\x{80}-\x{A0}' . // Non-printable ISO-8859-1 + NBSP
542 '\x{AD}' . // Soft-hyphen
543 '\x{2000}-\x{200F}' . // Various space characters
544 '\x{2028}-\x{202F}' . // Bidirectional text overrides
545 '\x{205F}-\x{206F}' . // Various text hinting characters
546 '\x{FEFF}' . // Byte order mark
547 '\x{FF01}-\x{FF60}' . // Full-width latin
548 '\x{FFF9}-\x{FFFD}' . // Replacement characters
549 '\x{0}-\x{1F}]/u', // NULL byte and control characters
550 $name)) {
551 return t('The username contains an illegal character.');
552 }
553 if (drupal_strlen($name) > USERNAME_MAX_LENGTH) {
554 return t('The username %name is too long: it must be %max characters or less.', array('%name' => $name, '%max' => USERNAME_MAX_LENGTH));
555 }
556 }
557
558 function user_validate_mail($mail) {
559 $mail = trim($mail);
560 if (!$mail) {
561 return t('You must enter an e-mail address.');
562 }
563 if (!valid_email_address($mail)) {
564 return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
565 }
566 }
567
568 function user_validate_picture(&$form, &$form_state) {
569 // If required, validate the uploaded picture.
570 $validators = array(
571 'file_validate_is_image' => array(),
572 'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')),
573 'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),
574 );
575
576 // Save the file as a temporary file.
577 $file = file_save_upload('picture_upload', $validators);
578 if ($file === FALSE) {
579 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'))));
580 }
581 elseif ($file !== NULL) {
582 $form_state['values']['picture_upload'] = $file;
583 }
584 }
585
586 /**
587 * Generate a random alphanumeric password.
588 */
589 function user_password($length = 10) {
590 // This variable contains the list of allowable characters for the
591 // password. Note that the number 0 and the letter 'O' have been
592 // removed to avoid confusion between the two. The same is true
593 // of 'I', 1, and 'l'.
594 $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
595
596 // Zero-based count of characters in the allowable list:
597 $len = strlen($allowable_characters) - 1;
598
599 // Declare the password as a blank string.
600 $pass = '';
601
602 // Loop the number of times specified by $length.
603 for ($i = 0; $i < $length; $i++) {
604
605 // Each iteration, pick a random character from the
606 // allowable string and append it to the password:
607 $pass .= $allowable_characters[mt_rand(0, $len)];
608 }
609
610 return $pass;
611 }
612
613 /**
614 * Determine the permissions for one or more roles.
615 *
616 * @param $roles
617 * An array whose keys are the role IDs of interest, such as $user->roles.
618 *
619 * @return
620 * An array indexed by role ID. Each value is an array whose keys are the
621 * permission strings for the given role ID.
622 */
623 function user_role_permissions($roles = array()) {
624 $cache = &drupal_static(__FUNCTION__, array());
625
626 $role_permissions = $fetch = array();
627
628 if ($roles) {
629 foreach ($roles as $rid => $name) {
630 if (isset($cache[$rid])) {
631 $role_permissions[$rid] = $cache[$rid];
632 }
633 else {
634 // Add this rid to the list of those needing to be fetched.
635 $fetch[] = $rid;
636 // Prepare in case no permissions are returned.
637 $cache[$rid] = array();
638 }
639 }
640
641 if ($fetch) {
642 // Get from the database permissions that were not in the static variable.
643 // Only role IDs with at least one permission assigned will return rows.
644 $result = db_query("SELECT rid, permission FROM {role_permission} WHERE rid IN (:fetch)", array(':fetch' => $fetch));
645
646 foreach ($result as $row) {
647 $cache[$row->rid][$row->permission] = TRUE;
648 }
649 foreach ($fetch as $rid) {
650 // For every rid, we know we at least assigned an empty array.
651 $role_permissions[$rid] = $cache[$rid];
652 }
653 }
654 }
655
656 return $role_permissions;
657 }
658
659 /**
660 * Determine whether the user has a given privilege.
661 *
662 * @param $string
663 * The permission, such as "administer nodes", being checked for.
664 * @param $account
665 * (optional) The account to check, if not given use currently logged in user.
666 *
667 * @return
668 * Boolean TRUE if the current user has the requested permission.
669 *
670 * All permission checks in Drupal should go through this function. This
671 * way, we guarantee consistent behavior, and ensure that the superuser
672 * can perform all actions.
673 */
674 function user_access($string, $account = NULL) {
675 global $user;
676 $perm = &drupal_static(__FUNCTION__, array());
677
678 if (!isset($account)) {
679 $account = $user;
680 }
681
682 // User #1 has all privileges:
683 if ($account->uid == 1) {
684 return TRUE;
685 }
686
687 // To reduce the number of SQL queries, we cache the user's permissions
688 // in a static variable.
689 if (!isset($perm[$account->uid])) {
690 $role_permissions = user_role_permissions($account->roles);
691
692 $perms = array();
693 foreach ($role_permissions as $one_role) {
694 $perms += $one_role;
695 }
696 $perm[$account->uid] = $perms;
697 }
698
699 return isset($perm[$account->uid][$string]);
700 }
701
702 /**
703 * Checks for usernames blocked by user administration.
704 *
705 * @return boolean TRUE for blocked users, FALSE for active.
706 */
707 function user_is_blocked($name) {
708 $deny = db_query("SELECT name FROM {users} WHERE status = 0 AND name = LOWER(:name)", array(':name' => $name))->fetchObject();
709
710 return $deny;
711 }
712
713 /**
714 * Implement hook_permission().
715 */
716 function user_permission() {
717 return array(
718 'administer permissions' => array(
719 'title' => t('Administer permissions'),
720 'description' => t('Manage the permissions assigned to user roles. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
721 ),
722 'administer users' => array(
723 'title' => t('Administer users'),
724 'description' => t('Manage or block users, and manage their role assignments. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
725 ),
726 'access user profiles' => array(
727 'title' => t('Access user profiles'),
728 'description' => t('View profiles of users on the site, which may contain personal information.'),
729 ),
730 'change own username' => array(
731 'title' => t('Change own username'),
732 'description' => t('Select a different username.'),
733 ),
734 'cancel account' => array(
735 'title' => t('Cancel account'),
736 'description' => t('Remove or disable own user account and unpublish, anonymize, or remove own submissions depending on the configured <a href="@user-settings-url">user settings</a>.', array('@user-settings-url' => url('admin/config/people/accounts'))),
737 ),
738 'select account cancellation method' => array(
739 'title' => t('Select method for cancelling own account'),
740 'description' => t('Select the method for cancelling own user account. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
741 ),
742 );
743 }
744
745 /**
746 * Implement hook_file_download().
747 *
748 * Ensure that user pictures (avatars) are always downloadable.
749 */
750 function user_file_download($uri) {
751 if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
752 $info = image_get_info($uri);
753 return array('Content-Type' => $info['mime_type']);
754 }
755 }
756
757 /**
758 * Implement hook_file_references().
759 */
760 function user_file_references($file) {
761 // Determine if the file is used by this module.
762 $file_used = (bool) db_query_range('SELECT 1 FROM {users} WHERE picture = :fid', 0, 1, array(':fid' => $file->fid))->fetchField();
763 if ($file_used) {
764 // Return the name of the module and how many references it has to the file.
765 return array('user' => $count);
766 }
767 }
768
769 /**
770 * Implement hook_file_delete().
771 */
772 function user_file_delete($file) {
773 // Remove any references to the file.
774 db_update('users')
775 ->fields(array('picture' => 0))
776 ->condition('picture', $file->fid)
777 ->execute();
778 }
779
780 /**
781 * Implement hook_search_info().
782 */
783 function user_search_info() {
784 return array(
785 'title' => 'Users',
786 );
787 }
788
789 /**
790 * Implement hook_search_access().
791 */
792 function user_search_access() {
793 return user_access('access user profiles');
794 }
795
796 /**
797 * Implement hook_search_execute().
798 */
799 function user_search_execute($keys = NULL) {
800 $find = array();
801 // Replace wildcards with MySQL/PostgreSQL wildcards.
802 $keys = preg_replace('!\*+!', '%', $keys);
803 $query = db_select('users')->extend('PagerDefault');
804 $query->fields('users', array('name', 'uid', 'mail'));
805 if (user_access('administer users')) {
806 // Administrators can also search in the otherwise private email field.
807 $query->condition(db_or()->
808 where('LOWER(name) LIKE LOWER(:name)', array(':name' => "%$keys%"))->
809 where('LOWER(mail) LIKE LOWER(:mail)', array(':mail' => "%$keys%")));
810 }
811 else {
812 $query->where('LOWER(name) LIKE LOWER(:name)', array(':name' => "%$keys%"));
813 }
814 $result = $query
815 ->limit(15)
816 ->execute();
817 foreach ($result as $account) {
818 $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
819 }
820 return $find;
821 }
822
823 /**
824 * Implement hook_element_info().
825 */
826 function user_element_info() {
827 $types['user_profile_category'] = array(
828 '#theme_wrappers' => array('user_profile_category'),
829 );
830 $types['user_profile_item'] = array(
831 '#theme' => 'user_profile_item',
832 );
833 return $types;
834 }
835
836 /**
837 * Implement hook_user_view().
838 */
839 function user_user_view($account) {
840 $account->content['user_picture'] = array(
841 '#markup' => theme('user_picture', array('account' => $account)),
842 '#weight' => -10,
843 );
844 if (!isset($account->content['summary'])) {
845 $account->content['summary'] = array();
846 }
847 $account->content['summary'] += array(
848 '#type' => 'user_profile_category',
849 '#attributes' => array('class' => array('user-member')),
850 '#weight' => 5,
851 '#title' => t('History'),
852 );
853 $account->content['summary']['member_for'] = array(
854 '#type' => 'user_profile_item',
855 '#title' => t('Member for'),
856 '#markup' => format_interval(REQUEST_TIME - $account->created),
857 );
858 }
859
860 /**
861 * Helper function to add default user account fields to user registration and edit form.
862 */
863 function user_account_form(&$form, &$form_state) {
864 global $user;
865
866 $account = $form['#user'];
867 $register = ($form['#user']->uid > 0 ? FALSE : TRUE);
868
869 _user_password_dynamic_validation();
870 $admin = user_access('administer users');
871
872 $form['#validate'][] = 'user_account_form_validate';
873
874 // Account information.
875 $form['account'] = array(
876 '#type' => 'fieldset',
877 '#title' => t('Account information'),
878 '#weight' => -10,
879 );
880 // Only show name field on registration form or user can change own username.
881 $form['account']['name'] = array(
882 '#type' => 'textfield',
883 '#title' => t('Username'),
884 '#maxlength' => USERNAME_MAX_LENGTH,
885 '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'),
886 '#required' => TRUE,
887 '#attributes' => array('class' => array('username')),
888 '#default_value' => (!$register ? $account->name : ''),
889 '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin),
890 );
891
892 $form['account']['mail'] = array(
893 '#type' => 'textfield',
894 '#title' => t('E-mail address'),
895 '#maxlength' => EMAIL_MAX_LENGTH,
896 '#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.'),
897 '#required' => TRUE,
898 '#default_value' => (!$register ? $account->mail : ''),
899 );
900
901 // Display password field only for existing users or when user is allowed to
902 // assign a password during registration.
903 if (!$register) {
904 $form['account']['pass'] = array(
905 '#type' => 'password_confirm',
906 '#size' => 25,
907 '#description' => t('To change the current user password, enter the new password in both fields.'),
908 );
909 }
910 elseif (!variable_get('user_email_verification', TRUE) || $admin) {
911 $form['account']['pass'] = array(
912 '#type' => 'password_confirm',
913 '#size' => 25,
914 '#description' => t('Provide a password for the new account in both fields.'),
915 '#required' => TRUE,
916 );
917 }
918
919 if ($admin) {
920 $status = (isset($account->status) ? $account->status : 1);
921 }
922 else {
923 $status = (variable_get('user_register', 1) == 1);
924 }
925 $form['account']['status'] = array(
926 '#type' => 'radios',
927 '#title' => t('Status'),
928 '#default_value' => $status,
929 '#options' => array(t('Blocked'), t('Active')),
930 '#access' => $admin,
931 );
932
933 $roles = user_roles(TRUE);
934 // The disabled checkbox subelement for the 'authenticated user' role
935 // must be generated separately and added to the checkboxes element,
936 // because of a limitation in Form API not supporting a single disabled
937 // checkbox within a set of checkboxes.
938 // @todo This should be solved more elegantly. See issue #119038.
939 $checkbox_authenticated = array(
940 '#type' => 'checkbox',
941 '#title' => $roles[DRUPAL_AUTHENTICATED_RID],
942 '#default_value' => TRUE,
943 '#disabled' => TRUE,
944 );
945 unset($roles[DRUPAL_AUTHENTICATED_RID]);
946 $form['account']['roles'] = array(
947 '#type' => 'checkboxes',
948 '#title' => t('Roles'),
949 '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()),
950 '#options' => $roles,
951 '#access' => $roles && user_access('administer permissions'),
952 DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
953 );
954
955 $form['account']['notify'] = array(
956 '#type' => 'checkbox',
957 '#title' => t('Notify user of new account'),
958 '#access' => $register && $admin,
959 );
960
961 // Signature.
962 $form['signature_settings'] = array(
963 '#type' => 'fieldset',
964 '#title' => t('Signature settings'),
965 '#weight' => 1,
966 '#access' => (!$register && variable_get('user_signatures', 0)),
967 );
968 $form['signature_settings']['signature'] = array(
969 '#type' => 'textarea',
970 '#title' => t('Signature'),
971 '#default_value' => isset($account->signature) ? $account->signature : '',
972 '#description' => t('Your signature will be publicly displayed at the end of your comments.'),
973 );
974
975 // Picture/avatar.
976 $form['picture'] = array(
977 '#type' => 'fieldset',
978 '#title' => t('Picture'),
979 '#weight' => 1,
980 '#access' => (!$register && variable_get('user_pictures', 0)),
981 );
982 $form['picture']['picture'] = array(
983 '#type' => 'value',
984 '#value' => isset($account->picture) ? $account->picture : NULL,
985 );
986 $form['picture']['picture_current'] = array(
987 '#markup' => theme('user_picture', array('account' => $account)),
988 );
989 $form['picture']['picture_delete'] = array(
990 '#type' => 'checkbox',
991 '#title' => t('Delete picture'),
992 '#access' => !empty($account->picture->fid),
993 '#description' => t('Check this box to delete your current picture.'),
994 );
995 $form['picture']['picture_upload'] = array(
996 '#type' => 'file',
997 '#title' => t('Upload picture'),
998 '#size' => 48,
999 '#description' => t('Your virtual face or picture. Maximum dimensions are %dimensions pixels and the maximum size is %size kB.', array('%dimensions' => variable_get('user_picture_dimensions', '85x85'), '%size' => variable_get('user_picture_file_size', '30'))) . ' ' . variable_get('user_picture_guidelines', ''),
1000 );
1001 $form['#validate'][] = 'user_validate_picture';
1002 }
1003
1004 /**
1005 * Form validation handler for user_account_form().
1006 */
1007 function user_account_form_validate($form, &$form_state) {
1008 if ($form['#user_category'] == 'account' || $form['#user_category'] == 'register') {
1009 $account = $form['#user'];
1010 // Validate new or changing username.
1011 if (isset($form_state['values']['name'])) {
1012 if ($error = user_validate_name($form_state['values']['name'])) {
1013 form_set_error('name', $error);
1014 }
1015 elseif ((bool) db_query_range("SELECT 1 FROM {users} WHERE uid <> :uid AND LOWER(name) = LOWER(:name)", 0, 1, array(':uid' => $account->uid, ':name' => $form_state['values']['name']))->fetchField()) {
1016 form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name'])));
1017 }
1018 }
1019
1020 // Validate the e-mail address, and check if it is taken by an existing user.
1021 if ($error = user_validate_mail($form_state['values']['mail'])) {
1022 form_set_error('mail', $error);
1023 }
1024 elseif ((bool) db_query_range("SELECT 1 FROM {users} WHERE uid <> :uid AND LOWER(mail) = LOWER(:mail)", 0, 1, array(':uid' => $account->uid, ':mail' => $form_state['values']['mail']))->fetchField()) {
1025 // Format error message dependent on whether the user is logged in or not.
1026 if ($GLOBALS['user']->uid) {
1027 form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $form_state['values']['mail'])));
1028 }
1029 else {
1030 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'))));
1031 }
1032 }
1033
1034 // Make sure the signature isn't longer than the size of the database field.
1035 // Signatures are disabled by default, so make sure it exists first.
1036 if (isset($form_state['values']['signature'])) {
1037 $user_schema = drupal_get_schema('users');
1038 if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) {
1039 form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length'])));
1040 }
1041 }
1042 }
1043 }
1044
1045 /**
1046 * Implement hook_user_presave().
1047 */
1048 function user_user_presave(&$edit, $account, $category) {
1049 if ($category == 'account' || $category == 'register') {
1050 if (!empty($edit['picture_upload'])) {
1051 $edit['picture'] = $edit['picture_upload'];
1052 }
1053 // Delete picture if requested, and if no replacement picture was given.
1054 elseif (!empty($edit['picture_delete'])) {
1055 $edit['picture'] = NULL;
1056 }
1057 // Remove these values so they don't end up serialized in the data field.
1058 $edit['picture_upload'] = NULL;
1059 $edit['picture_delete'] = NULL;
1060
1061 // Prepare user roles.
1062 if (isset($edit['roles'])) {
1063 $edit['roles'] = array_filter($edit['roles']);
1064 }
1065 }
1066 }
1067
1068 /**
1069 * Implement hook_user_categories().
1070 */
1071 function user_user_categories() {
1072 return array(array(
1073 'name' => 'account',
1074 'title' => t('Account settings'),
1075 'weight' => 1,
1076 ));
1077 }
1078
1079 function user_login_block($form) {
1080 $form['#action'] = url($_GET['q'], array('query' => drupal_get_destination()));
1081 $form['#id'] = 'user-login-form';
1082 $form['#validate'] = user_login_default_validators();
1083 $form['#submit'][] = 'user_login_submit';
1084 $form['name'] = array('#type' => 'textfield',
1085 '#title' => t('Username'),
1086 '#maxlength' => USERNAME_MAX_LENGTH,
1087 '#size' => 15,
1088 '#required' => TRUE,
1089 );
1090 $form['pass'] = array('#type' => 'password',
1091 '#title' => t('Password'),
1092 '#maxlength' => 60,
1093 '#size' => 15,
1094 '#required' => TRUE,
1095 );
1096 $form['submit'] = array('#type' => 'submit',
1097 '#value' => t('Log in'),
1098 );
1099 $items = array();
1100 if (variable_get('user_register', 1)) {
1101 $items[] = l(t('Create new account'), 'user/register', array('attributes' => array('title' => t('Create a new user account.'))));
1102 }
1103 $items[] = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
1104 $form['links'] = array('#markup' => theme('item_list', array('items' => $items)));
1105 return $form;
1106 }
1107
1108 /**
1109 * Implement hook_block_info().
1110 */
1111 function user_block_info() {
1112 global $user;
1113
1114 $blocks['login']['info'] = t('User login');
1115 // Not worth caching.
1116 $blocks['login']['cache'] = DRUPAL_NO_CACHE;
1117
1118 $blocks['new']['info'] = t('Who\'s new');
1119
1120 // Too dynamic to cache.
1121 $blocks['online']['info'] = t('Who\'s online');
1122 $blocks['online']['cache'] = DRUPAL_NO_CACHE;
1123 return $blocks;
1124 }
1125
1126 /**
1127 * Implement hook_block_configure().
1128 */
1129 function user_block_configure($delta = '') {
1130 global $user;
1131
1132 switch ($delta) {
1133 case 'new':
1134 $form['user_block_whois_new_count'] = array(
1135 '#type' => 'select',
1136 '#title' => t('Number of users to display'),
1137 '#default_value' => variable_get('user_block_whois_new_count', 5),
1138 '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
1139 );
1140 return $form;
1141
1142 case 'online':
1143 $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
1144 $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.'));
1145 $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.'));
1146 return $form;
1147 }
1148 }
1149