Skip to:
Content

BuddyPress.org

Changeset 7141


Ignore:
Timestamp:
06/03/2013 05:12:54 PM (13 years ago)
Author:
boonebgorges
Message:

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 #4977.

Fixes #4482

Props trishasalas for early patches and feedback. Props johnjamesjacoby for
review.

Location:
trunk
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/bp-core/bp-core-classes.php

    r7135 r7141  
    4949
    5050        /**
     51         * Unaltered params as passed to the constructor
     52         *
     53         * @since BuddyPress (1.8)
     54         * @var array
     55         */
     56        public $query_vars_raw = array();
     57
     58        /**
    5159         * Array of variables to query with
    5260         *
     
    120128         */
    121129        public function __construct( $query = null ) {
    122                 if ( ! empty( $query ) ) {
    123                         $this->query_vars = wp_parse_args( $query, array(
     130
     131                // Store the raw query vars for later access
     132                $this->query_vars_raw = $query;
     133
     134                // Allow extending classes to register action/filter hooks
     135                $this->setup_hooks();
     136
     137                if ( ! empty( $this->query_vars_raw ) ) {
     138                        $this->query_vars = wp_parse_args( $this->query_vars_raw, array(
    124139                                'type'            => 'newest',
    125140                                'per_page'        => 0,
     
    163178
    164179        /**
     180         * Allow extending classes to set up action/filter hooks
     181         *
     182         * When extending BP_User_Query, you may need to use some of its
     183         * internal hooks to modify the output. It's not convenient to call
     184         * add_action() or add_filter() in your class constructor, because
     185         * BP_User_Query::__construct() contains a fair amount of logic that
     186         * you may not want to override in your class. Define this method in
     187         * your own class if you need a place where your extending class can
     188         * add its hooks early in the query-building process. See
     189         * BP_Group_Member_Query::setup_hooks() for an example.
     190         *
     191         * @since BuddyPress (1.8)
     192         */
     193        public function setup_hooks() {}
     194
     195        /**
    165196         * Prepare the query for user_ids
    166197         *
     
    285316
    286317                // 'include' - User ids to include in the results
    287                 if ( false !== $include ) {
    288                         $include        = wp_parse_id_list( $include );
    289                         $include_ids    = $wpdb->escape( implode( ',', (array) $include ) );
     318                $include     = ! empty( $include ) ? wp_parse_id_list( $include ) : array();
     319                $include_ids = $this->get_include_ids( $include );
     320                if ( ! empty( $include_ids ) ) {
     321                        $include_ids    = implode( ',', wp_parse_id_list( $include_ids ) );
    290322                        $sql['where'][] = "u.{$this->uid_name} IN ({$include_ids})";
    291323                }
     
    428460                        }
    429461                }
     462        }
     463
     464        /**
     465         * Fetches the ids of users to put in the IN clause of the main query
     466         *
     467         * By default, returns the value passed to it
     468         * ($this->query_vars['include']). Having this abstracted into a
     469         * standalone method means that extending classes can override the
     470         * logic, parsing together their own user_id limits with the 'include'
     471         * ids passed to the class constructor. See BP_Group_Member_Query for
     472         * an example.
     473         *
     474         * @since BuddyPress (1.8)
     475         * @param array Sanitized array of user ids, as passed to the 'include'
     476         *   parameter of the class constructor
     477         * @return array The list of users to which the main query should be
     478         *   limited
     479         */
     480        public function get_include_ids( $include = array() ) {
     481                return $include;
    430482        }
    431483
  • trunk/bp-groups/bp-groups-classes.php

    r7140 r7141  
    948948}
    949949
     950/**
     951 * Query for the members of a group
     952 *
     953 * @since BuddyPress (1.8)
     954 */
     955class BP_Group_Member_Query extends BP_User_Query {
     956        /**
     957         * Set up action hooks
     958         *
     959         * @since BuddyPress (1.8)
     960         */
     961        public function setup_hooks() {
     962                // Take this early opportunity to set the default 'type' param
     963                // to 'last_modified', which will ensure that BP_User_Query
     964                // trusts our order and does not try to apply its own
     965                if ( empty( $this->query_vars_raw['type'] ) ) {
     966                        $this->query_vars_raw['type'] = 'last_modified';
     967                }
     968
     969                // Set up our populate_extras method
     970                add_action( 'bp_user_query_populate_extras', array( $this, 'populate_group_member_extras' ), 10, 2 );
     971        }
     972
     973        /**
     974         * Get a list of user_ids to include in the IN clause of the main query
     975         *
     976         * Overrides BP_User_Query::get_include_ids(), adding our additional
     977         * group-member logic.
     978         *
     979         * @since BuddyPress (1.8)
     980         * @param array
     981         * @return array
     982         */
     983        public function get_include_ids( $include ) {
     984                // The following args are specific to group member queries, and
     985                // are not present in the query_vars of a normal BP_User_Query.
     986                // We loop through to make sure that defaults are set (though
     987                // values passed to the constructor will, as usual, override
     988                // these defaults).
     989                $this->query_vars = wp_parse_args( $this->query_vars, array(
     990                        'group_id'       => 0,
     991                        'group_role'     => array( 'member' ),
     992                        'exclude_banned' => true,
     993                ) );
     994
     995                $group_member_ids = $this->get_group_member_ids();
     996
     997                if ( ! empty( $include ) ) {
     998                        $group_member_ids = array_intersect( $include, $group_member_ids );
     999                }
     1000
     1001                return $group_member_ids;
     1002        }
     1003
     1004        /**
     1005         * Get the members of the queried group
     1006         *
     1007         * @since BuddyPress (1.8)
     1008         * @return array $ids User IDs of relevant group member ids
     1009         */
     1010        protected function get_group_member_ids() {
     1011                global $wpdb;
     1012
     1013                $bp  = buddypress();
     1014                $sql = array(
     1015                        'select'  => "SELECT user_id FROM {$bp->groups->table_name_members}",
     1016                        'where'   => array(),
     1017                        'orderby' => '',
     1018                        'order'   => '',
     1019                        'limit'   => '',
     1020                );
     1021
     1022                /** WHERE clauses *****************************************************/
     1023
     1024                $sql['where'][] = $wpdb->prepare( "group_id = %d", $this->query_vars['group_id'] );
     1025
     1026                // Role information is stored as follows: admins have
     1027                // is_admin = 1, mods have is_mod = 1, and members have both
     1028                // set to 0.
     1029                $roles = !empty( $this->query_vars['group_role'] ) ? $this->query_vars['group_role'] : array();
     1030                if ( is_string( $roles ) ) {
     1031                        $roles = explode( ',', $roles );
     1032                }
     1033
     1034                // Sanitize: Only 'admin', 'mod', and 'member' are valid
     1035                foreach ( $roles as $role_key => $role_value ) {
     1036                        if ( ! in_array( $role_value, array( 'admin', 'mod', 'member' ) ) ) {
     1037                                unset( $roles[ $role_key ] );
     1038                        }
     1039                }
     1040
     1041                // Remove dupes to make the count accurate, and flip for faster
     1042                // isset() lookups
     1043                $roles = array_flip( array_unique( $roles ) );
     1044
     1045                switch ( count( $roles ) ) {
     1046
     1047                        // All three roles means we don't limit results
     1048                        case 3 :
     1049                        default :
     1050                                $roles_sql = '';
     1051                                break;
     1052
     1053                        case 2 :
     1054                                // member + mod = no admins
     1055                                // member + admin = no mods
     1056                                if ( isset( $roles['member'] ) ) {
     1057                                        $roles_sql = isset( $roles['admin'] ) ? "is_mod = 0" : "is_admin = 0";
     1058
     1059                                // Two non-member roles are 'admin' and 'mod'
     1060                                } else {
     1061                                        $roles_sql = "(is_admin = 1 OR is_mod = 1)";
     1062                                }
     1063                                break;
     1064
     1065                        case 1 :
     1066                                // member only means no admins or mods
     1067                                if ( isset( $roles['member'] ) ) {
     1068                                        $roles_sql = "is_admin = 0 AND is_mod = 0";
     1069
     1070                                // Filter by that role only
     1071                                } else {
     1072                                        $roles_sql = isset( $roles['admin'] ) ? "is_admin = 1" : "is_mod = 1";
     1073                                }
     1074                                break;
     1075
     1076                        // No roles means no users should be returned
     1077                        case 0 :
     1078                                $roles_sql = $this->no_results['where'];
     1079                                break;
     1080                }
     1081
     1082                if ( ! empty( $roles_sql ) ) {
     1083                        $sql['where'][] = $roles_sql;
     1084                }
     1085
     1086                if ( ! empty( $this->query_vars['exclude_banned'] ) ) {
     1087                        $sql['where'][] = "is_banned = 0";
     1088                }
     1089
     1090                $sql['where'] = ! empty( $sql['where'] ) ? 'WHERE ' . implode( ' AND ', $sql['where'] ) : '';
     1091
     1092                /** ORDER BY clause ***************************************************/
     1093
     1094                // @todo For now, mimicking legacy behavior of
     1095                // bp_group_has_members(), which has us order by date_modified
     1096                // only. Should abstract it in the future
     1097                $sql['orderby'] = "ORDER BY date_modified";
     1098                $sql['order']   = "DESC";
     1099
     1100                /** LIMIT clause ******************************************************/
     1101
     1102                // Technically, this is also handled by BP_User_Query, but
     1103                // repeating the limit here helps to make the query more
     1104                // efficient, by not fetching every single matching user
     1105                if ( ! empty( $this->query_vars['per_page'] ) && ! empty( $this->query_vars['page'] ) ) {
     1106                        $sql['limit'] = $wpdb->prepare( "LIMIT %d, %d", absint( ( $this->query_vars['page'] - 1 ) * $this->query_vars['per_page'] ), absint( $this->query_vars['per_page'] ) );
     1107                }
     1108
     1109                $ids = $wpdb->get_col( "{$sql['select']} {$sql['where']} {$sql['orderby']} {$sql['order']} {$sql['limit']}" );
     1110
     1111                return $ids;
     1112        }
     1113
     1114        /**
     1115         * Fetch additional data required in bp_group_has_members() loops
     1116         *
     1117         * @since BuddyPress (1.8)
     1118         * @param object $query BP_User_Query object. Because we're filtering
     1119         *   the current object, we use $this inside of the method instead
     1120         * @param string $user_ids_sql Sanitized, comma-separated string of
     1121         *   the user ids returned by the main query
     1122         */
     1123        public function populate_group_member_extras( $query, $user_ids_sql ) {
     1124                global $wpdb;
     1125
     1126                $bp     = buddypress();
     1127                $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'] ) );
     1128
     1129                foreach ( (array) $extras as $extra ) {
     1130                        if ( isset( $this->results[ $extra->user_id ] ) ) {
     1131                                // user_id is provided for backward compatibility
     1132                                $this->results[ $extra->user_id ]->user_id       = (int) $extra->user_id;
     1133                                $this->results[ $extra->user_id ]->is_banned     = (int) $extra->is_banned;
     1134                                $this->results[ $extra->user_id ]->date_modified = $extra->date_modified;
     1135                        }
     1136                }
     1137
     1138                // Don't filter other BP_User_Query objects on the same page
     1139                remove_action( 'bp_user_query_populate_extras', array( $this, 'populate_group_member_extras' ), 10, 2 );
     1140        }
     1141}
     1142
    9501143class BP_Groups_Member {
    9511144        var $id;
     
    13991592        function get_all_for_group( $group_id, $limit = false, $page = false, $exclude_admins_mods = true, $exclude_banned = true, $exclude = false ) {
    14001593                global $bp, $wpdb;
     1594
     1595                _deprecated_function( __METHOD__, '1.8', 'BP_Group_Member_Query' );
    14011596
    14021597                $pag_sql = '';
  • trunk/bp-groups/bp-groups-functions.php

    r7087 r7141  
    383383 * Fetch the members of a group
    384384 *
    385  * Procedural wrapper for BP_Groups_Member::get_all_for_group().
     385 * Since BuddyPress 1.8, a procedural wrapper for BP_Group_Member_Query.
     386 * Previously called BP_Groups_Member::get_all_for_group().
     387 *
     388 * To use the legacy query, filter 'bp_use_legacy_group_member_query',
     389 * returning true.
    386390 *
    387391 * @param int $group_id
     
    393397 * @return array Multi-d array of 'members' list and 'count'
    394398 */
    395 function groups_get_group_members( $group_id, $limit = false, $page = false, $exclude_admins_mods = true, $exclude_banned = true, $exclude = false ) {
    396         return BP_Groups_Member::get_all_for_group( $group_id, $limit, $page, $exclude_admins_mods, $exclude_banned, $exclude );
     399function groups_get_group_members( $group_id, $limit = false, $page = false, $exclude_admins_mods = true, $exclude_banned = true, $exclude = false, $group_role = false ) {
     400
     401        // For legacy users. Use of BP_Groups_Member::get_all_for_group()
     402        // is deprecated.
     403        if ( apply_filters( 'bp_use_legacy_group_member_query', false, __FUNCTION__, func_get_args() ) ) {
     404                $retval = BP_Groups_Member::get_all_for_group( $group_id, $limit, $page, $exclude_admins_mods, $exclude_banned, $exclude );
     405        } else {
     406
     407                // exclude_admins_mods is a legacy argument. Convert to group_role
     408                if ( empty( $group_role ) ) {
     409                        $group_role = $exclude_admins_mods ? array( 'member' ) : array( 'member', 'mod', 'admin' );
     410                }
     411
     412                // Perform the group member query (extends BP_User_Query)
     413                $members = new BP_Group_Member_Query( array(
     414                        'group_id'       => $group_id,
     415                        'per_page'       => $limit,
     416                        'page'           => $page,
     417                        'group_role'     => $group_role,
     418                        'exclude_banned' => $exclude_banned,
     419                        'exclude'        => $exclude,
     420                        'type'           => 'last_modified',
     421                ) );
     422
     423                // Structure the return value as expected by the template functions
     424                $retval = array(
     425                        'members' => array_values( $members->results ),
     426                        'count'   => $members->total_users,
     427                );
     428        }
     429
     430        return $retval;
    397431}
    398432
  • trunk/bp-groups/bp-groups-template.php

    r7091 r7141  
    18981898        var $total_group_count;
    18991899
    1900         function __construct( $group_id, $per_page, $max, $exclude_admins_mods, $exclude_banned, $exclude ) {
     1900        function __construct( $group_id, $per_page, $max, $exclude_admins_mods, $exclude_banned, $exclude, $group_role = false ) {
    19011901
    19021902                $this->pag_page = isset( $_REQUEST['mlpage'] ) ? intval( $_REQUEST['mlpage'] ) : 1;
    19031903                $this->pag_num  = isset( $_REQUEST['num'] ) ? intval( $_REQUEST['num'] ) : $per_page;
    1904                 $this->members  = BP_Groups_Member::get_all_for_group( $group_id, $this->pag_num, $this->pag_page, $exclude_admins_mods, $exclude_banned, $exclude );
     1904                $this->members  = groups_get_group_members( $group_id, $this->pag_num, $this->pag_page, $exclude_admins_mods, $exclude_banned, $exclude, $group_role );
    19051905
    19061906                if ( !$max || $max >= (int) $this->members['count'] )
     
    19841984                'exclude' => false,
    19851985                'exclude_admins_mods' => 1,
    1986                 'exclude_banned' => 1
     1986                'exclude_banned' => 1,
     1987                'group_role' => false,
    19871988        ) );
    19881989
    1989         $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'] );
     1990        $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'] );
    19901991        return apply_filters( 'bp_group_has_members', $members_template->has_members(), $members_template );
    19911992}
  • trunk/tests/includes/testcase.php

    r7090 r7141  
    224224        }
    225225
    226         public static function add_user_to_group( $user_id, $group_id ) {
     226        public static function add_user_to_group( $user_id, $group_id, $args = array() ) {
     227                $r = wp_parse_args( $args, array(
     228                        'date_modified' => bp_core_current_time(),
     229                ) );
     230
    227231                $new_member                = new BP_Groups_Member;
    228232                $new_member->group_id      = $group_id;
     
    231235                $new_member->is_admin      = 0;
    232236                $new_member->user_title    = '';
    233                 $new_member->date_modified = bp_core_current_time();
     237                $new_member->date_modified = $r['date_modified'];
    234238                $new_member->is_confirmed  = 1;
    235239
  • trunk/tests/testcases/groups/template.php

    r7125 r7141  
    119119
    120120        /**
     121         * Switching from BP_Groups_Member to BP_Group_Member_Query meant a
     122         * change in the format of the values returned from the query. For
     123         * backward compatibility, we translate some of the return values
     124         * of BP_Group_Member_Query to the older format. This test makes sure
     125         * that the translation happens properly.
     126         *
     127         * @group bp_group_has_members
     128         */
     129        public function test_bp_group_has_members_backpat_retval_format() {
     130                $g = $this->factory->group->create();
     131                $u1 = $this->create_user();
     132                $u2 = $this->create_user();
     133
     134                $date_modified = gmdate( 'Y-m-d H:i:s', time() - 100 );
     135
     136                $new_member                = new BP_Groups_Member;
     137                $new_member->group_id      = $g;
     138                $new_member->user_id       = $u1;
     139                $new_member->inviter_id    = $u2;
     140                $new_member->is_admin      = 0;
     141                $new_member->user_title    = '';
     142                $new_member->date_modified = $date_modified;
     143                $new_member->is_confirmed  = 1;
     144                $new_member->save();
     145
     146                global $members_template;
     147                bp_group_has_members( array(
     148                        'group_id' => $g,
     149                ) );
     150
     151                $u1_object = new WP_User( $u1 );
     152
     153                $expected = new stdClass;
     154                $expected->user_id = $u1;
     155                $expected->date_modified = $date_modified;
     156                $expected->is_banned = 0;
     157                $expected->user_login = $u1_object->user_login;
     158                $expected->user_nicename = $u1_object->user_nicename;
     159                $expected->user_email = $u1_object->user_email;
     160                $expected->display_name = $u1_object->display_name;
     161
     162                // In order to use assertEquals, we need to discard the
     163                // irrelevant properties of the found object. Hack alert
     164                $found = new stdClass;
     165                foreach ( array( 'user_id', 'date_modified', 'is_banned', 'user_login', 'user_nicename', 'user_email', 'display_name' ) as $key ) {
     166                        if ( isset( $members_template->members[0]->{$key} ) ) {
     167                                $found->{$key} = $members_template->members[0]->{$key};
     168                        }
     169                }
     170
     171                $this->assertEquals( $expected, $found );
     172        }
     173
     174        /**
    121175         * @group bp_group_has_members
    122176         */
Note: See TracChangeset for help on using the changeset viewer.