user.module 123 KB
Newer Older
Dries's avatar
 
Dries committed
1 2 3
<?php
// $Id$

Dries's avatar
 
Dries committed
4 5 6 7 8
/**
 * @file
 * Enables the user registration and login system.
 */

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

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

19 20 21 22 23 24 25 26 27 28
/**
 * Implement hook_help().
 */
function user_help($path, $arg) {
  global $user;

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

Dries's avatar
Dries committed
61 62 63
/**
 * Invokes hook_user() in every module.
 *
64
 * We cannot use module_invoke() for this, because the arguments need to
Dries's avatar
Dries committed
65 66
 * be passed by reference.
 */
67
function user_module_invoke($type, &$edit, $account, $category = NULL) {
68
  foreach (module_implements('user_' . $type) as $module) {
69
    $function = $module . '_user_' . $type;
70
    $function($edit, $account, $category);
Dries's avatar
 
Dries committed
71 72 73
  }
}

74
/**
75
 * Implements hook_theme().
76 77 78 79
 */
function user_theme() {
  return array(
    'user_picture' => array(
80
      'variables' => array('account' => NULL),
81
      'template' => 'user-picture',
82 83
    ),
    'user_profile' => array(
84
      'render element' => 'elements',
85
      'template' => 'user-profile',
86
      'file' => 'user.pages.inc',
87 88
    ),
    'user_profile_category' => array(
89
      'render element' => 'element',
90
      'template' => 'user-profile-category',
91
      'file' => 'user.pages.inc',
92 93
    ),
    'user_profile_item' => array(
94
      'render element' => 'element',
95
      'template' => 'user-profile-item',
96
      'file' => 'user.pages.inc',
97 98
    ),
    'user_list' => array(
99
      'variables' => array('users' => NULL, 'title' => NULL),
100
    ),
101
    'user_admin_permissions' => array(
102
      'render element' => 'form',
103
      'file' => 'user.admin.inc',
104
    ),
105
    'user_admin_roles' => array(
106
      'render element' => 'form',
107
      'file' => 'user.admin.inc',
108 109
    ),
    'user_filters' => array(
110
      'render element' => 'form',
111
      'file' => 'user.admin.inc',
112
    ),
113 114 115 116
    'user_permission_description' => array(
      'variables' => array('permission_item' => NULL, 'hide' => NULL),
      'file' => 'user.admin.inc',
    ),
117
    'user_signature' => array(
118
      'variables' => array('signature' => NULL),
119
    ),
120 121 122
  );
}

Dries's avatar
 
Dries committed
123
/**
124
 * Implements hook_entity_info().
Dries's avatar
 
Dries committed
125
 */
126
function user_entity_info() {
Dries's avatar
 
Dries committed
127 128
  $return = array(
    'user' => array(
129
      'label' => t('User'),
130 131
      'controller class' => 'UserController',
      'base table' => 'users',
132
      'uri callback' => 'user_uri',
133
      'fieldable' => TRUE,
134
      'entity keys' => array(
135 136 137 138 139 140
        'id' => 'uid',
      ),
      'bundles' => array(
        'user' => array(
          'label' => t('User'),
          'admin' => array(
141
            'path' => 'admin/config/people/accounts',
142 143 144 145
            'access arguments' => array('administer users'),
          ),
        ),
      ),
146 147 148 149 150
      'view modes' => array(
        'full' => array(
          'label' => t('User account'),
        ),
      ),
Dries's avatar
 
Dries committed
151 152 153 154 155
    ),
  );
  return $return;
}

156
/**
157
 * Entity uri callback.
158
 */
159 160 161 162
function user_uri($user) {
  return array(
    'path' => 'user/' . $user->uid,
  );
163 164
}

165
/**
166
 * Implements hook_field_extra_fields().
167
 */
168 169 170
function user_field_extra_fields() {
  $return['user']['user'] = array(
    'account' => array(
171 172 173
      'label' => 'User name and password',
      'description' => t('User module account form elements'),
      'weight' => -10,
174 175
    ),
    'timezone' => array(
176 177 178
      'label' => 'Timezone',
      'description' => t('User module timezone form element.'),
      'weight' => 6,
179 180
    ),
    'summary' => array(
181 182 183
      'label' => 'History',
      'description' => t('User module history view element.'),
      'weight' => 5,
184 185
    ),
  );
186

187
  return $return;
188 189
}

190 191 192 193 194 195 196 197 198
/**
 * Fetches a user object based on an external authentication source.
 *
 * @param string $authname
 *   The external authentication username.
 *
 * @return
 *   A fully-loaded user object if the user is found or FALSE if not found.
 */
Dries's avatar
 
Dries committed
199
function user_external_load($authname) {
200
  $uid = db_query("SELECT uid FROM {authmap} WHERE authname = :authname", array(':authname' => $authname))->fetchField();
Dries's avatar
 
Dries committed
201

202 203
  if ($uid) {
    return user_load($uid);
Dries's avatar
 
Dries committed
204 205
  }
  else {
206
    return FALSE;
Dries's avatar
 
Dries committed
207 208 209
  }
}

Dries's avatar
Dries committed
210
/**
211
 * Load multiple users based on certain conditions.
Dries's avatar
Dries committed
212
 *
213 214 215
 * This function should be used whenever you need to load more than one user
 * from the database. Users are loaded into memory and will not require
 * database access if loaded again during the same page request.
Dries's avatar
Dries committed
216
 *
217 218 219 220 221 222 223 224
 * @param $uids
 *   An array of user IDs.
 * @param $conditions
 *   An array of conditions to match against the {users} table. These
 *   should be supplied in the form array('field_name' => 'field_value').
 * @param $reset
 *   A boolean indicating that the internal cache should be reset. Use this if
 *   loading a user object which has been altered during the page request.
225
 * @return
226 227
 *   An array of user objects, indexed by uid.
 *
228
 * @see entity_load()
229 230 231
 * @see user_load()
 * @see user_load_by_mail()
 * @see user_load_by_name()
Dries's avatar
Dries committed
232
 */
233
function user_load_multiple($uids = array(), $conditions = array(), $reset = FALSE) {
234 235
  return entity_load('user', $uids, $conditions, $reset);
}
236

237 238 239 240 241 242 243
/**
 * Controller class for users.
 *
 * This extends the DrupalDefaultEntityController class, adding required
 * special handling for user objects.
 */
class UserController extends DrupalDefaultEntityController {
244 245

  function attachLoad(&$queried_users, $revision_id = FALSE) {
246 247
    // Build an array of user picture IDs so that these can be fetched later.
    $picture_fids = array();
248
    foreach ($queried_users as $key => $record) {
249
      $picture_fids[] = $record->picture;
250
      $queried_users[$key]->data = unserialize($record->data);
251
      $queried_users[$key]->roles = array();
252 253 254 255 256 257
      if ($record->uid) {
        $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
      }
      else {
        $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
      }
258
    }
259

260 261 262 263 264
    // Add any additional roles from the database.
    $result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users)));
    foreach ($result as $record) {
      $queried_users[$record->uid]->roles[$record->rid] = $record->name;
    }
