Make WordPress Core

Changeset 51943

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.

Location:
trunk
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/class-wp-posts-list-table.php

    r51737 r51943  
    16611661                        $users_opt = array(
    16621662                            'hide_if_only_one_author' => false,
    1663                             'who'                     => 'authors',
     1663                            ',
    16641664                            'name'                    => 'post_author',
    16651665                            'class'                   => 'authors',
  • trunk/src/wp-admin/includes/meta-boxes.php

    r51837 r51943  
    904904function post_author_meta_box( $post ) {
    905905    global $user_ID;
     906
     907
    906908    ?>
    907909<label class="screen-reader-text" for="post_author_override"><?php _e( 'Author' ); ?></label>
     
    909911    wp_dropdown_users(
    910912        array(
    911             'who'              => 'authors',
     913            ',
    912914            'name'             => 'post_author_override',
    913915            'selected'         => empty( $post->ID ) ? $user_ID : $post->post_author,
  • trunk/src/wp-content/themes/twentyfourteen/functions.php

    r51045 r51943  
    492492     */
    493493    function twentyfourteen_list_authors() {
    494         $contributor_ids = get_users(
    495             array(
    496                 'fields'  => 'ID',
    497                 'orderby' => 'post_count',
    498                 'order'   => 'DESC',
    499                 'who'     => 'authors',
    500             )
    501         );
     494        $args = array(
     495            'fields'     => 'ID',
     496            'orderby'    => 'post_count',
     497            'order'      => 'DESC',
     498            'capability' => array( 'edit_posts' ),
     499        );
     500
     501        /**
     502         * Filters query arguments for listing authors.
     503         *
     504         * @since 3.3
     505         *
     506         * @param array $args Query arguments.
     507         */
     508        $args = apply_filters( 'twentyfourteen_list_authors_query_args', $args );
     509
     510        $contributor_ids = get_users( $args );
    502511
    503512        foreach ( $contributor_ids as $contributor_id ) :
  • trunk/src/wp-includes/class-wp-user-query.php

    r49946 r51943  
    9494            'role__in'            => array(),
    9595            'role__not_in'        => array(),
     96
     97
     98
    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
    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
     156
     157
     158
     159
     160
     161
     162
     163
     164
     165
     166
     167
    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
     341
     342
     343
     344
     345
     346
     347
     348
     349
     350
    323351            $who_query = array(
    324352                'key'     => $wpdb->get_blog_prefix( $blog_id ) . 'user_level',
     
    344372        }
    345373
     374
    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
     395
     396
     397
     398
     399
     400
     401
     402
     403
     404
     405
     406
     407
     408
     409
     410
     411
     412
     413
     414
     415
     416
     417
     418
     419
     420
     421
     422
     423
     424
     425
     426
     427
     428
     429
     430
     431
     432
     433
     434
     435
     436
     437
     438
     439
     440
     441
     442
     443
     444
     445
     446
     447
     448
     449
     450
     451
     452
     453
     454
     455
     456
     457
     458
     459
     460
     461
     462
     463
     464
     465
     466
     467
     468
     469
     470
     471
     472
     473
     474
     475
     476
     477
     478
     479
     480
     481
     482
     483
     484
     485
     486
     487
     488
     489
     490
     491
     492
     493
     494
     495
     496
    363497        }
    364498
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php

    r51786 r51943  
    199199        }
    200200
     201
     202
     203
     204
     205
     206
     207
     208
     209
    201210        if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
    202211            return new WP_Error(
     
    255264         */
    256265        $parameter_mappings = array(
    257             'exclude'  => 'exclude',
    258             'include'  => 'include',
    259             'order'    => 'order',
    260             'per_page' => 'number',
    261             'search'   => 'search',
    262             'roles'    => 'role__in',
    263             'slug'     => 'nicename__in',
     266            'exclude'      => 'exclude',
     267            'include'      => 'include',
     268            'order'        => 'order',
     269            'per_page'     => 'number',
     270            'search'       => 'search',
     271            'roles'        => 'role__in',
     272            'capabilities' => 'capability__in',
     273            'slug'         => 'nicename__in',
    264274        );
    265275
     
    15551565        );
    15561566
     1567
     1568
     1569
     1570
     1571
     1572
     1573
     1574
    15571575        $query_params['who'] = array(
    15581576            'description' => __( 'Limit result set to users who are considered authors.' ),
  • trunk/src/wp-includes/user.php

    r51738 r51943  
    13211321        'role__in'                => array(),
    13221322        'role__not_in'            => array(),
     1323
     1324
     1325
    13231326    );
    13241327
     
    13271330    $parsed_args = wp_parse_args( $args, $defaults );
    13281331
    1329     $query_args = wp_array_slice_assoc( $parsed_args, array( 'blog_id', 'include', 'exclude', 'orderby', 'order', 'who', 'role', 'role__in', 'role__not_in' ) );
     1332    $query_args = wp_array_slice_assoc(
     1333        $parsed_args,
     1334        array(
     1335            'blog_id',
     1336            'include',
     1337            'exclude',
     1338            'orderby',
     1339            'order',
     1340            'who',
     1341            'role',
     1342            'role__in',
     1343            'role__not_in',
     1344            'capability',
     1345            'capability__in',
     1346            'capability__not_in',
     1347        )
     1348    );
    13301349
    13311350    $fields = array( 'ID', 'user_login' );
  • trunk/tests/phpunit/tests/rest-api/rest-users-controller.php

    r51568 r51943  
    1616    protected static $draft_editor;
    1717    protected static $subscriber;
     18
    1819
    1920    protected static $authors     = array();
     
    5455                'display_name' => 'subscriber',
    5556                'user_email'   => 'subscriber@example.com',
     57
     58
     59
     60
     61
     62
     63
    5664            )
    5765        );
     
    108116
    109117        // Set up users for pagination tests.
    110         for ( $i = 0; $i < self::$total_users - 10; $i++ ) {
     118        for ( $i = 0; $i < self::$total_users - 1; $i++ ) {
    111119            self::$user_ids[] = $factory->user->create(
    112120                array(
     
    122130        self::delete_user( self::$editor );
    123131        self::delete_user( self::$draft_editor );
     132
    124133
    125134        foreach ( self::$posts as $post ) {
     
    184193        $data     = $response->get_data();
    185194        $keys     = array_keys( $data['endpoints'][0]['args'] );
    186         sort( $keys );
    187         $this->assertSame(
     195        $this->assertEqualSets(
    188196            array(
    189197                'context',
     
    196204                'per_page',
    197205                'roles',
     206
    198207                'search',
    199208                'slug',
     
    796805        wp_set_current_user( self::$user );
    797806
    798         $tango = $this->factory->user->create(
    799             array(
    800                 'display_name' => 'tango',
    801                 'role'         => 'subscriber',
    802             )
    803         );
    804         $yolo  = $this->factory->user->create(
    805             array(
    806                 'display_name' => 'yolo',
    807                 'role'         => 'author',
    808             )
    809         );
    810 
    811807        $request = new WP_REST_Request( 'GET', '/wp/v2/users' );
    812808        $request->set_param( 'roles', 'author,subscriber' );
    813809        $response = rest_get_server()->dispatch( $request );
    814810        $data     = $response->get_data();
    815         $this->assertCount( 3, $data );
    816         $this->assertSame( $tango, $data[1]['id'] );
    817         $this->assertSame( $yolo, $data[2]['id'] );
     811        $this->assertCount( , $data );
     812        $this->assertSame( ]['id'] );
     813        $this->assertSame( ]['id'] );
    818814
    819815        $request->set_param( 'roles', 'author' );
     
    821817        $data     = $response->get_data();
    822818        $this->assertCount( 1, $data );
    823         $this->assertSame( $yolo, $data[0]['id'] );
     819        $this->assertSame( , $data[0]['id'] );
    824820
    825821        wp_set_current_user( 0 );
     
    839835        wp_set_current_user( self::$user );
    840836
    841         $lolz = $this->factory->user->create(
    842             array(
    843                 'display_name' => 'lolz',
    844                 'role'         => 'author',
    845             )
    846         );
    847 
    848837        $request = new WP_REST_Request( 'GET', '/wp/v2/users' );
    849838        $request->set_param( 'roles', 'ilovesteak,author' );
     
    851840        $data     = $response->get_data();
    852841        $this->assertCount( 1, $data );
    853         $this->assertSame( $lolz, $data[0]['id'] );
     842        $this->assertSame( , $data[0]['id'] );
    854843
    855844        $request = new WP_REST_Request( 'GET', '/wp/v2/users' );
     
    857846        $response = rest_get_server()->dispatch( $request );
    858847        $data     = $response->get_data();
    859         $this->assertCount( 0, $data );
    860         $this->assertSame( array(), $data );
    861     }
    862 
     848        $this->assertIsArray( $data );
     849        $this->assertEmpty( $data );
     850    }
     851
     852    /**
     853     * @ticket 16841
     854     */
     855    public function test_get_items_capabilities() {
     856        wp_set_current_user( self::$user );
     857
     858        $request = new WP_REST_Request( 'GET', '/wp/v2/users' );
     859        $request->set_param( 'capabilities', 'edit_posts' );
     860        $response = rest_get_server()->dispatch( $request );
     861        $data     = $response->get_data();
     862
     863        $this->assertNotEmpty( $data );
     864        foreach ( $data as $user ) {
     865            $this->assertTrue( user_can( $user['id'], 'edit_posts' ) );
     866        }
     867    }
     868
     869    /**
     870     * @ticket 16841
     871     */
     872    public function test_get_items_capabilities_no_permission_no_user() {
     873        wp_set_current_user( 0 );
     874
     875        $request = new WP_REST_Request( 'GET', '/wp/v2/users' );
     876        $request->set_param( 'capabilities', 'edit_posts' );
     877        $response = rest_get_server()->dispatch( $request );
     878        $this->assertErrorResponse( 'rest_user_cannot_view', $response, 401 );
     879    }
     880
     881    /**
     882     * @ticket 16841
     883     */
     884    public function test_get_items_capabilities_no_permission_editor() {
     885        wp_set_current_user( self::$editor );
     886
     887        $request = new WP_REST_Request( 'GET', '/wp/v2/users' );
     888        $request->set_param( 'capabilities', 'edit_posts' );
     889        $response = rest_get_server()->dispatch( $request );
     890        $this->assertErrorResponse( 'rest_user_cannot_view', $response, 403 );
     891    }
     892
     893    /**
     894     * @ticket 16841
     895     */
     896    public function test_get_items_invalid_capabilities() {
     897        wp_set_current_user( self::$user );
     898
     899        $request = new WP_REST_Request( 'GET', '/wp/v2/users' );
     900        $request->set_param( 'roles', 'ilovesteak,author' );
     901        $response = rest_get_server()->dispatch( $request );
     902        $data     = $response->get_data();
     903        $this->assertCount( 1, $data );
     904        $this->assertSame( self::$author, $data[0]['id'] );
     905
     906        $request = new WP_REST_Request( 'GET', '/wp/v2/users' );
     907        $request->set_param( 'capabilities', 'steakisgood' );
     908        $response = rest_get_server()->dispatch( $request );
     909        $data     = $response->get_data();
     910        $this->assertIsArray( $data );
     911        $this->assertEmpty( $data );
     912    }
     913
     914    /**
     915     * @expectedDeprecated WP_User_Query
     916     */
    863917    public function test_get_items_who_author_query() {
    864918        wp_set_current_user( self::$superadmin );
  • trunk/tests/phpunit/tests/user/query.php

    r51462 r51943  
    731731     * @ticket 32019
    732732     * @group ms-required
     733
    733734     */
    734735    public function test_who_authors() {
     
    756757     * @ticket 32019
    757758     * @group ms-required
     759
    758760     */
    759761    public function test_who_authors_should_work_alongside_meta_query() {
     
    790792     * @ticket 36724
    791793     * @group ms-required
     794
    792795     */
    793796    public function test_who_authors_should_work_alongside_meta_params() {
     
    17261729        return array( 555 );
    17271730    }
     1731
     1732
     1733
     1734
     1735
     1736
     1737
     1738
     1739
     1740
     1741
     1742
     1743
     1744
     1745
     1746
     1747
     1748
     1749
     1750
     1751
     1752
     1753
     1754
     1755
     1756
     1757
     1758
     1759
     1760
     1761
     1762
     1763
     1764
     1765
     1766
     1767
     1768
     1769
     1770
     1771
     1772
     1773
     1774
     1775
     1776
     1777
     1778
     1779
     1780
     1781
     1782
     1783
     1784
     1785
     1786
     1787
     1788
     1789
     1790
     1791
     1792
     1793
     1794
     1795
     1796
     1797
     1798
     1799
     1800
     1801
     1802
     1803
     1804
     1805
     1806
     1807
     1808
     1809
     1810
     1811
     1812
     1813
     1814
     1815
     1816
     1817
     1818
     1819
     1820
     1821
     1822
     1823
     1824
     1825
     1826
     1827
     1828
     1829
     1830
     1831
     1832
     1833
     1834
     1835
     1836
     1837
     1838
     1839
     1840
     1841
     1842
     1843
     1844
     1845
     1846
     1847
     1848
     1849
     1850
     1851
     1852
     1853
     1854
     1855
     1856
     1857
     1858
     1859
     1860
     1861
     1862
     1863
     1864
     1865
     1866
     1867
     1868
     1869
     1870
     1871
     1872
     1873
     1874
     1875
     1876
     1877
     1878
     1879
     1880
     1881
     1882
     1883
     1884
     1885
     1886
     1887
     1888
     1889
     1890
     1891
     1892
     1893
     1894
     1895
     1896
     1897
     1898
     1899
     1900
     1901
     1902
     1903
     1904
     1905
     1906
     1907
     1908
     1909
     1910
     1911
     1912
     1913
     1914
     1915
     1916
     1917
     1918
     1919
     1920
     1921
     1922
     1923
     1924
     1925
     1926
     1927
     1928
     1929
     1930
     1931
     1932
     1933
     1934
     1935
     1936
     1937
     1938
     1939
     1940
     1941
     1942
     1943
     1944
     1945
     1946
     1947
     1948
     1949
     1950
     1951
     1952
     1953
     1954
     1955
     1956
     1957
     1958
     1959
     1960
     1961
     1962
     1963
     1964
     1965
     1966
     1967
     1968
    17281969}
  • trunk/tests/phpunit/tests/xmlrpc/wp/getUsers.php

    r51367 r51943  
    5555    }
    5656
     57
     58
     59
    5760    function test_role_filter() {
    5861        $author_id        = $this->make_user_by_role( 'author' );
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r51463 r51943  
    53585358                            "required": false
    53595359                        },
     5360
     5361
     5362
     5363
     5364
     5365
     5366
     5367
    53605368                        "who": {
    53615369                            "description": "Limit result set to users who are considered authors.",
Note: See TracChangeset for help on using the changeset viewer.