diff --git a/wp-admin/includes/class-wp-posts-list-table.php b/wp-admin/includes/class-wp-posts-list-table.php
index 19686959e2..213ca201d9 100644
--- a/wp-admin/includes/class-wp-posts-list-table.php
+++ b/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/wp-admin/includes/meta-boxes.php b/wp-admin/includes/meta-boxes.php
index 193877ce96..c14ae52ddc 100644
--- a/wp-admin/includes/meta-boxes.php
+++ b/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/wp-content/themes/twentyfourteen/functions.php b/wp-content/themes/twentyfourteen/functions.php
index f39296b1ba..892d6ac939 100644
--- a/wp-content/themes/twentyfourteen/functions.php
+++ b/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/wp-includes/class-wp-user-query.php b/wp-includes/class-wp-user-query.php
index f760c25288..936ffe89bf 100644
--- a/wp-includes/class-wp-user-query.php
+++ b/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/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
index b3cdfc2e31..e3e5d935d7 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
+++ b/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/wp-includes/user.php b/wp-includes/user.php
index 55bd546568..018c960c03 100644
--- a/wp-includes/user.php
+++ b/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/wp-includes/version.php b/wp-includes/version.php
index 9362fa7678..9ccc542e74 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '5.9-alpha-51942';
+$wp_version = '5.9-alpha-51943';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.