Dries's avatar
 
Dries committed
265

266 267 268 269 270 271 272 273 274
    // Add the full file objects for user pictures if enabled.
    if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) {
      $pictures = file_load_multiple($picture_fids);
      foreach ($queried_users as $account) {
        if (!empty($account->picture) && isset($pictures[$account->picture])) {
          $account->picture = $pictures[$account->picture];
        }
        else {
          $account->picture = NULL;
275 276 277
        }
      }
    }
278 279
    // Call the default attachLoad() method. This will add fields and call
    // hook_user_load().
280
    parent::attachLoad($queried_users, $revision_id);
Dries's avatar
 
Dries committed
281
  }
282 283 284
}

/**
285 286 287 288 289 290 291 292 293
 * Loads a user object.
 *
 * Drupal has a global $user object, which represents the currently-logged-in
 * user. So to avoid confusion and to avoid clobbering the global $user object,
 * it is a good idea to assign the result of this function to a different local
 * variable, generally $account. If you actually do want to act as the user you
 * are loading, it is essential to call @code session_save_session(FALSE);
 * @endcode first. See @link http://drupal.org/node/218104 Safely impersonating
 * another user @endlink for more information.
294 295
 *
 * @param $uid
296
 *   Integer specifying the user ID to load.
297
 * @param $reset
298 299 300
 *   TRUE to reset the internal cache and load from the database; FALSE
 *   (default) to load from the internal cache, if set.
 *
301
 * @return
302
 *   A fully-loaded user object upon successful user load, or FALSE if the user
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load($uid, $reset = FALSE) {
  $users = user_load_multiple(array($uid), array(), $reset);
  return reset($users);
}

/**
 * Fetch a user object by email address.
 *
 * @param $mail
 *   String with the account's e-mail address.
 * @return
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load_by_mail($mail) {
  $users = user_load_multiple(array(), array('mail' => $mail));
  return reset($users);
}

/**
 * Fetch a user object by account name.
 *
 * @param $name
 *   String with the account's user name.
 * @return
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 *
 * @see user_load_multiple()
 */
