Skip to:
Content

bbPress.org

Changeset 7447


Ignore:
Timestamp:
09/07/2026 02:04:11 AM (2 weeks ago)
Author:
johnjamesjacoby
Message:

Users: distinguish forum and installation counts.

Add a cached aggregate count of current-site forum-role holders for dashboard and front-end statistics. Count each user once across forum roles and duplicate capabilities records, and invalidate cached counts when users or their capabilities change. Include the site, role list, and cache generation in each cache key.

Use WordPress's cached installation count and large-user threshold for installation-wide helpers, preserving the existing bbPress and multisite filters.

Add regression coverage for membership changes, cache reuse and invalidation, role filters, installation thresholds, and multisite isolation.

In trunk, for 2.7.

Props itzmekhokan.
Fixes #3457.
From https://github.com/bbpress/bbPress/pull/16.

Location:
trunk
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/CHANGELOG.md

    r7446 r7447  
    4848### Fixed
    4949
     50- Cache a distinct count of current-site forum-role holders for forum statistics,
     51  invalidating it when users or their capabilities change. Use the WordPress
     52  installation count for large-installation upgrade decisions.
    5053- Show an ellipsis for long topic metadata in the default templates, revealing
    5154  the full text when a link receives focus.
  • trunk/src/includes/common/functions.php

    r7397 r7447  
    445445        $trash   = bbp_get_trash_status_id();
    446446
    447         // Users
     447        // Users with a forum role on the current site
    448448        $user_count = ! empty( $r['count_users'] )
    449                 ? bbp_get_total_users()
     449                ? bbp_get_total_forum_users()
    450450                : 0;
    451451
  • trunk/src/includes/core/abstraction.php

    r7380 r7447  
    345345function bbp_is_large_install() {
    346346
    347         // Multisite has a function specifically for this
     347        // Preserve the network filter on multisite and use the core user threshold
    348348        $retval = function_exists( 'wp_is_large_network' )
    349349                ? wp_is_large_network( 'users' )
    350                 : ( bbp_get_total_users() > 10000 );
     350                : wp_is_large_user_count();
    351351
    352352        // Filter & return
     
    355355
    356356/**
    357  * Get the total number of users on the forums.
     357 * Get the cached installation-wide user count maintained by WordPress.
     358 *
     359 * On multisite, this is the network count, not the current site forum-role count.
    358360 *
    359361 * @since 2.0.0 bbPress (r2769)
     
    362364 */
    363365function bbp_get_total_users() {
     366        $count = get_user_count();
     367
     368        // Filter & return
     369        return (int) apply_filters( 'bbp_get_total_users', (int) $count );
     370}
     371
     372/**
     373 * Count users with a forum role on the current site.
     374 *
     375 * Includes blocked users and counts users with multiple forum roles only once.
     376 * Role membership is matched in stored capabilities, as in count_users().
     377 *
     378 * @since 2.7.0
     379 *
     380 * @return int Total number of forum-role holders.
     381 */
     382function bbp_get_total_forum_users() {
    364383        $bbp_db = bbp_db();
    365         $count  = $bbp_db->get_var( "SELECT COUNT(ID) as c FROM {$bbp_db->users} WHERE user_status = '0'" );
    366 
    367         // Filter & return
    368         return (int) apply_filters( 'bbp_get_total_users', (int) $count );
     384        $roles  = array_keys( bbp_get_dynamic_roles() );
     385        $count  = 0;
     386
     387        if ( ! empty( $roles ) ) {
     388                sort( $roles );
     389
     390                // Use the global users group so changes on other sites invalidate counts
     391                $last_changed = wp_cache_get( 'bbp_forum_users_last_changed', 'users' );
     392                if ( false === $last_changed ) {
     393                        wp_cache_add( 'bbp_forum_users_last_changed', microtime(), 'users' );
     394                        $last_changed = wp_cache_get( 'bbp_forum_users_last_changed', 'users' );
     395                }
     396
     397                $meta_key  = $bbp_db->get_blog_prefix() . 'capabilities';
     398                $cache_key = 'bbp_forum_users:' . md5( serialize( array( $bbp_db->users, $bbp_db->usermeta, $meta_key, $roles, $last_changed ) ) );
     399                $count     = wp_cache_get( $cache_key, 'users' );
     400
     401                if ( false === $count ) {
     402                        $clauses = array();
     403                        foreach ( $roles as $role ) {
     404                                $clauses[] = $bbp_db->prepare( 'meta_value LIKE %s', '%' . $bbp_db->esc_like( '"' . $role . '"' ) . '%' );
     405                        }
     406
     407                        // Match any forum role without returning users or pagination totals
     408                        $role_sql = implode( ' OR ', $clauses );
     409                        $key_sql  = $bbp_db->prepare( 'meta_key = %s', $meta_key );
     410                        $count    = $bbp_db->get_var( "SELECT COUNT(DISTINCT user_id) FROM {$bbp_db->usermeta} INNER JOIN {$bbp_db->users} ON user_id = ID WHERE {$key_sql} AND ({$role_sql})" );
     411
     412                        // Do not cache failed queries; expire superseded cache generations
     413                        if ( null !== $count ) {
     414                                $count = (int) $count;
     415                                wp_cache_set( $cache_key, $count, 'users', HOUR_IN_SECONDS );
     416                        }
     417                }
     418        }
     419
     420        // Filter the result after caching so request-specific overrides stay local
     421        return (int) apply_filters( 'bbp_get_total_forum_users', (int) $count );
    369422}
    370423
  • trunk/src/includes/core/actions.php

    r7435 r7447  
    417417add_action( 'clean_post_cache', 'bbp_clean_post_cache', 10, 2 );
    418418
     419// Invalidate forum-user counts after users or their capabilities change
     420add_action( 'clean_user_cache',  'bbp_clean_user_count_cache' );
     421add_action( 'added_user_meta',   'bbp_clean_user_count_cache_on_meta_change', 10, 3 );
     422add_action( 'updated_user_meta', 'bbp_clean_user_count_cache_on_meta_change', 10, 3 );
     423add_action( 'deleted_user_meta', 'bbp_clean_user_count_cache_on_meta_change', 10, 3 );
     424
    419425// User Registration
    420426add_action( 'added_existing_user', 'bbp_user_add_role_on_register', 10, 1 );
  • trunk/src/includes/core/cache.php

    r7445 r7447  
    170170        }
    171171}
     172
     173/**
     174 * Invalidate cached forum-user counts across sites sharing the users table.
     175 *
     176 * @since 2.7.0
     177 */
     178function bbp_clean_user_count_cache() {
     179        wp_cache_set( 'bbp_forum_users_last_changed', microtime(), 'users' );
     180}
     181
     182/**
     183 * Invalidate forum-user counts after capabilities metadata changes.
     184 *
     185 * @since 2.7.0
     186 *
     187 * @param int|array $meta_id  Metadata ID or IDs.
     188 * @param int       $user_id User ID.
     189 * @param string    $key     Metadata key.
     190 */
     191function bbp_clean_user_count_cache_on_meta_change( $meta_id, $user_id, $key ) {
     192        if ( preg_match( '/(^|_)capabilities$/', $key ) ) {
     193                bbp_clean_user_count_cache();
     194        }
     195}
  • trunk/tests/phpunit/testcases/users/functions/counts.php

    r6114 r7447  
    158158                $this->factory->user->create_many( 3 );
    159159
     160                wp_update_user_counts();
    160161                $users = (int) bbp_get_total_users();
    161162
    162                 // 15 + 1, the + 1 is the default admin user
     163                // Three users plus the default administrator
    163164                $this->assertSame( 4, $users );
    164165        }
Note: See TracChangeset for help on using the changeset viewer.