Make WordPress Core

Changeset 41624


Ignore:
Timestamp:
09/27/2017 09:09:11 PM (9 years ago)
Author:
flixos90
Message:

Multisite: Initialize a user's roles correctly when setting them up for a different site.

While it has always been possible to initialize a user's roles and capabilities for another site than the current one in a multisite, the actual roles available were not switched prior to this change, possibly causing invalid roles to show up or actually valid capabilities not being available.

In order to fix this bug in a clean way, relevant parts of the WP_User class have been refactored. The ID of the site for which capabilities are currently initialized are now stored in a private property WP_User::$site_id. The WP_User::for_blog( $blog_id ) and WP_User::_init_caps( $cap_key ) methods have been deprecated in favor of WP_User::for_site( $site_id ). In addition, a new method WP_User::get_site_id() has been introduced to retrieve the site ID for which the user's capabilities are currently initialized.

Props ryanduff, jeremyfelt, flixos90.
Fixes #36961.

Location:
trunk
Files:
4 edited

Legend:

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

    r41376 r41624  
    9494
    9595        /**
     96         * The site ID the capabilities of this user are initialized for.
     97         *
     98         * @since 4.9.0
     99         * @var int
     100         */
     101        private $site_id = 0;
     102
     103        /**
    96104         * @static
    97105         * @since 3.3.0
     
    111119         * @param int|string|stdClass|WP_User $id User's ID, a WP_User object, or a user object from the DB.
    112120         * @param string $name Optional. User's username
    113          * @param int $blog_id Optional Site ID, defaults to current site.
    114          */
    115         public function __construct( $id = 0, $name = '', $blog_id = '' ) {
     121         * @param int $site_id Optional Site ID, defaults to current site.
     122         */
     123        public function __construct( $id = 0, $name = '', $site_id = '' ) {
    116124                if ( ! isset( self::$back_compat_keys ) ) {
    117125                        $prefix = $GLOBALS['wpdb']->prefix;
     
    127135
    128136                if ( $id instanceof WP_User ) {
    129                         $this->init( $id->data, $blog_id );
     137                        $this->init( $id->data, $site_id );
    130138                        return;
    131139                } elseif ( is_object( $id ) ) {
    132                         $this->init( $id, $blog_id );
     140                        $this->init( $id, $site_id );
    133141                        return;
    134142                }
     
    146154
    147155                if ( $data ) {
    148                         $this->init( $data, $blog_id );
     156                        $this->init( $data, $site_id );
    149157                } else {
    150158                        $this->data = new stdClass;
     
    158166         *
    159167         * @param object $data    User DB row object.
    160          * @param int    $blog_id Optional. The site ID to initialize for.
    161          */
    162         public function init( $data, $blog_id = '' ) {
     168         * @param int    $site_id Optional. The site ID to initialize for.
     169         */
     170        public function init( $data, $site_id = '' ) {
    163171                $this->data = $data;
    164172                $this->ID = (int) $data->ID;
    165173
    166                 $this->for_blog( $blog_id );
     174                $this->for_site( $site_id );
    167175        }
    168176
     
    242250
    243251        /**
    244          * Makes private/protected methods readable for backward compatibility.
    245          *
    246          * @since 4.3.0
    247          *
    248          * @param callable $name      Method to call.
    249          * @param array    $arguments Arguments to pass when calling.
    250          * @return mixed|false Return value of the callback, false otherwise.
    251          */
    252         public function __call( $name, $arguments ) {
    253                 if ( '_init_caps' === $name ) {
    254                         return call_user_func_array( array( $this, $name ), $arguments );
    255                 }
    256                 return false;
    257         }
    258 
    259         /**
    260252         * Magic method for checking the existence of a certain custom field.
    261253         *
     
    426418
    427419        /**
     420         * Makes private/protected methods readable for backward compatibility.
     421         *
     422         * @since 4.3.0
     423         *
     424         * @param callable $name      Method to call.
     425         * @param array    $arguments Arguments to pass when calling.
     426         * @return mixed|false Return value of the callback, false otherwise.
     427         */
     428        public function __call( $name, $arguments ) {
     429                if ( '_init_caps' === $name ) {
     430                        return call_user_func_array( array( $this, $name ), $arguments );
     431                }
     432                return false;
     433        }
     434
     435        /**
    428436         * Set up capability object properties.
    429437         *
     
    434442         *
    435443         * @since 2.1.0
     444         * @deprecated 4.9.0 Use WP_User::for_site()
    436445         *
    437446         * @global wpdb $wpdb WordPress database abstraction object.
     
    442451                global $wpdb;
    443452
    444                 if ( empty($cap_key) )
    445                         $this->cap_key = $wpdb->get_blog_prefix() . 'capabilities';
    446                 else
     453                _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' );
     454
     455                if ( empty( $cap_key ) ) {
     456                        $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities';
     457                } else {
    447458                        $this->cap_key = $cap_key;
    448 
    449                 $this->caps = get_user_meta( $this->ID, $this->cap_key, true );
    450 
    451                 if ( ! is_array( $this->caps ) )
    452                         $this->caps = array();
     459                }
     460
     461                $this->caps = $this->get_caps_data();
    453462
    454463                $this->get_role_caps();
     
    468477         */
    469478        public function get_role_caps() {
     479                $switch_site = false;
     480                if ( is_multisite() && $this->site_id != get_current_blog_id() ) {
     481                        $switch_site = true;
     482
     483                        switch_to_blog( $this->site_id );
     484                }
     485
    470486                $wp_roles = wp_roles();
    471487
     
    481497                }
    482498                $this->allcaps = array_merge( (array) $this->allcaps, (array) $this->caps );
     499
     500                if ( $switch_site ) {
     501                        restore_current_blog();
     502                }
    483503
    484504                return $this->allcaps;
     
    755775         *
    756776         * @since 3.0.0
     777         * @deprecated 4.9.0 Use WP_User::for_site()
    757778         *
    758779         * @global wpdb $wpdb WordPress database abstraction object.
     
    761782         */
    762783        public function for_blog( $blog_id = '' ) {
     784                _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' );
     785
     786                $this->for_site( $blog_id );
     787        }
     788
     789        /**
     790         * Sets the site to operate on. Defaults to the current site.
     791         *
     792         * @since 4.9.0
     793         *
     794         * @global wpdb $wpdb WordPress database abstraction object.
     795         *
     796         * @param int $site_id Site ID to initialize user capabilities for. Default is the current site.
     797         */
     798        public function for_site( $site_id = '' ) {
    763799                global $wpdb;
    764                 if ( ! empty( $blog_id ) )
    765                         $cap_key = $wpdb->get_blog_prefix( $blog_id ) . 'capabilities';
    766                 else
    767                         $cap_key = '';
    768                 $this->_init_caps( $cap_key );
     800
     801                if ( ! empty( $site_id ) ) {
     802                        $this->site_id = absint( $site_id );
     803                } else {
     804                        $this->site_id = get_current_blog_id();
     805                }
     806
     807                $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities';
     808
     809                $this->caps = $this->get_caps_data();
     810
     811                $this->get_role_caps();
     812        }
     813
     814        /**
     815         * Gets the ID of the site for which the user's capabilities are currently initialized.
     816         *
     817         * @since 4.9.0
     818         *
     819         * @return int Site ID.
     820         */
     821        public function get_site_id() {
     822                return $this->site_id;
     823        }
     824
     825        /**
     826         * Gets the available user capabilities data.
     827         *
     828         * @since 4.9.0
     829         *
     830         * @return array User capabilities array.
     831         */
     832        private function get_caps_data() {
     833                $caps = get_user_meta( $this->ID, $this->cap_key, true );
     834
     835                if ( ! is_array( $caps ) ) {
     836                        return array();
     837                }
     838
     839                return $caps;
    769840        }
    770841}
  • trunk/src/wp-includes/ms-blogs.php

    r41373 r41624  
    851851                $wp_roles = new WP_Roles();
    852852                $current_user = wp_get_current_user();
    853                 $current_user->for_blog( $new_blog );
     853                $current_user->for_site( $new_blog );
    854854        }
    855855
     
    925925                $wp_roles = new WP_Roles();
    926926                $current_user = wp_get_current_user();
    927                 $current_user->for_blog( $blog );
     927                $current_user->for_site( $blog );
    928928        }
    929929
  • trunk/tests/phpunit/tests/user.php

    r41376 r41624  
    181181                $this->assertEquals( 'foo', $user->data->$key );  // This will fail with WP < 3.3
    182182
    183                 foreach ( (array) $user as $key => $value ) {
     183                foreach ( get_object_vars( $user ) as $key => $value ) {
    184184                        $this->assertEquals( $value, $user->$key );
    185185                }
  • trunk/tests/phpunit/tests/user/capabilities.php

    r41290 r41624  
    18491849                $this->assertFalse( user_can( self::$users['subscriber']->ID,    'remove_user', self::$users['subscriber']->ID ) );
    18501850        }
     1851
     1852        /**
     1853         * @ticket 36961
     1854         * @group ms-required
     1855         */
     1856        function test_init_user_caps_for_different_site() {
     1857                global $wpdb;
     1858
     1859                $site_id = self::factory()->blog->create( array( 'user_id' => self::$users['administrator']->ID ) );
     1860
     1861                switch_to_blog( $site_id );
     1862
     1863                $role_name = 'uploader';
     1864                add_role( $role_name, 'Uploader', array(
     1865                        'read'         => true,
     1866                        'upload_files' => true,
     1867                ) );
     1868                add_user_to_blog( $site_id, self::$users['subscriber']->ID, $role_name );
     1869
     1870                restore_current_blog();
     1871
     1872                $user = new WP_User( self::$users['subscriber']->ID, '', $site_id );
     1873                $this->assertTrue( $user->has_cap( 'upload_files' ) );
     1874        }
     1875
     1876        /**
     1877         * @ticket 36961
     1878         * @group ms-required
     1879         */
     1880        function test_init_user_caps_for_different_site_by_user_switch() {
     1881                global $wpdb;
     1882
     1883                $user = new WP_User( self::$users['subscriber']->ID );
     1884
     1885                $site_id = self::factory()->blog->create( array( 'user_id' => self::$users['administrator']->ID ) );
     1886
     1887                switch_to_blog( $site_id );
     1888
     1889                $role_name = 'uploader';
     1890                add_role( $role_name, 'Uploader', array(
     1891                        'read'         => true,
     1892                        'upload_files' => true,
     1893                ) );
     1894                add_user_to_blog( $site_id, self::$users['subscriber']->ID, $role_name );
     1895
     1896                restore_current_blog();
     1897
     1898                $user->for_site( $site_id );
     1899                $this->assertTrue( $user->has_cap( 'upload_files' ) );
     1900        }
     1901
     1902        /**
     1903         * @ticket 36961
     1904         */
     1905        function test_get_caps_data() {
     1906                global $wpdb;
     1907
     1908                $custom_caps = array(
     1909                        'do_foo' => true,
     1910                        'do_bar' => false,
     1911                );
     1912
     1913                // Test `WP_User::get_caps_data()` by manually setting capabilities metadata.
     1914                update_user_meta( self::$users['subscriber']->ID, $wpdb->get_blog_prefix( get_current_blog_id() ) . 'capabilities', $custom_caps );
     1915
     1916                $user = new WP_User( self::$users['subscriber']->ID );
     1917                $this->assertSame( $custom_caps, $user->caps );
     1918        }
     1919
     1920        /**
     1921         * @ticket 36961
     1922         */
     1923        function test_user_get_site_id_default() {
     1924                $user = new WP_User( self::$users['subscriber']->ID );
     1925                $this->assertSame( get_current_blog_id(), $user->get_site_id() );
     1926        }
     1927
     1928        /**
     1929         * @ticket 36961
     1930         */
     1931        function test_user_get_site_id() {
     1932                global $wpdb;
     1933
     1934                // Suppressing errors here allows to get around creating an actual site,
     1935                // which is unnecessary for this test.
     1936                $suppress = $wpdb->suppress_errors();
     1937                $user = new WP_User( self::$users['subscriber']->ID, '', 333 );
     1938                $wpdb->suppress_errors( $suppress );
     1939
     1940                $this->assertSame( 333, $user->get_site_id() );
     1941        }
    18511942}
Note: See TracChangeset for help on using the changeset viewer.