function user_load_by_name($name) {
  $users = user_load_multiple(array(), array('name' => $name));
  return reset($users);
Dries's avatar
 
Dries committed
342 343
}

344
/**
345
 * Save changes to a user account or add a new user.
346 347
 *
 * @param $account
348
 *   (optional) The user object to modify or add. If you want to modify
349 350
 *   an existing user account, you will need to ensure that (a) $account
 *   is an object, and (b) you have set $account->uid to the numeric
351 352
 *   user ID of the user account you wish to modify. If you
 *   want to create a new user account, you can set $account->is_new to
353
 *   TRUE or omit the $account->uid field.
354
 * @param $edit
355
 *   An array of fields and values to save. For example array('name'
356 357
 *   => 'My name'). Key / value pairs added to the $edit['data'] will be
 *   serialized and saved in the {users.data} column.
358 359
 * @param $category
 *   (optional) The category for storing profile information in.
360 361
 *
 * @return
362
 *   A fully-loaded $user object upon successful save or FALSE if the save failed.
363
 */
364
function user_save($account, $edit = array(), $category = 'account') {
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
  $transaction = db_transaction();
  try {
    $table = drupal_get_schema('users');

    if (!empty($edit['pass'])) {
      // Allow alternate password hashing schemes.
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
      $edit['pass'] = user_hash_password(trim($edit['pass']));
      // Abort if the hashing failed and returned FALSE.
      if (!$edit['pass']) {
        return FALSE;
      }
    }
    else {
      // Avoid overwriting an existing password with a blank password.
      unset($edit['pass']);
381
    }
382

383
    // Presave field allowing changing of $edit.
384 385 386
    $edit = (object) $edit;
    field_attach_presave('user', $edit);
    $edit = (array) $edit;
Dries's avatar
 
Dries committed
387

388 389 390
    if (empty($account)) {
      $account = new stdClass();
    }
391 392 393
    if (!isset($account->is_new)) {
      $account->is_new = empty($account->uid);
    }
394 395 396
    // Prepopulate $edit['data'] with the current value of $account->data.
    // Modules can add to or remove from this array in hook_user_presave().
    if (!empty($account->data)) {
397
      $edit['data'] = !empty($edit['data']) ? array_merge($account->data, $edit['data']) : $account->data;
398
    }
399
    user_module_invoke('presave', $edit, $account, $category);
400

401 402 403 404 405 406
    if (is_object($account) && !$account->is_new) {
      // Consider users edited by an administrator as logged in, if they haven't
      // already, so anonymous users can view the profile (if allowed).
      if (empty($edit['access']) && empty($account->access) && user_access('administer users')) {
        $edit['access'] = REQUEST_TIME;
      }
Dries's avatar
 
Dries committed
407

408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
      // Process picture uploads.
      if (!empty($edit['picture']->fid)) {
        $picture = $edit['picture'];
        // If the picture is a temporary file move it to its final location and
        // make it permanent.
        if (($picture->status & FILE_STATUS_PERMANENT) == 0) {
          $info = image_get_info($picture->uri);
          $picture_directory =  variable_get('file_default_scheme', 'public') . '://' . variable_get('user_picture_path', 'pictures');

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

          if ($picture = file_move($picture, $destination, FILE_EXISTS_REPLACE)) {
            $picture->status |= FILE_STATUS_PERMANENT;
            $edit['picture'] = file_save($picture);
          }
425 426
        }
      }
427 428 429 430 431 432 433 434 435 436 437
      $edit['picture'] = empty($edit['picture']->fid) ? 0 : $edit['picture']->fid;

      // Do not allow 'uid' to be changed.
      $edit['uid'] = $account->uid;
      // Save changes to the user table.
      $success = drupal_write_record('users', $edit, 'uid');
      if ($success === FALSE) {
        // The query failed - better to abort the save than risk further
        // data loss.
        return FALSE;
      }
Dries's avatar
 
Dries committed
438

439 440 441 442 443 444
      // If the picture changed or was unset, remove the old one. This step needs
      // to occur after updating the {users} record so that user_file_references()
      // doesn't report it in use and block the deletion.
      if (!empty($account->picture->fid) && ($edit['picture'] != $account->picture->fid)) {
        file_delete($account->picture);
      }
445

446 447 448 449 450 451 452 453 454 455 456 457 458 459
      // Reload user roles if provided.
      if (isset($edit['roles']) && is_array($edit['roles'])) {
        db_delete('users_roles')
          ->condition('uid', $account->uid)
          ->execute();

        $query = db_insert('users_roles')->fields(array('uid', 'rid'));
        foreach (array_keys($edit['roles']) as $rid) {
          if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
            $query->values(array(
              'uid' => $account->uid,
              'rid' => $rid,
            ));
          }
460
        }
461
        $query->execute();
462
      }
Dries's avatar
 
Dries committed
463

464 465 466 467
      // Delete a blocked user's sessions to kick them if they are online.
      if (isset($edit['status']) && $edit['status'] == 0) {
        drupal_session_destroy_uid($account->uid);
      }
468

469 470 471 472 473 474 475
      // If the password changed, delete all open sessions and recreate
      // the current one.
      if (!empty($edit['pass'])) {
        drupal_session_destroy_uid($account->uid);
        if ($account->uid == $GLOBALS['user']->uid) {
          drupal_session_regenerate();
        }
476
      }
477

478
      // Save Field data.
479 480
      $entity = (object) $edit;
      field_attach_update('user', $entity);
Dries's avatar
 
Dries committed
481

482 483
      // Refresh user object.
      $user = user_load($account->uid, TRUE);
484

485 486 487 488 489 490
      // Send emails after we have the new user object.
      if (isset($edit['status']) && $edit['status'] != $account->status) {
        // The user's status is changing; conditionally send notification email.
        $op = $edit['status'] == 1 ? 'status_activated' : 'status_blocked';
        _user_mail_notify($op, $user);
      }
491

492
      user_module_invoke('update', $edit, $user, $category);
493
      entity_invoke('update', 'user', $user);
494
    }
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
    else {
      // Allow 'uid' to be set by the caller. There is no danger of writing an
      // existing user as drupal_write_record will do an INSERT.
      if (empty($edit['uid'])) {
        $edit['uid'] = db_next_id(db_query('SELECT MAX(uid) FROM {users}')->fetchField());
      }
      // Allow 'created' to be set by the caller.
      if (!isset($edit['created'])) {
        $edit['created'] = REQUEST_TIME;
      }
      // Consider users created by an administrator as already logged in, so
      // anonymous users can view the profile (if allowed).
      if (empty($edit['access']) && user_access('administer users')) {
        $edit['access'] = REQUEST_TIME;
      }
510

511 512 513 514 515 516 517
      $edit['mail'] = trim($edit['mail']);
      $success = drupal_write_record('users', $edit);
      if ($success === FALSE) {
        // On a failed INSERT some other existing user's uid may be returned.
        // We must abort to avoid overwriting their account.
        return FALSE;
      }
518

519 520 521
      // Build a stub user object.
      $user = (object) $edit;
      $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
Dries's avatar
 
Dries committed
522

523
      field_attach_insert('user', $user);
Dries's avatar
 
Dries committed
524

525
      user_module_invoke('insert', $edit, $user, $category);
526
      entity_invoke('insert', 'user', $user);
527

528
      // Save user roles.
529 530 531 532 533 534 535 536 537
      if (isset($edit['roles']) && is_array($edit['roles'])) {
        $query = db_insert('users_roles')->fields(array('uid', 'rid'));
        foreach (array_keys($edit['roles']) as $rid) {
          if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
            $query->values(array(
              'uid' => $edit['uid'],
              'rid' => $rid,
            ));
          }
538
        }
539
        $query->execute();
540 541 542
      }
    }

