bugfix: the slice page was broken when nobody is in slice
[plewww.git] / drupal-hacks / user.module
1 <?php
2 // $Id: user.module,v 1.612.2.32 2007/09/13 20:42:42 killes Exp $
3
4 /**
5  * @file
6  * Enables the user registration and login system.
7  */
8
9 /**
10  * Invokes hook_user() in every module.
11  *
12  * We cannot use module_invoke() for this, because the arguments need to
13  * be passed by reference.
14  */
15 function user_module_invoke($type, &$array, &$user, $category = NULL) {
16   foreach (module_list() as $module) {
17     $function = $module .'_user';
18     if (function_exists($function)) {
19       $function($type, $array, $user, $category);
20     }
21   }
22 }
23
24 function user_external_load($authname) {
25   $result = db_query("SELECT uid FROM {authmap} WHERE authname = '%s'", $authname);
26
27   if ($user = db_fetch_object($result)) {
28     return user_load($user);
29   }
30   else {
31     return 0;
32   }
33 }
34
35 /**
36  * Fetch a user object.
37  *
38  * @param $array
39  *   An associative array of attributes to search for in selecting the
40  *   user, such as user name or e-mail address.
41  *
42  * @return
43  *   A fully-loaded $user object upon successful user load or FALSE if user cannot be loaded.
44  */
45 function user_load($array = array()) {
46   // Dynamically compose a SQL query:
47   $query = array();
48   $params = array();
49
50   foreach ($array as $key => $value) {
51     if ($key == 'uid' || $key == 'status') {
52       $query[] = "$key = %d";
53       $params[] = $value;
54     }
55     else if ($key == 'pass') {
56       $query[] = "pass = '%s'";
57       $params[] = md5($value);
58     }
59     else {
60       $query[]= "LOWER($key) = LOWER('%s')";
61       $params[] = $value;
62     }
63   }
64   $result = db_query('SELECT * FROM {users} u WHERE ' . implode(' AND ', $query), $params);
65
66   if (db_num_rows($result)) {
67     $user = db_fetch_object($result);
68     $user = drupal_unpack($user);
69
70     $user->roles = array();
71     if ($user->uid) {
72       $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
73     }
74     else {
75       $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
76     }
77     $result = db_query('SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d', $user->uid);
78     while ($role = db_fetch_object($result)) {
79       $user->roles[$role->rid] = $role->name;
80     }
81     user_module_invoke('load', $array, $user);
82   }
83   else {
84     $user = FALSE;
85   }
86
87   return $user;
88 }
89
90 /**
91  * Save changes to a user account or add a new user.
92  *
93  * @param $account
94  *   The $user object for the user to modify or add. If $user->uid is
95  *   omitted, a new user will be added.
96  *
97  * @param $array
98  *   An array of fields and values to save. For example array('name' => 'My name');
99  *   Setting a field to null deletes it from the data column.
100  *
101  * @param $category
102  *   (optional) The category for storing profile information in.
103  */
104 function user_save($account, $array = array(), $category = 'account') {
105   // Dynamically compose a SQL query:
106   $user_fields = user_fields();
107   if ($account->uid) {
108     user_module_invoke('update', $array, $account, $category);
109
110     $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
111     foreach ($array as $key => $value) {
112       if ($key == 'pass' && !empty($value)) {
113         $query .= "$key = '%s', ";
114         $v[] = md5($value);
115       }
116       else if ((substr($key, 0, 4) !== 'auth') && ($key != 'pass')) {
117         if (in_array($key, $user_fields)) {
118           // Save standard fields
119           $query .= "$key = '%s', ";
120           $v[] = $value;
121         }
122         else if ($key != 'roles') {
123           // Roles is a special case: it used below.
124           if ($value === null) {
125             unset($data[$key]);
126           }
127           else {
128             $data[$key] = $value;
129           }
130         }
131       }
132     }
133     $query .= "data = '%s' ";
134     $v[] = serialize($data);
135
136     db_query("UPDATE {users} SET $query WHERE uid = %d", array_merge($v, array($account->uid)));
137
138     // Reload user roles if provided
139     if (is_array($array['roles'])) {
140       db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
141
142       foreach (array_keys($array['roles']) as $rid) {
143         if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
144           db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $account->uid, $rid);
145         }
146       }
147     }
148
149     // Delete a blocked user's sessions to kick them if they are online.
150     if (isset($array['status']) && $array['status'] == 0) {
151       db_query('DELETE FROM {sessions} WHERE uid = %d', $account->uid);
152     }
153
154     // Refresh user object
155     $user = user_load(array('uid' => $account->uid));
156   }
157   else {
158     $array['created'] = time();
159     $array['uid'] = db_next_id('{users}_uid');
160
161     // Note, we wait with saving the data column to prevent module-handled
162     // fields from being saved there. We cannot invoke hook_user('insert') here
163     // because we don't have a fully initialized user object yet.
164     foreach ($array as $key => $value) {
165       switch($key) {
166         case 'pass':
167           $fields[] = $key;
168           $values[] = md5($value);
169           $s[] = "'%s'";
170           break;
171         case 'uid':        case 'mode':     case 'sort':
172         case 'threshold':  case 'created':  case 'access':
173         case 'login':      case 'status':
174           $fields[] = $key;
175           $values[] = $value;
176           $s[] = "%d";
177           break;
178         default:
179           if (substr($key, 0, 4) !== 'auth' && in_array($key, $user_fields)) {
180             $fields[] = $key;
181             $values[] = $value;
182             $s[] = "'%s'";
183           }
184           break;
185       }
186     }
187     db_query('INSERT INTO {users} ('. implode(', ', $fields) .') VALUES ('. implode(', ', $s) .')', $values);
188
189     // Reload user roles (delete just to be safe).
190     db_query('DELETE FROM {users_roles} WHERE uid = %d', $array['uid']);
191     foreach ((array)$array['roles'] as $rid) {
192       if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
193         db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $array['uid'], $rid);
194       }
195     }
196
197     // Build the initial user object.
198     $user = user_load(array('uid' => $array['uid']));
199
200     user_module_invoke('insert', $array, $user, $category);
201
202     // Build and save the serialized data field now
203     $data = array();
204     foreach ($array as $key => $value) {
205       if ((substr($key, 0, 4) !== 'auth') && ($key != 'roles') && (!in_array($key, $user_fields)) && ($value !== null)) {
206         $data[$key] = $value;
207       }
208     }
209     db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);
210
211     // Build the finished user object.
212     $user = user_load(array('uid' => $array['uid']));
213   }
214
215   // Save distributed authentication mappings
216   $authmaps = array();
217   foreach ($array as $key => $value) {
218     if (substr($key, 0, 4) == 'auth') {
219       $authmaps[$key] = $value;
220     }
221   }
222   if (sizeof($authmaps) > 0) {
223     user_set_authmaps($user, $authmaps);
224   }
225
226   return $user;
227 }
228
229 /**
230  * Verify the syntax of the given name.
231  */
232 function user_validate_name($name) {
233   if (!strlen($name)) return t('You must enter a username.');
234   if (substr($name, 0, 1) == ' ') return t('The username cannot begin with a space.');
235   if (substr($name, -1) == ' ') return t('The username cannot end with a space.');
236   if (preg_match('/  /', $name)) return t('The username cannot contain multiple spaces in a row.');
237   if (preg_match("/[^\x80-\xF7 [:alnum:]@_.-]/", $name)) return t('The username contains an illegal character.');
238   if (preg_match('/[\x{80}-\x{A0}'.          // Non-printable ISO-8859-1 + NBSP
239                    '\x{AD}'.                 // Soft-hyphen
240                    '\x{2000}-\x{200F}'.      // Various space characters
241                    '\x{2028}-\x{202F}'.      // Bidirectional text overrides
242                    '\x{205F}-\x{206F}'.      // Various text hinting characters
243                    '\x{FEFF}'.               // Byte order mark
244                    '\x{FF01}-\x{FF60}'.      // Full-width latin
245                    '\x{FFF9}-\x{FFFD}'.      // Replacement characters
246                    '\x{0}]/u',               // NULL byte
247                    $name)) {
248     return t('The username contains an illegal character.');
249   }
250   if (preg_match('/@/', $name) && !preg_match('/@([0-9a-z](-?[0-9a-z])*.)+[a-z]{2}([zmuvtg]|fo|me)?$/i', $name)) 
251     return t('The username is not a valid authentication ID.');
252   if (strlen($name) > 56) 
253     return t('The username %name is too long: it must be less than 56 characters.', 
254              array('%name' => theme('placeholder', $name)));
255 }
256
257 function user_validate_mail($mail) {
258   if (!$mail) return t('You must enter an e-mail address.');
259   if (!valid_email_address($mail)) {
260     return t('The e-mail address %mail is not valid.', array('%mail' => theme('placeholder', $mail)));
261   }
262 }
263
264 function user_validate_picture($file, &$edit, $user) {
265   global $form_values;
266   // Initialize the picture:
267   $form_values['picture'] = $user->picture;
268
269   // Check that uploaded file is an image, with a maximum file size
270   // and maximum height/width.
271   $info = image_get_info($file->filepath);
272   list($maxwidth, $maxheight) = explode('x', variable_get('user_picture_dimensions', '85x85'));
273
274   if (!$info || !$info['extension']) {
275     form_set_error('picture_upload', t('The uploaded file was not an image.'));
276   }
277   else if (image_get_toolkit()) {
278     image_scale($file->filepath, $file->filepath, $maxwidth, $maxheight);
279   }
280   else if (filesize($file->filepath) > (variable_get('user_picture_file_size', '30') * 1000)) {
281     form_set_error('picture_upload', t('The uploaded image is too large; the maximum file size is %size kB.', array('%size' => variable_get('user_picture_file_size', '30'))));
282   }
283   else if ($info['width'] > $maxwidth || $info['height'] > $maxheight) {
284     form_set_error('picture_upload', t('The uploaded image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => variable_get('user_picture_dimensions', '85x85'))));
285   }
286
287   if (!form_get_errors()) {
288     if ($file = file_save_upload('picture_upload', variable_get('user_picture_path', 'pictures') .'/picture-'. $user->uid . '.' . $info['extension'], 1)) {
289       $form_values['picture'] = $file->filepath;
290     }
291     else {
292       form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist.", array('%directory' => '<em>'. variable_get('user_picture_path', 'pictures') .'</em>')));
293     }
294   }
295 }
296
297 /**
298  * Generate a random alphanumeric password.
299  */
300 function user_password($length = 10) {
301   // This variable contains the list of allowable characters for the
302   // password.  Note that the number 0 and the letter 'O' have been
303   // removed to avoid confusion between the two.  The same is true
304   // of 'I', 1, and l.
305   $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
306
307   // Zero-based count of characters in the allowable list:
308   $len = strlen($allowable_characters) - 1;
309
310   // Declare the password as a blank string.
311   $pass = '';
312
313   // Loop the number of times specified by $length.
314   for ($i = 0; $i < $length; $i++) {
315
316     // Each iteration, pick a random character from the
317     // allowable string and append it to the password:
318     $pass .= $allowable_characters[mt_rand(0, $len)];
319   }
320
321   return $pass;
322 }
323
324 /**
325  * Determine whether the user has a given privilege.
326  *
327  * @param $string
328  *   The permission, such as "administer nodes", being checked for.
329  * @param $account
330  *   (optional) The account to check, if not given use currently logged in user.
331  *
332  * @return
333  *   boolean TRUE if the current user has the requested permission.
334  *
335  * All permission checks in Drupal should go through this function. This
336  * way, we guarantee consistent behavior, and ensure that the superuser
337  * can perform all actions.
338  */
339 function user_access($string, $account = NULL) {
340   global $user;
341   static $perm = array();
342
343   if (is_null($account)) {
344     $account = $user;
345   }
346
347   // User #1 has all privileges:
348   if ($account->uid == 1) {
349     return TRUE;
350   }
351
352   // To reduce the number of SQL queries, we cache the user's permissions
353   // in a static variable.
354   if (!isset($perm[$account->uid])) {
355     $result = db_query("SELECT DISTINCT(p.perm) FROM {role} r INNER JOIN {permission} p ON p.rid = r.rid WHERE r.rid IN (%s)", implode(',', array_keys($account->roles)));
356
357     $perm[$account->uid] = '';
358     while ($row = db_fetch_object($result)) {
359       $perm[$account->uid] .= "$row->perm, ";
360     }
361   }
362
363   if (isset($perm[$account->uid])) {
364     return strpos($perm[$account->uid], "$string, ") !== FALSE;
365   }
366
367   return FALSE;
368 }
369
370 /**
371  * Checks for usernames blocked by user administration
372  *
373  * @return boolean true for blocked users, false for active
374  */
375 function user_is_blocked($name) {
376   $allow = db_fetch_object(db_query("SELECT * FROM {users} WHERE status = 1 AND name = LOWER('%s')", $name));
377   $deny  = db_fetch_object(db_query("SELECT * FROM {users} WHERE status = 0 AND name = LOWER('%s')", $name));
378
379   return $deny && !$allow;
380 }
381
382 /**
383  * Send an e-mail message, using Drupal variables and default settings.
384  * More information in the <a href="http://php.net/manual/en/function.mail.php">PHP function reference for mail()</a>
385  * @param $mail
386  *  The mail adres or addresses where the message will be send to. The
387  *  formatting of this string must comply with RFC 2822. Some examples are:
388  *    user@example.com
389  *    user@example.com, anotheruser@example.com
390  *    User <user@example.com>
391  *    User <user@example.com>, Another User <anotheruser@example.com>
392  *  @param $subject
393  *    Subject of the email to be sent. This must not contain any newline characters, or the mail may not be sent properly.
394  *  @param $message
395  *    Message to be sent. Drupal will format the correct line endings for you.
396  *  @param $header
397  *    String to be inserted at the end of the email header. This is typically
398  *    used to add extra headers (From, Cc, and Bcc). Multiple extra headers
399  *    should be separated with a CRLF (\r\n).
400  *    <em>When sending mail, the mail must contain a From header.</em>
401  * @return Returns TRUE if the mail was successfully accepted for delivery, FALSE otherwise.
402  */
403 function user_mail($mail, $subject, $message, $header) {
404   if (variable_get('smtp_library', '') && file_exists(variable_get('smtp_library', ''))) {
405    include_once './' . variable_get('smtp_library', '');
406     return user_mail_wrapper($mail, $subject, $message, $header);
407   }
408   else {
409     /*
410     ** Note: if you are having problems with sending mail, or mails look wrong
411     ** when they are received you may have to modify the str_replace to suit
412     ** your systems.
413     **  - \r\n will work under dos and windows.
414     **  - \n will work for linux, unix and BSDs.
415     **  - \r will work for macs.
416     **
417     ** According to RFC 2646, it's quite rude to not wrap your e-mails:
418     **
419     ** "The Text/Plain media type is the lowest common denominator of
420     ** Internet e-mail, with lines of no more than 997 characters (by
421     ** convention usually no more than 80), and where the CRLF sequence
422     ** represents a line break [MIME-IMT]."
423     **
424     ** CRLF === \r\n
425     **
426     ** http://www.rfc-editor.org/rfc/rfc2646.txt
427     **
428     */
429     return mail(
430       $mail,
431       mime_header_encode($subject),
432       str_replace("\r", '', $message),
433       "MIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8; format=flowed\nContent-transfer-encoding: 8Bit\n" . $header
434     );
435   }
436 }
437
438 function user_fields() {
439   static $fields;
440
441   if (!$fields) {
442     $result = db_query('SELECT * FROM {users} WHERE uid = 1');
443     if (db_num_rows($result)) {
444       $fields = array_keys(db_fetch_array($result));
445     }
446     else {
447       // Make sure we return the default fields at least
448       $fields = array('uid', 'name', 'pass', 'mail', 'picture', 'mode', 'sort', 'threshold', 'theme', 'signature', 'created', 'access', 'login', 'status', 'timezone', 'language', 'init', 'data');
449     }
450   }
451
452   return $fields;
453 }
454
455 /**
456  * Implementation of hook_perm().
457  */
458 function user_perm() {
459   return array('administer access control', 'administer users', 'access user profiles', 'change own username');
460 }
461
462 /**
463  * Implementation of hook_file_download().
464  *
465  * Ensure that user pictures (avatars) are always downloadable.
466  */
467 function user_file_download($file) {
468   if (strpos($file, variable_get('user_picture_path', 'pictures') .'/picture-') === 0) {
469     $info = image_get_info(file_create_path($file));
470     return array('Content-type: '. $info['mime_type']);
471   }
472 }
473
474 /**
475  * Implementation of hook_search().
476  */
477 function user_search($op = 'search', $keys = null) {
478   switch ($op) {
479     case 'name':
480       if (user_access('access user profiles')) {
481         return t('users');
482       }
483     case 'search':
484       if (user_access('access user profiles')) {
485         $find = array();
486         // Replace wildcards with MySQL/PostgreSQL wildcards.
487         $keys = preg_replace('!\*+!', '%', $keys);
488         $result = pager_query("SELECT * FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys);
489         while ($account = db_fetch_object($result)) {
490           $find[] = array('title' => $account->name, 'link' => url('user/'. $account->uid));
491         }
492         return $find;
493       }
494   }
495 }
496
497 /**
498  * Implementation of hook_user().
499  */
500 function user_user($type, &$edit, &$user, $category = NULL) {
501   if ($type == 'view') {
502     $items[] = array('title' => t('Member for'),
503       'value' => format_interval(time() - $user->created),
504       'class' => 'member',
505     );
506
507     return array(t('History') => $items);
508   }
509   if ($type == 'form' && $category == 'account') {
510     return user_edit_form(arg(1), $edit);
511   }
512
513   if ($type == 'validate' && $category == 'account') {
514     return _user_edit_validate(arg(1), $edit);
515   }
516
517   if ($type == 'submit' && $category == 'account') {
518     return _user_edit_submit(arg(1), $edit);
519   }
520
521   if ($type == 'categories') {
522     return array(array('name' => 'account', 'title' => t('account settings'), 'weight' => 1));
523   }
524 }
525
526 /**
527  * Implementation of hook_block().
528  */
529 function user_block($op = 'list', $delta = 0, $edit = array()) {
530   global $user;
531
532   if ($op == 'list') {
533      $blocks[0]['info'] = t('User login');
534      $blocks[1]['info'] = t('Navigation');
535      $blocks[2]['info'] = t('Who\'s new');
536      $blocks[3]['info'] = t('Who\'s online');
537
538      return $blocks;
539   }
540   else if ($op == 'configure' && $delta == 3) {
541     $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
542     $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.'));
543     $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.'));
544
545     return $form;
546   }
547   else if ($op == 'save' && $delta == 3) {
548     variable_set('user_block_seconds_online', $edit['user_block_seconds_online']);
549     variable_set('user_block_max_list_count', $edit['user_block_max_list_count']);
550   }
551   else if ($op == 'view') {
552     $block = array();
553
554     switch ($delta) {
555       case 0:
556         // For usability's sake, avoid showing two login forms on one page.
557         if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
558           $form['#action'] = url($_GET['q'], drupal_get_destination());
559           $form['#id'] = 'user-login-form';
560           $form['name'] = array('#type' => 'textfield',
561             '#title' => t('Username'),
562             '#maxlength' => 60,
563             '#size' => 15,
564             '#required' => TRUE,
565           );
566           $form['pass'] = array('#type' => 'password',
567             '#title' => t('Password'),
568             '#maxlength' => 60,
569             '#size' => 15,
570             '#required' => TRUE,
571           );
572           $form['submit'] = array('#type' => 'submit',
573             '#value' => t('Log in'),
574           );
575
576           if (variable_get('user_register', 1)) {
577             $items[] = l(t('Create new account'), 'user/register', array('title' => t('Create a new user account.')));
578           }
579           $items[] = l(t('Request new password'), 'user/password', array('title' => t('Request new password via e-mail.')));
580           $form['links'] = array('#value' => theme('item_list', $items));
581
582           $block['subject'] = t('User login');
583           $block['content'] = drupal_get_form('user_login_block', $form, 'user_login');
584         }
585         return $block;
586
587       case 1:
588         if ($menu = theme('menu_tree')) {
589            $block['subject'] = $user->uid ? check_plain($user->name) : t('Navigation');
590            $block['content'] = $menu;
591         }
592         return $block;
593
594       case 2:
595         if (user_access('access content')) {
596           // Retrieve a list of new users who have subsequently accessed the site successfully.
597           $result = db_query_range('SELECT uid, name FROM {users} WHERE status != 0 AND access != 0 ORDER BY created DESC', 0, 5);
598           while ($account = db_fetch_object($result)) {
599             $items[] = $account;
600           }
601           $output = theme('user_list', $items);
602
603           $block['subject'] = t('Who\'s new');
604           $block['content'] = $output;
605         }
606         return $block;
607
608       case 3:
609         if (user_access('access content')) {
610           // Count users with activity in the past defined period.
611           $time_period = variable_get('user_block_seconds_online', 900);
612
613           // Perform database queries to gather online user lists.
614           $guests = db_fetch_object(db_query('SELECT COUNT(sid) AS count FROM {sessions} WHERE timestamp >= %d AND uid = 0', time() - $time_period));
615           $users = db_query('SELECT uid, name, access FROM {users} WHERE access >= %d AND uid != 0 ORDER BY access DESC', time() - $time_period);
616           $total_users = db_num_rows($users);
617
618           // Format the output with proper grammar.
619           if ($total_users == 1 && $guests->count == 1) {
620             $output = t('There is currently %members and %visitors online.', array('%members' => format_plural($total_users, '1 user', '%count users'), '%visitors' => format_plural($guests->count, '1 guest', '%count guests')));
621           }
622           else {
623             $output = t('There are currently %members and %visitors online.', array('%members' => format_plural($total_users, '1 user', '%count users'), '%visitors' => format_plural($guests->count, '1 guest', '%count guests')));
624           }
625
626           // Display a list of currently online users.
627           $max_users = variable_get('user_block_max_list_count', 10);
628           if ($total_users && $max_users) {
629             $items = array();
630
631             while ($max_users-- && $account = db_fetch_object($users)) {
632               $items[] = $account;
633             }
634
635             $output .= theme('user_list', $items, t('Online users'));
636           }
637
638           $block['subject'] = t('Who\'s online');
639           $block['content'] = $output;
640         }
641         return $block;
642     }
643   }
644 }
645
646 function theme_user_picture($account) {
647   if (variable_get('user_pictures', 0)) {
648     if ($account->picture && file_exists($account->picture)) {
649       $picture = file_create_url($account->picture);
650     }
651     else if (variable_get('user_picture_default', '')) {
652       $picture = variable_get('user_picture_default', '');
653     }
654
655     if (isset($picture)) {
656       $alt = t('%user\'s picture', array('%user' => $account->name ? $account->name : variable_get('anonymous', 'Anonymous')));
657       $picture = theme('image', $picture, $alt, $alt, '', false);
658       if (!empty($account->uid) && user_access('access user profiles')) {
659         $picture = l($picture, "user/$account->uid", array('title' => t('View user profile.')), NULL, NULL, FALSE, TRUE);
660       }
661
662       return "<div class=\"picture\">$picture</div>";
663     }
664   }
665 }
666
667 /**
668  * Theme a user page
669  * @param $account the user object
670  * @param $fields a multidimensional array for the fields, in the form of array (
671  *   'category1' => array(item_array1, item_array2), 'category2' => array(item_array3,
672  *    .. etc.). Item arrays are formatted as array(array('title' => 'item title',
673  * 'value' => 'item value', 'class' => 'class-name'), ... etc.). Module names are incorporated
674  * into the CSS class.
675  *
676  * @ingroup themeable
677  */
678 function theme_user_profile($account, $fields) {
679   $output = '<div class="profile">';
680   $output .= theme('user_picture', $account);
681   foreach ($fields as $category => $items) {
682     if (strlen($category) > 0) {
683       $output .= '<h2 class="title">'. check_plain($category) .'</h2>';
684     }
685     $output .= '<dl>';
686     foreach ($items as $item) {
687       if (isset($item['title'])) {
688         $output .= '<dt class="'. $item['class'] .'">'. $item['title'] .'</dt>';
689       }
690       $output .= '<dd class="'. $item['class'] .'">'. $item['value'] .'</dd>';
691     }
692     $output .= '</dl>';
693   }
694   $output .= '</div>';
695
696   return $output;
697 }
698
699 /**
700  * Make a list of users.
701  * @param $items an array with user objects. Should contain at least the name and uid
702  *
703  * @ingroup themeable
704  */
705 function theme_user_list($users, $title = NULL) {
706   if (!empty($users)) {
707     foreach ($users as $user) {
708       $items[] = theme('username', $user);
709     }
710   }
711   return theme('item_list', $items, $title);
712 }
713
714 /**
715  * Implementation of hook_menu().
716  */
717 function user_menu($may_cache) {
718   global $user;
719
720   $items = array();
721
722   $admin_access = user_access('administer users');
723   $access_access = user_access('administer access control');
724   $view_access = user_access('access user profiles');
725
726   if ($may_cache) {
727     $items[] = array('path' => 'user', 'title' => t('user account'),
728       'callback' => 'user_login', 'access' => TRUE, 'type' => MENU_CALLBACK);
729
730     $items[] = array('path' => 'user/autocomplete', 'title' => t('user autocomplete'),
731       'callback' => 'user_autocomplete', 'access' => $view_access, 'type' => MENU_CALLBACK);
732
733     // Registration and login pages.
734     $items[] = array('path' => 'user/login', 'title' => t('log in'),
735       'callback' => 'user_login', 'type' => MENU_DEFAULT_LOCAL_TASK);
736     $items[] = array('path' => 'user/register', 'title' => t('register'),
737       'callback' => 'user_register', 'access' => $user->uid == 0 && variable_get('user_register', 1), 'type' => MENU_LOCAL_TASK);
738     $items[] = array('path' => 'user/password', 'title' => t('request new password'),
739       'callback' => 'user_pass', 'access' => $user->uid == 0, 'type' => MENU_LOCAL_TASK);
740     $items[] = array('path' => 'user/reset', 'title' => t('reset password'),
741       'callback' => 'user_pass_reset', 'access' => TRUE, 'type' => MENU_CALLBACK);
742     $items[] = array('path' => 'user/help', 'title' => t('help'),
743       'callback' => 'user_help_page', 'type' => MENU_CALLBACK);
744
745     // Admin user pages
746     $items[] = array('path' => 'admin/user', 'title' => t('users'),
747       'callback' => 'user_admin', 'access' => $admin_access);
748     $items[] = array('path' => 'admin/user/list', 'title' => t('list'),
749       'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
750     $items[] = array('path' => 'admin/user/create', 'title' => t('add user'),
751       'callback' => 'user_admin', 'access' => $admin_access,
752       'type' => MENU_LOCAL_TASK);
753     $items[] = array('path' => 'admin/settings/user', 'title' => t('users'),
754       'callback' => 'user_configure');
755
756     // Admin access pages
757     $items[] = array('path' => 'admin/access', 'title' => t('access control'),
758       'callback' => 'user_admin_perm', 'access' => $access_access);
759     $items[] = array('path' => 'admin/access/permissions', 'title' => t('permissions'),
760       'callback' => 'user_admin_perm', 'access' => $access_access,
761       'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
762     $items[] = array('path' => 'admin/access/roles', 'title' => t('roles'),
763       'callback' => 'user_admin_role', 'access' => $access_access,
764       'type' => MENU_LOCAL_TASK);
765     $items[] = array('path' => 'admin/access/roles/edit', 'title' => t('edit role'),
766       'callback' => 'user_admin_role', 'access' => $access_access,
767       'type' => MENU_CALLBACK);
768     $items[] = array('path' => 'admin/access/rules', 'title' => t('access rules'),
769       'callback' => 'user_admin_access', 'access' => $access_access,
770       'type' => MENU_LOCAL_TASK, 'weight' => 10);
771     $items[] = array('path' => 'admin/access/rules/list', 'title' => t('list'),
772       'access' => $access_access, 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
773     $items[] = array('path' => 'admin/access/rules/add', 'title' => t('add rule'),
774       'callback' => 'user_admin_access_add', 'access' => $access_access,
775       'type' => MENU_LOCAL_TASK);
776     $items[] = array('path' => 'admin/access/rules/check', 'title' => t('check rules'),
777       'callback' => 'user_admin_access_check', 'access' => $access_access,
778       'type' => MENU_LOCAL_TASK);
779     $items[] = array('path' => 'admin/access/rules/edit', 'title' => t('edit rule'),
780       'callback' => 'user_admin_access_edit', 'access' => $access_access,
781       'type' => MENU_CALLBACK);
782     $items[] = array('path' => 'admin/access/rules/delete', 'title' => t('delete rule'),
783       'callback' => 'user_admin_access_delete', 'access' => $access_access,
784       'type' => MENU_CALLBACK);
785
786     if (module_exist('search')) {
787       $items[] = array('path' => 'admin/user/search', 'title' => t('search'),
788         'callback' => 'user_admin', 'access' => $admin_access,
789         'type' => MENU_LOCAL_TASK);
790     }
791
792     // Your personal page
793     if ($user->uid) {
794       $items[] = array('path' => 'user/'. $user->uid, 'title' => t('my account'),
795         'callback' => 'user_view', 'callback arguments' => array(arg(1)), 'access' => TRUE,
796         'type' => MENU_DYNAMIC_ITEM);
797     }
798
799     $items[] = array('path' => 'logout', 'title' => t('log out'),
800       'access' => $user->uid != 0,
801       'callback' => 'user_logout',
802       'weight' => 10);
803   }
804   else {
805     if (arg(0) == 'user' && is_numeric(arg(1)) && arg(1) > 0) {
806       $account = user_load(array('uid' => arg(1)));
807
808       if ($user !== FALSE) {
809         // Always let a user view their own account
810         $view_access |= $user->uid == arg(1);
811         // Only admins can view blocked accounts
812         $view_access &= $account->status || $admin_access;
813
814         $items[] = array('path' => 'user/'. arg(1), 'title' => t('user'),
815           'type' => MENU_CALLBACK, 'callback' => 'user_view',
816           'callback arguments' => array(arg(1)), 'access' => $view_access);
817
818         $items[] = array('path' => 'user/'. arg(1) .'/view', 'title' => t('view'),
819           'access' => $view_access, 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
820
821         $items[] = array('path' => 'user/'. arg(1) .'/edit', 'title' => t('edit'),
822           'callback' => 'user_edit', 'access' => $admin_access || $user->uid == arg(1),
823           'type' => MENU_LOCAL_TASK);
824         $items[] = array('path' => 'user/'. arg(1) .'/delete', 'title' => t('delete'),
825           'callback' => 'user_edit', 'access' => $admin_access,
826           'type' => MENU_CALLBACK);
827
828         if (arg(2) == 'edit') {
829           if (($categories = _user_categories($account)) && (count($categories) > 1)) {
830             foreach ($categories as $key => $category) {
831               $items[] = array(
832                 'path' => 'user/'. arg(1) .'/edit/'. $category['name'],
833                 'title' => $category['title'],
834                 'type' => $category['name'] == 'account' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
835                 'weight' => $category['weight'],
836                 'access' => ($admin_access || $user->uid == arg(1)));
837             }
838           }
839         }
840       }
841     }
842   }
843
844   return $items;
845 }
846
847 /**
848  * Accepts an user object, $account, or a DA name and returns an associative
849  * array of modules and DA names. Called at external login.
850  */
851 function user_get_authmaps($authname = NULL) {
852   $result = db_query("SELECT authname, module FROM {authmap} WHERE authname = '%s'", $authname);
853   if (db_num_rows($result) > 0) {
854     while ($authmap = db_fetch_object($result)) {
855       $authmaps[$authmap->module] = $authmap->authname;
856     }
857     return $authmaps;
858   }
859   else {
860     return 0;
861   }
862 }
863
864 function user_set_authmaps($account, $authmaps) {
865   foreach ($authmaps as $key => $value) {
866     $module = explode('_', $key, 2);
867     if ($value) {
868       db_query("UPDATE {authmap} SET authname = '%s' WHERE uid = %d AND module = '%s'", $value, $account->uid, $module['1']);
869       if (!db_affected_rows()) {
870         db_query("INSERT INTO {authmap} (authname, uid, module) VALUES ('%s', %d, '%s')", $value, $account->uid, $module[1]);
871       }
872     }
873     else {
874       db_query("DELETE FROM {authmap} WHERE uid = %d AND module = '%s'", $account->uid, $module['1']);
875     }
876   }
877 }
878
879 function user_auth_help_links() {
880   $links = array();
881   foreach (module_list() as $module) {
882     if (module_hook($module, 'auth')) {
883       $links[] = l(module_invoke($module, 'info', 'name'), "user/help#$module");
884     }
885   }
886   return $links;
887 }
888
889 /*** User features *********************************************************/
890
891 // disabling user management
892 function user_functions_disabled_message () {
893   return "Drupal native user management functions are disabled";
894 }
895
896 function user_login($msg = '') {
897
898   return user_functions_disabled_message();
899
900   global $user, $base_url;
901
902   // If we are already logged on, go to the user page instead
903   if ($user->uid) {
904     drupal_goto('user/'. $user->uid);
905   }
906
907   // Display login form:
908   if ($msg) {
909     $form['message'] = array('#value' => '<p>'. check_plain($msg) .'</p>');
910   }
911   unset($_GET['time']);
912   $form['#action'] = url($_GET['q'], drupal_get_destination());
913   $form['name'] = array('#type' => 'textfield',
914     '#title' => t('Username'),
915     '#size' => 30,
916     '#maxlength' => 60,
917     '#required' => TRUE,
918     '#attributes' => array('tabindex' => '1'),
919   );
920   if (variable_get('drupal_authentication_service', FALSE) && count(user_auth_help_links()) > 0) {
921     $form['name']['#description'] = t('Enter your %s username, or an ID from one of our affiliates: %a.', array('%s' => variable_get('site_name', 'local'), '%a' => implode(', ', user_auth_help_links())));
922   }
923   else {
924     $form['name']['#description'] = t('Enter your %s username.', array('%s' => variable_get('site_name', 'local')));
925   }
926   $form['pass'] = array('#type' => 'password',
927     '#title' => t('Password'),
928     '#description' => t('Enter the password that accompanies your username.'),
929     '#required' => TRUE,
930     '#attributes' => array('tabindex' => '2'),
931   );
932   $form['submit'] = array('#type' => 'submit', '#value' => t('Log in'), '#weight' => 2, '#attributes' => array('tabindex' => '3'));
933   return drupal_get_form('user_login', $form);
934 }
935
936 function user_login_validate($form_id, $form_values) {
937   if ($form_values['name']) {
938     if (user_is_blocked($form_values['name'])) {
939       // blocked in user administration
940       form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => theme('placeholder', $form_values['name']))));
941     }
942     else if (drupal_is_denied('user', $form_values['name'])) {
943       // denied by access controls
944       form_set_error('name', t('The name %name is a reserved username.', array('%name' => theme('placeholder', $form_values['name']))));
945     }
946     else if ($form_values['pass']) {
947       $user = user_authenticate($form_values['name'], trim($form_values['pass']));
948
949       if (!$user->uid) {
950         form_set_error('name', t('Sorry. Unrecognized username or password.') .' '. l(t('Have you forgotten your password?'), 'user/password'));
951         watchdog('user', t('Login attempt failed for %user.', array('%user' => theme('placeholder', $form_values['name']))));
952       }
953     }
954   }
955 }
956
957 function user_login_submit($form_id, $form_values) {
958   global $user;
959   if ($user->uid) {
960     // To handle the edge case where this function is called during a
961     // bootstrap, check for the existence of t().
962     if (function_exists('t')) {
963       $message = t('Session opened for %name.', array('%name' => theme('placeholder', $user->name)));
964     }
965     else {
966        $message = "Session opened for ". check_plain($user->name);
967     }
968     watchdog('user', $message);
969
970     // Update the user table timestamp noting user has logged in.
971     db_query("UPDATE {users} SET login = %d WHERE uid = %d", time(), $user->uid);
972
973     user_module_invoke('login', $form_values, $user);
974
975     sess_regenerate();
976     return 'user/'. $user->uid;
977   }
978 }
979
980 function user_authenticate($name, $pass) {
981   global $user;
982
983   // Try to log in the user locally. Don't set $user unless successful.
984   if ($account = user_load(array('name' => $name, 'pass' => $pass, 'status' => 1))) {
985     $user = $account;
986   }
987
988   // Strip name and server from ID:
989   if ($server = strrchr($name, '@')) {
990     $name = substr($name, 0, strlen($name) - strlen($server));
991     $server = substr($server, 1);
992   }
993
994   // When possible, determine corresponding external auth source. Invoke
995   // source, and log in user if successful:
996   if (!$user->uid && $server && $result = user_get_authmaps("$name@$server")) {
997     if (module_invoke(key($result), 'auth', $name, $pass, $server)) {
998       $user = user_external_load("$name@$server");
999       watchdog('user', t('External load by %user using module %module.', array('%user' => theme('placeholder', $name .'@'. $server), '%module' => theme('placeholder', key($result)))));
1000     }
1001     else {
1002       $error = t('Invalid password for %s.', array('%s' => theme('placeholder', $name .'@'. $server)));
1003     }
1004   }
1005
1006   // Try each external authentication source in series. Register user if
1007   // successful.
1008   else if (!$user->uid && $server) {
1009     foreach (module_list() as $module) {
1010       if (module_hook($module, 'auth')) {
1011         if (module_invoke($module, 'auth', $name, $pass, $server)) {
1012           if (variable_get('user_register', 1) == 1) {
1013             $account = user_load(array('name' => "$name@$server"));
1014             if (!$account->uid) { // Register this new user.
1015               $user = user_save('', array('name' => "$name@$server", 'pass' => user_password(), 'init' => "$name@$server", 'status' => 1, "authname_$module" => "$name@$server"));
1016               watchdog('user', t('New external user: %user using module %module.', array('%user' => theme('placeholder', $name .'@'. $server), '%module' => theme('placeholder', $module))), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $user->uid .'/edit'));
1017               break;
1018             }
1019           }
1020         }
1021       }
1022     }
1023   }
1024   return $user;
1025 }
1026
1027 /**
1028  * Menu callback; logs the current user out, and redirects to the home page.
1029  */
1030 function user_logout() {
1031   global $user;
1032
1033   watchdog('user', t('Session closed for %name.', array('%name' => theme('placeholder', $user->name))));
1034
1035   // Destroy the current session:
1036   session_destroy();
1037   module_invoke_all('user', 'logout', NULL, $user);
1038
1039   // We have to use $GLOBALS to unset a global variable:
1040   $user = user_load(array('uid' => 0));
1041
1042   drupal_goto();
1043 }
1044
1045 function user_pass() {
1046
1047   return user_functions_disabled_message();
1048
1049   // Display form:
1050   $form['name'] = array('#type' => 'textfield',
1051     '#title' => t('Username'),
1052     '#size' => 30,
1053     '#maxlength' => 60,
1054   );
1055   $form['mail'] = array('#type' => 'textfield',
1056     '#title' => t('E-mail address'),
1057     '#size' => 30,
1058     '#maxlength' => 64,
1059   );
1060   $form['submit'] = array('#type' => 'submit',
1061     '#value' => t('E-mail new password'),
1062     '#weight' => 2,
1063   );
1064   return drupal_get_form('user_pass', $form);
1065 }
1066
1067 function user_pass_validate() {
1068   global $form_values;
1069
1070   $name = $form_values['name'];
1071   $mail = $form_values['mail'];
1072   if ($name && !($form_values['account'] = user_load(array('name' => $name, 'status' => 1)))) {
1073     form_set_error('name', t('Sorry. The username %name is not recognized.', array('%name' => theme('placeholder', $name))));
1074   }
1075   else if ($mail && !($form_values['account'] = user_load(array('mail' => $mail, 'status' => 1)))) {
1076     form_set_error('mail', t('Sorry. The e-mail address %email is not recognized.', array('%email' => theme('placeholder', $mail))));
1077   }
1078   else if (!$mail && !$name) {
1079     form_set_error('password', t('You must provide either a username or e-mail address.'));
1080   }
1081 }
1082
1083 function user_pass_submit($form_id, $form_values) {
1084   global $base_url;
1085
1086   $account = $form_values['account'];
1087   $from = variable_get('site_mail', ini_get('sendmail_from'));
1088
1089   // Mail one time login URL and instructions.
1090   $variables = array('%username' => $account->name, '%site' => variable_get('site_name', 'drupal'), '%login_url' => user_pass_reset_url($account), '%uri' => $base_url, '%uri_brief' => substr($base_url, strlen('http://')), '%mailto' => $account->mail, '%date' => format_date(time()), '%login_uri' => url('user', NULL, NULL, TRUE), '%edit_uri' => url('user/'. $account->uid .'/edit', NULL, NULL, TRUE));
1091   $subject = _user_mail_text('pass_subject', $variables);
1092   $body = _user_mail_text('pass_body', $variables);
1093   $headers = "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from";
1094   $mail_success = user_mail($account->mail, $subject, $body, $headers);
1095
1096   if ($mail_success) {
1097     watchdog('user', t('Password reset instructions mailed to %name at %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', $account->mail))));
1098     drupal_set_message(t('Further instructions have been sent to your e-mail address.'));
1099   }
1100   else {
1101     watchdog('user', t('Error mailing password reset instructions to %name at %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', $account->mail))), WATCHDOG_ERROR);
1102     drupal_set_message(t('Unable to send mail. Please contact the site admin.'));
1103   }
1104   return 'user';
1105 }
1106
1107 function theme_user_pass($form) {
1108   $output = '<p>'. t('Enter your username <strong><em>or</em></strong> your e-mail address.') .'</p>';
1109   $output .= form_render($form);
1110   return $output;
1111 }
1112
1113 /**
1114  * Menu callback; process one time login link and redirects to the user page on success.
1115  */
1116 function user_pass_reset($uid, $timestamp, $hashed_pass, $action = NULL) {
1117   global $user;
1118
1119   // Check if the user is already logged in. The back button is often the culprit here.
1120   if ($user->uid) {
1121     drupal_set_message(t('You have already used this one-time login link. It is not necessary to use this link to login anymore. You are already logged in.'));
1122     drupal_goto();
1123   }
1124   else {
1125     // Time out, in seconds, until login URL expires. 24 hours = 86400 seconds.
1126     $timeout = 86400;
1127     $current = time();
1128     // Some redundant checks for extra security ?
1129     if ($timestamp < $current && $account = user_load(array('uid' => $uid, 'status' => 1)) ) {
1130       // No time out for first time login.
1131       if ($account->login && $current - $timestamp > $timeout) {
1132         drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
1133         drupal_goto('user/password');
1134       }
1135       else if ($account->uid && $timestamp > $account->login && $timestamp < $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) {
1136         // First stage is a confirmation form, then login
1137         if ($action == 'login') {
1138           watchdog('user', t('User %name used one-time login link at time %timestamp.', array('%name' => theme('placeholder', $account->name), '%timestamp' => theme('placeholder', $timestamp))));
1139           // Update the user table noting user has logged in.
1140           // And this also makes this hashed password a one-time-only login.
1141           db_query("UPDATE {users} SET login = %d WHERE uid = %d", time(), $account->uid);
1142           // Now we can set the new user.
1143           $user = $account;
1144           // And proceed with normal login, going to user page.
1145           user_module_invoke('login', $edit, $user);
1146           drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to login. Please change your password.'));
1147           drupal_goto('user/'. $user->uid .'/edit');
1148         }
1149         else {
1150           $form['message'] = array('#value' => t('<p>This is a one-time login for %user_name and will expire on %expiration_date</p><p>Click on this button to login to the site and change your password.</p>', array('%user_name' => theme('placeholder',$account->name), '%expiration_date' => format_date($timestamp + $timeout))));
1151           $form['help'] = array('#value' => t('<p>This login can be used only once.</p>'));
1152           $form['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
1153           $form['#action'] = url("user/reset/$uid/$timestamp/$hashed_pass/login");
1154           return drupal_get_form('user_pass_reset', $form);
1155         }
1156       }
1157       else {
1158         drupal_set_message(t('You have tried to use a one-time login link which has either been used or is no longer valid. Please request a new one using the form below.'));
1159         drupal_goto('user/password');
1160       }
1161     }
1162     else {
1163       // Deny access, no more clues.
1164       // Everything will be in the watchdog's URL for the administrator to check.
1165       drupal_access_denied();
1166     }
1167   }
1168 }
1169
1170 function user_pass_reset_url($account) {
1171   $timestamp = time();
1172   return url("user/reset/$account->uid/$timestamp/".user_pass_rehash($account->pass, $timestamp, $account->login), NULL, NULL, TRUE);
1173 }
1174
1175 function user_pass_rehash($password, $timestamp, $login) {
1176   return md5($timestamp . $password . $login);
1177 }
1178
1179 function user_register() {
1180
1181   return user_functions_disabled_message();
1182
1183   global $user;
1184
1185   $admin = user_access('administer users');
1186
1187   // If we aren't admin but already logged on, go to the user page instead.
1188   if (!$admin && $user->uid) {
1189     drupal_goto('user/'. $user->uid);
1190   }
1191
1192   // Display the registration form.
1193   if (!$admin) {
1194     $form['user_registration_help'] = array('#type' => 'markup', '#value' => filter_xss_admin(variable_get('user_registration_help', '')));
1195   }
1196   $affiliates = user_auth_help_links();
1197   if (!$admin && count($affiliates) > 0) {
1198     $affiliates = implode(', ', $affiliates);
1199     $form['affiliates'] = array('#type' => 'markup', '#value' => '<p>'. t('Note: if you have an account with one of our affiliates (%s), you may <a href="%login_uri">login now</a> instead of registering.', array('%s' => $affiliates, '%login_uri' => url('user'))) .'</p>');
1200   }
1201   $form['name'] = array('#type' => 'textfield',
1202     '#title' => t('Username'),
1203     '#size' => 30,
1204     '#maxlength' => 60,
1205     '#description' => t('Your full name or your preferred username; only letters, numbers and spaces are allowed.'),
1206     '#required' => TRUE);
1207   $form['mail'] = array('#type' => 'textfield',
1208     '#title' => t('E-mail address'),
1209     '#size' => 30,
1210     '#maxlength' => 64,
1211     '#description' => t('A password and instructions will be sent to this e-mail address, so make sure it is accurate.'),
1212     '#required' => TRUE,
1213   );
1214   if ($admin) {
1215     $form['pass'] = array('#type' => 'password',
1216       '#title' => t('Password'),
1217       '#size' => 30,
1218       '#description' => t('Provide a password for the new account.'),
1219       '#required' => TRUE,
1220     );
1221     $form['notify'] = array(
1222      '#type' => 'checkbox',
1223      '#title' => t('Notify user of new account')
1224     );
1225   }
1226   $extra = _user_forms($null, $null, $null, 'register');
1227
1228   // Only display form_group around default fields if there are other groups.
1229   if ($extra) {
1230     $form['account'] = array('#type' => 'fieldset', '#title' => t('Account information'));
1231     $form['account']['name'] = $form['name'];
1232     $form['account']['mail'] = $form['mail'];
1233     $form['account']['pass'] = $form['pass'];
1234     $form['account']['notify'] = $form['notify'];
1235     unset($form['name'], $form['mail'], $form['pass'], $form['notify']);
1236     $form = array_merge($form, $extra);
1237   }
1238   $form['submit'] = array('#type' => 'submit', '#value' => t('Create new account'), '#weight' => 30);
1239
1240   return drupal_get_form('user_register', $form);
1241 }
1242
1243 function user_register_validate($form_id, $form_values) {
1244   user_module_invoke('validate', $form_values, $form_values, 'account');
1245 }
1246
1247 function user_register_submit($form_id, $form_values) {
1248   global $base_url;
1249
1250   $admin = user_access('administer users');
1251
1252   $mail = $form_values['mail'];
1253   $name = $form_values['name'];
1254   $pass = $admin ? $form_values['pass'] : user_password();
1255   $notify = $form_values['notify'];
1256   $from = variable_get('site_mail', ini_get('sendmail_from'));
1257
1258   if (!$admin && array_intersect(array_keys($form_values), array('uid', 'roles', 'init', 'session', 'status'))) {
1259     watchdog('security', t('Detected malicious attempt to alter protected user fields.'), WATCHDOG_WARNING);
1260     return 'user/register';
1261   }
1262   //the unset below is needed to prevent these form values from being saved as user data
1263   unset($form_values['form_token'], $form_values['submit'], $form_values['op'], $form_values['notify'], $form_values['form_id'], $form_values['affiliates'], $form_values['destination']);
1264
1265   $account = user_save('', array_merge($form_values, array('pass' => $pass, 'init' => $mail, 'status' => ($admin || variable_get('user_register', 1) == 1))));
1266   watchdog('user', t('New user: %name %email.', array('%name' => theme('placeholder', $name), '%email' => theme('placeholder', '<'. $mail .'>'))), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $account->uid .'/edit'));
1267
1268   $variables = array('%username' => $name, '%site' => variable_get('site_name', 'drupal'), '%password' => $pass, '%uri' => $base_url, '%uri_brief' => substr($base_url, strlen('http://')), '%mailto' => $mail, '%date' => format_date(time()), '%login_uri' => url('user', NULL, NULL, TRUE), '%edit_uri' => url('user/'. $account->uid .'/edit', NULL, NULL, TRUE), '%login_url' => user_pass_reset_url($account));
1269
1270   // The first user may login immediately, and receives a customized welcome e-mail.
1271   if ($account->uid == 1) {
1272     user_mail($mail, t('Drupal user account details for %s', array('%s' => $name)), strtr(t("%username,\n\nYou may now login to %uri using the following username and password:\n\n  username: %username\n  password: %password\n\n%edit_uri\n\n--drupal"), $variables), "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from");
1273     drupal_set_message(t('<p>Welcome to Drupal. You are user #1, which gives you full and immediate access.  All future registrants will receive their passwords via e-mail, so please make sure your website e-mail address is set properly under the general settings on the <a href="%settings">settings page</a>.</p><p> Your password is <strong>%pass</strong>. You may change your password below.</p>', array('%pass' => $pass, '%settings' => url('admin/settings'))));
1274     user_authenticate($account->name, trim($pass));
1275
1276     // Set the installed schema version of the system module to the most recent version.
1277     include_once './includes/install.inc';
1278     drupal_set_installed_schema_version('system', max(drupal_get_schema_versions('system')));
1279
1280     return 'user/1/edit';
1281   }
1282   else {
1283     if ($admin && !$notify) {
1284       drupal_set_message(t('Created a new user account. No e-mail has been sent.'));
1285
1286       return 'admin/user';
1287     }
1288     else if ($account->status || $notify) {
1289       // Create new user account, no administrator approval required.
1290       $subject = $notify ? _user_mail_text('admin_subject', $variables) : _user_mail_text('welcome_subject', $variables);
1291       $body = $notify ? _user_mail_text('admin_body', $variables) : _user_mail_text('welcome_body', $variables);
1292
1293       user_mail($mail, $subject, $body, "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from");
1294
1295       if ($notify) {
1296         drupal_set_message(t('Password and further instructions have been e-mailed to the new user %user.', array('%user' => theme('placeholder', $name))));
1297         return 'admin/user';
1298       }
1299       else {
1300         drupal_set_message(t('Your password and further instructions have been sent to your e-mail address.'));
1301         return '';
1302       }
1303     }
1304     else {
1305       // Create new user account, administrator approval required.
1306       $subject = _user_mail_text('approval_subject', $variables);
1307       $body = _user_mail_text('approval_body', $variables);
1308
1309       user_mail($mail, $subject, $body, "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from");
1310       user_mail(variable_get('site_mail', ini_get('sendmail_from')), $subject, t("%u has applied for an account.\n\n%uri", array('%u' => $account->name, '%uri' => url("user/$account->uid/edit", NULL, NULL, TRUE))), "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from");
1311       drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.<br />In the meantime, your password and further instructions have been sent to your e-mail address.'));
1312
1313     }
1314   }
1315 }
1316
1317 function user_edit_form($uid, $edit) {
1318
1319   return user_functions_disabled_message();
1320
1321   // Account information:
1322   $form['account'] = array('#type' => 'fieldset',
1323     '#title' => t('Account information'),
1324   );
1325   if (user_access('change own username') || user_access('administer users')) {
1326     $form['account']['name'] = array('#type' => 'textfield',
1327       '#title' => t('Username'),
1328       '#default_value' => $edit['name'],
1329       '#maxlength' => 60,
1330       '#description' => t('Your full name or your preferred username: only letters, numbers and spaces are allowed.'),
1331       '#required' => TRUE,
1332     );
1333   }
1334   $form['account']['mail'] = array('#type' => 'textfield',
1335     '#title' => t('E-mail address'),
1336     '#default_value' => $edit['mail'],
1337     '#maxlength' => 64,
1338     '#description' => t('Insert 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.'),
1339     '#required' => TRUE,
1340   );
1341   $form['account']['pass'] = array('#type' => 'password_confirm',
1342     '#title' => t('Password'),
1343     '#description' => t('To change the current user password, enter the new password in both fields.'),
1344   );
1345   if (user_access('administer users')) {
1346     $form['account']['status'] = array('#type' => 'radios', '#title' => t('Status'), '#default_value' => $edit['status'], '#options' => array(t('Blocked'), t('Active')));
1347   }
1348   if (user_access('administer access control')) {
1349     $roles = user_roles(1);
1350     unset($roles[DRUPAL_AUTHENTICATED_RID]);
1351     if ($roles) {
1352       $form['account']['roles'] = array('#type' => 'checkboxes', '#title' => t('Roles'), '#default_value' => array_keys((array)$edit['roles']), '#options' => $roles, '#description' => t('The user receives the combined permissions of the %au role, and all roles selected here.', array('%au' => theme('placeholder', t('authenticated user')))));
1353     }
1354   }
1355
1356   // Picture/avatar:
1357   if (variable_get('user_pictures', 0)) {
1358     $form['picture'] = array('#type' => 'fieldset', '#title' => t('Picture'), '#weight' => 1);
1359     $picture = theme('user_picture', (object)$edit);
1360     if ($picture) {
1361       $form['picture']['current_picture'] = array('#type' => 'markup', '#value' => $picture);
1362       $form['picture']['picture_delete'] = array('#type' => 'checkbox', '#title' => t('Delete picture'), '#description' => t('Check this box to delete your current picture.'));
1363     } else {
1364       $form['picture']['picture_delete'] = array('#type' => 'hidden');
1365     }
1366     $form['picture']['picture_upload'] = array('#type' => 'file', '#title' => t('Upload picture'), '#size' => 48, '#description' => t('Your virtual face or picture.  Maximum dimensions are %dimensions 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', ''));
1367   }
1368
1369   return $form;
1370 }
1371
1372 function _user_edit_validate($uid, &$edit) {
1373   $user = user_load(array('uid' => $uid));
1374   // Validate the username:
1375   if (user_access('change own username') || user_access('administer users') || arg(1) == 'register') {
1376     if ($error = user_validate_name($edit['name'])) {
1377       form_set_error('name', $error);
1378     }
1379     else if (db_num_rows(db_query("SELECT uid FROM {users} WHERE uid != %d AND LOWER(name) = LOWER('%s')", $uid, $edit['name'])) > 0) {
1380       form_set_error('name', t('The name %name is already taken.', array('%name' => theme('placeholder', $edit['name']))));
1381     }
1382     else if (drupal_is_denied('user', $edit['name'])) {
1383       form_set_error('name', t('The name %name has been denied access.', array('%name' => theme('placeholder', $edit['name']))));
1384     }
1385   }
1386
1387   // Validate the e-mail address:
1388   if ($error = user_validate_mail($edit['mail'])) {
1389     form_set_error('mail', $error);
1390   }
1391   else if (db_num_rows(db_query("SELECT uid FROM {users} WHERE uid != %d AND LOWER(mail) = LOWER('%s')", $uid, $edit['mail'])) > 0) {
1392     form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => theme('placeholder', $edit['mail']))));
1393   }
1394   else if (drupal_is_denied('mail', $edit['mail'])) {
1395     form_set_error('mail', t('The e-mail address %email has been denied access.', array('%email' => theme('placeholder', $edit['mail']))));
1396   }
1397
1398   // If required, validate the uploaded picture.
1399   if ($file = file_check_upload('picture_upload')) {
1400     user_validate_picture($file, $edit, $user);
1401   }
1402 }
1403
1404 function _user_edit_submit($uid, &$edit) {
1405   $user = user_load(array('uid' => $uid));
1406   // Delete picture if requested, and if no replacement picture was given.
1407   if ($edit['picture_delete']) {
1408     if ($user->picture && file_exists($user->picture)) {
1409       file_delete($user->picture);
1410     }
1411     $edit['picture'] = '';
1412   }
1413   if (isset($edit['roles'])) {
1414     $edit['roles'] = array_filter($edit['roles']);
1415   }
1416 }
1417
1418 function user_edit($category = 'account') {
1419   global $user;
1420
1421   $account = user_load(array('uid' => arg(1)));
1422   if ($account === FALSE) {
1423     drupal_set_message(t('The account does not exist or has already been deleted.'));
1424     drupal_goto('admin/user');
1425   }
1426   $edit = $_POST['op'] ? $_POST['edit'] : (array)$account;
1427
1428   if (arg(2) == 'delete') {
1429     $form = array();
1430     $form['account'] = array('#type' => 'value', '#value' => $account);
1431     return confirm_form('user_confirm_delete', $form, t('Are you sure you want to delete the account %name?', array('%name' => theme('placeholder', $account->name))), 'user/'. $account->uid, t('All submissions made by this user will be attributed to the anonymous account. This action cannot be undone.'), t('Delete'), t('Cancel'));
1432   }
1433   else if ($_POST['op'] == t('Delete')) {
1434     if ($_REQUEST['destination']) {
1435       $destination = drupal_get_destination();
1436       unset($_REQUEST['destination']);
1437     }
1438     // Note: we redirect from user/uid/edit to user/uid/delete to make the tabs disappear.
1439     drupal_goto("user/$account->uid/delete", $destination);
1440   }
1441
1442   $form = _user_forms($edit, $account, $category);
1443   $form['_category'] = array('#type' => 'value', '#value' => $category);
1444   $form['_account'] = array('#type' => 'value', '#value' => $account);
1445   $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'), '#weight' => 30);
1446   if (user_access('administer users')) {
1447     $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'), '#weight' => 31);
1448   }
1449   $form['#attributes']['enctype'] = 'multipart/form-data';
1450
1451   drupal_set_title(check_plain($account->name));
1452   return drupal_get_form('user_edit', $form);
1453 }
1454
1455 function user_edit_validate($form_id, $form_values) {
1456   user_module_invoke('validate', $form_values, $form_values['_account'], $form_values['_category']);
1457   // Validate input to ensure that non-privileged users can't alter protected data.
1458   if ((!user_access('administer users') && array_intersect(array_keys($form_values), array('uid', 'init', 'session'))) || (!user_access('administer access control') && isset($form_values['roles']))) {
1459     $message = t('Detected malicious attempt to alter protected user fields.');
1460     watchdog('security', $message, WATCHDOG_WARNING);
1461     // set this to a value type field
1462     form_set_error('category', $message);
1463   }
1464 }
1465
1466 function user_edit_submit($form_id, $form_values) {
1467   $account = $form_values['_account'];
1468   $category = $form_values['_category'];
1469   unset($form_values['_account'], $form_values['op'], $form_values['submit'], $form_values['delete'], $form_values['form_token'], $form_values['form_id'], $form_values['_category']);
1470   user_module_invoke('submit', $form_values, $account, $category);
1471   user_save($account, $form_values, $category);
1472   // Delete that user's menu cache.
1473   cache_clear_all('menu:'. $account->uid, TRUE);
1474   drupal_set_message(t('The changes have been saved.'));
1475   return 'user/'. $account->uid;
1476 }
1477
1478 function user_confirm_delete_submit($form_id, $form_values) {
1479   $account = $form_values['account'];
1480   db_query('DELETE FROM {users} WHERE uid = %d', $account->uid);
1481   db_query('DELETE FROM {sessions} WHERE uid = %d', $account->uid);
1482   db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
1483   db_query('DELETE FROM {authmap} WHERE uid = %d', $account->uid);
1484   watchdog('user', t('Deleted user: %name %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', '<'. $account->mail .'>'))), WATCHDOG_NOTICE);
1485   drupal_set_message(t('The account has been deleted.'));
1486   module_invoke_all('user', 'delete', $form_values, $account);
1487   return 'admin/user';
1488 }
1489
1490 function user_view($uid = 0) {
1491   global $user;
1492
1493   $account = user_load(array('uid' => $uid));
1494   if ($account === FALSE || ($account->access == 0 && !user_access('administer users'))) {
1495     return drupal_not_found();
1496   }
1497   // Retrieve and merge all profile fields:
1498   $fields = array();
1499   foreach (module_list() as $module) {
1500     if ($data = module_invoke($module, 'user', 'view', '', $account)) {
1501       foreach ($data as $category => $items) {
1502         foreach ($items as $item) {
1503           $item['class'] = "$module-". $item['class'];
1504           $fields[$category][] = $item;
1505         }
1506       }
1507     }
1508   }
1509   drupal_set_title(check_plain($account->name));
1510   return theme('user_profile', $account, $fields);
1511 }
1512
1513 /*** Administrative features ***********************************************/
1514
1515 function _user_mail_text($messageid, $variables = array()) {
1516
1517   // Check if an admin setting overrides the default string.
1518   if ($admin_setting = variable_get('user_mail_' . $messageid, FALSE)) {
1519     return strtr($admin_setting, $variables);
1520   }
1521   // No override, return with default strings.
1522   else {
1523     switch ($messageid) {
1524       case 'welcome_subject':
1525         return t('Account details for %username at %site', $variables);
1526       case 'welcome_body':
1527         return t("%username,\n\nThank you for registering at %site. You may now log in to %login_uri using the following username and password:\n\nusername: %username\npassword: %password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n%login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to %edit_uri so you can change your password.\n\nYour new %site membership also enables to you to login to other Drupal powered websites (e.g. http://drupal.org/) without registering. Just use the following Drupal ID and password:\n\nDrupal ID: %username@%uri_brief\npassword: %password\n\n\n--  %site team", $variables);
1528       case 'admin_subject':
1529         return t('An administrator created an account for you at %site', $variables);
1530       case 'admin_body':
1531         return t("%username,\n\nA site administrator at %site has created an account for you. You may now log in to %login_uri using the following username and password:\n\nusername: %username\npassword: %password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n%login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to %edit_uri so you can change your password.\n\nYour new %site membership also enables to you to login to other Drupal powered websites (e.g. http://www.drupal.org/) without registering. Just use the following Drupal ID and password:\n\nDrupal ID: %username@%uri_brief\npassword: %password\n\n\n--  %site team", $variables);
1532       case 'approval_subject':
1533         return t('Account details for %username at %site (pending admin approval)', $variables);
1534       case 'approval_body':
1535         return t("%username,\n\nThank you for registering at %site. Your application for an account is currently pending approval. Once it has been granted, you may log in to %login_uri using the following username and password:\n\nusername: %username\npassword: %password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n%login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you may wish to change your password at %edit_uri\n\nYour new %site membership also enables to you to login to other Drupal powered websites (e.g. http://www.drop.org/) without registering. Just use the following Drupal ID and password:\n\nDrupal ID: %username@%uri_brief\npassword: %password\n\n\n--  %site team", $variables);
1536       case 'pass_subject':
1537         return t('Replacement login information for %username at %site', $variables);
1538       case 'pass_body':
1539         return t("%username,\n\nA request to reset the password for your account has been made at %site.\n\nYou may now log in to %uri_brief clicking on this link or copying and pasting it in your browser:\n\n%login_url\n\nThis is a one-time login, so it can be used only once. It expires after one day and nothing will happen if it's not used.\n\nAfter logging in, you will be redirected to %edit_uri so you can change your password.", $variables);
1540     }
1541   }
1542 }
1543
1544 function user_configure_settings() {
1545 }
1546
1547 /**
1548  * Menu callback: check an access rule
1549  */
1550 function user_admin_access_check() {
1551   $form['user'] = array('#type' => 'fieldset', '#title' => t('Username'));
1552   $form['user']['test'] = array('#type' => 'textfield', '#title' => '', '#description' => t('Enter a username to check if it will be denied or allowed.'), '#size' => 30, '#maxlength' => 64);
1553   $form['user']['type'] = array('#type' => 'hidden', '#value' => 'user');
1554   $form['user']['submit'] = array('#type' => 'submit', '#value' => t('Check username'));
1555   $output .= drupal_get_form('check_user', $form, 'user_admin_access_check');
1556   unset($form); // prevent endless loop?
1557
1558   $form['mail'] = array('#type' => 'fieldset', '#title' => t('E-mail'));
1559   $form['mail']['test'] = array('#type' => 'textfield', '#title' => '', '#description' => t('Enter an e-mail address to check if it will be denied or allowed.'), '#size' => 30, '#maxlength' => 64);
1560   $form['mail']['type'] = array('#type' => 'hidden', '#value' => 'mail');
1561   $form['mail']['submit'] = array('#type' => 'submit', '#value' => t('Check e-mail'));
1562   $output .= drupal_get_form('check_mail', $form, 'user_admin_access_check');
1563   unset($form); // prevent endless loop?
1564
1565   $form['host'] = array('#type' => 'fieldset', '#title' => t('Hostname'));
1566   $form['host']['test'] = array('#type' => 'textfield', '#title' => '', '#description' => t('Enter a hostname or IP address to check if it will be denied or allowed.'), '#size' => 30, '#maxlength' => 64);
1567   $form['host']['type'] = array('#type' => 'hidden', '#value' => 'host');
1568   $form['host']['submit'] = array('#type' => 'submit', '#value' => t('Check hostname'));
1569   $output .= drupal_get_form('check_host', $form, 'user_admin_access_check');
1570   unset($form); // prevent endless loop?
1571
1572   return $output;
1573 }
1574
1575 function user_admin_access_check_validate($form_id, $edit) {
1576   if (empty($edit['test'])) {
1577     form_set_error($edit['type'], t('No value entered. Please enter a test string and try again.'));
1578   }
1579 }
1580
1581 function user_admin_access_check_submit($form_id, $edit) {
1582   switch ($edit['type']) {
1583     case 'user':
1584       if (drupal_is_denied('user', $edit['test'])) {
1585         drupal_set_message(t('The username %name is not allowed.', array('%name' => theme('placeholder', $edit['test']))));
1586       }
1587       else {
1588         drupal_set_message(t('The username %name is allowed.', array('%name' => theme('placeholder', $edit['test']))));
1589       }
1590       break;
1591     case 'mail':
1592       if (drupal_is_denied('mail', $edit['test'])) {
1593         drupal_set_message(t('The e-mail address %mail is not allowed.', array('%mail' => theme('placeholder', $edit['test']))));
1594       }
1595       else {
1596         drupal_set_message(t('The e-mail address %mail is allowed.', array('%mail' => theme('placeholder', $edit['test']))));
1597       }
1598       break;
1599     case 'host':
1600       if (drupal_is_denied('host', $edit['test'])) {
1601         drupal_set_message(t('The hostname %host is not allowed.', array('%host' => theme('placeholder', $edit['test']))));
1602       }
1603       else {
1604         drupal_set_message(t('The hostname %host is allowed.', array('%host' => theme('placeholder', $edit['test']))));
1605       }
1606       break;
1607     default:
1608       break;
1609   }
1610 }
1611
1612 /**
1613  * Menu callback: add an access rule
1614  */
1615 function user_admin_access_add($mask = NULL, $type = NULL) {
1616   if ($edit = $_POST['edit']) {
1617     if (!$edit['mask']) {
1618       form_set_error('mask', t('You must enter a mask.'));
1619     }
1620     else if (drupal_valid_token($_POST['edit']['form_token'], 'access_rule', TRUE)) {
1621       $aid = db_next_id('{access}_aid');
1622       db_query("INSERT INTO {access} (aid, mask, type, status) VALUES ('%s', '%s', '%s', %d)", $aid, $edit['mask'], $edit['type'], $edit['status']);
1623       drupal_set_message(t('The access rule has been added.'));
1624       drupal_goto('admin/access/rules');
1625     }
1626     else {
1627       form_set_error('form_token', t('Validation error, please try again.  If this error persists, please contact the site administrator.'));
1628     }
1629   }
1630   else {
1631     $edit['mask'] = $mask;
1632     $edit['type'] = $type;
1633   }
1634
1635   $form = _user_admin_access_form($edit);
1636   $form['submit'] = array('#type' => 'submit', '#value' => t('Add rule'));
1637
1638   return drupal_get_form('access_rule', $form);
1639 }
1640
1641 /**
1642  * Menu callback: delete an access rule
1643  */
1644 function user_admin_access_delete($aid = 0) {
1645   $access_types = array('user' => t('username'), 'mail' => t('e-mail'));
1646   $edit = db_fetch_object(db_query('SELECT aid, type, status, mask FROM {access} WHERE aid = %d', $aid));
1647
1648   $form = array();
1649   $form['aid'] = array('#type' => 'hidden', '#value' => $aid);
1650   $output = confirm_form('user_admin_access_delete_confirm', $form,
1651                   t('Are you sure you want to delete the %type rule for %rule?', array('%type' => $access_types[$edit->type], '%rule' => theme('placeholder', $edit->mask))),
1652                   'admin/access/rules',
1653                   t('This action cannot be undone.'),
1654                   t('Delete'),
1655                   t('Cancel'));
1656   return $output;
1657 }
1658
1659 function user_admin_access_delete_confirm_submit($form_id, $edit) {
1660   db_query('DELETE FROM {access} WHERE aid = %d', $edit['aid']);
1661   drupal_set_message(t('The access rule has been deleted.'));
1662   return 'admin/access/rules';
1663 }
1664
1665 /**
1666  * Menu callback: edit an access rule
1667  */
1668 function user_admin_access_edit($aid = 0) {
1669   if ($edit = $_POST['edit']) {
1670     if (!$edit['mask']) {
1671       form_set_error('mask', t('You must enter a mask.'));
1672     }
1673     else if (drupal_valid_token($_POST['edit']['form_token'], 'access_rule', TRUE)) {
1674       db_query("UPDATE {access} SET mask = '%s', type = '%s', status = '%s' WHERE aid = %d", $edit['mask'], $edit['type'], $edit['status'], $aid);
1675       drupal_set_message(t('The access rule has been saved.'));
1676       drupal_goto('admin/access/rules');
1677     }
1678     else {
1679       form_set_error('form_token', t('Validation error, please try again.  If this error persists, please contact the site administrator.'));
1680     }
1681   }
1682   else {
1683     $edit = db_fetch_array(db_query('SELECT aid, type, status, mask FROM {access} WHERE aid = %d', $aid));
1684   }
1685   $form = _user_admin_access_form($edit);
1686   $form['submit'] = array('#type' => 'submit', '#value' => t('Save rule'));
1687
1688   return drupal_get_form('access_rule', $form);
1689 }
1690
1691 function _user_admin_access_form($edit) {
1692   $form['status'] = array(
1693     '#type' => 'radios',
1694     '#title' => t('Access type'),
1695     '#default_value' => $edit['status'],
1696     '#options' => array('1' => t('Allow'), '0' => t('Deny')),
1697   );
1698   $type_options = array('user' => t('Username'), 'mail' => t('E-mail'), 'host' => t('Host'));
1699   $form['type'] = array(
1700     '#type' => 'radios',
1701     '#title' => t('Rule type'),
1702     '#default_value' => (isset($type_options[$edit['type']]) ? $edit['type'] : 'user'),
1703     '#options' => $type_options,
1704   );
1705   $form['mask'] = array(
1706     '#type' => 'textfield',
1707     '#title' => t('Mask'),
1708     '#size' => 30,
1709     '#maxlength' => 64,
1710     '#default_value' => $edit['mask'],
1711     '#description' => '%: '. t('Matches any number of characters, even zero characters') .'.<br />_: '. t('Matches exactly one character.'),
1712     '#required' => TRUE,
1713   );
1714   return $form;
1715 }
1716
1717 /**
1718  * Menu callback: list all access rules
1719  */
1720 function user_admin_access() {
1721   $header = array(array('data' => t('Access type'), 'field' => 'status'), array('data' => t('Rule type'), 'field' => 'type'), array('data' =>t('Mask'), 'field' => 'mask'), array('data' => t('Operations'), 'colspan' => 2));
1722   $result = db_query("SELECT aid, type, status, mask FROM {access}". tablesort_sql($header));
1723   $access_types = array('user' => t('username'), 'mail' => t('e-mail'), 'host' => t('host'));
1724   $rows = array();
1725   while ($rule = db_fetch_object($result)) {
1726     $rows[] = array($rule->status ? t('allow') : t('deny'), $access_types[$rule->type], $rule->mask, l(t('edit'), 'admin/access/rules/edit/'. $rule->aid), l(t('delete'), 'admin/access/rules/delete/'. $rule->aid));
1727   }
1728   if (count($rows) == 0) {
1729     $rows[] = array(array('data' => '<em>'. t('There are currently no access rules.') .'</em>', 'colspan' => 5));
1730   }
1731   $output .= theme('table', $header, $rows);
1732
1733   return $output;
1734 }
1735
1736 /**
1737  * Retrieve an array of roles matching specified conditions.
1738  *
1739  * @param $membersonly
1740  *   Set this to TRUE to exclude the 'anonymous' role.
1741  * @param $permission
1742  *   A string containing a permission. If set, only roles containing that permission are returned.
1743  *
1744  * @return
1745  *   An associative array with the role id as the key and the role name as value.
1746  */
1747 function user_roles($membersonly = 0, $permission = 0) {
1748   $roles = array();
1749
1750   if ($permission) {
1751     $result = db_query("SELECT r.* FROM {role} r INNER JOIN {permission} p ON r.rid = p.rid WHERE p.perm LIKE '%%%s%%' ORDER BY r.name", $permission);
1752   }
1753   else {
1754     $result = db_query('SELECT * FROM {role} ORDER BY name');
1755   }
1756   while ($role = db_fetch_object($result)) {
1757     if (!$membersonly || ($membersonly && $role->rid != DRUPAL_ANONYMOUS_RID)) {
1758       $roles[$role->rid] = $role->name;
1759     }
1760   }
1761   return $roles;
1762 }
1763
1764 /**
1765  * Menu callback: administer permissions.
1766  */
1767 function user_admin_perm($str_rids = NULL) {
1768   if (preg_match('/^([0-9]+[+ ])*[0-9]+$/', $str_rids)) {
1769     // The '+' character in a query string may be parsed as ' '.
1770     $rids = preg_split('/[+ ]/', $str_rids);
1771   }
1772
1773   if($rids) {
1774     $breadcrumbs = drupal_get_breadcrumb();
1775     $breadcrumbs[] = l(t('all roles'), 'admin/access');
1776     drupal_set_breadcrumb($breadcrumbs);
1777     $result = db_query('SELECT r.rid, p.perm FROM {role} r LEFT JOIN {permission} p ON r.rid = p.rid WHERE r.rid IN (%s) ORDER BY name', implode(', ', $rids));
1778   }
1779   else {
1780     $result = db_query('SELECT r.rid, p.perm FROM {role} r LEFT JOIN {permission} p ON r.rid = p.rid ORDER BY name');
1781   }
1782
1783   // Compile role array:
1784   // Add a comma at the end so when searching for a permission, we can
1785   // always search for "$perm," to make sure we do not confuse
1786   // permissions that are substrings of each other.
1787   while ($role = db_fetch_object($result)) {
1788     $role_permissions[$role->rid] = $role->perm .',';
1789   }
1790
1791   if($rids) {
1792     $result = db_query('SELECT rid, name FROM {role} r WHERE r.rid IN (%s) ORDER BY name', implode(', ', $rids));
1793   }
1794   else {
1795     $result = db_query('SELECT rid, name FROM {role} ORDER BY name');
1796   }
1797   $role_names = array();
1798   while ($role = db_fetch_object($result)) {
1799     $role_names[$role->rid] = $role->name;
1800   }
1801
1802   // Render role/permission overview:
1803   $options = array();
1804   foreach (module_list(FALSE, FALSE, TRUE) as $module) {
1805     if ($permissions = module_invoke($module, 'perm')) {
1806       $form['permission'][] = array('#type' => 'markup', '#value' => t('%module module', array('%module' => $module)));
1807       asort($permissions);
1808       foreach ($permissions as $perm) {
1809         $options[$perm] = '';
1810         $form['permission'][$perm] = array('#type' => 'markup', '#value' => t($perm));
1811         foreach ($role_names as $rid => $name) {
1812           // Builds arrays for checked boxes for each role
1813           if (strpos($role_permissions[$rid], $perm .',') !== FALSE) {
1814             $status[$rid][] = $perm;
1815           }
1816         }
1817       }
1818     }
1819   }
1820   // Have to build checkboxes here after checkbox arrays are built
1821   foreach ($role_names as $rid => $name) {
1822     $form['checkboxes'][$rid] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => $status[$rid]);
1823     $form['role_names'][$rid] = array('#type' => 'markup', '#value' => l($name, 'admin/access/'. $rid), '#tree' => TRUE);
1824   }
1825   $form['submit'] = array('#type' => 'submit', '#value' => t('Save permissions'));
1826
1827   return drupal_get_form('user_admin_perm', $form);
1828 }
1829
1830 function theme_user_admin_perm($form) {
1831   foreach (element_children($form['permission']) as $key) {
1832     // Don't take form control structures
1833     if (is_array($form['permission'][$key])) {
1834       $row = array();
1835       // Module name
1836       if (is_numeric($key)) {
1837         $row[] = array('data' => form_render($form['permission'][$key]), 'class' => 'module', 'colspan' => count($form['role_names']) + 1);
1838       // Permissions
1839       }
1840       else {
1841         $row[] = array('data' => form_render($form['permission'][$key]), 'class' => 'permission');
1842         foreach (element_children($form['checkboxes']) as $rid) {
1843           if (is_array($form['checkboxes'][$rid])) {
1844             $row[] = array('data' => form_render($form['checkboxes'][$rid][$key]), 'align' => 'center', 'title' => t($key));
1845           }
1846         }
1847       }
1848       $rows[] = $row;
1849     }
1850   }
1851   $header[] = (t('Permission'));
1852   foreach (element_children($form['role_names']) as $rid) {
1853     if (is_array($form['role_names'][$rid])) {
1854       $header[] = form_render($form['role_names'][$rid]);
1855     }
1856   }
1857   $output = theme('table', $header, $rows, array('id' => 'permissions'));
1858   $output .= form_render($form);
1859   return $output;
1860 }
1861
1862 function user_admin_perm_submit($form_id, $edit) {
1863   // Save permissions:
1864   $result = db_query('SELECT * FROM {role}');
1865   while ($role = db_fetch_object($result)) {
1866     if(isset($edit[$role->rid])) {
1867       // Delete, so if we clear every checkbox we reset that role;
1868       // otherwise permissions are active and denied everywhere.
1869       db_query('DELETE FROM {permission} WHERE rid = %d', $role->rid);
1870       foreach ($edit[$role->rid] as $key => $value) {
1871         if (!$value) {
1872           unset($edit[$role->rid][$key]);
1873         }
1874       }
1875       if (count($edit[$role->rid])) {
1876         db_query("INSERT INTO {permission} (rid, perm) VALUES (%d, '%s')", $role->rid, implode(', ', array_keys($edit[$role->rid])));
1877       }
1878     }
1879   }
1880
1881   drupal_set_message(t('The changes have been saved.'));
1882
1883   // Clear the cached pages and menus:
1884   menu_rebuild();
1885
1886 }
1887
1888 /**
1889  * Menu callback: administer roles.
1890  */
1891 function user_admin_role() {
1892   $id = arg(4);
1893   if ($id) {
1894     // Display role edit form.
1895     $role = db_fetch_object(db_query('SELECT * FROM {role} WHERE rid = %d', $id));
1896     $form['name'] = array('#type' => 'textfield', '#title' => t('Role name'), '#default_value' => $role->name, '#size' => 30, '#required' => TRUE, '#maxlength' => 64, '#description' => t('The name for this role. Example: "moderator", "editorial board", "site architect".'));
1897     $form['rid'] = array('#type' => 'value', '#value' => $id);
1898     $form['submit'] = array('#type' => 'submit', '#value' => t('Save role'));
1899     $form['delete'] = array('#type' => 'submit', '#value' => t('Delete role'));
1900     return drupal_get_form('user_admin_edit_role', $form);
1901   }
1902   else {
1903     $form['name'] = array('#type' => 'textfield', '#size' => 32, '#maxlength' => 64);
1904     $form['submit'] = array('#type' => 'submit', '#value' => t('Add role'));
1905     return drupal_get_form('user_admin_new_role', $form);
1906   }
1907
1908 }
1909
1910 function user_admin_edit_role_validate($form_id, $form_values) {
1911   if (drupal_valid_token($form_values['form_token'], 'user_admin_edit_role', TRUE)) {
1912     if (db_result(db_query("SELECT COUNT(*) FROM {role} WHERE name = '%s' AND rid != %d", $form_values['name'], $form_values['rid']))) {
1913       form_set_error('name', t('The role name %name already exists. Please choose another role name.', array('%name' => $form_values['name'])));
1914     }
1915   }
1916   else {
1917     form_set_error('form_token', t('Validation error, please try again.  If this error persists, please contact the site administrator.'));
1918   }
1919 }
1920
1921 function user_admin_edit_role_submit($form_id, $form_values) {
1922   $op = isset($_POST['op']) ? $_POST['op'] : '';
1923
1924   if ($op == t('Save role')) {
1925     db_query("UPDATE {role} SET name = '%s' WHERE rid = %d", $form_values['name'], $form_values['rid']);
1926     drupal_set_message(t('The role has been renamed.'));
1927   }
1928   else if ($op == t('Delete role')) {
1929     db_query('DELETE FROM {role} WHERE rid = %d', $form_values['rid']);
1930     db_query('DELETE FROM {permission} WHERE rid = %d', $form_values['rid']);
1931     // Update the users who have this role set:
1932     db_query('DELETE FROM {users_roles} WHERE rid = %d', $form_values['rid']);
1933     drupal_set_message(t('The role has been deleted.'));
1934   }
1935   return 'admin/access/roles';
1936 }
1937
1938 function user_admin_new_role_validate($form_id, $form_values) {
1939   if ($form_values['name']) {
1940     if (drupal_valid_token($form_values['form_token'], 'user_admin_new_role', TRUE)) {
1941       if (db_result(db_query("SELECT COUNT(*) FROM {role} WHERE name = '%s'", $form_values['name']))) {
1942         form_set_error('name', t('The role name %name already exists. Please choose another role name.', array('%name' => $form_values['name'])));
1943       }
1944     }
1945     else {
1946       form_set_error('form_token', t('Validation error, please try again.  If this error persists, please contact the site administrator.'));
1947     }
1948   }
1949   else {
1950     form_set_error('name', t('You must specify a valid role name.'));
1951   }
1952 }
1953
1954 function user_admin_new_role_submit($form_id, $form_values) {
1955   db_query("INSERT INTO {role} (name) VALUES ('%s')", $form_values['name']);
1956   drupal_set_message(t('The role has been added.'));
1957   return 'admin/access/roles';
1958 }
1959
1960 function theme_user_admin_new_role($form) {
1961   $header = array(t('Name'), t('Operations'));
1962   foreach (user_roles() as $rid => $name) {
1963     if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
1964       $rows[] = array($name, l(t('edit'), 'admin/access/roles/edit/'. $rid));
1965     }
1966     else {
1967       $rows[] = array($name, '<span class="disabled">'. t('locked') .'</span>');
1968     }
1969   }
1970   $rows[] = array(form_render($form['name']), form_render($form['submit']));
1971
1972   $output = theme('table', $header, $rows);
1973   $output .= form_render($form);
1974
1975   return $output;
1976 }
1977
1978 function user_admin_account() {
1979   $header = array(
1980     array('data' => t('Username'), 'field' => 'u.name'),
1981     array('data' => t('Status'), 'field' => 'u.status'),
1982     array('data' => t('Member for'), 'field' => 'u.created', 'sort' => 'desc'),
1983     array('data' => t('Last access'), 'field' => 'u.access'),
1984     t('Operations')
1985   );
1986   $sql = 'SELECT u.uid, u.name, u.status, u.created, u.access FROM {users} u WHERE uid != 0';
1987   $sql .= tablesort_sql($header);
1988   $result = pager_query($sql, 50);
1989
1990   $status = array(t('blocked'), t('active'));
1991   while ($account = db_fetch_object($result)) {
1992     $rows[] = array(theme('username', $account),
1993                     $status[$account->status],
1994                     format_interval(time() - $account->created),
1995                     $account->access ? t('%time ago', array('%time' => format_interval(time() - $account->access))) : t('never'),
1996                     l(t('edit'), "user/$account->uid/edit", array()));
1997   }
1998
1999   $output = theme('table', $header, $rows);
2000   $output .= theme('pager', NULL, 50, 0);
2001   return $output;
2002 }
2003
2004 function user_configure() {
2005   // User registration settings.
2006   $form['registration'] = array('#type' => 'fieldset', '#title' => t('User registration settings'));
2007   $form['registration']['user_register'] = array('#type' => 'radios', '#title' => t('Public registrations'), '#default_value' => variable_get('user_register', 1), '#options' => array(t('Only site administrators can create new user accounts.'), t('Visitors can create accounts and no administrator approval is required.'), t('Visitors can create accounts but administrator approval is required.')));
2008   $form['registration']['user_registration_help'] = array('#type' => 'textarea', '#title' => t('User registration guidelines'), '#default_value' => variable_get('user_registration_help', ''), '#description' => t('This text is displayed at the top of the user registration form.  It\'s useful for helping or instructing your users.'));
2009
2010   // User e-mail settings.
2011   $form['email'] = array('#type' => 'fieldset', '#title' => t('User e-mail settings'));
2012   $form['email']['user_mail_welcome_subject'] = array('#type' => 'textfield', '#title' => t('Subject of welcome e-mail'), '#default_value' => _user_mail_text('welcome_subject'), '#maxlength' => 180, '#description' => t('Customize the subject of your welcome e-mail, which is sent to new members upon registering.') .' '. t('Available variables are:') .' %username, %site, %password, %uri, %uri_brief, %mailto, %date, %login_uri, %edit_uri, %login_url.');
2013   $form['email']['user_mail_welcome_body'] = array('#type' => 'textarea', '#title' => t('Body of welcome e-mail'), '#default_value' => _user_mail_text('welcome_body'), '#rows' => 15, '#description' => t('Customize the body of the welcome e-mail, which is sent to new members upon registering.') .' '. t('Available variables are:') .' %username, %site, %password, %uri, %uri_brief, %mailto, %login_uri, %edit_uri, %login_url.');
2014   $form['email']['user_mail_admin_subject'] = array('#type' => 'textfield', '#title' => t('Subject of welcome e-mail (user created by administrator)'), '#default_value' => _user_mail_text('admin_subject'), '#maxlength' => 180, '#description' => t('Customize the subject of your welcome e-mail, which is sent to new member accounts created by an administrator.') .' '. t('Available variables are:') .' %username, %site, %password, %uri, %uri_brief, %mailto, %date, %login_uri, %edit_uri, %login_url.');
2015   $form['email']['user_mail_admin_body'] = array('#type' => 'textarea', '#title' => t('Body of welcome e-mail (user created by administrator)'), '#default_value' => _user_mail_text('admin_body'), '#rows' => 15, '#description' => t('Customize the body of the welcome e-mail, which is sent to new member accounts created by an administrator.') .' '. t('Available variables are:') .' %username, %site, %password, %uri, %uri_brief, %mailto, %login_uri, %edit_uri, %login_url.');
2016   $form['email']['user_mail_approval_subject'] = array('#type' => 'textfield', '#title' => t('Subject of welcome e-mail (awaiting admin approval)'), '#default_value' => _user_mail_text('approval_subject'), '#maxlength' => 180, '#description' => t('Customize the subject of your awaiting approval welcome e-mail, which is sent to new members upon registering.') .' '. t('Available variables are:') .' %username, %site, %password, %uri, %uri_brief, %mailto, %date, %login_uri, %edit_uri, %login_url.');
2017   $form['email']['user_mail_approval_body'] = array('#type' => 'textarea', '#title' => t('Body of welcome e-mail (awaiting admin approval)'), '#default_value' => _user_mail_text('approval_body'), '#rows' => 15, '#description' => t('Customize the body of the awaiting approval welcome e-mail, which is sent to new members upon registering.') .' '. t('Available variables are:') .' %username, %site, %password, %uri, %uri_brief, %mailto, %login_uri, %edit_uri, %login_url.');
2018   $form['email']['user_mail_pass_subject'] = array('#type' => 'textfield', '#title' => t('Subject of password recovery e-mail'), '#default_value' => _user_mail_text('pass_subject'), '#maxlength' => 180, '#description' => t('Customize the Subject of your forgotten password e-mail.') .' '. t('Available variables are:') .' %username, %site, %login_url, %uri, %uri_brief, %mailto, %date, %login_uri, %edit_uri.');
2019   $form['email']['user_mail_pass_body'] = array('#type' => 'textarea', '#title' => t('Body of password recovery e-mail'), '#default_value' => _user_mail_text('pass_body'), '#rows' => 15, '#description' => t('Customize the body of the forgotten password e-mail.') .' '. t('Available variables are:') .' %username, %site, %login_url, %uri, %uri_brief, %mailto, %login_uri, %edit_uri.');
2020
2021   // If picture support is enabled, check whether the picture directory exists:
2022   if (variable_get('user_pictures', 0)) {
2023     $picture_path = file_create_path(variable_get('user_picture_path', 'pictures'));
2024     file_check_directory($picture_path, 1, 'user_picture_path');
2025   }
2026
2027   $form['pictures'] = array('#type' => 'fieldset', '#title' => t('Pictures'));
2028   $form['pictures']['user_pictures'] = array('#type' => 'radios', '#title' => t('Picture support'), '#default_value' => variable_get('user_pictures', 0), '#options' => array(t('Disabled'), t('Enabled')), '#description' => t('Enable picture support.'));
2029   $form['pictures']['user_picture_path'] = array('#type' => 'textfield', '#title' => t('Picture image path'), '#default_value' => variable_get('user_picture_path', 'pictures'), '#size' => 30, '#maxlength' => 255, '#description' => t('Subdirectory in the directory "%dir" where pictures will be stored.', array('%dir' => file_directory_path() .'/')));
2030   $form['pictures']['user_picture_default'] = array('#type' => 'textfield', '#title' => t('Default picture'), '#default_value' => variable_get('user_picture_default', ''), '#size' => 30, '#maxlength' => 255, '#description' => t('URL of picture to display for users with no custom picture selected. Leave blank for none.'));
2031   $form['pictures']['user_picture_dimensions'] = array('#type' => 'textfield', '#title' => t('Picture maximum dimensions'), '#default_value' => variable_get('user_picture_dimensions', '85x85'), '#size' => 15, '#maxlength' => 10, '#description' => t('Maximum dimensions for pictures.'));
2032   $form['pictures']['user_picture_file_size'] = array('#type' => 'textfield', '#title' => t('Picture maximum file size'), '#default_value' => variable_get('user_picture_file_size', '30'), '#size' => 15, '#maxlength' => 10, '#description' => t('Maximum file size for pictures, in kB.'));
2033   $form['pictures']['user_picture_guidelines'] = array('#type' => 'textarea', '#title' => t('Picture guidelines'), '#default_value' => variable_get('user_picture_guidelines', ''), '#description' => t('This text is displayed at the picture upload form in addition to the default guidelines.  It\'s useful for helping or instructing your users.'));
2034
2035   return system_settings_form('user_configure_settings', $form);
2036 }
2037
2038 function user_admin() {
2039   $edit = isset($_POST['edit']) ? $_POST['edit'] : '';
2040   $op = isset($_POST['op']) ? $_POST['op'] : '';
2041
2042   if (empty($op)) {
2043     $op = arg(2);
2044   }
2045
2046   switch ($op) {
2047     case 'search':
2048     case t('Search'):
2049       $output = search_form(url('admin/user/search'), $_POST['edit']['keys'], 'user') . search_data($_POST['edit']['keys'], 'user');
2050       break;
2051     case t('Create new account'):
2052     case 'create':
2053       $output = user_register();
2054       break;
2055     default:
2056       $output = user_admin_account();
2057   }
2058   return $output;
2059 }
2060
2061 /**
2062  * Implementation of hook_help().
2063  */
2064 function user_help($section) {
2065   global $user;
2066
2067   switch ($section) {
2068     case 'admin/help#user':
2069       $output = '<p>'. t('The user module allows users to register, login, and logout. Users benefit from being able to sign on because it associates content they create with their account and allows various permissions to be set for their roles.  The user module supports user roles which can setup fine grained permissions allowing each role to do only what the administrator wants them to. Each user is assigned to one or more roles. By default there are two roles <em>anonymous</em> - a user who has not logged in, and <em>authenticated</em> a user who has signed up and who has been authorized. ') .'</p>';
2070       $output .= '<p>'. t('Users can use their own name or handle and can fine tune some personal configuration settings through their individual my account page.  Registered users need to authenticate by supplying either a local username and password, or a remote username and password such as DelphiForums ID, or one from a Drupal powered website.  A visitor accessing your website is assigned an unique ID, the so-called session ID, which is stored in a cookie. For security\'s sake, the cookie does not contain personal information but acts as a key to retrieve the information stored on your server. ') .'</p>';
2071       $output .= t('<p>You can</p>
2072 <ul>
2073 <li>view your <a href="%user">user page</a>.</li>
2074 <li>administer users at <a href="%admin-user">administer &gt;&gt; user</a>.</li>
2075 <li>allow users who have the "select different theme" permission to select themes from their user account by enabling themes in <a href="%admin-themes">administer &gt;&gt; themes</a>.</li>
2076 <li>read user profile help at <a href="%admin-help-profile">administer &gt;&gt; help &gt;&gt; profile</a>.</li>
2077 <li>read about distributed authentication in the system module help at <a href="%admin-help-system">administer &gt;&gt; help &gt;&gt; system</a>.</li>
2078 </ul>
2079 ', array('%user' => url('user'), '%admin-user' => url('admin/user'), '%admin-themes' => url('admin/themes'), '%admin-help-profile' => url('admin/help/profile'), '%admin-help-system' => url('admin/help/system')));
2080       $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="%user">User page</a>.', array('%user' => 'http://drupal.org/handbook/modules/user/')) .'</p>';
2081       return $output;
2082     case 'admin/modules#description':
2083       return t('Manages the user registration and login system.');
2084     case 'admin/user':
2085       return t('<p>Drupal allows users to register, login, logout, maintain user profiles, etc. No participant can use his own name to post content until he signs up for a user account.</p>');
2086     case 'admin/user/create':
2087     case 'admin/user/account/create':
2088       return t('<p>This web page allows the administrators to register a new users by hand. Note that you cannot have a user where either the e-mail address or the username match another user in the system.</p>');
2089     case strstr($section, 'admin/access/rules'):
2090       return t('<p>Set up username and e-mail address access rules for new <em>and</em> existing accounts (currently logged in accounts will not be logged out). If a username or e-mail address for an account matches any deny rule, but not an allow rule, then the account will not be allowed to be created or to log in. A host rule is effective for every page view, not just registrations.</p>');
2091     case 'admin/access':
2092       return t('<p>Permissions let you control what users can do on your site.  Each user role (defined on the <a href="%role">user roles page</a>) has its own set of permissions. For example, you could give users classified as "Administrators" permission to "administer nodes" but deny this power to ordinary, "authenticated" users. You can use permissions to reveal new features to privileged users (those with subscriptions, for example). Permissions also allow trusted users to share the administrative burden of running a busy site.</p>', array('%role' => url('admin/access/roles')));
2093     case 'admin/access/roles':
2094       return t('<p>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 in <a href="%permissions">user permissions</a>. Examples of roles include: anonymous user, authenticated user, moderator, administrator and so on. In this area you will define the <em>role names</em> of the various roles. To delete a role choose "edit".</p><p>By default, Drupal comes with two user roles:</p>
2095       <ul>
2096       <li>Anonymous user: this role is used for users that don\'t have a user account or that are not authenticated.</li>
2097       <li>Authenticated user: this role is automatically granted to all logged in users.</li>
2098       </ul>', array('%permissions' => url('admin/access/permissions')));
2099     case 'admin/user/search':
2100       return t('<p>Enter a simple pattern ("*" may be used as a wildcard match) to search for a username.  For example, one may search for "br" and Drupal might return "brian", "brad", and "brenda".</p>');
2101     case 'admin/user/configure':
2102     case 'admin/user/configure/settings':
2103       return t('<p>In order to use the full power of Drupal a visitor must sign up for an account. This page lets you setup how a user signs up, logs out, the guidelines from the system about user subscriptions, and the e-mails the system will send to the user.</p>');
2104     case 'user/help#user':
2105       $site = variable_get('site_name', 'this website');
2106
2107       $output = t("
2108       <h3>Distributed authentication<a id=\"da\"></a></h3>
2109       <p>One of the more tedious moments in visiting a new website is filling out the registration form. Here at %site, you do not have to fill out a registration form if you are already a member of %help-links. This capability is called <em>distributed authentication</em>, and is unique to <a href=\"%drupal\">Drupal</a>, the software which powers %site.</p>
2110       <p>Distributed authentication enables a new user to input a username and password into the login box, and immediately be recognized, even if that user never registered at %site. This works because Drupal knows how to communicate with external registration databases. For example, lets say that new user 'Joe' is already a registered member of <a href=\"%delphi-forums\">Delphi Forums</a>. Drupal informs Joe on registration and login screens that he may login with his Delphi ID instead of registering with %site. Joe likes that idea, and logs in with a username of joe@remote.delphiforums.com and his usual Delphi password. Drupal then contacts the <em>remote.delphiforums.com</em> server behind the scenes (usually using <a href=\"%xml\">XML-RPC</a>, <a href=\"%http-post\">HTTP POST</a>, or <a href=\"%soap\">SOAP</a>) and asks: \"Is the password for user Joe correct?\".  If Delphi replies yes, then we create a new %site account for Joe and log him into it.  Joe may keep on logging into %site in the same manner, and he will always be logged into the same account.</p>", array('%help-links' => (implode(', ', user_auth_help_links())), '%site' => "<em>$site</em>", '%drupal' => 'http://drupal.org', '%delphi-forums' => 'http://www.delphiforums.com', '%xml' => 'http://www.xmlrpc.com', '%http-post' => 'http://www.w3.org/Protocols/', '%soap' => 'http://www.soapware.org'));
2111
2112         foreach (module_list() as $module) {
2113           if (module_hook($module, 'auth')) {
2114             $output .= "<h4><a id=\"$module\"></a>". module_invoke($module, 'info', 'name') .'</h4>';
2115             $output .= module_invoke($module, 'help', "user/help#$module");
2116           }
2117         }
2118
2119         return $output;
2120   }
2121
2122 }
2123
2124 /**
2125  * Menu callback; Prints user-specific help information.
2126  */
2127 function user_help_page() {
2128   return user_help('user/help#user');
2129 }
2130
2131 /**
2132  * Retrieve a list of all user setting/information categories and sort them by weight.
2133  */
2134 function _user_categories($account) {
2135   $categories = array();
2136
2137   foreach (module_list() as $module) {
2138     if ($data = module_invoke($module, 'user', 'categories', NULL, $account, '')) {
2139       $categories = array_merge($data, $categories);
2140     }
2141   }
2142
2143   usort($categories, '_user_sort');
2144
2145   return $categories;
2146 }
2147
2148 function _user_sort($a, $b) {
2149   return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1));
2150 }
2151
2152 /**
2153  * Retrieve a list of all form elements for the specified category.
2154  */
2155 function _user_forms(&$edit, $account, $category, $hook = 'form') {
2156   $groups = array();
2157   foreach (module_list() as $module) {
2158     if ($data = module_invoke($module, 'user', $hook, $edit, $account, $category)) {
2159       $groups = array_merge_recursive($data, $groups);
2160     }
2161   }
2162   uasort($groups, '_user_sort');
2163
2164   return empty($groups) ? FALSE : $groups;
2165 }
2166
2167 /**
2168  * Retrieve a pipe delimited string of autocomplete suggestions for existing users
2169  */
2170 function user_autocomplete($string) {
2171   $matches = array();
2172   if ($string) {
2173     $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $string, 0, 10);
2174     while ($user = db_fetch_object($result)) {
2175       $matches[$user->name] = check_plain($user->name);
2176    }
2177   }
2178   print drupal_to_js($matches);
2179   exit();
2180 }