<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[BuddyPress][6314] trunk: Introduces BP_User_Query for improved member query performance</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg > ul, #logmsg > ol { margin-left: 0; margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://buddypress.trac.wordpress.org/changeset/6314">6314</a></dd>
<dt>Author</dt> <dd>boonebgorges</dd>
<dt>Date</dt> <dd>2012-09-07 02:14:42 +0000 (Fri, 07 Sep 2012)</dd>
</dl>

<h3>Log Message</h3>
<pre>Introduces BP_User_Query for improved member query performance

- Introduces BP_User_Query class
- Deprecates use of BP_Core_User::get_users() in bp_core_get_users(), the main
  member query function in BuddyPress
- Introduces bp_use_legacy_user_query filter, to allow plugins to use
  legacy query method during transition
- Expands user query functionality to support 'user_ids' parameter, which
  can be used to skip the user_id query altogether, for maximum flexibility
  in plugins and themes

Props boonebgorges, johnjamesjacoby.

See <a href="http://buddypress.trac.wordpress.org/ticket/4060">#4060</a></pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkbpcorebpcoreclassesphp">trunk/bp-core/bp-core-classes.php</a></li>
<li><a href="#trunkbpcorebpcorewidgetsphp">trunk/bp-core/bp-core-widgets.php</a></li>
<li><a href="#trunkbpmembersbpmembersfunctionsphp">trunk/bp-members/bp-members-functions.php</a></li>
<li><a href="#trunkbpxprofilebpxprofilefiltersphp">trunk/bp-xprofile/bp-xprofile-filters.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkbpfriendsbpfriendsfiltersphp">trunk/bp-friends/bp-friends-filters.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkbpcorebpcoreclassesphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-core/bp-core-classes.php (6313 => 6314)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-core/bp-core-classes.php        2012-09-06 17:13:45 UTC (rev 6313)
+++ trunk/bp-core/bp-core-classes.php        2012-09-07 02:14:42 UTC (rev 6314)
</span><span class="lines">@@ -1,8 +1,524 @@
</span><span class="cx"> &lt;?php
</span><ins>+
</ins><span class="cx"> // Exit if accessed directly
</span><span class="cx"> if ( !defined( 'ABSPATH' ) ) exit;
</span><span class="cx"> 
</span><span class="cx"> /**
</span><ins>+ * BuddyPress User Query class
+ *
+ * Used for querying users in a BuddyPress context, in situations where
+ * WP_User_Query won't do the trick: Member directories, the Friends component,
+ * etc.
+ *
+ * Accepted parameters:
+ *   type             - Determines sort order. Select from 'newest', 'active',
+ *                     'online', 'random', 'popular', 'alphabetical'
+ *   per_page        - Number of results to return
+ *   page            - Page offset (together with per_page)
+ *   user_id         - Pass a single numeric user id to limit results to
+ *                     friends of that user. Requires the Friends component
+ *   search_terms    - Terms to search by. Search happens across xprofile
+ *                     fields. Requires XProfile component
+ *   include         - An array or comma-separated list of user ids. Results
+ *                     will be limited to users in this list
+ *   exclude         - An array or comma-separated list of user ids. Results
+ *                     will not include any users in this list
+ *   user_ids        - An array or comma-separated list of user ids. When
+ *                     this parameter is passed, it will override all other
+ *                     parameters; BP User objects will be constructed using
+ *                     these IDs only
+ *   meta_key        - Limit results to users that have usermeta associated
+ *                     with this meta_key. Usually used with meta_value
+ *   meta_value      - When used with meta_key, limits results to users whose
+ *                     usermeta value associated with meta_key matches
+ *                     meta_value
+ *   populate_extras - Boolean. True if you want to fetch extra metadata about
+ *                     returned users, such as total group and friend counts
+ *   count_total     - Determines how BP_User_Query will do a count of total
+ *                     users matching the other filter criteria. Default value
+ *                     is 'count_query', which does a separate SELECT COUNT
+ *                     query to determine the total. 'sql_count_found_rows'
+ *                     uses SQL_COUNT_FOUND_ROWS and SELECT FOUND_ROWS(). Pass
+ *                     an empty string to skip the total user count query.
+ *
+ * @since BuddyPress (1.7)
+ */
+class BP_User_Query {
+
+        /** Variables *************************************************************/
+
+        /**
+         * Array of variables to query with
+         *
+         * @since BuddyPress (1.7)
+         * @var array
+         */
+        public $query_vars = array();
+
+        /**
+         * List of found users and their respective data
+         *
+         * @since BuddyPress (1.7)
+         * @access public To allow components to manipulate them
+         * @var array
+         */
+        public $results = array();
+
+        /**
+         * Total number of found users for the current query
+         *
+         * @since BuddyPress (1.7)
+         * @access public To allow components to manipulate it
+         * @var int
+         */
+        public $total_users = 0;
+
+        /**
+         * List of found user ID's
+         *
+         * @since BuddyPress (1.7)
+         * @access public To allow components to manipulate it
+         * @var array
+         */
+        public $user_ids = array();
+
+        /**
+         * SQL clauses for the user ID query
+         *
+         * @since BuddyPress (1.7)
+         * @access public To allow components to manipulate it
+         * @var array()
+         */
+        public $uid_clauses = array();
+
+        /**
+         * SQL database column name to order by
+         *
+         * @since BuddyPress (1.7)
+         * @var string
+         */
+        public $uid_name = '';
+
+        /** Methods ***************************************************************/
+
+        /**
+         * Constructor
+         *
+         * @since 1.7
+         *
+         * @param string|array $query The query variables
+         */
+        public function __construct( $query = null ) {
+                if ( ! empty( $query ) ) {
+                        $this-&gt;query_vars = wp_parse_args( $query, array(
+                                'type'            =&gt; 'newest',
+                                'per_page'        =&gt; 0,
+                                'page'            =&gt; 1,
+                                'user_id'         =&gt; 0,
+                                'search_terms'    =&gt; false,
+                                'include'         =&gt; false,
+                                'exclude'         =&gt; false,
+                                'user_ids'        =&gt; false,
+                                'meta_key'        =&gt; false,
+                                'meta_value'      =&gt; false,
+                                'populate_extras' =&gt; true,
+                                'count_total'     =&gt; 'count_query'
+                        ) );
+
+                        // Plugins can use this filter to modify query args
+                        // before the query is constructed
+                        do_action_ref_array( 'bp_pre_user_query_construct', array( &amp;$this ) );
+
+                        // Get user ids
+                        // If the user_ids param is present, we skip the query
+                        if ( false !== $this-&gt;query_vars['user_ids'] ) {
+                                $this-&gt;user_ids = wp_parse_id_list( $this-&gt;query_vars['user_ids'] );
+                        } else {
+                                $this-&gt;prepare_user_ids_query();
+                                $this-&gt;do_user_ids_query();
+                        }
+                }
+
+                // Bail if no user IDs were found
+                if ( empty( $this-&gt;user_ids ) ) {
+                        return;
+                }
+
+                // Fetch additional data. First, using WP_User_Query
+                $this-&gt;do_wp_user_query();
+
+                // Get BuddyPress specific user data
+                $this-&gt;populate_extras();
+        }
+
+        /**
+         * Prepare the query for user_ids
+         *
+         * @since BuddyPress (1.7)
+         */
+        public function prepare_user_ids_query() {
+                global $wpdb, $bp;
+
+                // Default query variables used here
+                $type         = '';
+                $per_page     = 0;
+                $page         = 1;
+                $user_id      = 0;
+                $include      = false;
+                $search_terms = false;
+                $exclude      = false;
+                $meta_key     = false;
+                $meta_value   = false;
+
+                extract( $this-&gt;query_vars );
+
+                // Setup the main SQL query container
+                $sql = array(
+                        'select'  =&gt; '',
+                        'where'   =&gt; array(),
+                        'orderby' =&gt; '',
+                        'order'   =&gt; '',
+                        'limit'   =&gt; ''
+                );
+
+                /** TYPE **************************************************************/
+
+                // Determines the sort order, which means it also determines where the
+                // user IDs are drawn from (the SELECT and WHERE statements)
+                switch ( $type ) {
+
+                        // 'active', 'online', 'newest', and 'random' queries
+                        // all happen against the last_activity usermeta key
+                        case 'active' :
+                        case 'online' :
+                        case 'newest' :
+                        case 'random' :
+                                $this-&gt;uid_name = 'user_id';
+                                $sql['select']  = &quot;SELECT DISTINCT u.{$this-&gt;uid_name} as id FROM {$wpdb-&gt;usermeta} u&quot;;
+                                $sql['where'][] = $wpdb-&gt;prepare( &quot;u.meta_key = %s&quot;, bp_get_user_meta_key( 'last_activity' ) );
+
+                                if ( 'newest' == $type ) {
+                                        $sql['orderby'] = &quot;ORDER BY u.user_id&quot;;
+                                        $sql['order'] = &quot;DESC&quot;;
+                                } else if ( 'random' == $type ) {
+                                        $sql['orderby'] = &quot;ORDER BY rand()&quot;;
+                                } else {
+                                        $sql['orderby'] = &quot;ORDER BY u.meta_value&quot;;
+                                        $sql['order'] = &quot;DESC&quot;;
+                                }
+
+                                break;
+
+                        // 'popular' sorts by the 'total_friend_count' usermeta
+                        case 'popular' :
+                                $this-&gt;uid_name = 'user_id';
+                                $sql['select']  = &quot;SELECT DISTINCT u.{$this-&gt;uid_name} as id FROM {$wpdb-&gt;usermeta} u&quot;;
+                                $sql['where'][] = $wpdb-&gt;prepare( &quot;u.meta_key = %s&quot;, bp_get_user_meta_key( 'total_friend_count' ) );
+                                $sql['orderby'] = &quot;ORDER BY u.meta_value&quot;;
+                                $sql['order']   = &quot;DESC&quot;;
+
+                                break;
+
+                        // 'alphabetical' sorts depend on the xprofile setup
+                        case 'alphabetical' :
+
+                                // We prefer to do alphabetical sorts against the display_name field
+                                // of wp_users, because the table is smaller and better indexed. We
+                                // can do so if xprofile sync is enabled, or if xprofile is inactive.
+                                //
+                                // @todo remove need for bp_is_active() check
+                                if ( ! bp_disable_profile_sync() || ! bp_is_active( 'xprofile' ) ) {
+                                        $this-&gt;uid_name = 'ID';
+                                        $sql['select']  = &quot;SELECT DISTINCT u.{$this-&gt;uid_name} as id FROM {$wpdb-&gt;users} u&quot;;
+                                        $sql['orderby'] = &quot;ORDER BY u.display_name&quot;;
+                                        $sql['order']   = &quot;ASC&quot;;
+
+                                // When profile sync is disabled, alphabetical sorts must happen against
+                                // the xprofile table
+                                } else {
+                                        $fullname_field_id = $wpdb-&gt;get_var( $wpdb-&gt;prepare( &quot;SELECT id FROM {$bp-&gt;profile-&gt;table_name_fields} WHERE name = %s&quot;, bp_xprofile_fullname_field_name() ) );
+
+                                        $this-&gt;uid_name = 'user_id';
+                                        $sql['select']  = &quot;SELECT DISTINCT u.{$this-&gt;uid_name} as id FROM {$bp-&gt;profile-&gt;table_name_data} u&quot;;
+                                        $sql['where'][] = &quot;u.field_id = {$fullname_field_id}&quot;;
+                                        $sql['orderby'] = &quot;ORDER BY u.value&quot;;
+                                        $sql['order']   = &quot;ASC&quot;;
+                                }
+
+                                break;
+
+                        // Any other 'type' falls through
+                        default :
+                                $this-&gt;uid_name = 'ID';
+                                $sql['select']  = &quot;SELECT DISTINCT u.{$this-&gt;uid_name} as id FROM {$wpdb-&gt;users} u&quot;;
+
+                                // In this case, we assume that a plugin is
+                                // handling order, so we leave those clauses
+                                // blank
+
+                                break;
+                }
+
+                /** WHERE *************************************************************/
+
+                // 'include' - User ids to include in the results
+                if ( false !== $include ) {
+                        $include        = wp_parse_id_list( $include );
+                        $include_ids    = $wpdb-&gt;escape( implode( ',', (array) $include ) );
+                        $sql['where'][] = &quot;u.{$this-&gt;uid_name} IN ({$include_ids})&quot;;
+                }
+
+                // 'exclude' - User ids to exclude from the results
+                if ( false !== $exclude ) {
+                        $exclude        = wp_parse_id_list( $exclude );
+                        $exclude_ids    = $wpdb-&gt;escape( implode( ',', (array) $exclude ) );
+                        $sql['where'][] = &quot;u.{$this-&gt;uid_name} NOT IN ({$exclude_ids})&quot;;
+                }
+
+                // 'user_id' - When a user id is passed, limit to the friends of the user
+                // @todo remove need for bp_is_active() check
+                if ( !empty( $user_id ) &amp;&amp; bp_is_active( 'friends' ) ) {
+                        $friend_ids = friends_get_friend_user_ids( $user_id );
+                        $friend_ids = $wpdb-&gt;escape( implode( ',', (array) $friend_ids ) );
+
+                        if ( !empty( $friend_ids ) ) {
+                                $sql['where'][] = &quot;u.{$this-&gt;uid_name} NOT IN ({$friend_ids})&quot;;
+                        } else {
+                                // If the user has no friends, make sure the query returns null
+                                $sql['where'][] = &quot;0 = 1&quot;;
+                        }
+                }
+
+                /** Search Terms ******************************************************/
+
+                // 'search_terms' searches the xprofile fields
+                // To avoid global joins, do a separate query
+                // @todo remove need for bp_is_active() check
+                if ( false !== $search_terms &amp;&amp; bp_is_active( 'xprofile' ) ) {
+                        $found_user_ids = $wpdb-&gt;get_col( $wpdb-&gt;prepare( &quot;SELECT user_id FROM {$bp-&gt;profile-&gt;table_name_data} WHERE value LIKE %s&quot;, '%%' . like_escape( $search_terms ) . '%%' ), ARRAY_N );
+
+                        if ( ! empty( $found_user_ids ) ) {
+                                $sql['where'][] = &quot;u.{$this-&gt;uid_name} IN (&quot; . implode( ',', wp_parse_id_list( $found_user_ids ) ) . &quot;)&quot;;
+                        }
+                }
+
+                // 'meta_key', 'meta_value' allow usermeta search
+                // To avoid global joins, do a separate query
+                if ( false !== $meta_key ) {
+                        $meta_sql = $wpdb-&gt;prepare( &quot;SELECT user_id FROM {$wpdb-&gt;usermeta} WHERE meta_key = %s&quot;, $meta_key );
+
+                        if ( false !== $meta_value ) {
+                                $meta_sql .= $wpdb-&gt;prepare( &quot; AND meta_value = %s&quot;, $meta_value );
+                        }
+
+                        $found_user_ids = $wpdb-&gt;get_col( $meta_sql );
+
+                        if ( ! empty( $found_user_ids ) ) {
+                                $sql['where'][] = &quot;u.{$this-&gt;uid_name} IN (&quot; . implode( ',', wp_parse_id_list( $found_user_ids ) ) . &quot;)&quot;;
+                        }
+                }
+
+                // 'per_page', 'page' - handles LIMIT
+                if ( !empty( $per_page ) &amp;&amp; !empty( $page ) ) {
+                        $sql['limit'] = $wpdb-&gt;prepare( &quot;LIMIT %d, %d&quot;, intval( ( $page - 1 ) * $per_page ), intval( $per_page ) );
+                } else {
+                        $sql['limit'] = '';
+                }
+
+                // Assemble the query chunks
+                $this-&gt;uid_clauses['select']  = $sql['select'];
+                $this-&gt;uid_clauses['where']   = ! empty( $sql['where'] ) ? 'WHERE ' . implode( ' AND ', $sql['where'] ) : '';
+                $this-&gt;uid_clauses['orderby'] = $sql['orderby'];
+                $this-&gt;uid_clauses['order']   = $sql['order'];
+                $this-&gt;uid_clauses['limit']   = $sql['limit'];
+
+                do_action_ref_array( 'bp_pre_user_query', array( &amp;$this ) );
+        }
+
+        /**
+         * Perform a database query to specifically get only user IDs, using
+         * existing query variables set previously in the constructor.
+         *
+         * Also used to quickly perform user total counts.
+         *
+         * @since BuddyPress (1.7)
+         */
+        public function do_user_ids_query() {
+                global $wpdb;
+
+                // If counting using SQL_CALC_FOUND_ROWS, set it up here
+                if ( 'sql_calc_found_rows' == $this-&gt;query_vars['count_total'] ) {
+                        $this-&gt;uid_clauses['select'] = str_replace( 'SELECT', 'SELECT SQL_CALC_FOUND_ROWS', $this-&gt;uid_clauses['select'] );
+                }
+
+                // Get the specific user ids
+                $this-&gt;user_ids = $wpdb-&gt;get_col( $wpdb-&gt;prepare( &quot;{$this-&gt;uid_clauses['select']} {$this-&gt;uid_clauses['where']} {$this-&gt;uid_clauses['orderby']} {$this-&gt;uid_clauses['order']} {$this-&gt;uid_clauses['limit']}&quot; ) );
+
+                // Get the total user count
+                if ( 'sql_calc_found_rows' == $this-&gt;query_vars['count_total'] ) {
+                        $this-&gt;total_users = $wpdb-&gt;get_var( apply_filters( 'bp_found_user_query', &quot;SELECT FOUND_ROWS()&quot;, $this ) );
+                } elseif ( 'count_query' == $this-&gt;query_vars['count_total'] ) {
+                        $count_select      = preg_replace( '/^SELECT.*?FROM (\S+) u/', &quot;SELECT COUNT(DISTINCT u.{$this-&gt;uid_name}) FROM $1 u&quot;, $this-&gt;uid_clauses['select'] );
+                        $this-&gt;total_users = $wpdb-&gt;get_var( apply_filters( 'bp_found_user_query', &quot;{$count_select} {$this-&gt;uid_clauses['where']}&quot;, $this ) );
+                }
+        }
+
+        /**
+         * Perform a database query using the WP_User_Query() object, using existing
+         * fields, variables, and user ID's set previously in this class.
+         *
+         * @since BuddyPress (1.7)
+         */
+        public function do_wp_user_query() {
+                $wp_user_query = new WP_User_Query( apply_filters( 'bp_wp_user_query_args', array(
+
+                        // Relevant
+                        'fields'      =&gt; array( 'ID', 'user_registered', 'user_login', 'user_nicename', 'display_name', 'user_email' ),
+                        'include'     =&gt; $this-&gt;user_ids,
+
+                        // Overrides
+                        'blog_id'     =&gt; 0,    // BP does not require blog roles
+                        'count_total' =&gt; false // We already have a count
+
+                ), $this ) );
+
+                // Reindex for easier matching
+                $r = array();
+                foreach ( $wp_user_query-&gt;results as $u ) {
+                        $r[ $u-&gt;ID ] = $u;
+                }
+
+                // Match up to the user ids from the main query
+                foreach ( $this-&gt;user_ids as $uid ) {
+                        if ( isset( $r[ $uid ] ) ) {
+                                $this-&gt;results[ $uid ] = $r[ $uid ];
+
+                                // The BP template functions expect an 'id'
+                                // (as opposed to 'ID') property
+                                $this-&gt;results[ $uid ]-&gt;id = $uid;
+                        }
+                }
+        }
+
+        /**
+         * Perform a database query to populate any extra metadata we might need.
+         * Different components will hook into the 'bp_user_query_populate_extras'
+         * action to loop in the things they want.
+         *
+         * @since BuddyPress (1.7)
+         *
+         * @global BuddyPress $bp
+         * @global WPDB $wpdb
+         * @return
+         */
+        public function populate_extras() {
+                global $wpdb;
+
+                // Bail if no users
+                if ( empty( $this-&gt;user_ids ) || empty( $this-&gt;results ) ) {
+                        return;
+                }
+
+                // Bail if the populate_extras flag is set to false
+                // In the case of the 'popular' sort type, we force
+                // populate_extras to true, because we need the friend counts
+                if ( 'popular' == $this-&gt;query_vars['type'] ) {
+                        $this-&gt;query_vars['populate_extras'] = 1;
+                }
+
+                if ( ! (bool) $this-&gt;query_vars['populate_extras'] ) {
+                        return;
+                }
+
+                // Turn user ID's into a query-usable, comma separated value
+                $user_ids_sql = implode( ',', wp_parse_id_list( $this-&gt;user_ids ) );
+
+                /**
+                 * Use this action to independently populate your own custom extras.
+                 *
+                 * Note that anything you add here should query using $user_ids_sql, to
+                 * avoid running multiple queries per user in the loop.
+                 *
+                 * Two BuddyPress components currently do this:
+                 * - XProfile: To override display names
+                 * - Friends:  To set whether or not a user is the current users friend
+                 *
+                 * @see bp_xprofile_filter_user_query_populate_extras()
+                 * @see bp_friends_filter_user_query_populate_extras()
+                 */
+                do_action_ref_array( 'bp_user_query_populate_extras', array( $this, $user_ids_sql ) );
+
+                // Fetch usermeta data
+                // We want the three following pieces of info from usermeta:
+                // - friend count
+                // - last activity
+                // - latest update
+                $total_friend_count_key = bp_get_user_meta_key( 'total_friend_count' );
+                $last_activity_key      = bp_get_user_meta_key( 'last_activity'      );
+                $bp_latest_update_key   = bp_get_user_meta_key( 'bp_latest_update'   );
+
+                // total_friend_count must be set for each user, even if its
+                // value is 0
+                foreach ( $this-&gt;results as $uindex =&gt; $user ) {
+                        $this-&gt;results[$uindex]-&gt;total_friend_count = 0;
+                }
+
+                // Create, prepare, and run the seperate usermeta query
+                $user_metas = $wpdb-&gt;get_results( $wpdb-&gt;prepare( &quot;SELECT user_id, meta_key, meta_value FROM {$wpdb-&gt;usermeta} WHERE meta_key IN (%s,%s,%s) AND user_id IN ({$user_ids_sql})&quot;, $total_friend_count_key, $last_activity_key, $bp_latest_update_key ) );
+
+                // The $members_template global expects the index key to be different
+                // from the meta_key in some cases, so we rejig things here.
+                foreach ( $user_metas as $user_meta ) {
+                        switch ( $user_meta-&gt;meta_key ) {
+                                case $total_friend_count_key :
+                                        $key = 'total_friend_count';
+                                        break;
+
+                                case $last_activity_key :
+                                        $key = 'last_activity';
+                                        break;
+
+                                case $bp_latest_update_key :
+                                        $key = 'latest_update';
+                                        break;
+                        }
+
+                        if ( isset( $this-&gt;results[ $user_meta-&gt;user_id ] ) ) {
+                                $this-&gt;results[ $user_meta-&gt;user_id ]-&gt;{$key} = $user_meta-&gt;meta_value;
+                        }
+                }
+
+                // When meta_key or meta_value have been passed to the query,
+                // fetch the resulting values for use in the template functions
+                if ( ! empty( $this-&gt;query_vars['meta_key'] ) ) {
+                        $meta_sql = array(
+                                'select' =&gt; &quot;SELECT user_id, meta_key, meta_value&quot;,
+                                'from'   =&gt; &quot;FROM $wpdb-&gt;usermeta&quot;,
+                                'where'  =&gt; $wpdb-&gt;prepare( &quot;WHERE meta_key = %s&quot;, $this-&gt;query_vars['meta_key'] )
+                        );
+
+                        if ( false !== $this-&gt;query_vars['meta_value'] ) {
+                                $meta_sql['where'] .= $wpdb-&gt;prepare( &quot; AND meta_value = %s&quot;, $this-&gt;query_vars['meta_value'] );
+                        }
+
+                        $metas = $wpdb-&gt;get_results( $wpdb-&gt;prepare( &quot;{$meta_sql['select']} {$meta_sql['from']} {$meta_sql['where']}&quot; ) );
+
+                        if ( ! empty( $metas ) ) {
+                                foreach ( $metas as $meta ) {
+                                        if ( isset( $this-&gt;results[ $meta-&gt;user_id ] ) ) {
+                                                $this-&gt;results[ $meta-&gt;user_id ]-&gt;meta_key = $meta-&gt;meta_key;
+
+                                                if ( ! empty( $meta-&gt;meta_value ) ) {
+                                                        $this-&gt;results[ $meta-&gt;user_id ]-&gt;meta_value = $meta-&gt;meta_value;
+                                                }
+                                        }
+                                }
+                        }
+                }
+        }
+}
+
+/**
</ins><span class="cx">  * BP_Core_User class can be used by any component. It will fetch useful
</span><span class="cx">  * details for any user when provided with a user_id.
</span><span class="cx">  *
</span><span class="lines">@@ -201,6 +717,8 @@
</span><span class="cx">         function get_users( $type, $limit = 0, $page = 1, $user_id = 0, $include = false, $search_terms = false, $populate_extras = true, $exclude = false, $meta_key = false, $meta_value = false ) {
</span><span class="cx">                 global $wpdb, $bp;
</span><span class="cx"> 
</span><ins>+                _deprecated_function( __METHOD__, '1.7', 'BP_User_Query' );
+
</ins><span class="cx">                 $sql = array();
</span><span class="cx"> 
</span><span class="cx">                 $sql['select_main'] = &quot;SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.display_name, u.user_email&quot;;
</span></span></pre></div>
<a id="trunkbpcorebpcorewidgetsphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-core/bp-core-widgets.php (6313 => 6314)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-core/bp-core-widgets.php        2012-09-06 17:13:45 UTC (rev 6313)
+++ trunk/bp-core/bp-core-widgets.php        2012-09-07 02:14:42 UTC (rev 6314)
</span><span class="lines">@@ -39,7 +39,7 @@
</span><span class="cx">                    . $title
</span><span class="cx">                    . $after_title; ?&gt;
</span><span class="cx"> 
</span><del>-                &lt;?php if ( bp_has_members( 'user_id=0&amp;type=' . $instance['member_default'] . '&amp;max=' . $instance['max_members'] . '&amp;populate_extras=0' ) ) : ?&gt;
</del><ins>+                &lt;?php if ( bp_has_members( 'user_id=0&amp;type=' . $instance['member_default'] . '&amp;max=' . $instance['max_members'] . '&amp;populate_extras=1' ) ) : ?&gt;
</ins><span class="cx">                         &lt;div class=&quot;item-options&quot; id=&quot;members-list-options&quot;&gt;
</span><span class="cx">                                 &lt;a href=&quot;&lt;?php echo site_url( bp_get_members_root_slug() ); ?&gt;&quot; id=&quot;newest-members&quot; &lt;?php if ( $instance['member_default'] == 'newest' ) : ?&gt;class=&quot;selected&quot;&lt;?php endif; ?&gt;&gt;&lt;?php _e( 'Newest', 'buddypress' ) ?&gt;&lt;/a&gt;
</span><span class="cx">                                 |  &lt;a href=&quot;&lt;?php echo site_url( bp_get_members_root_slug() ); ?&gt;&quot; id=&quot;recently-active-members&quot; &lt;?php if ( $instance['member_default'] == 'active' ) : ?&gt;class=&quot;selected&quot;&lt;?php endif; ?&gt;&gt;&lt;?php _e( 'Active', 'buddypress' ) ?&gt;&lt;/a&gt;
</span><span class="lines">@@ -156,7 +156,7 @@
</span><span class="cx">                    . $instance['title']
</span><span class="cx">                    . $after_title; ?&gt;
</span><span class="cx"> 
</span><del>-                &lt;?php if ( bp_has_members( 'user_id=0&amp;type=online&amp;per_page=' . $instance['max_members'] . '&amp;max=' . $instance['max_members'] . '&amp;populate_extras=0' ) ) : ?&gt;
</del><ins>+                &lt;?php if ( bp_has_members( 'user_id=0&amp;type=online&amp;per_page=' . $instance['max_members'] . '&amp;max=' . $instance['max_members'] . '&amp;populate_extras=1' ) ) : ?&gt;
</ins><span class="cx">                         &lt;div class=&quot;avatar-block&quot;&gt;
</span><span class="cx">                                 &lt;?php while ( bp_members() ) : bp_the_member(); ?&gt;
</span><span class="cx">                                         &lt;div class=&quot;item-avatar&quot;&gt;
</span><span class="lines">@@ -220,7 +220,7 @@
</span><span class="cx">                    . $instance['title']
</span><span class="cx">                    . $after_title; ?&gt;
</span><span class="cx"> 
</span><del>-                &lt;?php if ( bp_has_members( 'user_id=0&amp;type=active&amp;per_page=' . $instance['max_members'] . '&amp;max=' . $instance['max_members'] . '&amp;populate_extras=0' ) ) : ?&gt;
</del><ins>+                &lt;?php if ( bp_has_members( 'user_id=0&amp;type=active&amp;per_page=' . $instance['max_members'] . '&amp;max=' . $instance['max_members'] . '&amp;populate_extras=1' ) ) : ?&gt;
</ins><span class="cx">                         &lt;div class=&quot;avatar-block&quot;&gt;
</span><span class="cx">                                 &lt;?php while ( bp_members() ) : bp_the_member(); ?&gt;
</span><span class="cx">                                         &lt;div class=&quot;item-avatar&quot;&gt;
</span><span class="lines">@@ -290,7 +290,7 @@
</span><span class="cx">                         break;
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        if ( bp_has_members( 'user_id=0&amp;type=' . $type . '&amp;per_page=' . $_POST['max-members'] . '&amp;max=' . $_POST['max-members'] . '&amp;populate_extras=0' ) ) : ?&gt;
</del><ins>+        if ( bp_has_members( 'user_id=0&amp;type=' . $type . '&amp;per_page=' . $_POST['max-members'] . '&amp;max=' . $_POST['max-members'] . '&amp;populate_extras=1' ) ) : ?&gt;
</ins><span class="cx">                 &lt;?php echo '0[[SPLIT]]'; // return valid result. TODO: remove this. ?&gt;
</span><span class="cx">                 &lt;div class=&quot;avatar-block&quot;&gt;
</span><span class="cx">                         &lt;?php while ( bp_members() ) : bp_the_member(); ?&gt;
</span></span></pre></div>
<a id="trunkbpfriendsbpfriendsfiltersphp"></a>
<div class="addfile"><h4>Added: trunk/bp-friends/bp-friends-filters.php (0 => 6314)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-friends/bp-friends-filters.php                                (rev 0)
+++ trunk/bp-friends/bp-friends-filters.php        2012-09-07 02:14:42 UTC (rev 6314)
</span><span class="lines">@@ -0,0 +1,35 @@
</span><ins>+&lt;?php
+
+/**
+ * BuddyPress Friend Filters
+ *
+ * @package BuddyPress
+ * @subpackage FriendsFilters
+ */
+
+/**
+ * Filter BP_User_Query::populate_extras to override each queried users fullname
+ *
+ * @since BuddyPress (1.7)
+ *
+ * @global BuddyPress $bp
+ * @global WPDB $wpdb
+ * @param BP_User_Query $user_query
+ * @param string $user_ids_sql
+ */
+function bp_friends_filter_user_query_populate_extras( BP_User_Query $user_query, $user_ids_sql ) {
+        global $bp, $wpdb;
+
+        // Fetch whether or not the user is a friend of the current user
+        $friend_status = $wpdb-&gt;get_results( $wpdb-&gt;prepare( &quot;SELECT initiator_user_id, friend_user_id, is_confirmed FROM {$bp-&gt;friends-&gt;table_name} WHERE (initiator_user_id = %d AND friend_user_id IN ( {$user_ids_sql} ) ) OR (initiator_user_id IN ( {$user_ids_sql} ) AND friend_user_id = %d )&quot;, bp_loggedin_user_id(), bp_loggedin_user_id() ) );
+
+        // The &quot;friend&quot; is the user ID in the pair who is *not* the logged in user
+        foreach ( (array) $friend_status as $fs ) {
+                $friend_id = bp_loggedin_user_id() == $fs-&gt;initiator_user_id ? $fs-&gt;friend_user_id : $fs-&gt;initiator_user_id;
+
+                if ( isset( $user_query-&gt;results[ $friend_id ] ) ) {
+                        $user_query-&gt;results[ $friend_id ]-&gt;is_friend = $fs-&gt;is_confirmed;
+                }
+        }
+}
+add_filter( 'bp_user_query_populate_extras', 'bp_friends_filter_user_query_populate_extras', 4, 2 );
</ins></span></pre></div>
<a id="trunkbpmembersbpmembersfunctionsphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-members/bp-members-functions.php (6313 => 6314)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-members/bp-members-functions.php        2012-09-06 17:13:45 UTC (rev 6313)
+++ trunk/bp-members/bp-members-functions.php        2012-09-07 02:14:42 UTC (rev 6314)
</span><span class="lines">@@ -71,28 +71,48 @@
</span><span class="cx"> /**
</span><span class="cx">  * Return an array of users IDs based on the parameters passed.
</span><span class="cx">  *
</span><ins>+ * Since BuddyPress 1.7, bp_core_get_users() uses BP_User_Query. If you
+ * need backward compatibility with BP_Core_User::get_users(), filter the
+ * bp_use_legacy_user_query value, returning true.
+ *
</ins><span class="cx">  * @package BuddyPress Core
</span><span class="cx">  */
</span><span class="cx"> function bp_core_get_users( $args = '' ) {
</span><span class="cx"> 
</span><del>-        $defaults = array(
-                'type'            =&gt; 'active', // active, newest, alphabetical, random or popular
-                'user_id'         =&gt; false,    // Pass a user_id to limit to only friend connections for this user
-                'exclude'         =&gt; false,    // Users to exclude from results
-                'search_terms'    =&gt; false,    // Limit to users that match these search terms
-                'meta_key'        =&gt; false,    // Limit to users who have this piece of usermeta
-                'meta_value'      =&gt; false,    // With meta_key, limit to users where usermeta matches this value
</del><ins>+        // Parse the user query arguments
+        $params = wp_parse_args( $args, array(
+                'type'            =&gt; 'active',     // active, newest, alphabetical, random or popular
+                'user_id'         =&gt; false,        // Pass a user_id to limit to only friend connections for this user
+                'exclude'         =&gt; false,        // Users to exclude from results
+                'search_terms'    =&gt; false,        // Limit to users that match these search terms
+                'meta_key'        =&gt; false,        // Limit to users who have this piece of usermeta
+                'meta_value'      =&gt; false,        // With meta_key, limit to users where usermeta matches this value
+                'include'         =&gt; false,        // Pass comma separated list of user_ids to limit to only these users
+                'per_page'        =&gt; 20,           // The number of results to return per page
+                'page'            =&gt; 1,            // The page to return if limiting per page
+                'populate_extras' =&gt; true,         // Fetch the last active, where the user is a friend, total friend count, latest update
+                'count_total'     =&gt; 'count_query' // What kind of total user count to do, if any. 'count_query', 'sql_calc_found_rows', or false
+        ) );
</ins><span class="cx"> 
</span><del>-                'include'         =&gt; false,    // Pass comma separated list of user_ids to limit to only these users
-                'per_page'        =&gt; 20,       // The number of results to return per page
-                'page'            =&gt; 1,        // The page to return if limiting per page
-                'populate_extras' =&gt; true,     // Fetch the last active, where the user is a friend, total friend count, latest update
-        );
</del><ins>+        // For legacy users. Use of BP_Core_User::get_users() is deprecated.
+        if ( apply_filters( 'bp_use_legacy_user_query', false, __FUNCTION__, $params ) ) {
+                extract( $params, EXTR_SKIP );
+                $retval = BP_Core_User::get_users( $type, $per_page, $page, $user_id, $include, $search_terms, $populate_extras, $exclude, $meta_key, $meta_value );
</ins><span class="cx"> 
</span><del>-        $params = wp_parse_args( $args, $defaults );
-        extract( $params, EXTR_SKIP );
</del><ins>+        // Default behavior as of BuddyPress 1.7
+        } else {                
</ins><span class="cx"> 
</span><del>-        return apply_filters( 'bp_core_get_users', BP_Core_User::get_users( $type, $per_page, $page, $user_id, $include, $search_terms, $populate_extras, $exclude, $meta_key, $meta_value ), $params );
</del><ins>+                // Get users like we were asked to do...
+                $users = new BP_User_Query( $params );
+
+                // ...but reformat the results to match bp_core_get_users() behavior.
+                $retval = array(
+                        'users' =&gt; array_values( $users-&gt;results ),
+                        'total' =&gt; $users-&gt;total_users
+                );
+        }
+
+        return apply_filters( 'bp_core_get_users', $retval, $params );
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> /**
</span></span></pre></div>
<a id="trunkbpxprofilebpxprofilefiltersphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-xprofile/bp-xprofile-filters.php (6313 => 6314)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-xprofile/bp-xprofile-filters.php        2012-09-06 17:13:45 UTC (rev 6313)
+++ trunk/bp-xprofile/bp-xprofile-filters.php        2012-09-07 02:14:42 UTC (rev 6314)
</span><span class="lines">@@ -219,6 +219,31 @@
</span><span class="cx"> }
</span><span class="cx"> add_filter( 'comments_array', 'xprofile_filter_comments', 10, 2 );
</span><span class="cx"> 
</span><ins>+/**
+ * Filter BP_User_Query::populate_extras to override each queries users fullname
+ *
+ * @since BuddyPress (1.7)
+ *
+ * @global BuddyPress $bp
+ * @global WPDB $wpdb
+ * @param BP_User_Query $user_query
+ * @param string $user_ids_sql
+ */
+function bp_xprofile_filter_user_query_populate_extras( BP_User_Query $user_query, $user_ids_sql ) {
+        global $bp, $wpdb;
</ins><span class="cx"> 
</span><ins>+        if ( bp_is_active( 'xprofile' ) ) {
+                $fullname_field_id = $wpdb-&gt;get_var( $wpdb-&gt;prepare( &quot;SELECT id FROM {$bp-&gt;profile-&gt;table_name_fields} WHERE name = %s&quot;, bp_xprofile_fullname_field_name() ) );
+                $user_id_names     = $wpdb-&gt;get_results( $wpdb-&gt;prepare( &quot;SELECT user_id, value as fullname FROM {$bp-&gt;profile-&gt;table_name_data} WHERE user_id IN ({$user_ids_sql}) AND field_id = {$fullname_field_id}&quot; ) );
</ins><span class="cx"> 
</span><ins>+                // Loop through names and override each user's fullname
+                foreach ( $user_id_names as $user ) {
+                        if ( isset( $user_query-&gt;results[ $user-&gt;user_id ] ) ) {
+                                $user_query-&gt;results[ $user-&gt;user_id ]-&gt;fullname = $user-&gt;fullname;
+                        }
+                }
+        }
+}
+add_filter( 'bp_user_query_populate_extras', 'bp_xprofile_filter_user_query_populate_extras', 2, 2 );
+
</ins><span class="cx"> ?&gt;
</span></span></pre>
</div>
</div>

</body>
</html>