543 544 545 546
    return $user;
  }
  catch (Exception $e) {
    $transaction->rollback('user', $e->getMessage(), array(), WATCHDOG_ERROR);
547
    throw $e;
Dries's avatar
 
Dries committed
548 549 550
  }
}

Dries's avatar
Dries committed
551 552 553
/**
 * Verify the syntax of the given name.
 */
Dries's avatar
 
Dries committed
554
function user_validate_name($name) {
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
  if (!$name) {
    return t('You must enter a username.');
  }
  if (substr($name, 0, 1) == ' ') {
    return t('The username cannot begin with a space.');
  }
  if (substr($name, -1) == ' ') {
    return t('The username cannot end with a space.');
  }
  if (strpos($name, '  ') !== FALSE) {
    return t('The username cannot contain multiple spaces in a row.');
  }
  if (preg_match('/[^\x{80}-\x{F7} a-z0-9@_.\'-]/i', $name)) {
    return t('The username contains an illegal character.');
  }
570
  if (preg_match('/[\x{80}-\x{A0}' .         // Non-printable ISO-8859-1 + NBSP
571 572 573 574 575 576 577 578 579
                  '\x{AD}' .                // Soft-hyphen
                  '\x{2000}-\x{200F}' .     // Various space characters
                  '\x{2028}-\x{202F}' .     // Bidirectional text overrides
                  '\x{205F}-\x{206F}' .     // Various text hinting characters
                  '\x{FEFF}' .              // Byte order mark
                  '\x{FF01}-\x{FF60}' .     // Full-width latin
                  '\x{FFF9}-\x{FFFD}' .     // Replacement characters
                  '\x{0}-\x{1F}]/u',        // NULL byte and control characters
                  $name)) {
580 581
    return t('The username contains an illegal character.');
  }
582 583 584
  if (drupal_strlen($name) > USERNAME_MAX_LENGTH) {
    return t('The username %name is too long: it must be %max characters or less.', array('%name' => $name, '%max' => USERNAME_MAX_LENGTH));
  }
Dries's avatar
 
Dries committed
585 586
}

