Make WordPress Core

Changeset 62413


Ignore:
Timestamp:
05/23/2026 06:16:45 PM (less than one hour ago)
Author:
SergeyBiryukov
Message:

Role/Capability: Add is_user_member_of_blog filter.

This aims to make it easier to dynamically grant or revoke a user access to the site without giving them a role directly.

Follow-up to mu:804, [19016].

Props dd32, mukesh27.
Fixes #65096.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/user.php

    r61735 r62413  
    11531153 *
    11541154 * @since MU (3.0.0)
     1155 * @since 7.1.0 Introduced the {@see 'is_user_member_of_blog'} filter.
    11551156 *
    11561157 * @global wpdb $wpdb WordPress database abstraction object.
     
    12021203        $capabilities_key = $wpdb->base_prefix . $blog_id . '_capabilities';
    12031204    }
    1204     $has_cap = get_user_meta( $user_id, $capabilities_key, true );
    1205 
    1206     return is_array( $has_cap );
     1205
     1206    $has_cap   = get_user_meta( $user_id, $capabilities_key, true );
     1207    $is_member = is_array( $has_cap );
     1208
     1209    /**
     1210     * Filters whether the user is a member of a given blog.
     1211     *
     1212     * This filter only runs when the user and blog have both been resolved
     1213     * to valid records on a multisite installation; it is not invoked for
     1214     * logged-out requests, unknown users, or archived/spammed/deleted sites.
     1215     *
     1216     * @since 7.1.0
     1217     *
     1218     * @param bool $is_member Whether the user is a member of the blog.
     1219     * @param int  $user_id   The user ID being checked.
     1220     * @param int  $blog_id   The blog ID being checked.
     1221     */
     1222    return (bool) apply_filters( 'is_user_member_of_blog', $is_member, $user_id, $blog_id );
    12071223}
    12081224
  • trunk/tests/phpunit/tests/user/multisite.php

    r61037 r62413  
    175175
    176176        wp_set_current_user( $old_current );
     177    }
     178
     179    /**
     180     * Ensures the `is_user_member_of_blog` filter can override the return value
     181     * and receives the resolved user ID and blog ID.
     182     *
     183     * @ticket 65096
     184     *
     185     * @covers ::is_user_member_of_blog
     186     */
     187    public function test_is_user_member_of_blog_filter() {
     188        $user_id = self::factory()->user->create();
     189        $blog_id = self::factory()->blog->create();
     190
     191        // Sanity check: the user is not a member of the blog by default.
     192        $this->assertFalse( is_user_member_of_blog( $user_id, $blog_id ) );
     193
     194        $filter_args = array();
     195        $filter      = function ( $is_member, $filtered_user_id, $filtered_blog_id ) use ( &$filter_args ) {
     196            $filter_args[] = array( $is_member, $filtered_user_id, $filtered_blog_id );
     197            return true;
     198        };
     199
     200        add_filter( 'is_user_member_of_blog', $filter, 10, 3 );
     201        $result = is_user_member_of_blog( $user_id, $blog_id );
     202
     203        $this->assertTrue( $result, 'Filter should be able to force a truthy return value.' );
     204        $this->assertCount( 1, $filter_args, 'Filter should run exactly once per call on a valid multisite blog.' );
     205        $this->assertSame( array( false, $user_id, $blog_id ), $filter_args[0], 'Filter should receive the computed membership, user ID, and blog ID.' );
     206    }
     207
     208    /**
     209     * Ensures the `is_user_member_of_blog` filter is not invoked for requests
     210     * that short-circuit before the membership is computed.
     211     *
     212     * @ticket 65096
     213     *
     214     * @covers ::is_user_member_of_blog
     215     */
     216    public function test_is_user_member_of_blog_filter_not_called_for_invalid_input() {
     217        $filter_calls = 0;
     218        $filter       = function ( $is_member ) use ( &$filter_calls ) {
     219            ++$filter_calls;
     220            return $is_member;
     221        };
     222
     223        add_filter( 'is_user_member_of_blog', $filter );
     224
     225        // No current user, and no user ID provided.
     226        $old_current = get_current_user_id();
     227        wp_set_current_user( 0 );
     228        $is_member = is_user_member_of_blog();
     229        wp_set_current_user( $old_current );
     230        $this->assertFalse( $is_member, 'Filter should not run when no user ID was provided.' );
     231
     232        // Unknown user ID.
     233        $this->assertFalse( is_user_member_of_blog( PHP_INT_MAX ), 'Filter should not run without a valid user ID.' );
     234
     235        // Known user, but an archived/deleted/spam site short-circuits.
     236        $user_id = self::factory()->user->create();
     237        $blog_id = self::factory()->blog->create();
     238        update_blog_details( $blog_id, array( 'archived' => 1 ) );
     239        $this->assertFalse( is_user_member_of_blog( $user_id, $blog_id ) );
     240
     241        $this->assertSame( 0, $filter_calls, 'Filter should not run when the function short-circuits before computing membership.' );
    177242    }
    178243
Note: See TracChangeset for help on using the changeset viewer.