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",