Wordpress 3.0.6-scripts
[autoinstalls/wordpress.git] / wp-includes / user.php
index 71fd7a3222c32d10f3f6f513ed9e583287a35d97..66d5d2e960115dc0a9ff0e996d00812a543c01ce 100644 (file)
@@ -41,7 +41,7 @@ function wp_signon( $credentials = '', $secure_cookie = '' ) {
        do_action_ref_array('wp_authenticate', array(&$credentials['user_login'], &$credentials['user_password']));
 
        if ( '' === $secure_cookie )
-               $secure_cookie = is_ssl() ? true : false;
+               $secure_cookie = is_ssl();
 
        global $auth_secure_cookie; // XXX ugly hack to pass this to wp_authenticate_cookie
        $auth_secure_cookie = $secure_cookie;
@@ -83,20 +83,30 @@ function wp_authenticate_username_password($user, $username, $password) {
                return $error;
        }
 
-       $userdata = get_userdatabylogin($username);
+       $userdata = get_user_by('login', $username);
 
-       if ( !$userdata ) {
+       if ( !$userdata )
                return new WP_Error('invalid_username', sprintf(__('<strong>ERROR</strong>: Invalid username. <a href="%s" title="Password Lost and Found">Lost your password</a>?'), site_url('wp-login.php?action=lostpassword', 'login')));
+
+       if ( is_multisite() ) {
+               // Is user marked as spam?
+               if ( 1 == $userdata->spam)
+                       return new WP_Error('invalid_username', __('<strong>ERROR</strong>: Your account has been marked as a spammer.'));
+
+               // Is a user's blog marked as spam?
+               if ( !is_super_admin( $userdata->ID ) && isset($userdata->primary_blog) ) {
+                       $details = get_blog_details( $userdata->primary_blog );
+                       if ( is_object( $details ) && $details->spam == 1 )
+                               return new WP_Error('blog_suspended', __('Site Suspended.'));
+               }
        }
 
        $userdata = apply_filters('wp_authenticate_user', $userdata, $password);
-       if ( is_wp_error($userdata) ) {
+       if ( is_wp_error($userdata) )
                return $userdata;
-       }
 
-       if ( !wp_check_password($password, $userdata->user_pass, $userdata->ID) ) {
+       if ( !wp_check_password($password, $userdata->user_pass, $userdata->ID) )
                return new WP_Error('incorrect_password', sprintf(__('<strong>ERROR</strong>: Incorrect password. <a href="%s" title="Password Lost and Found">Lost your password</a>?'), site_url('wp-login.php?action=lostpassword', 'login')));
-       }
 
        $user =  new WP_User($userdata->ID);
        return $user;
@@ -130,47 +140,52 @@ function wp_authenticate_cookie($user, $username, $password) {
 }
 
 /**
- * Retrieve user data based on field.
- *
- * Use get_profile() will make a database query to get the value of the table
- * column. The value might be cached using the query cache, but care should be
- * taken when using the function to not make a lot of queries for retrieving
- * user profile information.
- *
- * If the $user parameter is not used, then the user will be retrieved from a
- * cookie of the user. Therefore, if the cookie does not exist, then no value
- * might be returned. Sanity checking must be done to ensure that when using
- * get_profile() that empty/null/false values are handled and that something is
- * at least displayed.
+ * Number of posts user has written.
  *
- * @since 1.5.0
- * @uses $wpdb WordPress database object to create queries.
+ * @since 3.0.0
+ * @uses $wpdb WordPress database object for queries.
  *
- * @param string $field User field to retrieve.
- * @param string $user Optional. User username.
- * @return string The value in the field.
+ * @param int $userid User ID.
+ * @return int Amount of posts user has written.
  */
-function get_profile($field, $user = false) {
+function count_user_posts($userid) {
        global $wpdb;
-       if ( !$user )
-               $user = esc_sql( $_COOKIE[USER_COOKIE] );
-       return $wpdb->get_var( $wpdb->prepare("SELECT $field FROM $wpdb->users WHERE user_login = %s", $user) );
+
+       $where = get_posts_by_author_sql('post', TRUE, $userid);
+
+       $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts $where" );
+
+       return apply_filters('get_usernumposts', $count, $userid);
 }
 
 /**
- * Number of posts user has written.
+ * Number of posts written by a list of users.
  *
- * @since 0.71
- * @uses $wpdb WordPress database object for queries.
- *
- * @param int $userid User ID.
- * @return int Amount of posts user has written.
+ * @since 3.0.0
+ * @param array $userid User ID number list.
+ * @return array Amount of posts each user has written.
  */
-function get_usernumposts($userid) {
+function count_many_users_posts($users) {
        global $wpdb;
-       $userid = (int) $userid;
-       $count = $wpdb->get_var( $wpdb->prepare("SELECT COUNT(*) FROM $wpdb->posts WHERE post_author = %d AND post_type = 'post' AND ", $userid) . get_private_posts_cap_sql('post'));
-       return apply_filters('get_usernumposts', $count, $userid);
+
+       $count = array();
+       if ( ! is_array($users) || empty( $users ) )
+               return $count;
+
+       $userlist = implode( ',', $users );
+       $where = get_posts_by_author_sql( 'post' );
+
+       $result = $wpdb->get_results( "SELECT post_author, COUNT(*) FROM $wpdb->posts $where AND post_author IN ($userlist) GROUP BY post_author", ARRAY_N );
+       foreach ( $result as $row ) {
+               $count[ $row[0] ] = $row[1];
+       }
+
+       foreach ( $users as $id ) {
+               if ( ! isset( $count[ $id ] ) )
+                       $count[ $id ] = 0;
+       }
+
+       return $count;
 }
 
 /**
@@ -196,16 +211,28 @@ function user_pass_ok($user_login, $user_pass) {
 //
 
 /**
- * Retrieve user option that can be either global, user, or blog.
+ * Get the current user's ID
+ *
+ * @since MU
+ *
+ * @uses wp_get_current_user
+ *
+ * @return int The current user's ID
+ */
+function get_current_user_id() {
+       $user = wp_get_current_user();
+       return ( isset( $user->ID ) ? (int) $user->ID : 0 );
+}
+
+/**
+ * Retrieve user option that can be either per Site or per Network.
  *
  * If the user ID is not given, then the current user will be used instead. If
  * the user ID is given, then the user data will be retrieved. The filter for
  * the result, will also pass the original option name and finally the user data
  * object as the third parameter.
  *
- * The option will first check for the non-global name, then the global name,
- * and if it still doesn't find it, it will try the blog option. The option can
- * either be modified or set by a plugin.
+ * The option will first check for the per site name and then the per Network name.
  *
  * @since 2.0.0
  * @uses $wpdb WordPress database object for queries.
@@ -214,24 +241,29 @@ function user_pass_ok($user_login, $user_pass) {
  *
  * @param string $option User option name.
  * @param int $user Optional. User ID.
- * @param bool $check_blog_options Whether to check for an option in the options table if a per-user option does not exist. Default is true.
+ * @param bool $deprecated Use get_option() to check for an option in the options table.
  * @return mixed
  */
-function get_user_option( $option, $user = 0, $check_blog_options = true ) {
+function get_user_option( $option, $user = 0, $deprecated = '' ) {
        global $wpdb;
 
-       $option = preg_replace('|[^a-z0-9_]|i', '', $option);
-       if ( empty($user) )
+       if ( !empty( $deprecated ) )
+               _deprecated_argument( __FUNCTION__, '3.0' );
+
+       if ( empty($user) ) {
                $user = wp_get_current_user();
-       else
-               $user = get_userdata($user);
-
-       if ( isset( $user->{$wpdb->prefix . $option} ) ) // Blog specific
-               $result = $user->{$wpdb->prefix . $option};
-       elseif ( isset( $user->{$option} ) ) // User specific and cross-blog
-               $result = $user->{$option};
-       elseif ( $check_blog_options ) // Blog global
-               $result = get_option( $option );
+               $user = $user->ID;
+       }
+
+       $user = get_userdata($user);
+
+       // Keys used as object vars cannot have dashes.
+       $key = str_replace('-', '', $option);
+
+       if ( isset( $user->{$wpdb->prefix . $key} ) ) // Blog specific
+               $result = $user->{$wpdb->prefix . $key};
+       elseif ( isset( $user->{$key} ) ) // User specific and cross-blog
+               $result = $user->{$key};
        else
                $result = false;
 
@@ -245,20 +277,52 @@ function get_user_option( $option, $user = 0, $check_blog_options = true ) {
  * global blog options. If the 'global' parameter is false, which it is by default
  * it will prepend the WordPress table prefix to the option name.
  *
+ * Deletes the user option if $newvalue is empty.
+ *
  * @since 2.0.0
  * @uses $wpdb WordPress database object for queries
  *
  * @param int $user_id User ID
  * @param string $option_name User option name.
  * @param mixed $newvalue User option value.
- * @param bool $global Optional. Whether option name is blog specific or not.
+ * @param bool $global Optional. Whether option name is global or blog specific. Default false (blog specific).
  * @return unknown
  */
 function update_user_option( $user_id, $option_name, $newvalue, $global = false ) {
        global $wpdb;
+
        if ( !$global )
                $option_name = $wpdb->prefix . $option_name;
-       return update_usermeta( $user_id, $option_name, $newvalue );
+
+       // For backward compatibility. See differences between update_user_meta() and deprecated update_usermeta().
+       // http://core.trac.wordpress.org/ticket/13088
+       if ( is_null( $newvalue ) || is_scalar( $newvalue ) && empty( $newvalue ) )
+               return delete_user_meta( $user_id, $option_name );
+
+       return update_user_meta( $user_id, $option_name, $newvalue );
+}
+
+/**
+ * Delete user option with global blog capability.
+ *
+ * User options are just like user metadata except that they have support for
+ * global blog options. If the 'global' parameter is false, which it is by default
+ * it will prepend the WordPress table prefix to the option name.
+ *
+ * @since 3.0.0
+ * @uses $wpdb WordPress database object for queries
+ *
+ * @param int $user_id User ID
+ * @param string $option_name User option name.
+ * @param bool $global Optional. Whether option name is global or blog specific. Default false (blog specific).
+ * @return unknown
+ */
+function delete_user_option( $user_id, $option_name, $global = false ) {
+       global $wpdb;
+
+       if ( !$global )
+               $option_name = $wpdb->prefix . $option_name;
+       return delete_user_meta( $user_id, $option_name );
 }
 
 /**
@@ -278,154 +342,165 @@ function get_users_of_blog( $id = '' ) {
        global $wpdb, $blog_id;
        if ( empty($id) )
                $id = (int) $blog_id;
-       $users = $wpdb->get_results( "SELECT user_id, user_id AS ID, user_login, display_name, user_email, meta_value FROM $wpdb->users, $wpdb->usermeta WHERE {$wpdb->users}.ID = {$wpdb->usermeta}.user_id AND meta_key = '{$wpdb->prefix}capabilities' ORDER BY {$wpdb->usermeta}.user_id" );
+       $blog_prefix = $wpdb->get_blog_prefix($id);
+       $users = $wpdb->get_results( "SELECT user_id, user_id AS ID, user_login, display_name, user_email, meta_value FROM $wpdb->users, $wpdb->usermeta WHERE {$wpdb->users}.ID = {$wpdb->usermeta}.user_id AND meta_key = '{$blog_prefix}capabilities' ORDER BY {$wpdb->usermeta}.user_id" );
        return $users;
 }
 
-//
-// User meta functions
-//
-
 /**
- * Remove user meta data.
+ * Add meta data field to a user.
  *
- * @since 2.0.0
- * @uses $wpdb WordPress database object for queries.
+ * Post meta data is called "Custom Fields" on the Administration Panels.
+ *
+ * @since 3.0.0
+ * @uses add_metadata()
+ * @link http://codex.wordpress.org/Function_Reference/add_user_meta
  *
- * @param int $user_id User ID.
- * @param string $meta_key Metadata key.
- * @param mixed $meta_value Metadata value.
- * @return bool True deletion completed and false if user_id is not a number.
+ * @param int $user_id Post ID.
+ * @param string $key Metadata name.
+ * @param mixed $value Metadata value.
+ * @param bool $unique Optional, default is false. Whether the same key should not be added.
+ * @return bool False for failure. True for success.
  */
-function delete_usermeta( $user_id, $meta_key, $meta_value = '' ) {
-       global $wpdb;
-       if ( !is_numeric( $user_id ) )
-               return false;
-       $meta_key = preg_replace('|[^a-z0-9_]|i', '', $meta_key);
-
-       if ( is_array($meta_value) || is_object($meta_value) )
-               $meta_value = serialize($meta_value);
-       $meta_value = trim( $meta_value );
-
-       $cur = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->usermeta WHERE user_id = %d AND meta_key = %s", $user_id, $meta_key) );
-
-       if ( $cur && $cur->umeta_id )
-               do_action( 'delete_usermeta', $cur->umeta_id, $user_id, $meta_key, $meta_value );
-
-       if ( ! empty($meta_value) )
-               $wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->usermeta WHERE user_id = %d AND meta_key = %s AND meta_value = %s", $user_id, $meta_key, $meta_value) );
-       else
-               $wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->usermeta WHERE user_id = %d AND meta_key = %s", $user_id, $meta_key) );
-
-       wp_cache_delete($user_id, 'users');
-
-       if ( $cur && $cur->umeta_id )
-               do_action( 'deleted_usermeta', $cur->umeta_id, $user_id, $meta_key, $meta_value );
-
-       return true;
+function add_user_meta($user_id, $meta_key, $meta_value, $unique = false) {
+       return add_metadata('user', $user_id, $meta_key, $meta_value, $unique);
 }
 
 /**
- * Retrieve user metadata.
+ * Remove metadata matching criteria from a user.
  *
- * If $user_id is not a number, then the function will fail over with a 'false'
- * boolean return value. Other returned values depend on whether there is only
- * one item to be returned, which be that single item type. If there is more
- * than one metadata value, then it will be list of metadata values.
+ * You can match based on the key, or key and value. Removing based on key and
+ * value, will keep from removing duplicate metadata with the same key. It also
+ * allows removing all metadata matching key, if needed.
  *
- * @since 2.0.0
- * @uses $wpdb WordPress database object for queries.
+ * @since 3.0.0
+ * @uses delete_metadata()
+ * @link http://codex.wordpress.org/Function_Reference/delete_user_meta
  *
- * @param int $user_id User ID
- * @param string $meta_key Optional. Metadata key.
- * @return mixed
+ * @param int $user_id user ID
+ * @param string $meta_key Metadata name.
+ * @param mixed $meta_value Optional. Metadata value.
+ * @return bool False for failure. True for success.
  */
-function get_usermeta( $user_id, $meta_key = '') {
-       global $wpdb;
-       $user_id = (int) $user_id;
-
-       if ( !$user_id )
-               return false;
-
-       if ( !empty($meta_key) ) {
-               $meta_key = preg_replace('|[^a-z0-9_]|i', '', $meta_key);
-               $user = wp_cache_get($user_id, 'users');
-               // Check the cached user object
-               if ( false !== $user && isset($user->$meta_key) )
-                       $metas = array($user->$meta_key);
-               else
-                       $metas = $wpdb->get_col( $wpdb->prepare("SELECT meta_value FROM $wpdb->usermeta WHERE user_id = %d AND meta_key = %s", $user_id, $meta_key) );
-       } else {
-               $metas = $wpdb->get_col( $wpdb->prepare("SELECT meta_value FROM $wpdb->usermeta WHERE user_id = %d", $user_id) );
-       }
-
-       if ( empty($metas) ) {
-               if ( empty($meta_key) )
-                       return array();
-               else
-                       return '';
-       }
-
-       $metas = array_map('maybe_unserialize', $metas);
+function delete_user_meta($user_id, $meta_key, $meta_value = '') {
+       return delete_metadata('user', $user_id, $meta_key, $meta_value);
+}
 
-       if ( count($metas) == 1 )
-               return $metas[0];
-       else
-               return $metas;
+/**
+ * Retrieve user meta field for a user.
+ *
+ * @since 3.0.0
+ * @uses get_metadata()
+ * @link http://codex.wordpress.org/Function_Reference/get_user_meta
+ *
+ * @param int $user_id Post ID.
+ * @param string $key The meta key to retrieve.
+ * @param bool $single Whether to return a single value.
+ * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
+ *  is true.
+ */
+function get_user_meta($user_id, $key, $single = false) {
+       return get_metadata('user', $user_id, $key, $single);
 }
 
 /**
- * Update metadata of user.
+ * Update user meta field based on user ID.
  *
- * There is no need to serialize values, they will be serialized if it is
- * needed. The metadata key can only be a string with underscores. All else will
- * be removed.
+ * Use the $prev_value parameter to differentiate between meta fields with the
+ * same key and user ID.
  *
- * Will remove the metadata, if the meta value is empty.
+ * If the meta field for the user does not exist, it will be added.
  *
- * @since 2.0.0
- * @uses $wpdb WordPress database object for queries
+ * @since 3.0.0
+ * @uses update_metadata
+ * @link http://codex.wordpress.org/Function_Reference/update_user_meta
  *
- * @param int $user_id User ID
- * @param string $meta_key Metadata key.
- * @param mixed $meta_value Metadata value.
- * @return bool True on successful update, false on failure.
+ * @param int $user_id Post ID.
+ * @param string $key Metadata key.
+ * @param mixed $value Metadata value.
+ * @param mixed $prev_value Optional. Previous value to check before removing.
+ * @return bool False on failure, true if success.
  */
-function update_usermeta( $user_id, $meta_key, $meta_value ) {
-       global $wpdb;
-       if ( !is_numeric( $user_id ) )
-               return false;
-       $meta_key = preg_replace('|[^a-z0-9_]|i', '', $meta_key);
+function update_user_meta($user_id, $meta_key, $meta_value, $prev_value = '') {
+       return update_metadata('user', $user_id, $meta_key, $meta_value, $prev_value);
+}
+
+/**
+ * Count number of users who have each of the user roles.
+ *
+ * Assumes there are neither duplicated nor orphaned capabilities meta_values.
+ * Assumes role names are unique phrases.  Same assumption made by WP_User_Search::prepare_query()
+ * Using $strategy = 'time' this is CPU-intensive and should handle around 10^7 users.
+ * Using $strategy = 'memory' this is memory-intensive and should handle around 10^5 users, but see WP Bug #12257.
+ *
+ * @since 3.0.0
+ * @param string $strategy 'time' or 'memory'
+ * @return array Includes a grand total and an array of counts indexed by role strings.
+ */
+function count_users($strategy = 'time') {
+       global $wpdb, $blog_id, $wp_roles;
 
-       /** @todo Might need fix because usermeta data is assumed to be already escaped */
-       if ( is_string($meta_value) )
-               $meta_value = stripslashes($meta_value);
-       $meta_value = maybe_serialize($meta_value);
+       // Initialize
+       $id = (int) $blog_id;
+       $blog_prefix = $wpdb->get_blog_prefix($id);
+       $result = array();
 
-       if (empty($meta_value)) {
-               return delete_usermeta($user_id, $meta_key);
-       }
+       if ( 'time' == $strategy ) {
+               global $wp_roles;
 
-       $cur = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->usermeta WHERE user_id = %d AND meta_key = %s", $user_id, $meta_key) );
+               if ( ! isset( $wp_roles ) )
+                       $wp_roles = new WP_Roles();
 
-       if ( $cur )
-               do_action( 'update_usermeta', $cur->umeta_id, $user_id, $meta_key, $meta_value );
+               $avail_roles = $wp_roles->get_names();
 
-       if ( !$cur )
-               $wpdb->insert($wpdb->usermeta, compact('user_id', 'meta_key', 'meta_value') );
-       else if ( $cur->meta_value != $meta_value )
-               $wpdb->update($wpdb->usermeta, compact('meta_value'), compact('user_id', 'meta_key') );
-       else
-               return false;
+               // Build a CPU-intensive query that will return concise information.
+               $select_count = array();
+               foreach ( $avail_roles as $this_role => $name ) {
+                       $select_count[] = "COUNT(NULLIF(`meta_value` LIKE '%" . like_escape($this_role) . "%', FALSE))";
+               }
+               $select_count = implode(', ', $select_count);
+
+               // Add the meta_value index to the selection list, then run the query.
+               $row = $wpdb->get_row( "SELECT $select_count, COUNT(*) FROM $wpdb->usermeta WHERE meta_key = '{$blog_prefix}capabilities'", ARRAY_N );
+
+               // Run the previous loop again to associate results with role names.
+               $col = 0;
+               $role_counts = array();
+               foreach ( $avail_roles as $this_role => $name ) {
+                       $count = (int) $row[$col++];
+                       if ($count > 0) {
+                               $role_counts[$this_role] = $count;
+                       }
+               }
 
-       wp_cache_delete($user_id, 'users');
+               // Get the meta_value index from the end of the result set.
+               $total_users = (int) $row[$col];
 
-       if ( !$cur )
-               do_action( 'added_usermeta', $wpdb->insert_id, $user_id, $meta_key, $meta_value );
-       else
-               do_action( 'updated_usermeta', $cur->umeta_id, $user_id, $meta_key, $meta_value );
+               $result['total_users'] = $total_users;
+               $result['avail_roles'] =& $role_counts;
+       } else {
+               $avail_roles = array();
+
+               $users_of_blog = $wpdb->get_col( "SELECT meta_value FROM $wpdb->usermeta WHERE meta_key = '{$blog_prefix}capabilities'" );
+
+               foreach ( $users_of_blog as $caps_meta ) {
+                       $b_roles = unserialize($caps_meta);
+                       if ( is_array($b_roles) ) {
+                               foreach ( $b_roles as $b_role => $val ) {
+                                       if ( isset($avail_roles[$b_role]) ) {
+                                               $avail_roles[$b_role]++;
+                                       } else {
+                                               $avail_roles[$b_role] = 1;
+                                       }
+                               }
+                       }
+               }
 
-       return true;
+               $result['total_users'] = count( $users_of_blog );
+               $result['avail_roles'] =& $avail_roles;
+       }
+
+       return $result;
 }
 
 //
@@ -433,10 +508,9 @@ function update_usermeta( $user_id, $meta_key, $meta_value ) {
 //
 
 /**
- * Setup global user vars.
+ * Set up global user vars.
  *
- * Used by set_current_user() for back compat. Might be deprecated in the
- * future.
+ * Used by wp_set_current_user() for back compat. Might be deprecated in the future.
  *
  * @since 2.0.4
  * @global string $userdata User description.
@@ -448,7 +522,7 @@ function update_usermeta( $user_id, $meta_key, $meta_value ) {
  * @global string $user_pass_md5 MD5 of the user's password
  * @global string $user_identity The display name of the user
  *
- * @param int $for_user_id Optional. User ID to setup global data.
+ * @param int $for_user_id Optional. User ID to set up global data.
  */
 function setup_userdata($for_user_id = '') {
        global $user_login, $userdata, $user_level, $user_ID, $user_email, $user_url, $user_pass_md5, $user_identity;
@@ -458,13 +532,16 @@ function setup_userdata($for_user_id = '') {
        else
                $user = new WP_User($for_user_id);
 
-       if ( 0 == $user->ID )
+       $userdata   = $user->data;
+       $user_ID    = (int) $user->ID;
+       $user_level = (int) isset($user->user_level) ? $user->user_level : 0;
+
+       if ( 0 == $user->ID ) {
+               $user_login = $user_email = $user_url = $user_pass_md5 = $user_identity = '';
                return;
+       }
 
-       $userdata = $user->data;
        $user_login     = $user->user_login;
-       $user_level     = (int) isset($user->user_level) ? $user->user_level : 0;
-       $user_ID = (int) $user->ID;
        $user_email     = $user->user_email;
        $user_url       = $user->user_url;
        $user_pass_md5  = md5($user->user_pass);
@@ -489,12 +566,14 @@ function setup_userdata($for_user_id = '') {
  * <li>order - Default is 'ASC'. Can also be 'DESC'.</li>
  * <li>include - User IDs to include.</li>
  * <li>exclude - User IDs to exclude.</li>
- * <li>multi - Default is 'false'. Whether to skip the ID attribute on the 'select' element.</li>
+ * <li>multi - Default is 'false'. Whether to skip the ID attribute on the 'select' element. A 'true' value is overridden when id argument is set.</li>
  * <li>show - Default is 'display_name'. User table column to display. If the selected item is empty then the user_login will be displayed in parentesis</li>
  * <li>echo - Default is '1'. Whether to display or retrieve content.</li>
  * <li>selected - Which User ID is selected.</li>
  * <li>name - Default is 'user'. Name attribute of select element.</li>
+ * <li>id - Default is the value of the 'name' parameter. ID attribute of select element.</li>
  * <li>class - Class attribute of select element.</li>
+ * <li>blog_id - ID of blog (Multisite only). Defaults to ID of current blog.</li>
  * </ol>
  *
  * @since 2.3.0
@@ -510,7 +589,8 @@ function wp_dropdown_users( $args = '' ) {
                'orderby' => 'display_name', 'order' => 'ASC',
                'include' => '', 'exclude' => '', 'multi' => 0,
                'show' => 'display_name', 'echo' => 1,
-               'selected' => 0, 'name' => 'user', 'class' => ''
+               'selected' => 0, 'name' => 'user', 'class' => '', 'blog_id' => $GLOBALS['blog_id'],
+               'id' => '',
        );
 
        $defaults['selected'] = is_author() ? get_query_var( 'author' ) : 0;
@@ -518,7 +598,8 @@ function wp_dropdown_users( $args = '' ) {
        $r = wp_parse_args( $args, $defaults );
        extract( $r, EXTR_SKIP );
 
-       $query = "SELECT * FROM $wpdb->users";
+       $blog_prefix = $wpdb->get_blog_prefix( $blog_id );
+       $query = "SELECT {$wpdb->users}.* FROM $wpdb->users, $wpdb->usermeta WHERE {$wpdb->users}.ID = {$wpdb->usermeta}.user_id AND meta_key = '{$blog_prefix}capabilities'";
 
        $query_where = array();
 
@@ -535,7 +616,7 @@ function wp_dropdown_users( $args = '' ) {
                $query_where[] = "ID NOT IN ($exclude)";
 
        if ( $query_where )
-               $query .= " WHERE " . join(' AND', $query_where);
+               $query .= " AND " . join(' AND', $query_where);
 
        $query .= " ORDER BY $orderby $order";
 
@@ -543,19 +624,25 @@ function wp_dropdown_users( $args = '' ) {
 
        $output = '';
        if ( !empty($users) ) {
-               $id = $multi ? "" : "id='$name'";
+               $name = esc_attr( $name );
+               if ( $multi && ! $id )
+                       $id = '';
+               else
+                       $id = $id ? " id='" . esc_attr( $id ) . "'" : " id='$name'";
 
-               $output = "<select name='$name' $id class='$class'>\n";
+               $output = "<select name='{$name}'{$id} class='$class'>\n";
 
                if ( $show_option_all )
                        $output .= "\t<option value='0'>$show_option_all</option>\n";
 
-               if ( $show_option_none )
-                       $output .= "\t<option value='-1'>$show_option_none</option>\n";
+               if ( $show_option_none ) {
+                       $_selected = selected( -1, $selected, false );
+                       $output .= "\t<option value='-1'$_selected>$show_option_none</option>\n";
+               }
 
                foreach ( (array) $users as $user ) {
                        $user->ID = (int) $user->ID;
-                       $_selected = $user->ID == $selected ? " selected='selected'" : '';
+                       $_selected = selected( $user->ID, $selected, false );
                        $display = !empty($user->$show) ? $user->$show : '('. $user->user_login . ')';
                        $output .= "\t<option value='$user->ID'$_selected>" . esc_html($display) . "</option>\n";
                }
@@ -576,8 +663,8 @@ function wp_dropdown_users( $args = '' ) {
  *
  * The finished user data is cached, but the cache is not used to fill in the
  * user data for the given object. Once the function has been used, the cache
- * should be used to retrieve user data. The purpose seems then to be to ensure
- * that the data in the object is always fresh.
+ * should be used to retrieve user data. The intention is if the current data
+ * had been cached already, there would be no need to call this function.
  *
  * @access private
  * @since 2.5.0
@@ -586,17 +673,56 @@ function wp_dropdown_users( $args = '' ) {
  * @param object $user The user data object.
  */
 function _fill_user( &$user ) {
+       $metavalues = get_user_metavalues(array($user->ID));
+       _fill_single_user($user, $metavalues[$user->ID]);
+}
+
+/**
+ * Perform the query to get the $metavalues array(s) needed by _fill_user and _fill_many_users
+ *
+ * @since 3.0.0
+ * @param array $ids User ID numbers list.
+ * @return array of arrays. The array is indexed by user_id, containing $metavalues object arrays.
+ */
+function get_user_metavalues($ids) {
        global $wpdb;
 
+       $clean = array_map('intval', $ids);
+       if ( 0 == count($clean) )
+               return $objects;
+
+       $list = implode(',', $clean);
+
        $show = $wpdb->hide_errors();
-       $metavalues = $wpdb->get_results($wpdb->prepare("SELECT meta_key, meta_value FROM $wpdb->usermeta WHERE user_id = %d", $user->ID));
+       $metavalues = $wpdb->get_results("SELECT user_id, meta_key, meta_value FROM $wpdb->usermeta WHERE user_id IN ($list)");
        $wpdb->show_errors($show);
 
-       if ( $metavalues ) {
-               foreach ( (array) $metavalues as $meta ) {
-                       $value = maybe_unserialize($meta->meta_value);
-                       $user->{$meta->meta_key} = $value;
-               }
+       $objects = array();
+       foreach($clean as $id) {
+               $objects[$id] = array();
+       }
+       foreach($metavalues as $meta_object) {
+               $objects[$meta_object->user_id][] = $meta_object;
+       }
+
+       return $objects;
+}
+
+/**
+ * Unserialize user metadata, fill $user object, then cache everything.
+ *
+ * @since 3.0.0
+ * @param object $user The User object.
+ * @param array $metavalues An array of objects provided by get_user_metavalues()
+ */
+function _fill_single_user( &$user, &$metavalues ) {
+       global $wpdb;
+
+       foreach ( $metavalues as $meta ) {
+               $value = maybe_unserialize($meta->meta_value);
+               // Keys used as object vars cannot have dashes.
+               $key = str_replace('-', '', $meta->meta_key);
+               $user->{$key} = $value;
        }
 
        $level = $wpdb->prefix . 'user_level';
@@ -611,10 +737,28 @@ function _fill_user( &$user ) {
        if ( isset($user->description) )
                $user->user_description = $user->description;
 
-       wp_cache_add($user->ID, $user, 'users');
-       wp_cache_add($user->user_login, $user->ID, 'userlogins');
-       wp_cache_add($user->user_email, $user->ID, 'useremail');
-       wp_cache_add($user->user_nicename, $user->ID, 'userslugs');
+       update_user_caches($user);
+}
+
+/**
+ * Take an array of user objects, fill them with metas, and cache them.
+ *
+ * @since 3.0.0
+ * @param array $users User objects
+ */
+function _fill_many_users( &$users ) {
+       $ids = array();
+       foreach($users as $user_object) {
+               $ids[] = $user_object->ID;
+       }
+
+    $metas = get_user_metavalues($ids);
+
+       foreach($users as $user_object) {
+               if (isset($metas[$user_object->ID])) {
+               _fill_single_user($user_object, $metas[$user_object->ID]);
+               }
+       }
 }
 
 /**
@@ -638,7 +782,7 @@ function sanitize_user_object($user, $context = 'display') {
                else
                        $vars = get_object_vars($user);
                foreach ( array_keys($vars) as $field ) {
-                       if ( is_string($user->$field) || is_numeric($user->$field) ) 
+                       if ( is_string($user->$field) || is_numeric($user->$field) )
                                $user->$field = sanitize_user_field($field, $user->$field, $user->ID, $context);
                }
                $user->filter = $context;
@@ -733,4 +877,34 @@ function sanitize_user_field($field, $value, $user_id, $context) {
        return $value;
 }
 
+/**
+ * Update all user caches
+ *
+ * @since 3.0.0
+ *
+ * @param object $user User object to be cached
+ */
+function update_user_caches(&$user) {
+       wp_cache_add($user->ID, $user, 'users');
+       wp_cache_add($user->user_login, $user->ID, 'userlogins');
+       wp_cache_add($user->user_email, $user->ID, 'useremail');
+       wp_cache_add($user->user_nicename, $user->ID, 'userslugs');
+}
+
+/**
+ * Clean all user caches
+ *
+ * @since 3.0.0
+ *
+ * @param int $id User ID
+ */
+function clean_user_cache($id) {
+       $user = new WP_User($id);
+
+       wp_cache_delete($id, 'users');
+       wp_cache_delete($user->user_login, 'userlogins');
+       wp_cache_delete($user->user_email, 'useremail');
+       wp_cache_delete($user->user_nicename, 'userslugs');
+}
+
 ?>