Skip to:
Content

BuddyPress.org

Changeset 10794


Ignore:
Timestamp:
05/21/2016 02:16:38 AM (4 years ago)
Author:
boonebgorges
Message:

Introduce caching for group memberships.

The new system works like this: The bp_groups_memberships_for_user cache
group stores arrays of membership IDs for individual users. The
bp_groups_memberships cache group stores data about individual memberships.
The new function bp_get_user_groups() populates a user's group memberships
from these caches, and filters them as requested in the function parameters.
Then, the various groups_is_user_*() functions use bp_get_user_groups()
instead of direct, uncached database queries to fetch their data.

In addition, the get_group_extras() method of BP_Groups_Group can now be
greatly simplified, since all necessary pre-fetching of current-user group
memberships happens via the bp_get_user_groups() cache.

Props boonebgorges, dcavins.
See #6327.

Location:
trunk
Files:
1 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/bp-groups/bp-groups-cache.php

    r10766 r10794  
    226226}
    227227add_action( 'groups_delete_group', 'groups_clear_group_type_cache' );
     228
     229/**
     230 * Clear caches on membership save.
     231 *
     232 * @since 2.6.0
     233 */
     234function bp_groups_clear_user_group_cache_on_membership_save( BP_Groups_Member $member ) {
     235    wp_cache_delete( $member->user_id, 'bp_groups_memberships_for_user' );
     236    wp_cache_delete( $member->id, 'bp_groups_memberships' );
     237}
     238add_action( 'groups_member_before_save', 'bp_groups_clear_user_group_cache_on_membership_save' );
     239add_action( 'groups_member_before_remove', 'bp_groups_clear_user_group_cache_on_membership_save' );
     240
     241/**
     242 * Clear group memberships cache on miscellaneous actions not covered by the 'after_save' hook.
     243 *
     244 * @since 2.6.0
     245 */
     246function bp_groups_clear_user_group_cache_on_other_events( $user_id, $group_id ) {
     247    wp_cache_delete( $user_id, 'bp_groups_memberships_for_user' );
     248
     249    $membership = new BP_Groups_Member( $user_id, $group_id );
     250    wp_cache_delete( $membership->id, 'bp_groups_memberships' );
     251}
     252add_action( 'bp_groups_member_before_delete', 'bp_groups_clear_user_group_cache_on_other_events', 10, 2 );
     253add_action( 'bp_groups_member_before_delete_invite', 'bp_groups_clear_user_group_cache_on_other_events', 10, 2 );
    228254
    229255/* List actions to clear super cached pages on, if super cache is installed */
  • trunk/src/bp-groups/bp-groups-functions.php

    r10767 r10794  
    792792
    793793/**
     794 * Get a list of groups of which the specified user is a member.
     795 *
     796 * @since 2.6.0
     797 *
     798 * @param int $user_id ID of the user.
     799 * @param array $args {
     800 *     Array of optional args.
     801 *     @param bool|null   $is_confirmed Whether to return only confirmed memberships. Pass `null` to disable this
     802 *                                      filter. Default: true.
     803 *     @param bool|null   $is_banned    Whether to return only banned memberships. Pass `null` to disable this filter.
     804 *                                      Default: false.
     805 *     @param bool|null   $is_admin     Whether to return only admin memberships. Pass `null` to disable this filter.
     806 *                                      Default: false.
     807 *     @param bool|null   $is_mod       Whether to return only mod memberships. Pass `null` to disable this filter.
     808 *                                      Default: false.
     809 *     @param bool|null   $invite_sent  Whether to return only memberships with 'invite_sent'. Pass `null` to disable
     810 *                                      this filter. Default: false.
     811 *     @param string      $orderby      Field to order by. Accepts 'id' (membership ID), 'group_id', 'date_modified'.
     812 *                                      Default: 'group_id'.
     813 *     @param string      $order        Sort order. Accepts 'ASC' or 'DESC'. Default: 'ASC'.
     814 * }
     815 * @return array Array of matching group memberships, keyed by group ID.
     816 */
     817function bp_get_user_groups( $user_id, $args = array() ) {
     818    $r = bp_parse_args( $args, array(
     819        'is_confirmed' => true,
     820        'is_banned'    => false,
     821        'is_admin'     => false,
     822        'is_mod'       => false,
     823        'invite_sent'  => null,
     824        'orderby'      => 'group_id',
     825        'order'        => 'ASC',
     826    ), 'get_user_groups' );
     827
     828    $user_id = intval( $user_id );
     829
     830    $membership_ids = wp_cache_get( $user_id, 'bp_groups_memberships_for_user' );
     831    if ( false === $membership_ids ) {
     832        $membership_ids = BP_Groups_Member::get_membership_ids_for_user( $user_id );
     833        wp_cache_set( $user_id, $membership_ids, 'bp_groups_memberships_for_user' );
     834    }
     835
     836    // Prime the membership cache.
     837    $uncached_membership_ids = bp_get_non_cached_ids( $membership_ids, 'bp_groups_memberships' );
     838    if ( ! empty( $uncached_membership_ids ) ) {
     839        $uncached_memberships = BP_Groups_Member::get_memberships_by_id( $uncached_membership_ids );
     840
     841        foreach ( $uncached_memberships as $uncached_membership ) {
     842            wp_cache_set( $uncached_membership->id, $uncached_membership, 'bp_groups_memberships' );
     843        }
     844    }
     845
     846    // Populate group membership array from cache.
     847    $groups = array();
     848    foreach ( $membership_ids as $membership_id ) {
     849        $membership = wp_cache_get( $membership_id, 'bp_groups_memberships' );
     850
     851        // Sanity check.
     852        if ( ! isset( $membership->group_id ) ) {
     853            continue;
     854        }
     855
     856        $group_id = (int) $membership->group_id;
     857
     858        $groups[ $group_id ] = $membership;
     859    }
     860
     861    // Normalize group data.
     862    foreach ( $groups as &$group ) {
     863        // Integer values.
     864        foreach ( array( 'id', 'group_id', 'user_id', 'inviter_id' ) as $index ) {
     865            $group->{$index} = intval( $group->{$index} );
     866        }
     867
     868        // Boolean values.
     869        foreach ( array( 'is_admin', 'is_mod', 'is_confirmed', 'is_banned', 'invite_sent' ) as $index ) {
     870            $group->{$index} = (bool) $group->{$index};
     871        }
     872    }
     873
     874    // Assemble filter array for use in `wp_list_filter()`.
     875    $filters = wp_array_slice_assoc( $r, array( 'is_confirmed', 'is_banned', 'is_admin', 'is_mod', 'invite_sent' ) );
     876    foreach ( $filters as $filter_name => $filter_value ) {
     877        if ( is_null( $filter_value ) ) {
     878            unset( $filters[ $filter_name ] );
     879        }
     880    }
     881
     882    if ( ! empty( $filters ) ) {
     883        $groups = wp_list_filter( $groups, $filters );
     884    }
     885
     886    // By default, results are ordered by membership id.
     887    if ( 'group_id' === $r['orderby'] ) {
     888        ksort( $groups );
     889    } elseif ( in_array( $r['orderby'], array( 'id', 'date_modified' ) ) ) {
     890        $groups = bp_sort_by_key( $groups, $r['orderby'] );
     891    }
     892
     893    // By default, results are ordered ASC.
     894    if ( 'DESC' === strtoupper( $r['order'] ) ) {
     895        // `true` to preserve keys.
     896        $groups = array_reverse( $groups, true );
     897    }
     898
     899    return $groups;
     900}
     901
     902/**
    794903 * Get the count of groups of which the specified user is a member.
    795904 *
     
    890999 */
    8911000function groups_is_user_admin( $user_id, $group_id ) {
    892     return BP_Groups_Member::check_is_admin( $user_id, $group_id );
     1001    $is_admin = false;
     1002
     1003    $user_groups = bp_get_user_groups( $user_id, array(
     1004        'is_admin' => true,
     1005    ) );
     1006
     1007    if ( isset( $user_groups[ $group_id ] ) ) {
     1008        $is_admin = $user_groups[ $group_id ]->id;
     1009    }
     1010
     1011    return $is_admin;
    8931012}
    8941013
     
    9031022 */
    9041023function groups_is_user_mod( $user_id, $group_id ) {
    905     return BP_Groups_Member::check_is_mod( $user_id, $group_id );
     1024    $is_mod = false;
     1025
     1026    $user_groups = bp_get_user_groups( $user_id, array(
     1027        'is_mod' => true,
     1028    ) );
     1029
     1030    if ( isset( $user_groups[ $group_id ] ) ) {
     1031        $is_mod = $user_groups[ $group_id ]->id;
     1032    }
     1033
     1034    return $is_mod;
    9061035}
    9071036
     
    9161045 */
    9171046function groups_is_user_member( $user_id, $group_id ) {
    918     return BP_Groups_Member::check_is_member( $user_id, $group_id );
     1047    $is_member = false;
     1048
     1049    $user_groups = bp_get_user_groups( $user_id, array(
     1050        'is_admin' => null,
     1051        'is_mod' => null,
     1052    ) );
     1053
     1054    if ( isset( $user_groups[ $group_id ] ) ) {
     1055        $is_member = $user_groups[ $group_id ]->id;
     1056    }
     1057
     1058    return $is_member;
    9191059}
    9201060
     
    9301070 */
    9311071function groups_is_user_banned( $user_id, $group_id ) {
    932     return BP_Groups_Member::check_is_banned( $user_id, $group_id );
     1072    $is_banned = false;
     1073
     1074    $user_groups = bp_get_user_groups( $user_id, array(
     1075        'is_confirmed' => null,
     1076        'is_banned' => true,
     1077    ) );
     1078
     1079    if ( isset( $user_groups[ $group_id ] ) ) {
     1080        $is_banned = $user_groups[ $group_id ]->id;
     1081    }
     1082
     1083    return $is_banned;
     1084}
     1085
     1086/**
     1087 * Check whether a user has an outstanding invitation to a group.
     1088 *
     1089 * @since 2.6.0
     1090 *
     1091 * @param int $user_id ID of the user.
     1092 * @param int $group_id ID of the group.
     1093 * @return int|null ID of the membership if found.
     1094 */
     1095function groups_is_user_invited( $user_id, $group_id ) {
     1096    $is_invited = false;
     1097
     1098    $user_groups = bp_get_user_groups( $user_id, array(
     1099        'invite_sent' => true,
     1100        'is_confirmed' => false,
     1101    ) );
     1102
     1103    if ( isset( $user_groups[ $group_id ] ) ) {
     1104        $is_invited = $user_groups[ $group_id ]->id;
     1105    }
     1106
     1107    return $is_invited;
     1108}
     1109
     1110/**
     1111 * Check whether a user has a pending membership request for a group.
     1112 *
     1113 * @since 2.6.0
     1114 *
     1115 * @param int $user_id ID of the user.
     1116 * @param int $group_id ID of the group.
     1117 * @return int|null ID of the membership if found.
     1118 */
     1119function groups_is_user_pending( $user_id, $group_id ) {
     1120    $is_pending = false;
     1121
     1122    $user_groups = bp_get_user_groups( $user_id, array(
     1123        'invite_sent' => false,
     1124        'is_confirmed' => false,
     1125    ) );
     1126
     1127    if ( isset( $user_groups[ $group_id ] ) ) {
     1128        $is_pending = $user_groups[ $group_id ]->id;
     1129    }
     1130
     1131    return $is_pending;
    9331132}
    9341133
  • trunk/src/bp-groups/classes/class-bp-groups-component.php

    r10768 r10794  
    846846            'bp_group_admins',
    847847            'bp_group_invite_count',
    848             'group_meta'
     848            'group_meta',
     849            'bp_groups_memberships',
     850            'bp_groups_memberships_for_user',
    849851        ) );
    850852
  • trunk/src/bp-groups/classes/class-bp-groups-group.php

    r10767 r10794  
    14231423     */
    14241424    public static function get_group_extras( &$paged_groups, &$group_ids, $type = false ) {
    1425         global $wpdb;
    1426 
    1427         if ( empty( $group_ids ) )
    1428             return $paged_groups;
    1429 
    1430         $bp = buddypress();
    1431 
    1432         // Sanitize group IDs.
    1433         $group_ids = implode( ',', wp_parse_id_list( $group_ids ) );
    1434 
    1435         // Fetch the logged-in user's status within each group.
    1436         if ( is_user_logged_in() ) {
    1437             $user_status_results = $wpdb->get_results( $wpdb->prepare( "SELECT group_id, is_confirmed, invite_sent FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id IN ( {$group_ids} ) AND is_banned = 0", bp_loggedin_user_id() ) );
    1438         } else {
    1439             $user_status_results = array();
    1440         }
    1441 
    1442         // Reindex.
    1443         $user_status = array();
    1444         foreach ( $user_status_results as $user_status_result ) {
    1445             $user_status[ $user_status_result->group_id ] = $user_status_result;
    1446         }
    1447 
    1448         for ( $i = 0, $count = count( $paged_groups ); $i < $count; ++$i ) {
    1449             $is_member = $is_invited = $is_pending = '0';
    1450             $gid = $paged_groups[ $i ]->id;
    1451 
    1452             if ( isset( $user_status[ $gid ] ) ) {
    1453 
    1454                 // The is_confirmed means the user is a member.
    1455                 if ( $user_status[ $gid ]->is_confirmed ) {
    1456                     $is_member = '1';
    1457 
    1458                 // The invite_sent means the user has been invited.
    1459                 } elseif ( $user_status[ $gid ]->invite_sent ) {
    1460                     $is_invited = '1';
    1461 
    1462                 // User has sent request, but has not been confirmed.
    1463                 } else {
    1464                     $is_pending = '1';
    1465                 }
    1466             }
    1467 
    1468             $paged_groups[ $i ]->is_member = $is_member;
    1469             $paged_groups[ $i ]->is_invited = $is_invited;
    1470             $paged_groups[ $i ]->is_pending = $is_pending;
    1471         }
    1472 
    1473         if ( is_user_logged_in() ) {
    1474             $user_banned = $wpdb->get_col( $wpdb->prepare( "SELECT group_id FROM {$bp->groups->table_name_members} WHERE is_banned = 1 AND user_id = %d AND group_id IN ( {$group_ids} )", bp_loggedin_user_id() ) );
    1475         } else {
    1476             $user_banned = array();
    1477         }
    1478 
    1479         for ( $i = 0, $count = count( $paged_groups ); $i < $count; ++$i ) {
    1480             $paged_groups[$i]->is_banned = false;
    1481 
    1482             foreach ( (array) $user_banned as $group_id ) {
    1483                 if ( $group_id == $paged_groups[$i]->id ) {
    1484                     $paged_groups[$i]->is_banned = true;
    1485                 }
    1486             }
     1425        $user_id = bp_loggedin_user_id();
     1426
     1427        foreach ( $paged_groups as &$group ) {
     1428            $group->is_member  = groups_is_user_member( $user_id, $group->id ) ? '1' : '0';
     1429            $group->is_invited = groups_is_user_invited( $user_id, $group->id ) ? '1' : '0';
     1430            $group->is_pending = groups_is_user_pending( $user_id, $group->id ) ? '1' : '0';
     1431            $group->is_banned  = (bool) groups_is_user_banned( $user_id, $group->id );
    14871432        }
    14881433
  • trunk/src/bp-groups/classes/class-bp-groups-member.php

    r10487 r10794  
    822822        }
    823823
     824        /**
     825         * Fires before a group invitation is deleted.
     826         *
     827         * @since 2.6.0
     828         *
     829         * @param int $user_id  ID of the user.
     830         * @param int $group_id ID of the group.
     831         */
     832        do_action( 'bp_groups_member_before_delete_invite', $user_id, $group_id );
     833
    824834        $table_name = buddypress()->groups->table_name_members;
    825835
     
    10511061
    10521062        return $wpdb->get_results( $wpdb->prepare( "SELECT user_id, date_modified FROM {$bp->groups->table_name_members} WHERE group_id = %d AND is_mod = 1 AND is_banned = 0", $group_id ) );
     1063    }
     1064
     1065    /**
     1066     * Get group membership objects by ID (or an array of IDs).
     1067     *
     1068     * @since 2.6.0
     1069     *
     1070     * @param int|string|array $membership_ids Single membership ID or comma-separated/array list of membership IDs.
     1071     * @return array
     1072     */
     1073    public static function get_memberships_by_id( $membership_ids ) {
     1074        global $wpdb;
     1075
     1076        $bp = buddypress();
     1077
     1078        $membership_ids = implode( ',', wp_parse_id_list( $membership_ids ) );
     1079        return $wpdb->get_results( "SELECT * FROM {$bp->groups->table_name_members} WHERE id IN ({$membership_ids})" );
    10531080    }
    10541081
     
    11621189
    11631190    /**
     1191     * Get all membership IDs for a user.
     1192     *
     1193     * @since 2.6.0
     1194     *
     1195     * @param int $user_id ID of the user.
     1196     * @return array
     1197     */
     1198    public static function get_membership_ids_for_user( $user_id ) {
     1199        global $wpdb;
     1200
     1201        $bp = buddypress();
     1202
     1203        $group_ids = $wpdb->get_col( $wpdb->prepare( "SELECT id FROM {$bp->groups->table_name_members} WHERE user_id = %d ORDER BY id ASC", $user_id ) );
     1204
     1205        return $group_ids;
     1206    }
     1207
     1208    /**
    11641209     * Delete all memberships for a given group.
    11651210     *
  • trunk/tests/phpunit/testcases/groups/class-bp-groups-member.php

    r10687 r10794  
    12691269        $this->assertInternalType( 'int', BP_Groups_Member::total_group_count( 123 ) );
    12701270    }
     1271
     1272    /**
     1273     * @group get_memberships_by_id
     1274     */
     1275    public function test_get_memberships_by_id_with_single_id() {
     1276        $users = $this->factory->user->create_many( 2 );
     1277        $groups = $this->factory->group->create_many( 2 );
     1278
     1279        $m0 = $this->add_user_to_group( $users[0], $groups[0] );
     1280        $m1 = $this->add_user_to_group( $users[1], $groups[1] );
     1281
     1282        $found = BP_Groups_Member::get_memberships_by_id( $m0 );
     1283
     1284        $this->assertSame( 1, count( $found ) );
     1285        $this->assertEquals( $m0, $found[0]->id );
     1286    }
     1287
     1288    /**
     1289     * @group get_memberships_by_id
     1290     */
     1291    public function test_get_memberships_by_id_with_multiple_ids() {
     1292        $users = $this->factory->user->create_many( 2 );
     1293        $groups = $this->factory->group->create_many( 2 );
     1294
     1295        $m0 = $this->add_user_to_group( $users[0], $groups[0] );
     1296        $m1 = $this->add_user_to_group( $users[1], $groups[1] );
     1297
     1298        $found = BP_Groups_Member::get_memberships_by_id( array( $m0, $m1 ) );
     1299
     1300        $this->assertSame( 2, count( $found ) );
     1301        $this->assertEqualSets( array( $m0, $m1 ), wp_list_pluck( $found, 'id' ) );
     1302    }
    12711303}
  • trunk/tests/phpunit/testcases/groups/functions/groupsIsUser.php

    r9819 r10794  
    5555        ) );
    5656
    57         $this->assertEquals( true, groups_is_user_admin( self::$user, self::$groups[1] ) );
     57        $this->assertNotEmpty( groups_is_user_admin( self::$user, self::$groups[1] ) );
    5858    }
    5959
     
    7777        ) );
    7878
    79         $this->assertEquals( true, groups_is_user_mod( self::$user, self::$groups[1] ) );
     79        $this->assertNotEmpty( groups_is_user_mod( self::$user, self::$groups[1] ) );
    8080    }
    8181
     
    108108        $this->add_user_to_group( self::$user, self::$groups[1] );
    109109
    110         $this->assertEquals( true, groups_is_user_member( self::$user, self::$groups[1] ) );
     110        $this->assertNotEmpty( groups_is_user_member( self::$user, self::$groups[1] ) );
     111    }
     112
     113    public function test_groups_is_user_member_should_return_true_for_admin() {
     114        $this->add_user_to_group( self::$user, self::$groups[1], array(
     115            'is_admin' => true,
     116        ) );
     117
     118        $this->assertNotEmpty( groups_is_user_member( self::$user, self::$groups[1] ) );
     119    }
     120
     121    public function test_groups_is_user_member_should_return_true_for_mod() {
     122        $this->add_user_to_group( self::$user, self::$groups[1], array(
     123            'is_mod' => true,
     124        ) );
     125
     126        $this->assertNotEmpty( groups_is_user_member( self::$user, self::$groups[1] ) );
    111127    }
    112128
     
    144160        $m->ban();
    145161
    146         $this->assertEquals( true, groups_is_user_banned( self::$user, self::$groups[1] ) );
     162        $this->assertNotEmpty( groups_is_user_banned( self::$user, self::$groups[1] ) );
     163    }
     164
     165    public function test_groups_is_user_invited_should_return_false_for_confirmed_member() {
     166        $this->add_user_to_group( self::$user, self::$groups[1] );
     167        $this->assertEquals( false, groups_is_user_invited( self::$user, self::$groups[1] ) );
     168    }
     169
     170    public function test_groups_is_user_invited_should_return_false_for_uninvited_member() {
     171        $this->assertEquals( false, groups_is_user_invited( self::$user, self::$groups[1] ) );
     172    }
     173
     174    public function test_groups_is_user_invited_should_return_true_for_invited_member() {
     175        $i = groups_invite_user( array(
     176            'user_id' => self::$user,
     177            'group_id' => self::$groups[1],
     178            'inviter_id' => 123,
     179        ) );
     180
     181        // Send invite.
     182        $m = new BP_Groups_Member( self::$user, self::$groups[1] );
     183        $m->invite_sent = 1;
     184        $m->save();
     185
     186        $this->assertNotEmpty( groups_is_user_invited( self::$user, self::$groups[1] ) );
     187    }
     188
     189    public function test_groups_is_user_pending_should_return_false_for_pending_member() {
     190        groups_invite_user( array(
     191            'user_id' => self::$user,
     192            'group_id' => self::$groups[1],
     193            'inviter_id' => 123,
     194        ) );
     195
     196        // Send invite.
     197        $m = new BP_Groups_Member( self::$user, self::$groups[1] );
     198        $m->invite_sent = 1;
     199        $m->save();
     200
     201        $this->assertEquals( false, groups_is_user_pending( self::$user, self::$groups[1] ) );
     202    }
     203
     204    public function test_groups_is_user_pending_should_return_false_for_member_with_no_request() {
     205        $this->assertEquals( false, groups_is_user_pending( self::$user, self::$groups[1] ) );
     206    }
     207
     208    public function test_groups_is_user_pending_should_return_true_for_pending_member() {
     209
     210        $m                = new BP_Groups_Member;
     211        $m->group_id      = self::$groups[1];
     212        $m->user_id       = self::$user;
     213        $m->inviter_id    = 0;
     214        $m->is_admin      = 0;
     215        $m->user_title    = '';
     216        $m->date_modified = bp_core_current_time();
     217        $m->is_confirmed  = 0;
     218        $m->comments      = 'request';
     219        $m->save();
     220
     221        $this->assertNotEmpty( groups_is_user_pending( self::$user, self::$groups[1] ) );
    147222    }
    148223}
Note: See TracChangeset for help on using the changeset viewer.