diff --git a/includes/classes/Indexable/User/User.php b/includes/classes/Indexable/User/User.php index c72b0110f0..40136f9367 100644 --- a/includes/classes/Indexable/User/User.php +++ b/includes/classes/Indexable/User/User.php @@ -129,7 +129,25 @@ public function format_args( $query_vars, $query ) { * Support `role` query arg */ if ( ! empty( $blog_id ) ) { - if ( ! empty( $query_vars['role'] ) ) { + // If a blog id is set, we will apply at least one filter for roles. + $use_filters = true; + + // If there are no specific roles named, make sure the user is a member of the site. + if ( empty( $query_vars['role'] ) && empty( $query_vars['role__in'] ) && empty( $query_vars['role__not_in'] ) ) { + $filter['bool']['must'][] = array( + 'exists' => array( + 'field' => 'capabilities.' . $blog_id . '.roles', + ), + ); + /** + * EP versions prior to 4.1.0 set non-existent roles as `0`. + */ + $filter['bool']['must_not'][] = array( + 'term' => array( + 'capabilities.' . $blog_id . '.roles' => 0, + ), + ); + } elseif ( ! empty( $query_vars['role'] ) ) { $roles = (array) $query_vars['role']; foreach ( $roles as $role ) { @@ -141,8 +159,6 @@ public function format_args( $query_vars, $query ) { ), ); } - - $use_filters = true; } else { if ( ! empty( $query_vars['role__in'] ) ) { $roles_in = (array) $query_vars['role__in']; @@ -154,8 +170,6 @@ public function format_args( $query_vars, $query ) { 'capabilities.' . $blog_id . '.roles' => $roles_in, ), ); - - $use_filters = true; } if ( ! empty( $query_vars['role__not_in'] ) ) { @@ -170,8 +184,6 @@ public function format_args( $query_vars, $query ) { ), ); } - - $use_filters = true; } } } @@ -864,7 +876,7 @@ public function prepare_capabilities( $user_id ) { if ( ! empty( $roles ) ) { $prepared_roles[ (int) $site['blog_id'] ] = [ - 'roles' => array_keys( $roles ), + 'roles' => array_keys( (array) $roles ), ]; } } diff --git a/tests/cypress/integration/indexables/user.spec.js b/tests/cypress/integration/indexables/user.spec.js index 1e33486084..b4e8c45119 100644 --- a/tests/cypress/integration/indexables/user.spec.js +++ b/tests/cypress/integration/indexables/user.spec.js @@ -104,4 +104,49 @@ describe('User Indexable', () => { expect(text).to.contain('"value": "Doe"'); }); }); + + it('Only returns users from the current blog', () => { + cy.login(); + + cy.maybeEnableFeature('users'); + + const newUserData = { + userLogin: 'nobloguser', + userEmail: 'no-blog-user@example.com', + }; + + cy.wpCli(`wp user get ${newUserData.userLogin} --field=ID`, true).then((wpCliResponse) => { + if (wpCliResponse.code === 0) { + cy.wpCli(`wp user delete ${newUserData.userLogin} --yes --network`); + cy.wpCli('wp elasticpress index --setup --yes'); + } + }); + + // Create a user without a blog. + cy.visitAdminPage('network/user-new.php'); + cy.get('#username').clearThenType(newUserData.userLogin); + cy.get('#email').clearThenType(newUserData.userEmail); + cy.get('#add-user').click(); + cy.get('#message.updated').should('be.visible'); + + // Searching for it should not return anything. + searchUser('nobloguser'); + cy.get('.wp-list-table').should('contain.text', 'No users found.'); + cy.getTotal(0); + cy.get('.ep-query-debug').should('contain.text', 'Query Response Code: HTTP 200'); + + // Add user to the blog. + cy.visitAdminPage('user-new.php'); + cy.get('#adduser-email').clearThenType(newUserData.userLogin); + cy.get('#adduser-noconfirmation').check(); + cy.get('#addusersub').click(); + cy.get('#message.updated').should('be.visible'); + + // Searching for it should return it. + searchUser('nobloguser'); + cy.get('.wp-list-table').should('contain.text', 'nobloguser'); + cy.getTotal(1); + cy.get('.ep-query-debug').should('contain.text', 'Query Response Code: HTTP 200'); + cy.get('.query-results').should('contain.text', '"user_email": "no-blog-user@example.com"'); + }); }); diff --git a/tests/php/indexables/TestUser.php b/tests/php/indexables/TestUser.php index 7cfacfb101..464a3e7a0d 100644 --- a/tests/php/indexables/TestUser.php +++ b/tests/php/indexables/TestUser.php @@ -1439,4 +1439,58 @@ public function testIntegrateSearchQueries() { $this->assertTrue( $this->get_feature()->integrate_search_queries( false, $query ) ); } + + /** + * Test users that does not belong to any blog. + * + * @since 4.1.0 + */ + public function testUserSearchLimitedToOneBlog() { + // This user does not belong to any blog. + Functions\create_and_sync_user( + [ + 'user_login' => 'users-and-blogs-1', + 'role' => '', + 'first_name' => 'No Blog', + 'last_name' => 'User', + 'user_email' => 'no-blog@test.com', + 'user_url' => 'http://domain.test', + ] + ); + Functions\create_and_sync_user( + [ + 'user_login' => 'users-and-blogs-2', + 'role' => 'contributor', + 'first_name' => 'Blog', + 'last_name' => 'User', + 'user_email' => 'blog@test.com', + 'user_url' => 'http://domain.test', + ] + ); + + ElasticPress\Elasticsearch::factory()->refresh_indices(); + + // Here `blog_id` defaults to `get_current_blog_id()`. + $query = new \WP_User_Query( [ + 'search' => 'users-and-blogs' + ] ); + + $this->assertTrue( $this->get_feature()->integrate_search_queries( false, $query ) ); + $this->assertEquals( 1, $query->total_users ); + foreach ( $query->results as $user ) { + $this->assertTrue( $user->elasticsearch ); + } + + // Search accross all blogs. + $query = new \WP_User_Query( [ + 'search' => 'users-and-blogs', + 'blog_id' => 0, + ] ); + + $this->assertTrue( $this->get_feature()->integrate_search_queries( false, $query ) ); + $this->assertEquals( 2, $query->total_users ); + foreach ( $query->results as $user ) { + $this->assertTrue( $user->elasticsearch ); + } + } }