Commit 3b305095 authored by webchick's avatar webchick

#347250 by catch, drewish, and Berdir: Add function for loading multiple users in one request.

parent 8a0d326a
......@@ -1283,7 +1283,7 @@ function comment_validate($edit) {
form_set_error('date', t('You have to specify a valid date.'));
}
}
if (isset($edit['author']) && !$account = user_load(array('name' => $edit['author']))) {
if (isset($edit['author']) && !$account = user_load_by_name($edit['author'])) {
form_set_error('author', t('You have to specify a valid author.'));
}
......@@ -1607,7 +1607,7 @@ function comment_form_add_preview($form, &$form_state) {
// Attach the user and time information.
if (!empty($edit['author'])) {
$account = user_load(array('name' => $edit['author']));
$account = user_load_by_name($edit['author']);
}
elseif ($user->uid && !isset($edit['is_anonymous'])) {
$account = $user;
......@@ -1687,7 +1687,7 @@ function _comment_form_submit(&$comment_values) {
$comment_values['timestamp'] = strtotime($comment_values['date']);
if (isset($comment_values['author'])) {
$account = user_load(array('name' => $comment_values['author']));
$account = user_load_by_name($comment_values['author']);
$comment_values['uid'] = $account->uid;
$comment_values['name'] = $comment_values['author'];
}
......
......@@ -188,7 +188,7 @@ class DBLogTestCase extends DrupalWebTestCase {
$this->drupalPost('admin/user/user/create', $edit, t('Create new account'));
$this->assertResponse(200);
// Retrieve user object.
$user = user_load(array('name' => $name)); //, 'pass' => $pass, 'status' => 1));
$user = user_load_by_name($name);
$this->assertTrue($user != null, t('User @name was loaded', array('@name' => $name)));
$user->pass_raw = $pass; // Needed by drupalLogin.
// Login user.
......
......@@ -984,7 +984,7 @@ function node_validate($node, $form = array()) {
if (user_access('administer nodes')) {
// Validate the "authored by" field.
if (!empty($node->name) && !($account = user_load(array('name' => $node->name)))) {
if (!empty($node->name) && !($account = user_load_by_name($node->name))) {
// The use of empty() is mandatory in the context of usernames
// as the empty string denotes the anonymous user. In case we
// are dealing with an anonymous user we set the user ID to 0.
......@@ -1035,7 +1035,7 @@ function node_submit($node) {
if (user_access('administer nodes')) {
// Populate the "authored by" field.
if ($account = user_load(array('name' => $node->name))) {
if ($account = user_load_by_name($node->name)) {
$node->uid = $account->uid;
}
else {
......
......@@ -353,7 +353,7 @@ function node_preview($node) {
if (isset($node->name)) {
// The use of isset() is mandatory in the context of user IDs, because
// user ID 0 denotes the anonymous user.
if ($user = user_load(array('name' => $node->name))) {
if ($user = user_load_by_name($node->name)) {
$node->uid = $user->uid;
$node->picture = $user->picture;
}
......@@ -362,7 +362,7 @@ function node_preview($node) {
}
}
elseif ($node->uid) {
$user = user_load(array('uid' => $node->uid));
$user = user_load($node->uid);
$node->name = $user->name;
$node->picture = $user->picture;
}
......
......@@ -202,13 +202,6 @@ function profile_block_view($delta = '') {
}
}
/**
* Implementation of hook_user_load().
*/
function profile_user_load(&$edit, &$user, $category = NULL) {
return profile_load_profile($user);
}
/**
* Implementation of hook_user_register().
*/
......@@ -270,11 +263,14 @@ function profile_user_cancel(&$edit, &$account, $method) {
}
}
function profile_load_profile(&$user) {
$result = db_query('SELECT f.name, f.type, v.value FROM {profile_field} f INNER JOIN {profile_value} v ON f.fid = v.fid WHERE uid = %d', $user->uid);
while ($field = db_fetch_object($result)) {
if (empty($user->{$field->name})) {
$user->{$field->name} = _profile_field_serialize($field->type) ? unserialize($field->value) : $field->value;
/**
* Implementation of hook_user_load().
*/
function profile_user_load($users) {
$result = db_query('SELECT f.name, f.type, v.uid, v.value FROM {profile_field} f INNER JOIN {profile_value} v ON f.fid = v.fid WHERE uid IN (:uids)', array(':uids' => array_keys($users)));
foreach ($result as $record) {
if (empty($users[$record->uid]->{$record->name})) {
$users[$record->uid]->{$record->name} = _profile_field_serialize($record->type) ? unserialize($record->value) : $record->value;
}
}
}
......@@ -341,7 +337,7 @@ function profile_view_field($user, $field) {
function profile_view_profile(&$user) {
profile_load_profile($user);
$user = user_load($user->uid);
// Show private fields to administrators and people viewing their own account.
if (user_access('administer users') || $GLOBALS['user']->uid == $user->uid) {
......
......@@ -55,11 +55,13 @@ function profile_browse() {
}
// Extract the affected users:
$result = pager_query("SELECT u.uid, u.access FROM {users} u INNER JOIN {profile_value} v ON u.uid = v.uid WHERE v.fid = %d AND $query AND u.access != 0 AND u.status != 0 ORDER BY u.access DESC", 20, 0, NULL, $arguments);
$result = pager_query("SELECT u.uid, u.access FROM {users} u INNER JOIN {profile_value} v ON u.uid = v.uid WHERE v.fid = %d AND $query AND u.access != 0 AND u.status != 0 ORDER BY u.access DESC", 20, 0, NULL, $arguments)->fetchAllAssoc('uid');
// Load the users.
$users = user_load_multiple(array_keys($result));
$content = '';
while ($account = db_fetch_object($result)) {
$account = user_load(array('uid' => $account->uid));
foreach ($users as $account) {
$profile = _profile_update_user_fields($fields, $account);
$content .= theme('profile_listing', $account, $profile);
}
......@@ -88,11 +90,10 @@ function profile_browse() {
}
// Extract the affected users:
$result = pager_query('SELECT uid, access FROM {users} WHERE uid > 0 AND status != 0 AND access != 0 ORDER BY access DESC', 20, 0, NULL);
$result = pager_query('SELECT uid, access FROM {users} WHERE uid > 0 AND status != 0 AND access != 0 ORDER BY access DESC', 20, 0, NULL)->fetchAllAssoc('uid');
$users = user_load_multiple(array_keys($result));
$content = '';
while ($account = db_fetch_object($result)) {
$account = user_load(array('uid' => $account->uid));
foreach ($users as $account) {
$profile = _profile_update_user_fields($fields, $account);
$content .= theme('profile_listing', $account, $profile);
}
......
......@@ -849,7 +849,7 @@ protected function setUp() {
// Log in with a clean $user.
$this->originalUser = $user;
drupal_save_session(FALSE);
$user = user_load(array('uid' => 1));
$user = user_load(1);
// Restore necessary variables.
variable_set('install_profile', 'default');
......
......@@ -434,7 +434,7 @@ class FileValidatorTest extends DrupalWebTestCase {
drupal_save_session(FALSE);
// Run these test as uid = 1.
$user = user_load(array('uid' => 1));
$user = user_load(1);
$file = new stdClass();
$file->filesize = 999999;
......
......@@ -40,7 +40,7 @@ function statistics_node_tracker() {
}
function statistics_user_tracker() {
if ($account = user_load(array('uid' => arg(1)))) {
if ($account = user_load(arg(1))) {
$header = array(
array('data' => t('Timestamp'), 'field' => 'timestamp', 'sort' => 'desc'),
......
......@@ -2013,7 +2013,7 @@ function system_send_email_action($object, $context) {
if (isset($node)) {
if (!isset($account)) {
$account = user_load(array('uid' => $node->uid));
$account = user_load($node->uid);
}
if ($recipient == '%author') {
$recipient = $account->mail;
......
......@@ -195,7 +195,7 @@ function _trigger_normalize_node_context($type, $node) {
// An action that works on users is being called in a node context.
// Load the user object of the node's author.
case 'user':
return user_load(array('uid' => $node->uid));
return user_load($node->uid);
}
}
......@@ -298,7 +298,7 @@ function _trigger_normalize_comment_context($type, $comment) {
// An action that works on users is being called in a comment context.
case 'user':
return user_load(array('uid' => is_array($comment) ? $comment['uid'] : $comment->uid));
return user_load(is_array($comment) ? $comment['uid'] : $comment->uid);
}
}
......
......@@ -121,7 +121,7 @@ function _update_cron_notify() {
if (!empty($notify_list)) {
$default_language = language_default();
foreach ($notify_list as $target) {
if ($target_user = user_load(array('mail' => $target))) {
if ($target_user = user_load_by_mail($target)) {
$target_language = user_preferred_language($target_user);
}
else {
......
......@@ -84,6 +84,25 @@ function hook_user($op, &$edit, &$account, $category = NULL) {
return $form;
}
}
/**
* Act on user objects when loaded from the database.
*
* Due to the static cache in user_load_multiple() you should not use this
* hook to modify the user properties returned by the {users} table itself
* since this may result in unreliable results when loading from cache.
*
* @param $users
* An array of user objects, indexed by uid.
*
* @see user_load_multiple()
* @see profile_user_load()
*/
function hook_user_load($users) {
$result = db_query('SELECT * FROM {my_table} WHERE uid IN (:uids)', array(':uids' => array_keys($users)));
foreach ($result as $record) {
$users[$record->uid]->foo = $result->foo;
}
}
/**
* Act on user account cancellations.
......
......@@ -160,77 +160,191 @@ function user_external_login($account, $edit = array()) {
}
/**
* Fetch a user object.
* Load multiple users based on certain conditions.
*
* @param $array
* An associative array of attributes to search for in selecting the
* user, such as user name or e-mail address.
* 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.
*
* @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.
* @return
* A fully-loaded $user object upon successful user load or FALSE if user
* cannot be loaded.
* An array of user objects, indexed by uid.
*
* @see user_load()
* @see user_load_by_mail()
* @see user_load_by_name()
*/
function user_load($array = array()) {
// Dynamically compose a SQL query:
$query = array();
$params = array();
if (is_numeric($array)) {
$array = array('uid' => $array);
}
elseif (!is_array($array)) {
return FALSE;
function user_load_multiple($uids = array(), $conditions = array(), $reset = FALSE) {
static $user_cache = array();
if ($reset) {
$user_cache = array();
}
foreach ($array as $key => $value) {
if ($key == 'uid' || $key == 'status') {
$query[] = "$key = %d";
$params[] = $value;
}
elseif ($key == 'pass') {
$query[] = "pass = '%s'";
$params[] = $value;
}
else {
$query[]= "LOWER($key) = LOWER('%s')";
$params[] = $value;
$users = array();
// Create a new variable which is either a prepared version of the $uids
// array for later comparison with the user cache, or FALSE if no $uids were
// passed. The $uids array is reduced as items are loaded from cache, and we
// need to know if it's empty for this reason to avoid querying the database
// when all requested users are loaded from cache.
$passed_uids = !empty($uids) ? array_flip($uids) : FALSE;
// Load any available users from the internal cache.
if ($user_cache) {
if ($uids && !$conditions) {
$users += array_intersect_key($user_cache, $passed_uids);
// If any users were loaded, remove them from the $uids still to load.
$uids = array_keys(array_diff_key($passed_uids, $users));
}
}
$result = db_query('SELECT * FROM {users} u WHERE ' . implode(' AND ', $query), $params);
if ($user = db_fetch_object($result)) {
$user = drupal_unpack($user);
// Load any remaining users from the database, this is necessary if we have
// $uids still to load, or if $conditions was passed without $uids.
if ($uids || ($conditions && !$passed_uids)) {
$query = db_select('users', 'u')->fields('u');
$user->roles = array();
if ($user->uid) {
$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
// If the $uids array is populated, add those to the query.
if ($uids) {
$query->condition('u.uid', $uids, 'IN');
}
else {
$user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
// If the conditions array is populated, add those to the query.
if ($conditions) {
// TODO D7: Using LIKE() to get a case insensitive comparison because Crell
// and chx promise that dbtng will map it to ILIKE in postgres.
if (isset($conditions['name'])) {
$query->condition('u.name', $conditions['name'], 'LIKE');
unset($conditions['name']);
}
if (isset($conditions['mail'])) {
$query->condition('u.mail', $conditions['mail'], 'LIKE');
unset($conditions['mail']);
}
foreach ($conditions as $field => $value) {
$query->condition('u.' . $field, $value);
}
}
$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);
while ($role = db_fetch_object($result)) {
$user->roles[$role->rid] = $role->name;
$result = $query->execute();
$queried_users = array();
// Build an array of user picture IDs so that these can be fetched later.
$picture_fids = array();
foreach ($result as $record) {
$picture_fids[] = $record->picture;
$queried_users[$record->uid] = drupal_unpack($record);
$queried_users[$record->uid]->roles = array();
if ($record->uid) {
$queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
}
else {
$queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
}
}
// Attach fields.
// TODO D7 : not sure the 3rd param ($types) is needed.
field_attach_load('user', array($user->uid => $user));
if (!empty($queried_users)) {
// 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;
}
if (!empty($user->picture) && ($file = file_load($user->picture))) {
$user->picture = $file;
}
else {
$user->picture = NULL;
}
// 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;
}
}
}
// Invoke hook_user_load() on the users loaded from the database
// and add them to the static cache.
foreach (module_implements('user_load') as $module) {
$function = $module . '_user_load';
$function($queried_users);
}
user_module_invoke('load', $array, $user);
// TODO D7 : not sure the 3rd param ($types) is needed.
field_attach_load('user', $queried_users);
$users = $users + $queried_users;
$user_cache = $user_cache + $queried_users;
}
}
else {
$user = FALSE;
// Ensure that the returned array is ordered the same as the original $uids
// array if this was passed in and remove any invalid uids.
if ($passed_uids) {
// Remove any invalid uids from the array.
$passed_uids = array_intersect_key($passed_uids, $users);
foreach ($users as $user) {
$passed_uids[$user->uid] = $user;
}
$users = $passed_uids;
}
return $user;
return $users;
}
/**
* Fetch a user object.
*
* @param $uid
* Integer specifying the user id.
* @param $reset
* A boolean indicating that the internal cache should be reset.
* @return
* A fully-loaded $user object upon successful user load or FALSE if user
* 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);
}
/**
......@@ -374,7 +488,7 @@ function user_save($account, $edit = array(), $category = 'account') {
field_attach_update('user', $obj);
// Refresh user object.
$user = user_load(array('uid' => $account->uid));
$user = user_load($account->uid, TRUE);
// Send emails after we have the new user object.
if (isset($edit['status']) && $edit['status'] != $account->status) {
......@@ -404,7 +518,7 @@ function user_save($account, $edit = array(), $category = 'account') {
}
// Build the initial user object.
$user = user_load(array('uid' => $edit['uid']));
$user = user_load($edit['uid'], TRUE);
$obj = (object) $edit;
field_attach_insert('user', $obj);
......@@ -438,7 +552,7 @@ function user_save($account, $edit = array(), $category = 'account') {
}
// Build the finished user object.
$user = user_load(array('uid' => $edit['uid']));
$user = user_load($edit['uid'], TRUE);
}
return $user;
......@@ -1291,7 +1405,6 @@ function user_menu() {
'access callback' => 'user_edit_access',
'access arguments' => array(1),
'type' => MENU_LOCAL_TASK,
'load arguments' => array('%map', '%index'),
);
$items['user/%user_category/edit/account'] = array(
......@@ -1554,8 +1667,8 @@ function user_authenticate($form_values = array()) {
db_query("UPDATE {users} SET pass = '%s' WHERE uid = %d", $new_hash, $account->uid);
}
}
$account = user_load(array('uid' => $account->uid, 'status' => 1));
$user = $account;
$users = user_load_multiple(array($account->uid), array('status' => '1'));
$user = reset($users);
user_authenticate_finalize($form_values);
return $user;
}
......@@ -1608,7 +1721,7 @@ function user_login_submit($form, &$form_state) {
function user_external_login_register($name, $module) {
global $user;
$existing_user = user_load(array('name' => $name));
$existing_user = user_load_by_name($name);
if (isset($existing_user->uid)) {
$user = $existing_user;
}
......@@ -1799,7 +1912,7 @@ function user_edit_form(&$form_state, $uid, $edit, $register = FALSE) {
function user_cancel($edit, $uid, $method) {
global $user;
$account = user_load(array('uid' => $uid));
$account = user_load($uid);
if (!$account) {
drupal_set_message(t('The user account %id does not exist.', array('%id' => $uid)), 'error');
......@@ -2110,7 +2223,7 @@ function user_user_operations($form_state = array()) {
*/
function user_user_operations_unblock($accounts) {
foreach ($accounts as $uid) {
$account = user_load(array('uid' => (int)$uid));
$account = user_load($uid);
// Skip unblocking user if they are already unblocked.
if ($account !== FALSE && $account->status == 0) {
user_save($account, array('status' => 1));
......@@ -2123,7 +2236,7 @@ function user_user_operations_unblock($accounts) {
*/
function user_user_operations_block($accounts) {
foreach ($accounts as $uid) {
$account = user_load(array('uid' => (int)$uid));
$account = user_load($uid);
// Skip blocking user if they are already blocked.
if ($account !== FALSE && $account->status == 1) {
user_save($account, array('status' => 0));
......@@ -2142,7 +2255,7 @@ function user_multiple_role_edit($accounts, $operation, $rid) {
switch ($operation) {
case 'add_role':
foreach ($accounts as $uid) {
$account = user_load(array('uid' => (int)$uid));
$account = user_load($uid);
// Skip adding the role to the user if they already have it.
if ($account !== FALSE && !isset($account->roles[$rid])) {
$roles = $account->roles + array($rid => $role_name);
......@@ -2152,7 +2265,7 @@ function user_multiple_role_edit($accounts, $operation, $rid) {
break;
case 'remove_role':
foreach ($accounts as $uid) {
$account = user_load(array('uid' => (int)$uid));
$account = user_load($uid);
// Skip removing the role from the user if they already don't have it.
if ($account !== FALSE && isset($account->roles[$rid])) {
$roles = array_diff($account->roles, array($rid => $role_name));
......
......@@ -44,10 +44,12 @@ function user_pass() {
function user_pass_validate($form, &$form_state) {
$name = trim($form_state['values']['name']);
// Try to load by email.
$account = user_load(array('mail' => $name, 'status' => 1));
$users = user_load_multiple(array(), array('mail' => $name, 'status' => '1'));
$account = reset($users);
if (!$account) {
// No success, try to load by name.
$account = user_load(array('name' => $name, 'status' => 1));
$users = user_load_multiple(array(), array('name' => $name, 'status' => '1'));
$account = reset($users);
}
if (isset($account->uid)) {
form_set_value(array('#parents' => array('account')), $account, $form_state);
......@@ -86,7 +88,8 @@ function user_pass_reset(&$form_state, $uid, $timestamp, $hashed_pass, $action =
$timeout = 86400;
$current = REQUEST_TIME;
// Some redundant checks for extra security ?
if ($timestamp < $current && $account = user_load(array('uid' => $uid, 'status' => 1)) ) {
$users = user_load_multiple(array($uid), array('status' => '1'));
if (