diff --git a/src/wp-admin/includes/class-wp-posts-list-table.php b/src/wp-admin/includes/class-wp-posts-list-table.php index 19686959e2..213ca201d9 100644 --- a/src/wp-admin/includes/class-wp-posts-list-table.php +++ b/src/wp-admin/includes/class-wp-posts-list-table.php @@ -1660,7 +1660,7 @@ class WP_Posts_List_Table extends WP_List_Table { if ( current_user_can( $post_type_object->cap->edit_others_posts ) ) { $users_opt = array( 'hide_if_only_one_author' => false, - 'who' => 'authors', + 'capability' => array( $post_type_object->cap->edit_posts ), 'name' => 'post_author', 'class' => 'authors', 'multi' => 1, diff --git a/src/wp-admin/includes/meta-boxes.php b/src/wp-admin/includes/meta-boxes.php index 193877ce96..c14ae52ddc 100644 --- a/src/wp-admin/includes/meta-boxes.php +++ b/src/wp-admin/includes/meta-boxes.php @@ -903,12 +903,14 @@ function post_slug_meta_box( $post ) { */ function post_author_meta_box( $post ) { global $user_ID; + + $post_type_object = get_post_type_object( $post->post_type ); ?> 'authors', + 'capability' => array( $post_type_object->cap->edit_posts ), 'name' => 'post_author_override', 'selected' => empty( $post->ID ) ? $user_ID : $post->post_author, 'include_selected' => true, diff --git a/src/wp-content/themes/twentyfourteen/functions.php b/src/wp-content/themes/twentyfourteen/functions.php index f39296b1ba..892d6ac939 100644 --- a/src/wp-content/themes/twentyfourteen/functions.php +++ b/src/wp-content/themes/twentyfourteen/functions.php @@ -491,15 +491,24 @@ if ( ! function_exists( 'twentyfourteen_list_authors' ) ) : * @since Twenty Fourteen 1.0 */ function twentyfourteen_list_authors() { - $contributor_ids = get_users( - array( - 'fields' => 'ID', - 'orderby' => 'post_count', - 'order' => 'DESC', - 'who' => 'authors', - ) + $args = array( + 'fields' => 'ID', + 'orderby' => 'post_count', + 'order' => 'DESC', + 'capability' => array( 'edit_posts' ), ); + /** + * Filters query arguments for listing authors. + * + * @since 3.3 + * + * @param array $args Query arguments. + */ + $args = apply_filters( 'twentyfourteen_list_authors_query_args', $args ); + + $contributor_ids = get_users( $args ); + foreach ( $contributor_ids as $contributor_id ) : $post_count = count_user_posts( $contributor_id ); diff --git a/src/wp-includes/class-wp-user-query.php b/src/wp-includes/class-wp-user-query.php index f760c25288..936ffe89bf 100644 --- a/src/wp-includes/class-wp-user-query.php +++ b/src/wp-includes/class-wp-user-query.php @@ -93,6 +93,9 @@ class WP_User_Query { 'role' => '', 'role__in' => array(), 'role__not_in' => array(), + 'capability' => '', + 'capability__in' => array(), + 'capability__not_in' => array(), 'meta_key' => '', 'meta_value' => '', 'meta_compare' => '', @@ -133,6 +136,7 @@ class WP_User_Query { * querying for all users with using -1. * @since 4.7.0 Added 'nicename', 'nicename__in', 'nicename__not_in', 'login', 'login__in', * and 'login__not_in' parameters. + * @since 5.9.0 Added 'capability', 'capability__in', and 'capability__not_in' parameters. * * @global wpdb $wpdb WordPress database abstraction object. * @global int $blog_id @@ -148,6 +152,19 @@ class WP_User_Query { * roles. Default empty array. * @type string[] $role__not_in An array of role names to exclude. Users matching one or more of these * roles will not be included in results. Default empty array. + * @type string $capability An array or a comma-separated list of capability names that users must match + * to be included in results. Note that this is an inclusive list: users + * must match *each* capability. + * Does NOT work for capabilities not in the database or filtered via {@see 'map_meta_cap'}. + * Default empty. + * @type string[] $capability__in An array of capability names. Matched users must have at least one of these + * capabilities. + * Does NOT work for capabilities not in the database or filtered via {@see 'map_meta_cap'}. + * Default empty array. + * @type string[] $capability__not_in An array of capability names to exclude. Users matching one or more of these + * capabilities will not be included in results. + * Does NOT work for capabilities not in the database or filtered via {@see 'map_meta_cap'}. + * Default empty array. * @type string $meta_key User meta key. Default empty. * @type string $meta_value User meta value. Default empty. * @type string $meta_compare Comparison operator to test the `$meta_value`. Accepts '=', '!=', @@ -320,6 +337,17 @@ class WP_User_Query { $this->meta_query->parse_query_vars( $qv ); if ( isset( $qv['who'] ) && 'authors' === $qv['who'] && $blog_id ) { + _deprecated_argument( + 'WP_User_Query', + '5.9.0', + sprintf( + /* translators: 1: who, 2: capability */ + __( '%1$s is deprecated. Use %2$s instead.' ), + 'who', + 'capability' + ) + ); + $who_query = array( 'key' => $wpdb->get_blog_prefix( $blog_id ) . 'user_level', 'value' => 0, @@ -343,6 +371,7 @@ class WP_User_Query { $this->meta_query->parse_query_vars( $this->meta_query->queries ); } + // Roles. $roles = array(); if ( isset( $qv['role'] ) ) { if ( is_array( $qv['role'] ) ) { @@ -362,6 +391,111 @@ class WP_User_Query { $role__not_in = (array) $qv['role__not_in']; } + // Capabilities. + $available_roles = array(); + + if ( ! empty( $qv['capability'] ) || ! empty( $qv['capability__in'] ) || ! empty( $qv['capability__not_in'] ) ) { + global $wp_roles; + + $wp_roles->for_site( $blog_id ); + $available_roles = $wp_roles->roles; + } + + $capabilities = array(); + if ( ! empty( $qv['capability'] ) ) { + if ( is_array( $qv['capability'] ) ) { + $capabilities = $qv['capability']; + } elseif ( is_string( $qv['capability'] ) ) { + $capabilities = array_map( 'trim', explode( ',', $qv['capability'] ) ); + } + } + + $capability__in = array(); + if ( ! empty( $qv['capability__in'] ) ) { + $capability__in = (array) $qv['capability__in']; + } + + $capability__not_in = array(); + if ( ! empty( $qv['capability__not_in'] ) ) { + $capability__not_in = (array) $qv['capability__not_in']; + } + + // Keep track of all capabilities and the roles they're added on. + $caps_with_roles = array(); + + foreach ( $available_roles as $role => $role_data ) { + $role_caps = array_keys( array_filter( $role_data['capabilities'] ) ); + + foreach ( $capabilities as $cap ) { + if ( in_array( $cap, $role_caps, true ) ) { + $caps_with_roles[ $cap ][] = $role; + break; + } + } + + foreach ( $capability__in as $cap ) { + if ( in_array( $cap, $role_caps, true ) ) { + $role__in[] = $role; + break; + } + } + + foreach ( $capability__not_in as $cap ) { + if ( in_array( $cap, $role_caps, true ) ) { + $role__not_in[] = $role; + break; + } + } + } + + $role__in = array_merge( $role__in, $capability__in ); + $role__not_in = array_merge( $role__not_in, $capability__not_in ); + + $roles = array_unique( $roles ); + $role__in = array_unique( $role__in ); + $role__not_in = array_unique( $role__not_in ); + + // Support querying by capabilities added directly to users. + if ( $blog_id && ! empty( $capabilities ) ) { + $capabilities_clauses = array( 'relation' => 'AND' ); + + foreach ( $capabilities as $cap ) { + $clause = array( 'relation' => 'OR' ); + + $clause[] = array( + 'key' => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities', + 'value' => '"' . $cap . '"', + 'compare' => 'LIKE', + ); + + if ( ! empty( $caps_with_roles[ $cap ] ) ) { + foreach ( $caps_with_roles[ $cap ] as $role ) { + $clause[] = array( + 'key' => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities', + 'value' => '"' . $role . '"', + 'compare' => 'LIKE', + ); + } + } + + $capabilities_clauses[] = $clause; + } + + $role_queries[] = $capabilities_clauses; + + if ( empty( $this->meta_query->queries ) ) { + $this->meta_query->queries[] = $capabilities_clauses; + } else { + // Append the cap query to the original queries and reparse the query. + $this->meta_query->queries = array( + 'relation' => 'AND', + array( $this->meta_query->queries, array( $capabilities_clauses ) ), + ); + } + + $this->meta_query->parse_query_vars( $this->meta_query->queries ); + } + if ( $blog_id && ( ! empty( $roles ) || ! empty( $role__in ) || ! empty( $role__not_in ) || is_multisite() ) ) { $role_queries = array(); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php index b3cdfc2e31..e3e5d935d7 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php @@ -198,6 +198,15 @@ class WP_REST_Users_Controller extends WP_REST_Controller { ); } + // Check if capabilities is specified in GET request and if user can list users. + if ( ! empty( $request['capabilities'] ) && ! current_user_can( 'list_users' ) ) { + return new WP_Error( + 'rest_user_cannot_view', + __( 'Sorry, you are not allowed to filter users by capability.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) { return new WP_Error( 'rest_forbidden_context', @@ -254,13 +263,14 @@ class WP_REST_Users_Controller extends WP_REST_Controller { * present in $registered will be set. */ $parameter_mappings = array( - 'exclude' => 'exclude', - 'include' => 'include', - 'order' => 'order', - 'per_page' => 'number', - 'search' => 'search', - 'roles' => 'role__in', - 'slug' => 'nicename__in', + 'exclude' => 'exclude', + 'include' => 'include', + 'order' => 'order', + 'per_page' => 'number', + 'search' => 'search', + 'roles' => 'role__in', + 'capabilities' => 'capability__in', + 'slug' => 'nicename__in', ); $prepared_args = array(); @@ -1554,6 +1564,14 @@ class WP_REST_Users_Controller extends WP_REST_Controller { ), ); + $query_params['capabilities'] = array( + 'description' => __( 'Limit result set to users matching at least one specific capability provided. Accepts csv list or single capability.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ); + $query_params['who'] = array( 'description' => __( 'Limit result set to users who are considered authors.' ), 'type' => 'string', diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 55bd546568..018c960c03 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -1320,13 +1320,32 @@ function wp_dropdown_users( $args = '' ) { 'role' => '', 'role__in' => array(), 'role__not_in' => array(), + 'capability' => '', + 'capability__in' => array(), + 'capability__not_in' => array(), ); $defaults['selected'] = is_author() ? get_query_var( 'author' ) : 0; $parsed_args = wp_parse_args( $args, $defaults ); - $query_args = wp_array_slice_assoc( $parsed_args, array( 'blog_id', 'include', 'exclude', 'orderby', 'order', 'who', 'role', 'role__in', 'role__not_in' ) ); + $query_args = wp_array_slice_assoc( + $parsed_args, + array( + 'blog_id', + 'include', + 'exclude', + 'orderby', + 'order', + 'who', + 'role', + 'role__in', + 'role__not_in', + 'capability', + 'capability__in', + 'capability__not_in', + ) + ); $fields = array( 'ID', 'user_login' ); diff --git a/tests/phpunit/tests/rest-api/rest-users-controller.php b/tests/phpunit/tests/rest-api/rest-users-controller.php index 1705cba2eb..478148ed56 100644 --- a/tests/phpunit/tests/rest-api/rest-users-controller.php +++ b/tests/phpunit/tests/rest-api/rest-users-controller.php @@ -15,6 +15,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { protected static $editor; protected static $draft_editor; protected static $subscriber; + protected static $author; protected static $authors = array(); protected static $posts = array(); @@ -55,6 +56,13 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { 'user_email' => 'subscriber@example.com', ) ); + self::$author = $factory->user->create( + array( + 'display_name' => 'author', + 'role' => 'author', + 'user_email' => 'author@example.com', + ) + ); foreach ( array( true, false ) as $show_in_rest ) { foreach ( array( true, false ) as $public ) { @@ -107,7 +115,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { } // Set up users for pagination tests. - for ( $i = 0; $i < self::$total_users - 10; $i++ ) { + for ( $i = 0; $i < self::$total_users - 11; $i++ ) { self::$user_ids[] = $factory->user->create( array( 'role' => 'contributor', @@ -121,6 +129,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { self::delete_user( self::$user ); self::delete_user( self::$editor ); self::delete_user( self::$draft_editor ); + self::delete_user( self::$author ); foreach ( self::$posts as $post ) { wp_delete_post( $post, true ); @@ -183,8 +192,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $keys = array_keys( $data['endpoints'][0]['args'] ); - sort( $keys ); - $this->assertSame( + $this->assertEqualSets( array( 'context', 'exclude', @@ -195,6 +203,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { 'page', 'per_page', 'roles', + 'capabilities', 'search', 'slug', 'who', @@ -795,32 +804,19 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { public function test_get_items_roles() { wp_set_current_user( self::$user ); - $tango = $this->factory->user->create( - array( - 'display_name' => 'tango', - 'role' => 'subscriber', - ) - ); - $yolo = $this->factory->user->create( - array( - 'display_name' => 'yolo', - 'role' => 'author', - ) - ); - $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); $request->set_param( 'roles', 'author,subscriber' ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); - $this->assertCount( 3, $data ); - $this->assertSame( $tango, $data[1]['id'] ); - $this->assertSame( $yolo, $data[2]['id'] ); + $this->assertCount( 2, $data ); + $this->assertSame( self::$author, $data[0]['id'] ); + $this->assertSame( self::$subscriber, $data[1]['id'] ); $request->set_param( 'roles', 'author' ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $this->assertCount( 1, $data ); - $this->assertSame( $yolo, $data[0]['id'] ); + $this->assertSame( self::$author, $data[0]['id'] ); wp_set_current_user( 0 ); @@ -838,28 +834,86 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { public function test_get_items_invalid_roles() { wp_set_current_user( self::$user ); - $lolz = $this->factory->user->create( - array( - 'display_name' => 'lolz', - 'role' => 'author', - ) - ); + $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); + $request->set_param( 'roles', 'ilovesteak,author' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertCount( 1, $data ); + $this->assertSame( self::$author, $data[0]['id'] ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); + $request->set_param( 'roles', 'steakisgood' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertIsArray( $data ); + $this->assertEmpty( $data ); + } + + /** + * @ticket 16841 + */ + public function test_get_items_capabilities() { + wp_set_current_user( self::$user ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); + $request->set_param( 'capabilities', 'edit_posts' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertNotEmpty( $data ); + foreach ( $data as $user ) { + $this->assertTrue( user_can( $user['id'], 'edit_posts' ) ); + } + } + + /** + * @ticket 16841 + */ + public function test_get_items_capabilities_no_permission_no_user() { + wp_set_current_user( 0 ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); + $request->set_param( 'capabilities', 'edit_posts' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_user_cannot_view', $response, 401 ); + } + + /** + * @ticket 16841 + */ + public function test_get_items_capabilities_no_permission_editor() { + wp_set_current_user( self::$editor ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); + $request->set_param( 'capabilities', 'edit_posts' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_user_cannot_view', $response, 403 ); + } + + /** + * @ticket 16841 + */ + public function test_get_items_invalid_capabilities() { + wp_set_current_user( self::$user ); $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); $request->set_param( 'roles', 'ilovesteak,author' ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $this->assertCount( 1, $data ); - $this->assertSame( $lolz, $data[0]['id'] ); + $this->assertSame( self::$author, $data[0]['id'] ); $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); - $request->set_param( 'roles', 'steakisgood' ); + $request->set_param( 'capabilities', 'steakisgood' ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); - $this->assertCount( 0, $data ); - $this->assertSame( array(), $data ); + $this->assertIsArray( $data ); + $this->assertEmpty( $data ); } + /** + * @expectedDeprecated WP_User_Query + */ public function test_get_items_who_author_query() { wp_set_current_user( self::$superadmin ); diff --git a/tests/phpunit/tests/user/query.php b/tests/phpunit/tests/user/query.php index f0a0827038..170e5b24a2 100644 --- a/tests/phpunit/tests/user/query.php +++ b/tests/phpunit/tests/user/query.php @@ -730,6 +730,7 @@ class Tests_User_Query extends WP_UnitTestCase { /** * @ticket 32019 * @group ms-required + * @expectedDeprecated WP_User_Query */ public function test_who_authors() { $b = self::factory()->blog->create(); @@ -755,6 +756,7 @@ class Tests_User_Query extends WP_UnitTestCase { /** * @ticket 32019 * @group ms-required + * @expectedDeprecated WP_User_Query */ public function test_who_authors_should_work_alongside_meta_query() { $b = self::factory()->blog->create(); @@ -789,6 +791,7 @@ class Tests_User_Query extends WP_UnitTestCase { /** * @ticket 36724 * @group ms-required + * @expectedDeprecated WP_User_Query */ public function test_who_authors_should_work_alongside_meta_params() { $b = self::factory()->blog->create(); @@ -1725,4 +1728,242 @@ class Tests_User_Query extends WP_UnitTestCase { return array( 555 ); } + + /** + * @ticket 16841 + * @group ms-excluded + */ + public function test_get_single_capability_by_string() { + $wp_user_search = new WP_User_Query( array( 'capability' => 'install_plugins' ) ); + $users = $wp_user_search->get_results(); + + $this->assertNotEmpty( $users ); + foreach ( $users as $user ) { + // User has the capability, but on Multisite they would also need to be a super admin. + // Hence using get_role_caps() instead of has_cap(). + $role_caps = $user->get_role_caps(); + $this->assertArrayHasKey( 'install_plugins', $role_caps ); + $this->assertTrue( $role_caps['install_plugins'] ); + } + } + + /** + * @ticket 16841 + * @group ms-required + */ + public function test_get_single_capability_by_string_multisite() { + $wp_user_search = new WP_User_Query( array( 'capability' => array( 'install_plugins' ) ) ); + $users = $wp_user_search->get_results(); + + $this->assertNotEmpty( $users ); + foreach ( $users as $user ) { + $role_caps = $user->get_role_caps(); + $this->assertArrayHasKey( 'install_plugins', $role_caps ); + $this->assertTrue( $role_caps['install_plugins'] ); + // While the user can have the capability, on Multisite they also need to be a super admin. + if ( is_super_admin( $user->ID ) ) { + $this->assertTrue( $user->has_cap( 'install_plugins' ) ); + } else { + $this->assertFalse( $user->has_cap( 'install_plugins' ) ); + } + } + } + + /** + * @ticket 16841 + */ + public function test_get_single_capability_invalid() { + $wp_user_search = new WP_User_Query( array( 'capability' => 'foo_bar' ) ); + $users = $wp_user_search->get_results(); + + $this->assertEmpty( $users ); + } + + /** + * @ticket 16841 + */ + public function test_get_single_capability_by_array() { + $wp_user_search = new WP_User_Query( array( 'capability' => array( 'install_plugins' ) ) ); + $users = $wp_user_search->get_results(); + + $this->assertNotEmpty( $users ); + foreach ( $users as $user ) { + // User has the capability, but on Multisite they would also need to be a super admin. + // Hence using get_role_caps() instead of has_cap(). + $role_caps = $user->get_role_caps(); + $this->assertArrayHasKey( 'install_plugins', $role_caps ); + $this->assertTrue( $role_caps['install_plugins'] ); + } + } + + /** + * @ticket 16841 + */ + public function test_get_single_capability_added_to_user() { + foreach ( self::$sub_ids as $subscriber ) { + $subscriber = get_user_by( 'ID', $subscriber ); + $subscriber->add_cap( 'custom_cap' ); + } + + $wp_user_search = new WP_User_Query( array( 'capability' => 'custom_cap' ) ); + $users = $wp_user_search->get_results(); + + $this->assertCount( 2, $users ); + $this->assertEqualSets( self::$sub_ids, wp_list_pluck( $users, 'ID' ) ); + + foreach ( $users as $user ) { + $this->assertTrue( $user->has_cap( 'custom_cap' ) ); + } + } + + /** + * @ticket 16841 + */ + public function test_get_multiple_capabilities_should_only_match_users_who_have_each_capability_test() { + wp_roles()->add_role( 'role_1', 'Role 1', array( 'role_1_cap' => true ) ); + wp_roles()->add_role( 'role_2', 'Role 2', array( 'role_2_cap' => true ) ); + + $subscriber1 = get_user_by( 'ID', self::$sub_ids[0] ); + $subscriber1->add_role( 'role_1' ); + + $subscriber2 = get_user_by( 'ID', self::$sub_ids[1] ); + $subscriber2->add_role( 'role_1' ); + $subscriber2->add_role( 'role_2' ); + + $wp_user_search = new WP_User_Query( array( 'capability' => array( 'role_1_cap', 'role_2_cap' ) ) ); + $users = $wp_user_search->get_results(); + + $this->assertCount( 1, $users ); + $this->assertSame( $users[0]->ID, $subscriber2->ID ); + foreach ( $users as $user ) { + $this->assertTrue( $user->has_cap( 'role_1_cap' ) ); + $this->assertTrue( $user->has_cap( 'role_2_cap' ) ); + } + } + + /** + * @ticket 16841 + */ + public function test_get_multiple_capabilities_should_only_match_users_who_have_each_capability_added_to_user() { + $admin1 = get_user_by( 'ID', self::$admin_ids[0] ); + $admin1->add_cap( 'custom_cap' ); + + $wp_user_search = new WP_User_Query( array( 'capability' => array( 'manage_options', 'custom_cap' ) ) ); + $users = $wp_user_search->get_results(); + + $this->assertCount( 1, $users ); + $this->assertSame( $users[0]->ID, $admin1->ID ); + $this->assertTrue( $users[0]->has_cap( 'custom_cap' ) ); + $this->assertTrue( $users[0]->has_cap( 'manage_options' ) ); + } + + /** + * @ticket 16841 + */ + public function test_get_multiple_capabilities_or() { + $wp_user_search = new WP_User_Query( array( 'capability__in' => array( 'publish_posts', 'edit_posts' ) ) ); + $users = $wp_user_search->get_results(); + + $this->assertNotEmpty( $users ); + foreach ( $users as $user ) { + $this->assertTrue( $user->has_cap( 'publish_posts' ) || $user->has_cap( 'edit_posts' ) ); + } + } + + /** + * @ticket 16841 + */ + public function test_get_multiple_capabilities_or_added_to_user() { + $user = self::factory()->user->create_and_get( array( 'role' => 'subscriber' ) ); + $user->add_cap( 'custom_cap' ); + + $wp_user_search = new WP_User_Query( array( 'capability__in' => array( 'publish_posts', 'custom_cap' ) ) ); + $users = $wp_user_search->get_results(); + + $this->assertNotEmpty( $users ); + foreach ( $users as $user ) { + $this->assertTrue( $user->has_cap( 'publish_posts' ) || $user->has_cap( 'custom_cap' ) ); + } + } + + /** + * @ticket 16841 + */ + public function test_capability_exclusion() { + $wp_user_search = new WP_User_Query( array( 'capability__not_in' => array( 'publish_posts', 'edit_posts' ) ) ); + $users = $wp_user_search->get_results(); + + $this->assertNotEmpty( $users ); + foreach ( $users as $user ) { + $this->assertFalse( $user->has_cap( 'publish_posts' ) ); + $this->assertFalse( $user->has_cap( 'edit_posts' ) ); + } + } + + /** + * @ticket 16841 + */ + public function test_capability_exclusion_added_to_user() { + $user = self::factory()->user->create_and_get( array( 'role' => 'subscriber' ) ); + $user->add_cap( 'custom_cap' ); + + $wp_user_search = new WP_User_Query( array( 'capability__not_in' => array( 'publish_posts', 'custom_cap' ) ) ); + $users = $wp_user_search->get_results(); + + $this->assertNotEmpty( $users ); + foreach ( $users as $user ) { + $this->assertFalse( $user->has_cap( 'publish_posts' ) ); + $this->assertFalse( $user->has_cap( 'custom_cap' ) ); + } + } + + /** + * @ticket 16841 + */ + public function test_capability__in_capability__not_in_combined() { + $wp_user_search = new WP_User_Query( + array( + 'capability__in' => array( 'read' ), + 'capability__not_in' => array( 'manage_options' ), + ) + ); + $users = $wp_user_search->get_results(); + + $this->assertNotEmpty( $users ); + foreach ( $users as $user ) { + $this->assertTrue( $user->has_cap( 'read' ) ); + $this->assertFalse( $user->has_cap( 'manage_options' ) ); + } + } + + /** + * @ticket 16841 + * @group ms-required + */ + public function test_get_single_capability_multisite_blog_id() { + $blog_id = self::factory()->blog->create(); + + add_user_to_blog( $blog_id, self::$author_ids[0], 'subscriber' ); + add_user_to_blog( $blog_id, self::$author_ids[1], 'author' ); + add_user_to_blog( $blog_id, self::$author_ids[2], 'editor' ); + + $wp_user_search = new WP_User_Query( + array( + 'capability' => 'publish_posts', + 'blog_id' => $blog_id, + ) + ); + $users = $wp_user_search->get_results(); + + $found = wp_list_pluck( $wp_user_search->get_results(), 'ID' ); + + $this->assertNotEmpty( $users ); + foreach ( $users as $user ) { + $this->assertTrue( $user->has_cap( 'publish_posts' ) ); + } + + $this->assertNotContains( self::$author_ids[0], $found ); + $this->assertContains( self::$author_ids[1], $found ); + $this->assertContains( self::$author_ids[2], $found ); + } } diff --git a/tests/phpunit/tests/xmlrpc/wp/getUsers.php b/tests/phpunit/tests/xmlrpc/wp/getUsers.php index 627facea5c..4975b68381 100644 --- a/tests/phpunit/tests/xmlrpc/wp/getUsers.php +++ b/tests/phpunit/tests/xmlrpc/wp/getUsers.php @@ -54,6 +54,9 @@ class Tests_XMLRPC_wp_getUsers extends WP_XMLRPC_UnitTestCase { $this->assertSame( 403, $results->code ); } + /** + * @expectedDeprecated WP_User_Query + */ function test_role_filter() { $author_id = $this->make_user_by_role( 'author' ); $editor_id = $this->make_user_by_role( 'editor' ); diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 63dbf37f05..0d5b2230e0 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -5357,6 +5357,14 @@ mockedApiResponse.Schema = { }, "required": false }, + "capabilities": { + "description": "Limit result set to users matching at least one specific capability provided. Accepts csv list or single capability.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + }, "who": { "description": "Limit result set to users who are considered authors.", "type": "string",