<!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][7141] trunk: Introduces BP_Group_Member_Query and refactors bp_group_has_members() to use it</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 { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { 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/7141">7141</a></dd>
<dt>Author</dt> <dd>boonebgorges</dd>
<dt>Date</dt> <dd>2013-06-03 17:12:54 +0000 (Mon, 03 Jun 2013)</dd>
</dl>

<h3>Log Message</h3>
<pre>Introduces BP_Group_Member_Query and refactors bp_group_has_members() to use it

BP_Group_Member_Query extends BP_User_Query, which has a number of notable
benefits:
- Group member queries no longer JOIN against global user tables
- Less code duplication, since general logic like 'exclude' is handled by
  BP_User_Query
- Future access to the additional parameters of BP_User_Query, such as 'type'

Using the new BP_Group_Member_Query, this changeset also changes the way that
group member queries filter by group roles (member, mod, admin). The new
group_role parameter in the bp_group_has_members() stack accepts an array of
group roles. The legacy argument 'exclude_admins_mods' is still accepted, and
translates to 'group_role' => array( 'member' ) when true. These group_role
enhancements will allow for future enhancements in the Groups Admin section of
the Dashboard, and other places where it might be useful to query for the
members of a group matching a specific role. See <a href="http://buddypress.trac.wordpress.org/ticket/4977">#4977</a>.

Fixes <a href="http://buddypress.trac.wordpress.org/ticket/4482">#4482</a>

Props trishasalas for early patches and feedback. Props johnjamesjacoby for
review.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkbpcorebpcoreclassesphp">trunk/bp-core/bp-core-classes.php</a></li>
<li><a href="#trunkbpgroupsbpgroupsclassesphp">trunk/bp-groups/bp-groups-classes.php</a></li>
<li><a href="#trunkbpgroupsbpgroupsfunctionsphp">trunk/bp-groups/bp-groups-functions.php</a></li>
<li><a href="#trunkbpgroupsbpgroupstemplatephp">trunk/bp-groups/bp-groups-template.php</a></li>
<li><a href="#trunktestsincludestestcasephp">trunk/tests/includes/testcase.php</a></li>
<li><a href="#trunkteststestcasesgroupstemplatephp">trunk/tests/testcases/groups/template.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkteststestcasesgroupsclassbpgroupmemberqueryphp">trunk/tests/testcases/groups/class-bp-group-member-query.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 (7140 => 7141)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-core/bp-core-classes.php        2013-06-03 14:09:18 UTC (rev 7140)
+++ trunk/bp-core/bp-core-classes.php   2013-06-03 17:12:54 UTC (rev 7141)
</span><span class="lines">@@ -48,6 +48,14 @@
</span><span class="cx">  /** Variables *************************************************************/
</span><span class="cx"> 
</span><span class="cx">  /**
</span><ins>+        * Unaltered params as passed to the constructor
+        *
+        * @since BuddyPress (1.8)
+        * @var array
+        */
+       public $query_vars_raw = array();
+
+       /**
</ins><span class="cx">    * Array of variables to query with
</span><span class="cx">   *
</span><span class="cx">   * @since BuddyPress (1.7)
</span><span class="lines">@@ -119,8 +127,15 @@
</span><span class="cx">   * @param string|array $query The query variables
</span><span class="cx">   */
</span><span class="cx">  public function __construct( $query = null ) {
</span><del>-               if ( ! empty( $query ) ) {
-                       $this->query_vars = wp_parse_args( $query, array(
</del><ins>+
+               // Store the raw query vars for later access
+               $this->query_vars_raw = $query;
+
+               // Allow extending classes to register action/filter hooks
+               $this->setup_hooks();
+
+               if ( ! empty( $this->query_vars_raw ) ) {
+                       $this->query_vars = wp_parse_args( $this->query_vars_raw, array(
</ins><span class="cx">                           'type'            => 'newest',
</span><span class="cx">                          'per_page'        => 0,
</span><span class="cx">                          'page'            => 1,
</span><span class="lines">@@ -162,6 +177,22 @@
</span><span class="cx">  }
</span><span class="cx"> 
</span><span class="cx">  /**
</span><ins>+        * Allow extending classes to set up action/filter hooks
+        *
+        * When extending BP_User_Query, you may need to use some of its
+        * internal hooks to modify the output. It's not convenient to call
+        * add_action() or add_filter() in your class constructor, because
+        * BP_User_Query::__construct() contains a fair amount of logic that
+        * you may not want to override in your class. Define this method in
+        * your own class if you need a place where your extending class can
+        * add its hooks early in the query-building process. See
+        * BP_Group_Member_Query::setup_hooks() for an example.
+        *
+        * @since BuddyPress (1.8)
+        */
+       public function setup_hooks() {}
+
+       /**
</ins><span class="cx">    * Prepare the query for user_ids
</span><span class="cx">   *
</span><span class="cx">   * @since BuddyPress (1.7)
</span><span class="lines">@@ -284,9 +315,10 @@
</span><span class="cx">          /** WHERE *************************************************************/
</span><span class="cx"> 
</span><span class="cx">          // 'include' - User ids to include in the results
</span><del>-               if ( false !== $include ) {
-                       $include        = wp_parse_id_list( $include );
-                       $include_ids    = $wpdb->escape( implode( ',', (array) $include ) );
</del><ins>+                $include     = ! empty( $include ) ? wp_parse_id_list( $include ) : array();
+               $include_ids = $this->get_include_ids( $include );
+               if ( ! empty( $include_ids ) ) {
+                       $include_ids    = implode( ',', wp_parse_id_list( $include_ids ) );
</ins><span class="cx">                   $sql['where'][] = "u.{$this->uid_name} IN ({$include_ids})";
</span><span class="cx">          }
</span><span class="cx"> 
</span><span class="lines">@@ -430,6 +462,26 @@
</span><span class="cx">  }
</span><span class="cx"> 
</span><span class="cx">  /**
</span><ins>+        * Fetches the ids of users to put in the IN clause of the main query
+        *
+        * By default, returns the value passed to it
+        * ($this->query_vars['include']). Having this abstracted into a
+        * standalone method means that extending classes can override the
+        * logic, parsing together their own user_id limits with the 'include'
+        * ids passed to the class constructor. See BP_Group_Member_Query for
+        * an example.
+        *
+        * @since BuddyPress (1.8)
+        * @param array Sanitized array of user ids, as passed to the 'include'
+        *   parameter of the class constructor
+        * @return array The list of users to which the main query should be
+        *   limited
+        */
+       public function get_include_ids( $include = array() ) {
+               return $include;
+       }
+
+       /**
</ins><span class="cx">    * Perform a database query to populate any extra metadata we might need.
</span><span class="cx">   * Different components will hook into the 'bp_user_query_populate_extras'
</span><span class="cx">   * action to loop in the things they want.
</span></span></pre></div>
<a id="trunkbpgroupsbpgroupsclassesphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-groups/bp-groups-classes.php (7140 => 7141)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-groups/bp-groups-classes.php    2013-06-03 14:09:18 UTC (rev 7140)
+++ trunk/bp-groups/bp-groups-classes.php       2013-06-03 17:12:54 UTC (rev 7141)
</span><span class="lines">@@ -947,6 +947,199 @@
</span><span class="cx">  }
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+/**
+ * Query for the members of a group
+ *
+ * @since BuddyPress (1.8)
+ */
+class BP_Group_Member_Query extends BP_User_Query {
+       /**
+        * Set up action hooks
+        *
+        * @since BuddyPress (1.8)
+        */
+       public function setup_hooks() {
+               // Take this early opportunity to set the default 'type' param
+               // to 'last_modified', which will ensure that BP_User_Query
+               // trusts our order and does not try to apply its own
+               if ( empty( $this->query_vars_raw['type'] ) ) {
+                       $this->query_vars_raw['type'] = 'last_modified';
+               }
+
+               // Set up our populate_extras method
+               add_action( 'bp_user_query_populate_extras', array( $this, 'populate_group_member_extras' ), 10, 2 );
+       }
+
+       /**
+        * Get a list of user_ids to include in the IN clause of the main query
+        *
+        * Overrides BP_User_Query::get_include_ids(), adding our additional
+        * group-member logic.
+        *
+        * @since BuddyPress (1.8)
+        * @param array
+        * @return array
+        */
+       public function get_include_ids( $include ) {
+               // The following args are specific to group member queries, and
+               // are not present in the query_vars of a normal BP_User_Query.
+               // We loop through to make sure that defaults are set (though
+               // values passed to the constructor will, as usual, override
+               // these defaults).
+               $this->query_vars = wp_parse_args( $this->query_vars, array(
+                       'group_id'       => 0,
+                       'group_role'     => array( 'member' ),
+                       'exclude_banned' => true,
+               ) );
+
+               $group_member_ids = $this->get_group_member_ids();
+
+               if ( ! empty( $include ) ) {
+                       $group_member_ids = array_intersect( $include, $group_member_ids );
+               }
+
+               return $group_member_ids;
+       }
+
+       /**
+        * Get the members of the queried group
+        *
+        * @since BuddyPress (1.8)
+        * @return array $ids User IDs of relevant group member ids
+        */
+       protected function get_group_member_ids() {
+               global $wpdb;
+
+               $bp  = buddypress();
+               $sql = array(
+                       'select'  => "SELECT user_id FROM {$bp->groups->table_name_members}",
+                       'where'   => array(),
+                       'orderby' => '',
+                       'order'   => '',
+                       'limit'   => '',
+               );
+
+               /** WHERE clauses *****************************************************/
+
+               $sql['where'][] = $wpdb->prepare( "group_id = %d", $this->query_vars['group_id'] );
+
+               // Role information is stored as follows: admins have
+               // is_admin = 1, mods have is_mod = 1, and members have both
+               // set to 0.
+               $roles = !empty( $this->query_vars['group_role'] ) ? $this->query_vars['group_role'] : array();
+               if ( is_string( $roles ) ) {
+                       $roles = explode( ',', $roles );
+               }
+
+               // Sanitize: Only 'admin', 'mod', and 'member' are valid
+               foreach ( $roles as $role_key => $role_value ) {
+                       if ( ! in_array( $role_value, array( 'admin', 'mod', 'member' ) ) ) {
+                               unset( $roles[ $role_key ] );
+                       }
+               }
+
+               // Remove dupes to make the count accurate, and flip for faster
+               // isset() lookups
+               $roles = array_flip( array_unique( $roles ) );
+
+               switch ( count( $roles ) ) {
+
+                       // All three roles means we don't limit results
+                       case 3 :
+                       default :
+                               $roles_sql = '';
+                               break;
+
+                       case 2 :
+                               // member + mod = no admins
+                               // member + admin = no mods
+                               if ( isset( $roles['member'] ) ) {
+                                       $roles_sql = isset( $roles['admin'] ) ? "is_mod = 0" : "is_admin = 0";
+
+                               // Two non-member roles are 'admin' and 'mod'
+                               } else {
+                                       $roles_sql = "(is_admin = 1 OR is_mod = 1)";
+                               }
+                               break;
+
+                       case 1 :
+                               // member only means no admins or mods
+                               if ( isset( $roles['member'] ) ) {
+                                       $roles_sql = "is_admin = 0 AND is_mod = 0";
+
+                               // Filter by that role only
+                               } else {
+                                       $roles_sql = isset( $roles['admin'] ) ? "is_admin = 1" : "is_mod = 1";
+                               }
+                               break;
+
+                       // No roles means no users should be returned
+                       case 0 :
+                               $roles_sql = $this->no_results['where'];
+                               break;
+               }
+
+               if ( ! empty( $roles_sql ) ) {
+                       $sql['where'][] = $roles_sql;
+               }
+
+               if ( ! empty( $this->query_vars['exclude_banned'] ) ) {
+                       $sql['where'][] = "is_banned = 0";
+               }
+
+               $sql['where'] = ! empty( $sql['where'] ) ? 'WHERE ' . implode( ' AND ', $sql['where'] ) : '';
+
+               /** ORDER BY clause ***************************************************/
+
+               // @todo For now, mimicking legacy behavior of
+               // bp_group_has_members(), which has us order by date_modified
+               // only. Should abstract it in the future
+               $sql['orderby'] = "ORDER BY date_modified";
+               $sql['order']   = "DESC";
+
+               /** LIMIT clause ******************************************************/
+
+               // Technically, this is also handled by BP_User_Query, but
+               // repeating the limit here helps to make the query more
+               // efficient, by not fetching every single matching user
+               if ( ! empty( $this->query_vars['per_page'] ) && ! empty( $this->query_vars['page'] ) ) {
+                       $sql['limit'] = $wpdb->prepare( "LIMIT %d, %d", absint( ( $this->query_vars['page'] - 1 ) * $this->query_vars['per_page'] ), absint( $this->query_vars['per_page'] ) );
+               }
+
+               $ids = $wpdb->get_col( "{$sql['select']} {$sql['where']} {$sql['orderby']} {$sql['order']} {$sql['limit']}" );
+
+               return $ids;
+       }
+
+       /**
+        * Fetch additional data required in bp_group_has_members() loops
+        *
+        * @since BuddyPress (1.8)
+        * @param object $query BP_User_Query object. Because we're filtering
+        *   the current object, we use $this inside of the method instead
+        * @param string $user_ids_sql Sanitized, comma-separated string of
+        *   the user ids returned by the main query
+        */
+       public function populate_group_member_extras( $query, $user_ids_sql ) {
+               global $wpdb;
+
+               $bp     = buddypress();
+               $extras = $wpdb->get_results( $wpdb->prepare( "SELECT user_id, date_modified, is_banned FROM {$bp->groups->table_name_members} WHERE user_id IN ({$user_ids_sql}) AND group_id = %d", $this->query_vars['group_id'] ) );
+
+               foreach ( (array) $extras as $extra ) {
+                       if ( isset( $this->results[ $extra->user_id ] ) ) {
+                               // user_id is provided for backward compatibility
+                               $this->results[ $extra->user_id ]->user_id       = (int) $extra->user_id;
+                               $this->results[ $extra->user_id ]->is_banned     = (int) $extra->is_banned;
+                               $this->results[ $extra->user_id ]->date_modified = $extra->date_modified;
+                       }
+               }
+
+               // Don't filter other BP_User_Query objects on the same page
+               remove_action( 'bp_user_query_populate_extras', array( $this, 'populate_group_member_extras' ), 10, 2 );
+       }
+}
+
</ins><span class="cx"> class BP_Groups_Member {
</span><span class="cx">  var $id;
</span><span class="cx">  var $group_id;
</span><span class="lines">@@ -1399,6 +1592,8 @@
</span><span class="cx">  function get_all_for_group( $group_id, $limit = false, $page = false, $exclude_admins_mods = true, $exclude_banned = true, $exclude = false ) {
</span><span class="cx">          global $bp, $wpdb;
</span><span class="cx"> 
</span><ins>+               _deprecated_function( __METHOD__, '1.8', 'BP_Group_Member_Query' );
+
</ins><span class="cx">           $pag_sql = '';
</span><span class="cx">          if ( !empty( $limit ) && !empty( $page ) )
</span><span class="cx">                  $pag_sql = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
</span></span></pre></div>
<a id="trunkbpgroupsbpgroupsfunctionsphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-groups/bp-groups-functions.php (7140 => 7141)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-groups/bp-groups-functions.php  2013-06-03 14:09:18 UTC (rev 7140)
+++ trunk/bp-groups/bp-groups-functions.php     2013-06-03 17:12:54 UTC (rev 7141)
</span><span class="lines">@@ -382,8 +382,12 @@
</span><span class="cx"> /**
</span><span class="cx">  * Fetch the members of a group
</span><span class="cx">  *
</span><del>- * Procedural wrapper for BP_Groups_Member::get_all_for_group().
</del><ins>+ * Since BuddyPress 1.8, a procedural wrapper for BP_Group_Member_Query.
+ * Previously called BP_Groups_Member::get_all_for_group().
</ins><span class="cx">  *
</span><ins>+ * To use the legacy query, filter 'bp_use_legacy_group_member_query',
+ * returning true.
+ *
</ins><span class="cx">  * @param int $group_id
</span><span class="cx">  * @param int $limit Maximum members to return
</span><span class="cx">  * @param int $page The page of results to return (requires $limit)
</span><span class="lines">@@ -392,8 +396,38 @@
</span><span class="cx">  * @param array|string $exclude Array or comma-sep list of users to exclude
</span><span class="cx">  * @return array Multi-d array of 'members' list and 'count'
</span><span class="cx">  */
</span><del>-function groups_get_group_members( $group_id, $limit = false, $page = false, $exclude_admins_mods = true, $exclude_banned = true, $exclude = false ) {
-       return BP_Groups_Member::get_all_for_group( $group_id, $limit, $page, $exclude_admins_mods, $exclude_banned, $exclude );
</del><ins>+function groups_get_group_members( $group_id, $limit = false, $page = false, $exclude_admins_mods = true, $exclude_banned = true, $exclude = false, $group_role = false ) {
+
+       // For legacy users. Use of BP_Groups_Member::get_all_for_group()
+       // is deprecated.
+       if ( apply_filters( 'bp_use_legacy_group_member_query', false, __FUNCTION__, func_get_args() ) ) {
+               $retval = BP_Groups_Member::get_all_for_group( $group_id, $limit, $page, $exclude_admins_mods, $exclude_banned, $exclude );
+       } else {
+
+               // exclude_admins_mods is a legacy argument. Convert to group_role
+               if ( empty( $group_role ) ) {
+                       $group_role = $exclude_admins_mods ? array( 'member' ) : array( 'member', 'mod', 'admin' );
+               }
+
+               // Perform the group member query (extends BP_User_Query)
+               $members = new BP_Group_Member_Query( array(
+                       'group_id'       => $group_id,
+                       'per_page'       => $limit,
+                       'page'           => $page,
+                       'group_role'     => $group_role,
+                       'exclude_banned' => $exclude_banned,
+                       'exclude'        => $exclude,
+                       'type'           => 'last_modified',
+               ) );
+
+               // Structure the return value as expected by the template functions
+               $retval = array(
+                       'members' => array_values( $members->results ),
+                       'count'   => $members->total_users,
+               );
+       }
+
+       return $retval;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function groups_get_total_member_count( $group_id ) {
</span></span></pre></div>
<a id="trunkbpgroupsbpgroupstemplatephp"></a>
<div class="modfile"><h4>Modified: trunk/bp-groups/bp-groups-template.php (7140 => 7141)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-groups/bp-groups-template.php   2013-06-03 14:09:18 UTC (rev 7140)
+++ trunk/bp-groups/bp-groups-template.php      2013-06-03 17:12:54 UTC (rev 7141)
</span><span class="lines">@@ -1897,11 +1897,11 @@
</span><span class="cx">  var $pag_links;
</span><span class="cx">  var $total_group_count;
</span><span class="cx"> 
</span><del>-       function __construct( $group_id, $per_page, $max, $exclude_admins_mods, $exclude_banned, $exclude ) {
</del><ins>+        function __construct( $group_id, $per_page, $max, $exclude_admins_mods, $exclude_banned, $exclude, $group_role = false ) {
</ins><span class="cx"> 
</span><span class="cx">          $this->pag_page = isset( $_REQUEST['mlpage'] ) ? intval( $_REQUEST['mlpage'] ) : 1;
</span><span class="cx">          $this->pag_num  = isset( $_REQUEST['num'] ) ? intval( $_REQUEST['num'] ) : $per_page;
</span><del>-               $this->members  = BP_Groups_Member::get_all_for_group( $group_id, $this->pag_num, $this->pag_page, $exclude_admins_mods, $exclude_banned, $exclude );
</del><ins>+                $this->members  = groups_get_group_members( $group_id, $this->pag_num, $this->pag_page, $exclude_admins_mods, $exclude_banned, $exclude, $group_role );
</ins><span class="cx"> 
</span><span class="cx">          if ( !$max || $max >= (int) $this->members['count'] )
</span><span class="cx">                  $this->total_member_count = (int) $this->members['count'];
</span><span class="lines">@@ -1983,10 +1983,11 @@
</span><span class="cx">          'max' => false,
</span><span class="cx">          'exclude' => false,
</span><span class="cx">          'exclude_admins_mods' => 1,
</span><del>-               'exclude_banned' => 1
</del><ins>+                'exclude_banned' => 1,
+               'group_role' => false,
</ins><span class="cx">   ) );
</span><span class="cx"> 
</span><del>-       $members_template = new BP_Groups_Group_Members_Template( $r['group_id'], $r['per_page'], $r['max'], (int) $r['exclude_admins_mods'], (int) $r['exclude_banned'], $r['exclude'] );
</del><ins>+        $members_template = new BP_Groups_Group_Members_Template( $r['group_id'], $r['per_page'], $r['max'], (int) $r['exclude_admins_mods'], (int) $r['exclude_banned'], $r['exclude'], $r['group_role'] );
</ins><span class="cx">   return apply_filters( 'bp_group_has_members', $members_template->has_members(), $members_template );
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunktestsincludestestcasephp"></a>
<div class="modfile"><h4>Modified: trunk/tests/includes/testcase.php (7140 => 7141)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/tests/includes/testcase.php        2013-06-03 14:09:18 UTC (rev 7140)
+++ trunk/tests/includes/testcase.php   2013-06-03 17:12:54 UTC (rev 7141)
</span><span class="lines">@@ -223,14 +223,18 @@
</span><span class="cx">          return $user_id;
</span><span class="cx">  }
</span><span class="cx"> 
</span><del>-       public static function add_user_to_group( $user_id, $group_id ) {
</del><ins>+        public static function add_user_to_group( $user_id, $group_id, $args = array() ) {
+               $r = wp_parse_args( $args, array(
+                       'date_modified' => bp_core_current_time(),
+               ) );
+
</ins><span class="cx">           $new_member                = new BP_Groups_Member;
</span><span class="cx">          $new_member->group_id      = $group_id;
</span><span class="cx">          $new_member->user_id       = $user_id;
</span><span class="cx">          $new_member->inviter_id    = 0;
</span><span class="cx">          $new_member->is_admin      = 0;
</span><span class="cx">          $new_member->user_title    = '';
</span><del>-               $new_member->date_modified = bp_core_current_time();
</del><ins>+                $new_member->date_modified = $r['date_modified'];
</ins><span class="cx">           $new_member->is_confirmed  = 1;
</span><span class="cx"> 
</span><span class="cx">          $new_member->save();
</span></span></pre></div>
<a id="trunkteststestcasesgroupsclassbpgroupmemberqueryphp"></a>
<div class="addfile"><h4>Added: trunk/tests/testcases/groups/class-bp-group-member-query.php (0 => 7141)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/tests/testcases/groups/class-bp-group-member-query.php                             (rev 0)
+++ trunk/tests/testcases/groups/class-bp-group-member-query.php        2013-06-03 17:12:54 UTC (rev 7141)
</span><span class="lines">@@ -0,0 +1,226 @@
</span><ins>+<?php
+/**
+ * @group groups
+ * @group BP_Group_Member_Query
+ */
+class BP_Tests_BP_Group_Member_Query_TestCases extends BP_UnitTestCase {
+       /**
+        * Make sure that a manual 'include' param is parsed correctly with
+        * BP_Group_Member_Query's limiting of the query to group members
+        */
+       public function test_with_include() {
+               $g = $this->factory->group->create();
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+               $u3 = $this->create_user();
+
+               $this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+               $this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+
+               $query = new BP_Group_Member_Query( array(
+                       'group_id' => $g,
+                       'include' => array( $u2 ),
+               ) );
+
+               $ids = wp_parse_id_list( array_keys( $query->results ) );
+               $this->assertEquals( array( $u2, ), $ids );
+       }
+
+       // Make sure we're falling back on 'member'
+       public function test_with_group_role_null() {
+               $g = $this->factory->group->create();
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+               $u3 = $this->create_user();
+
+               $this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+               $this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+               $this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+               $m1 = new BP_Groups_Member( $u1, $g );
+               $m1->promote( 'admin' );
+               $m2 = new BP_Groups_Member( $u2, $g );
+               $m2->promote( 'mod' );
+
+               $query = new BP_Group_Member_Query( array(
+                       'group_id' => $g,
+               ) );
+
+               $expected = new BP_Group_Member_Query( array(
+                       'group_id' => $g,
+                       'group_role' => array( 'member' ),
+               ) );
+
+               $this->assertEquals( $expected->results, $query->results );
+       }
+
+       public function test_with_group_role_member() {
+               $g = $this->factory->group->create();
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+               $u3 = $this->create_user();
+
+               $this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+               $this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+               $this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+               $m1 = new BP_Groups_Member( $u1, $g );
+               $m1->promote( 'admin' );
+               $m2 = new BP_Groups_Member( $u2, $g );
+               $m2->promote( 'mod' );
+
+               $query_members = new BP_Group_Member_Query( array(
+                       'group_id' => $g,
+                       'group_role' => array( 'member' ),
+               ) );
+
+               $ids = wp_parse_id_list( array_keys( $query_members->results ) );
+               $this->assertEquals( array( $u3, ), $ids );
+       }
+
+       public function test_with_group_role_mod() {
+               $g = $this->factory->group->create();
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+               $u3 = $this->create_user();
+
+               $this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+               $this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+               $this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+               $m1 = new BP_Groups_Member( $u1, $g );
+               $m1->promote( 'admin' );
+               $m2 = new BP_Groups_Member( $u2, $g );
+               $m2->promote( 'mod' );
+
+               $query_members = new BP_Group_Member_Query( array(
+                       'group_id' => $g,
+                       'group_role' => array( 'mod' ),
+               ) );
+
+               $ids = wp_parse_id_list( array_keys( $query_members->results ) );
+               $this->assertEquals( array( $u2, ), $ids );
+       }
+
+       public function test_with_group_role_admin() {
+               $g = $this->factory->group->create();
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+               $u3 = $this->create_user();
+
+               $this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+               $this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+               $this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+               $m1 = new BP_Groups_Member( $u1, $g );
+               $m1->promote( 'admin' );
+               $m2 = new BP_Groups_Member( $u2, $g );
+               $m2->promote( 'mod' );
+
+               $query_members = new BP_Group_Member_Query( array(
+                       'group_id' => $g,
+                       'group_role' => array( 'admin' ),
+               ) );
+
+               $ids = wp_parse_id_list( array_keys( $query_members->results ) );
+               $this->assertEquals( array( $u1, ), $ids );
+       }
+
+       public function test_with_group_role_member_mod() {
+               $g = $this->factory->group->create();
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+               $u3 = $this->create_user();
+
+               $this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+               $this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+               $this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+               $m1 = new BP_Groups_Member( $u1, $g );
+               $m1->promote( 'admin' );
+               $m2 = new BP_Groups_Member( $u2, $g );
+               $m2->promote( 'mod' );
+
+               $query_members = new BP_Group_Member_Query( array(
+                       'group_id' => $g,
+                       'group_role' => array( 'member', 'mod' ),
+               ) );
+
+               $ids = wp_parse_id_list( array_keys( $query_members->results ) );
+               $this->assertEquals( array( $u2, $u3, ), $ids );
+       }
+
+       public function test_with_group_role_member_admin() {
+               $g = $this->factory->group->create();
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+               $u3 = $this->create_user();
+
+               $this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+               $this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+               $this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+               $m1 = new BP_Groups_Member( $u1, $g );
+               $m1->promote( 'admin' );
+               $m2 = new BP_Groups_Member( $u2, $g );
+               $m2->promote( 'mod' );
+
+               $query_members = new BP_Group_Member_Query( array(
+                       'group_id' => $g,
+                       'group_role' => array( 'member', 'admin' ),
+               ) );
+
+               $ids = wp_parse_id_list( array_keys( $query_members->results ) );
+               $this->assertEquals( array( $u1, $u3, ), $ids );
+       }
+
+       public function test_with_group_role_mod_admin() {
+               $g = $this->factory->group->create();
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+               $u3 = $this->create_user();
+
+               $this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+               $this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+               $this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+               $m1 = new BP_Groups_Member( $u1, $g );
+               $m1->promote( 'admin' );
+               $m2 = new BP_Groups_Member( $u2, $g );
+               $m2->promote( 'mod' );
+
+               $query_members = new BP_Group_Member_Query( array(
+                       'group_id' => $g,
+                       'group_role' => array( 'mod', 'admin' ),
+               ) );
+
+               $ids = wp_parse_id_list( array_keys( $query_members->results ) );
+               $this->assertEquals( array( $u1, $u2, ), $ids );
+       }
+
+       public function test_with_group_role_member_mod_admin() {
+               $g = $this->factory->group->create();
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+               $u3 = $this->create_user();
+
+               $this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+               $this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+               $this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+               $m1 = new BP_Groups_Member( $u1, $g );
+               $m1->promote( 'admin' );
+               $m2 = new BP_Groups_Member( $u2, $g );
+               $m2->promote( 'mod' );
+
+               $query_members = new BP_Group_Member_Query( array(
+                       'group_id' => $g,
+                       'group_role' => array( 'member', 'mod', 'admin' ),
+               ) );
+
+               $ids = wp_parse_id_list( array_keys( $query_members->results ) );
+               $this->assertEquals( array( $u1, $u2, $u3, ), $ids );
+       }
+
+
+}
</ins></span></pre></div>
<a id="trunkteststestcasesgroupstemplatephp"></a>
<div class="modfile"><h4>Modified: trunk/tests/testcases/groups/template.php (7140 => 7141)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/tests/testcases/groups/template.php        2013-06-03 14:09:18 UTC (rev 7140)
+++ trunk/tests/testcases/groups/template.php   2013-06-03 17:12:54 UTC (rev 7141)
</span><span class="lines">@@ -118,8 +118,62 @@
</span><span class="cx">  }
</span><span class="cx"> 
</span><span class="cx">  /**
</span><ins>+        * Switching from BP_Groups_Member to BP_Group_Member_Query meant a
+        * change in the format of the values returned from the query. For
+        * backward compatibility, we translate some of the return values
+        * of BP_Group_Member_Query to the older format. This test makes sure
+        * that the translation happens properly.
+        *
</ins><span class="cx">    * @group bp_group_has_members
</span><span class="cx">   */
</span><ins>+       public function test_bp_group_has_members_backpat_retval_format() {
+               $g = $this->factory->group->create();
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+
+               $date_modified = gmdate( 'Y-m-d H:i:s', time() - 100 );
+
+               $new_member                = new BP_Groups_Member;
+               $new_member->group_id      = $g;
+               $new_member->user_id       = $u1;
+               $new_member->inviter_id    = $u2;
+               $new_member->is_admin      = 0;
+               $new_member->user_title    = '';
+               $new_member->date_modified = $date_modified;
+               $new_member->is_confirmed  = 1;
+               $new_member->save();
+
+               global $members_template;
+               bp_group_has_members( array(
+                       'group_id' => $g,
+               ) );
+
+               $u1_object = new WP_User( $u1 );
+
+               $expected = new stdClass;
+               $expected->user_id = $u1;
+               $expected->date_modified = $date_modified;
+               $expected->is_banned = 0;
+               $expected->user_login = $u1_object->user_login;
+               $expected->user_nicename = $u1_object->user_nicename;
+               $expected->user_email = $u1_object->user_email;
+               $expected->display_name = $u1_object->display_name;
+
+               // In order to use assertEquals, we need to discard the
+               // irrelevant properties of the found object. Hack alert
+               $found = new stdClass;
+               foreach ( array( 'user_id', 'date_modified', 'is_banned', 'user_login', 'user_nicename', 'user_email', 'display_name' ) as $key ) {
+                       if ( isset( $members_template->members[0]->{$key} ) ) {
+                               $found->{$key} = $members_template->members[0]->{$key};
+                       }
+               }
+
+               $this->assertEquals( $expected, $found );
+       }
+
+       /**
+        * @group bp_group_has_members
+        */
</ins><span class="cx">   public function test_bp_group_has_members_with_per_page() {
</span><span class="cx">          $g = $this->factory->group->create();
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>