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/tests/phpunit/tests/user/query.php

    r51462 r51943  
    731731     * @ticket 32019
    732732     * @group ms-required
     733     * @expectedDeprecated WP_User_Query
    733734     */
    734735    public function test_who_authors() {
     
    756757     * @ticket 32019
    757758     * @group ms-required
     759     * @expectedDeprecated WP_User_Query
    758760     */
    759761    public function test_who_authors_should_work_alongside_meta_query() {
     
    790792     * @ticket 36724
    791793     * @group ms-required
     794     * @expectedDeprecated WP_User_Query
    792795     */
    793796    public function test_who_authors_should_work_alongside_meta_params() {
     
    17261729        return array( 555 );
    17271730    }
     1731
     1732    /**
     1733     * @ticket 16841
     1734     * @group ms-excluded
     1735     */
     1736    public function test_get_single_capability_by_string() {
     1737        $wp_user_search = new WP_User_Query( array( 'capability' => 'install_plugins' ) );
     1738        $users          = $wp_user_search->get_results();
     1739
     1740        $this->assertNotEmpty( $users );
     1741        foreach ( $users as $user ) {
     1742            // User has the capability, but on Multisite they would also need to be a super admin.
     1743            // Hence using get_role_caps() instead of has_cap().
     1744            $role_caps = $user->get_role_caps();
     1745            $this->assertArrayHasKey( 'install_plugins', $role_caps );
     1746            $this->assertTrue( $role_caps['install_plugins'] );
     1747        }
     1748    }
     1749
     1750    /**
     1751     * @ticket 16841
     1752     * @group ms-required
     1753     */
     1754    public function test_get_single_capability_by_string_multisite() {
     1755        $wp_user_search = new WP_User_Query( array( 'capability' => array( 'install_plugins' ) ) );
     1756        $users          = $wp_user_search->get_results();
     1757
     1758        $this->assertNotEmpty( $users );
     1759        foreach ( $users as $user ) {
     1760            $role_caps = $user->get_role_caps();
     1761            $this->assertArrayHasKey( 'install_plugins', $role_caps );
     1762            $this->assertTrue( $role_caps['install_plugins'] );
     1763            // While the user can have the capability, on Multisite they also need to be a super admin.
     1764            if ( is_super_admin( $user->ID ) ) {
     1765                $this->assertTrue( $user->has_cap( 'install_plugins' ) );
     1766            } else {
     1767                $this->assertFalse( $user->has_cap( 'install_plugins' ) );
     1768            }
     1769        }
     1770    }
     1771
     1772    /**
     1773     * @ticket 16841
     1774     */
     1775    public function test_get_single_capability_invalid() {
     1776        $wp_user_search = new WP_User_Query( array( 'capability' => 'foo_bar' ) );
     1777        $users          = $wp_user_search->get_results();
     1778
     1779        $this->assertEmpty( $users );
     1780    }
     1781
     1782    /**
     1783     * @ticket 16841
     1784     */
     1785    public function test_get_single_capability_by_array() {
     1786        $wp_user_search = new WP_User_Query( array( 'capability' => array( 'install_plugins' ) ) );
     1787        $users          = $wp_user_search->get_results();
     1788
     1789        $this->assertNotEmpty( $users );
     1790        foreach ( $users as $user ) {
     1791            // User has the capability, but on Multisite they would also need to be a super admin.
     1792            // Hence using get_role_caps() instead of has_cap().
     1793            $role_caps = $user->get_role_caps();
     1794            $this->assertArrayHasKey( 'install_plugins', $role_caps );
     1795            $this->assertTrue( $role_caps['install_plugins'] );
     1796        }
     1797    }
     1798
     1799    /**
     1800     * @ticket 16841
     1801     */
     1802    public function test_get_single_capability_added_to_user() {
     1803        foreach ( self::$sub_ids as $subscriber ) {
     1804            $subscriber = get_user_by( 'ID', $subscriber );
     1805            $subscriber->add_cap( 'custom_cap' );
     1806        }
     1807
     1808        $wp_user_search = new WP_User_Query( array( 'capability' => 'custom_cap' ) );
     1809        $users          = $wp_user_search->get_results();
     1810
     1811        $this->assertCount( 2, $users );
     1812        $this->assertEqualSets( self::$sub_ids, wp_list_pluck( $users, 'ID' ) );
     1813
     1814        foreach ( $users as $user ) {
     1815            $this->assertTrue( $user->has_cap( 'custom_cap' ) );
     1816        }
     1817    }
     1818
     1819    /**
     1820     * @ticket 16841
     1821     */
     1822    public function test_get_multiple_capabilities_should_only_match_users_who_have_each_capability_test() {
     1823        wp_roles()->add_role( 'role_1', 'Role 1', array( 'role_1_cap' => true ) );
     1824        wp_roles()->add_role( 'role_2', 'Role 2', array( 'role_2_cap' => true ) );
     1825
     1826        $subscriber1 = get_user_by( 'ID', self::$sub_ids[0] );
     1827        $subscriber1->add_role( 'role_1' );
     1828
     1829        $subscriber2 = get_user_by( 'ID', self::$sub_ids[1] );
     1830        $subscriber2->add_role( 'role_1' );
     1831        $subscriber2->add_role( 'role_2' );
     1832
     1833        $wp_user_search = new WP_User_Query( array( 'capability' => array( 'role_1_cap', 'role_2_cap' ) ) );
     1834        $users          = $wp_user_search->get_results();
     1835
     1836        $this->assertCount( 1, $users );
     1837        $this->assertSame( $users[0]->ID, $subscriber2->ID );
     1838        foreach ( $users as $user ) {
     1839            $this->assertTrue( $user->has_cap( 'role_1_cap' ) );
     1840            $this->assertTrue( $user->has_cap( 'role_2_cap' ) );
     1841        }
     1842    }
     1843
     1844    /**
     1845     * @ticket 16841
     1846     */
     1847    public function test_get_multiple_capabilities_should_only_match_users_who_have_each_capability_added_to_user() {
     1848        $admin1 = get_user_by( 'ID', self::$admin_ids[0] );
     1849        $admin1->add_cap( 'custom_cap' );
     1850
     1851        $wp_user_search = new WP_User_Query( array( 'capability' => array( 'manage_options', 'custom_cap' ) ) );
     1852        $users          = $wp_user_search->get_results();
     1853
     1854        $this->assertCount( 1, $users );
     1855        $this->assertSame( $users[0]->ID, $admin1->ID );
     1856        $this->assertTrue( $users[0]->has_cap( 'custom_cap' ) );
     1857        $this->assertTrue( $users[0]->has_cap( 'manage_options' ) );
     1858    }
     1859
     1860    /**
     1861     * @ticket 16841
     1862     */
     1863    public function test_get_multiple_capabilities_or() {
     1864        $wp_user_search = new WP_User_Query( array( 'capability__in' => array( 'publish_posts', 'edit_posts' ) ) );
     1865        $users          = $wp_user_search->get_results();
     1866
     1867        $this->assertNotEmpty( $users );
     1868        foreach ( $users as $user ) {
     1869            $this->assertTrue( $user->has_cap( 'publish_posts' ) || $user->has_cap( 'edit_posts' ) );
     1870        }
     1871    }
     1872
     1873    /**
     1874     * @ticket 16841
     1875     */
     1876    public function test_get_multiple_capabilities_or_added_to_user() {
     1877        $user = self::factory()->user->create_and_get( array( 'role' => 'subscriber' ) );
     1878        $user->add_cap( 'custom_cap' );
     1879
     1880        $wp_user_search = new WP_User_Query( array( 'capability__in' => array( 'publish_posts', 'custom_cap' ) ) );
     1881        $users          = $wp_user_search->get_results();
     1882
     1883        $this->assertNotEmpty( $users );
     1884        foreach ( $users as $user ) {
     1885            $this->assertTrue( $user->has_cap( 'publish_posts' ) || $user->has_cap( 'custom_cap' ) );
     1886        }
     1887    }
     1888
     1889    /**
     1890     * @ticket 16841
     1891     */
     1892    public function test_capability_exclusion() {
     1893        $wp_user_search = new WP_User_Query( array( 'capability__not_in' => array( 'publish_posts', 'edit_posts' ) ) );
     1894        $users          = $wp_user_search->get_results();
     1895
     1896        $this->assertNotEmpty( $users );
     1897        foreach ( $users as $user ) {
     1898            $this->assertFalse( $user->has_cap( 'publish_posts' ) );
     1899            $this->assertFalse( $user->has_cap( 'edit_posts' ) );
     1900        }
     1901    }
     1902
     1903    /**
     1904     * @ticket 16841
     1905     */
     1906    public function test_capability_exclusion_added_to_user() {
     1907        $user = self::factory()->user->create_and_get( array( 'role' => 'subscriber' ) );
     1908        $user->add_cap( 'custom_cap' );
     1909
     1910        $wp_user_search = new WP_User_Query( array( 'capability__not_in' => array( 'publish_posts', 'custom_cap' ) ) );
     1911        $users          = $wp_user_search->get_results();
     1912
     1913        $this->assertNotEmpty( $users );
     1914        foreach ( $users as $user ) {
     1915            $this->assertFalse( $user->has_cap( 'publish_posts' ) );
     1916            $this->assertFalse( $user->has_cap( 'custom_cap' ) );
     1917        }
     1918    }
     1919
     1920    /**
     1921     * @ticket 16841
     1922     */
     1923    public function test_capability__in_capability__not_in_combined() {
     1924        $wp_user_search = new WP_User_Query(
     1925            array(
     1926                'capability__in'     => array( 'read' ),
     1927                'capability__not_in' => array( 'manage_options' ),
     1928            )
     1929        );
     1930        $users          = $wp_user_search->get_results();
     1931
     1932        $this->assertNotEmpty( $users );
     1933        foreach ( $users as $user ) {
     1934            $this->assertTrue( $user->has_cap( 'read' ) );
     1935            $this->assertFalse( $user->has_cap( 'manage_options' ) );
     1936        }
     1937    }
     1938
     1939    /**
     1940     * @ticket 16841
     1941     * @group ms-required
     1942     */
     1943    public function test_get_single_capability_multisite_blog_id() {
     1944        $blog_id = self::factory()->blog->create();
     1945
     1946        add_user_to_blog( $blog_id, self::$author_ids[0], 'subscriber' );
     1947        add_user_to_blog( $blog_id, self::$author_ids[1], 'author' );
     1948        add_user_to_blog( $blog_id, self::$author_ids[2], 'editor' );
     1949
     1950        $wp_user_search = new WP_User_Query(
     1951            array(
     1952                'capability' => 'publish_posts',
     1953                'blog_id'    => $blog_id,
     1954            )
     1955        );
     1956        $users          = $wp_user_search->get_results();
     1957
     1958        $found = wp_list_pluck( $wp_user_search->get_results(), 'ID' );
     1959
     1960        $this->assertNotEmpty( $users );
     1961        foreach ( $users as $user ) {
     1962            $this->assertTrue( $user->has_cap( 'publish_posts' ) );
     1963        }
     1964
     1965        $this->assertNotContains( self::$author_ids[0], $found );
     1966        $this->assertContains( self::$author_ids[1], $found );
     1967        $this->assertContains( self::$author_ids[2], $found );
     1968    }
    17281969}
Note: See TracChangeset for help on using the changeset viewer.