587 588 589 590 591 592 593 594 595 596 597 598 599
/**
 * Validates a user's email address.
 *
 * Checks that a user's email address exists and follows all standard
 * validation rules. Returns error messages when the address is invalid.
 *
 * @param $mail
 *   A user's email address.
 *
 * @return
 *   If the address is invalid, a human-readable error message is returned.
 *   If the address is valid, nothing is returned.
 */
Dries's avatar
 
Dries committed
600
function user_validate_mail($mail) {
601
  $mail = trim($mail);
602 603 604
  if (!$mail) {
    return t('You must enter an e-mail address.');
  }
605
  if (!valid_email_address($mail)) {
606
    return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
Dries's avatar
 
Dries committed
607 608 609
  }
}

610
function user_validate_picture(&$form, &$form_state) {
611
  // If required, validate the uploaded picture.
612 613 614 615 616
  $validators = array(
    'file_validate_is_image' => array(),
    'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')),
    'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),
  );
617

618 619 620 621 622 623 624
  // Save the file as a temporary file.
  $file = file_save_upload('picture_upload', $validators);
  if ($file === FALSE) {
    form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures'))));
  }
  elseif ($file !== NULL) {
    $form_state['values']['picture_upload'] = $file;
Dries's avatar
 
Dries committed
625 626 627
  }
}

Dries's avatar
Dries committed
628 629 630
/**
 * Generate a random alphanumeric password.
 */
Dries's avatar
 
Dries committed
631 632
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
633 634
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
635
  // of 'I', 1, and 'l'.
636
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
Dries's avatar
Dries committed
637

638 639
  // Zero-based count of characters in the allowable list:
  $len = strlen($allowable_characters) - 1;
Dries's avatar
 
Dries committed
640

Dries's avatar
Dries committed
641 642
  // Declare the password as a blank string.
  $pass = '';
Dries's avatar
 
Dries committed
643

Dries's avatar
Dries committed
644
  // Loop the number of times specified by $length.
Dries's avatar
 
Dries committed
645 646 647 648
  for ($i = 0; $i < $length; $i++) {

    // Each iteration, pick a random character from the
    // allowable string and append it to the password:
649
    $pass .= $allowable_characters[mt_rand(0, $len)];
Dries's avatar
 
Dries committed
650 651 652
  }

  return $pass;
Dries's avatar
 
Dries committed
653 654
}

655 656 657 658 659 660 661 662 663 664
/**
 * Determine the permissions for one or more roles.
 *
 * @param $roles
 *   An array whose keys are the role IDs of interest, such as $user->roles.
 *
 * @return
 *   An array indexed by role ID. Each value is an array whose keys are the
 *   permission strings for the given role ID.
 */
