Make WordPress Core


Ignore:
Timestamp:
10/27/2021 06:42:13 PM (3 years ago)
Author:
swissspidy
Message:

Role/Capability: Add support for capability queries in WP_User_Query.

Similar to the existing role/role__in/role__not_in query arguments, this adds support for three new query arguments in WP_User_Query:

  • capability
  • capability__in
  • capability__not_in

These can be used to fetch users with (or without) a specific set of capabilities, for example to get all users
with the capability to edit a certain post type.

Under the hood, this will check all existing roles on the site and perform a LIKE query against the capabilities user meta field to find:

  • all users with a role that has this capability
  • all users with the capability being assigned directly

Note: In WordPress, not all capabilities are stored in the database. Capabilities can also be modified using filters like map_meta_cap. These new query arguments do NOT work for such capabilities.

The prime use case for capability queries is to get all "authors", i.e. users with the capability to edit a certain post type.

Until now, 'who' => 'authors' was used for this, which relies on user levels. However, user levels were deprecated a long time ago and thus never added to custom roles. This led to constant frustration due to users with custom roles missing from places like author dropdowns.

This updates any usage of 'who' => 'authors' in core to use capability queries instead.

Subsequently, 'who' => 'authors' queries are being deprecated in favor of these new query arguments.

Also adds a new capabilities parameter (mapping to capability__in in WP_User_Query) to the REST API users controller.

Also updates twentyfourteen_list_authors() in Twenty Fourteen to make use of this new functionality, adding a new twentyfourteen_list_authors_query_args filter to make it easier to override this behavior.

Props scribu, lgladdly, boonebgorges, spacedmonkey, peterwilsoncc, SergeyBiryukov, swissspidy.
Fixes #16841.

File:
1 edited