665 666
function user_role_permissions($roles = array()) {
  $cache = &drupal_static(__FUNCTION__, array());
667 668 669 670 671

  $role_permissions = $fetch = array();

  if ($roles) {
    foreach ($roles as $rid => $name) {
672 673
      if (isset($cache[$rid])) {
        $role_permissions[$rid] = $cache[$rid];
674 675 676 677 678
      }
      else {
        // Add this rid to the list of those needing to be fetched.
        $fetch[] = $rid;
        // Prepare in case no permissions are returned.
679
        $cache[$rid] = array();
680 681 682 683 684 685
      }
    }

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

688
      foreach ($result as $row) {
689
        $cache[$row->rid][$row->permission] = TRUE;
690 691 692
      }
      foreach ($fetch as $rid) {
        // For every rid, we know we at least assigned an empty array.
693
        $role_permissions[$rid] = $cache[$rid];
694 695 696 697 698 699 700
      }
    }
  }

  return $role_permissions;
}

Dries's avatar
Dries committed
701 702 703 704 705
/**
 * Determine whether the user has a given privilege.
 *
 * @param $string
 *   The permission, such as "administer nodes", being checked for.
Dries's avatar
 
Dries committed
706 707
 * @param $account
 *   (optional) The account to check, if not given use currently logged in user.
Dries's avatar
Dries committed
708 709
 *
 * @return
710
 *   Boolean TRUE if the current user has the requested permission.
Dries's avatar
Dries committed
711 712 713 714 715
 *
 * All permission checks in Drupal should go through this function. This
 * way, we guarantee consistent behavior, and ensure that the superuser
 * can perform all actions.
 */
716
function user_access($string, $account = NULL) {
Dries's avatar
 
Dries committed
717
  global $user;
718

719
  if (!isset($account)) {
720 721 722
    $account = $user;
  }

723
  // User #1 has all privileges:
724
  if ($account->uid == 1) {
725
    return TRUE;
Dries's avatar
 
Dries committed
726 727
  }

Dries's avatar
Dries committed
728 729
  // To reduce the number of SQL queries, we cache the user's permissions
  // in a static variable.
730
  // Use the advanced drupal_static() pattern, since this is called very often.
731 732 733 734 735
  static $drupal_static_fast;
  if (!isset($drupal_static_fast)) {
    $drupal_static_fast['perm'] = &drupal_static(__FUNCTION__);
  }
  $perm = &$drupal_static_fast['perm'];
736
  if (!isset($perm[$account->uid])) {
737
    $role_permissions = user_role_permissions($account->roles);
Dries's avatar
 
Dries committed
738

739
    $perms = array();
740 741
    foreach ($role_permissions as $one_role) {
      $perms += $one_role;
Dries's avatar
 
Dries committed
742
    }
743
    $perm[$account->uid] = $perms;
Dries's avatar
 
Dries committed
744
  }
745

746
  return isset($perm[$account->uid][$string]);
Dries's avatar
 
Dries committed
747 748
}

749
/**
750
 * Checks for usernames blocked by user administration.
751
 *
752
 * @return boolean TRUE for blocked users, FALSE for active.
753 754
 */
function user_is_blocked($name) {
755 756 757 758 759
  return db_select('users')
    ->fields('users', array('name'))
    ->condition('name', db_like($name), 'LIKE')
    ->condition('status', 0)
    ->execute()->fetchObject();
760 761
}

Dries's avatar
Dries committed
762
/**
763
 * Implements hook_permission().
Dries's avatar
Dries committed
764
 */
765
function user_permission() {
766 767 768
  return array(
    'administer permissions' =>  array(
      'title' => t('Administer permissions'),
769
      'restrict access' => TRUE,
770 771 772
    ),
    'administer users' => array(
      'title' => t('Administer users'),
773
      'restrict access' => TRUE,
774 775
    ),
    'access user profiles' => array(
776
      'title' => t('View user profiles'),
777 778 779 780 781
    ),
    'change own username' => array(
      'title' => t('Change own username'),
    ),
    'cancel account' => array(
782
      'title' => t('Cancel own user account'),
783
      'description' => t('Note: content may be kept, unpublished, deleted or transferred to the %anonymous-name user depending on the configured <a href="@user-settings-url">user settings</a>.', array('%anonymous-name' => variable_get('anonymous', t('Anonymous')), '@user-settings-url' => url('admin/config/people/accounts'))),
784 785 786
    ),
    'select account cancellation method' => array(
      'title' => t('Select method for cancelling own account'),
787
      'restrict access' => TRUE,
788 789
    ),
  );
Dries's avatar
 
Dries committed
790 791
}

Dries's avatar
Dries committed
792
/**
793
 * Implements hook_file_download().
Dries's avatar
Dries committed
794 795 796
 *
 * Ensure that user pictures (avatars) are always downloadable.
 */
797 798 799
function user_file_download($uri) {
  if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
    $info = image_get_info($uri);
800
    return array('Content-Type' => $info['mime_type']);
Dries's avatar
 
Dries committed
801 802 803
  }
}

804
/**
805
 * Implements hook_file_references().
806 807 808
 */
function user_file_references($file) {
  // Determine if the file is used by this module.
809 810
  $file_used = (bool) db_query_range('SELECT 1 FROM {users} WHERE picture = :fid', 0, 1, array(':fid' => $file->fid))->fetchField();
  if ($file_used) {
811
    // Return the name of the module and how many references it has to the file.
812 813
    // If file is still used then 1 is enough to indicate this.
    return array('user' => 1);
814 815 816 817
  }
}

/**
818
 * Implements hook_file_delete().
819 820 821
 */
function user_file_delete($file) {
  // Remove any references to the file.
822
  db_update('users')
823 824 825 826 827
    ->fields(array('picture' => 0))
    ->condition('picture', $file->fid)
    ->execute();
}

Dries's avatar
Dries committed
828
/**
829
 * Implements hook_search_info().
Dries's avatar
Dries committed
830
 */
831 832 833 834 835 836 837
function user_search_info() {
  return array(
    'title' => 'Users',
  );
}

/**
838
 * Implements hook_search_access().
839 840 841 842 843 844
 */
function user_search_access() {
  return user_access('access user profiles');
}

/**
845
 * Implements hook_search_execute().
846 847 848 849 850 851
 */
function user_search_execute($keys = NULL) {
  $find = array();
  // Replace wildcards with MySQL/PostgreSQL wildcards.
  $keys = preg_replace('!\*+!', '%', $keys);
  $query = db_select('users')->extend('PagerDefault');
852
  $query->fields('users', array('name', 'uid'));
853 854
  if (user_access('administer users')) {
    // Administrators can also search in the otherwise private email field.
855
    $query->fields('users', array('mail'));
856
    $query->condition(db_or()->
857 858
      condition('name', '%' . db_like($keys) . '%', 'LIKE')->
      condition('mail', '%' . db_like($keys) . '%', 'LIKE'));
859 860
  }
  else {
861
    $query->condition('name', '%' . db_like($keys) . '%', 'LIKE');
862 863 864 865
  }
  $result = $query
    ->limit(15)
    ->execute();
866 867 868 869 870 871 872 873 874
  if (user_access('administer users')) {
    foreach ($result as $account) {
      $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
    }
  }
  else {
    foreach ($result as $account) {
      $find[] = array('title' => $account->name, 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
    }
Dries's avatar
 
Dries committed
875
  }
876
  return $find;
Dries's avatar
 
Dries committed
877 878
}

Dries's avatar
Dries committed
879
/**
880
 * Implements hook_element_info().
Dries's avatar
Dries committed
881
 */
882 883 884 885 886 887
function user_element_info() {
  $types['user_profile_category'] = array(
    '#theme_wrappers' => array('user_profile_category'),
  );
  $types['user_profile_item'] = array(
    '#theme' => 'user_profile_item',
Dries's avatar
Dries committed
888
  );
889
  return $types;
Dries's avatar
Dries committed
890 891
}

Dries's avatar
Dries committed
892
/**
893
 * Implements hook_user_view().
Dries's avatar
Dries committed
894
 */
895
function user_user_view($account) {
896
  $account->content['user_picture'] = array(
897
    '#markup' => theme('user_picture', array('account' => $account)),
898 899 900 901 902 903 904
    '#weight' => -10,
  );
  if (!isset($account->content['summary'])) {
    $account->content['summary'] = array();
  }
  $account->content['summary'] += array(
    '#type' => 'user_profile_category',
905
    '#attributes' => array('class' => array('user-member')),
906 907 908
    '#weight' => 5,
    '#title' => t('History'),
  );