Legend:

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

    r49946 r51943  
    9494            'role__in'            => array(),
    9595            'role__not_in'        => array(),
     96            'capability'          => '',
     97            'capability__in'      => array(),
     98            'capability__not_in'  => array(),
    9699            'meta_key'            => '',
    97100            'meta_value'          => '',
     
    134137     * @since 4.7.0 Added 'nicename', 'nicename__in', 'nicename__not_in', 'login', 'login__in',
    135138     *              and 'login__not_in' parameters.
     139     * @since 5.9.0 Added 'capability', 'capability__in', and 'capability__not_in' parameters.
    136140     *
    137141     * @global wpdb $wpdb WordPress database abstraction object.
     
    149153     *     @type string[]     $role__not_in        An array of role names to exclude. Users matching one or more of these
    150154     *                                             roles will not be included in results. Default empty array.
     155     *     @type string       $capability          An array or a comma-separated list of capability names that users must match
     156     *                                             to be included in results. Note that this is an inclusive list: users
     157     *                                             must match *each* capability.
     158     *                                             Does NOT work for capabilities not in the database or filtered via {@see 'map_meta_cap'}.
     159     *                                             Default empty.
     160     *     @type string[]     $capability__in      An array of capability names. Matched users must have at least one of these
     161     *                                             capabilities.
     162     *                                             Does NOT work for capabilities not in the database or filtered via {@see 'map_meta_cap'}.
     163     *                                             Default empty array.
     164     *     @type string[]     $capability__not_in  An array of capability names to exclude. Users matching one or more of these
     165     *                                             capabilities will not be included in results.
     166     *                                             Does NOT work for capabilities not in the database or filtered via {@see 'map_meta_cap'}.
     167     *                                             Default empty array.
    151168     *     @type string       $meta_key            User meta key. Default empty.
    152169     *     @type string       $meta_value          User meta value. Default empty.
     
    321338
    322339        if ( isset( $qv['who'] ) && 'authors' === $qv['who'] && $blog_id ) {
     340            _deprecated_argument(
     341                'WP_User_Query',
     342                '5.9.0',
     343                sprintf(
     344                    /* translators: 1: who, 2: capability */
     345                    __( '%1$s is deprecated. Use %2$s instead.' ),
     346                    '<code>who</code>',
     347                    '<code>capability</code>'
     348                )
     349            );
     350
    323351            $who_query = array(
    324352                'key'     => $wpdb->get_blog_prefix( $blog_id ) . 'user_level',
     
    344372        }
    345373
     374        // Roles.
    346375        $roles = array();
    347376        if ( isset( $qv['role'] ) ) {
     
    361390        if ( isset( $qv['role__not_in'] ) ) {
    362391            $role__not_in = (array) $qv['role__not_in'];
     392        }
     393
     394        // Capabilities.
     395        $available_roles = array();
     396
     397        if ( ! empty( $qv['capability'] ) || ! empty( $qv['capability__in'] ) || ! empty( $qv['capability__not_in'] ) ) {
     398            global $wp_roles;
     399
     400            $wp_roles->for_site( $blog_id );
     401            $available_roles = $wp_roles->roles;
     402        }
     403
     404        $capabilities = array();
     405        if ( ! empty( $qv['capability'] ) ) {
     406            if ( is_array( $qv['capability'] ) ) {
     407                $capabilities = $qv['capability'];
     408            } elseif ( is_string( $qv['capability'] ) ) {
     409                $capabilities = array_map( 'trim', explode( ',', $qv['capability'] ) );
     410            }
     411        }
     412
     413        $capability__in = array();
     414        if ( ! empty( $qv['capability__in'] ) ) {
     415            $capability__in = (array) $qv['capability__in'];
     416        }
     417
     418        $capability__not_in = array();
     419        if ( ! empty( $qv['capability__not_in'] ) ) {
     420            $capability__not_in = (array) $qv['capability__not_in'];
     421        }
     422
     423        // Keep track of all capabilities and the roles they're added on.
     424        $caps_with_roles = array();
     425
     426        foreach ( $available_roles as $role => $role_data ) {
     427            $role_caps = array_keys( array_filter( $role_data['capabilities'] ) );
     428
     429            foreach ( $capabilities as $cap ) {
     430                if ( in_array( $cap, $role_caps, true ) ) {
     431                    $caps_with_roles[ $cap ][] = $role;
     432                    break;
     433                }
     434            }
     435
     436            foreach ( $capability__in as $cap ) {
     437                if ( in_array( $cap, $role_caps, true ) ) {
     438                    $role__in[] = $role;
     439                    break;
     440                }
     441            }
     442
     443            foreach ( $capability__not_in as $cap ) {
     444                if ( in_array( $cap, $role_caps, true ) ) {
     445                    $role__not_in[] = $role;
     446                    break;
     447                }
     448            }
     449        }
     450
     451        $role__in     = array_merge( $role__in, $capability__in );
     452        $role__not_in = array_merge( $role__not_in, $capability__not_in );
     453
     454        $roles        = array_unique( $roles );
     455        $role__in     = array_unique( $role__in );
     456        $role__not_in = array_unique( $role__not_in );
     457
     458        // Support querying by capabilities added directly to users.
     459        if ( $blog_id && ! empty( $capabilities ) ) {
     460            $capabilities_clauses = array( 'relation' => 'AND' );
     461
     462            foreach ( $capabilities as $cap ) {
     463                $clause = array( 'relation' => 'OR' );
     464
     465                $clause[] = array(
     466                    'key'     => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
     467                    'value'   => '"' . $cap . '"',
     468                    'compare' => 'LIKE',
     469                );
     470
     471                if ( ! empty( $caps_with_roles[ $cap ] ) ) {
     472                    foreach ( $caps_with_roles[ $cap ] as $role ) {
     473                        $clause[] = array(
     474                            'key'     => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
     475                            'value'   => '"' . $role . '"',
     476                            'compare' => 'LIKE',
     477                        );
     478                    }
     479                }
     480
     481                $capabilities_clauses[] = $clause;
     482            }
     483
     484            $role_queries[] = $capabilities_clauses;
     485
     486            if ( empty( $this->meta_query->queries ) ) {
     487                $this->meta_query->queries[] = $capabilities_clauses;
     488            } else {
     489                // Append the cap query to the original queries and reparse the query.
     490                $this->meta_query->queries = array(
     491                    'relation' => 'AND',
     492                    array( $this->meta_query->queries, array( $capabilities_clauses ) ),
     493                );
     494            }
     495
     496            $this->meta_query->parse_query_vars( $this->meta_query->queries );
    363497        }
    364498
Note: See TracChangeset for help on using the changeset viewer.