diff --git a/src/wp-includes/class-wp-user.php b/src/wp-includes/class-wp-user.php index 85b98654d2..4a90439ba3 100644 --- a/src/wp-includes/class-wp-user.php +++ b/src/wp-includes/class-wp-user.php @@ -92,6 +92,14 @@ class WP_User { */ public $filter = null; + /** + * The site ID the capabilities of this user are initialized for. + * + * @since 4.9.0 + * @var int + */ + private $site_id = 0; + /** * @static * @since 3.3.0 @@ -110,9 +118,9 @@ class WP_User { * * @param int|string|stdClass|WP_User $id User's ID, a WP_User object, or a user object from the DB. * @param string $name Optional. User's username - * @param int $blog_id Optional Site ID, defaults to current site. + * @param int $site_id Optional Site ID, defaults to current site. */ - public function __construct( $id = 0, $name = '', $blog_id = '' ) { + public function __construct( $id = 0, $name = '', $site_id = '' ) { if ( ! isset( self::$back_compat_keys ) ) { $prefix = $GLOBALS['wpdb']->prefix; self::$back_compat_keys = array( @@ -126,10 +134,10 @@ class WP_User { } if ( $id instanceof WP_User ) { - $this->init( $id->data, $blog_id ); + $this->init( $id->data, $site_id ); return; } elseif ( is_object( $id ) ) { - $this->init( $id, $blog_id ); + $this->init( $id, $site_id ); return; } @@ -145,7 +153,7 @@ class WP_User { } if ( $data ) { - $this->init( $data, $blog_id ); + $this->init( $data, $site_id ); } else { $this->data = new stdClass; } @@ -157,13 +165,13 @@ class WP_User { * @since 3.3.0 * * @param object $data User DB row object. - * @param int $blog_id Optional. The site ID to initialize for. + * @param int $site_id Optional. The site ID to initialize for. */ - public function init( $data, $blog_id = '' ) { + public function init( $data, $site_id = '' ) { $this->data = $data; $this->ID = (int) $data->ID; - $this->for_blog( $blog_id ); + $this->for_site( $site_id ); } /** @@ -240,22 +248,6 @@ class WP_User { return $user; } - /** - * Makes private/protected methods readable for backward compatibility. - * - * @since 4.3.0 - * - * @param callable $name Method to call. - * @param array $arguments Arguments to pass when calling. - * @return mixed|false Return value of the callback, false otherwise. - */ - public function __call( $name, $arguments ) { - if ( '_init_caps' === $name ) { - return call_user_func_array( array( $this, $name ), $arguments ); - } - return false; - } - /** * Magic method for checking the existence of a certain custom field. * @@ -424,6 +416,22 @@ class WP_User { return get_object_vars( $this->data ); } + /** + * Makes private/protected methods readable for backward compatibility. + * + * @since 4.3.0 + * + * @param callable $name Method to call. + * @param array $arguments Arguments to pass when calling. + * @return mixed|false Return value of the callback, false otherwise. + */ + public function __call( $name, $arguments ) { + if ( '_init_caps' === $name ) { + return call_user_func_array( array( $this, $name ), $arguments ); + } + return false; + } + /** * Set up capability object properties. * @@ -433,6 +441,7 @@ class WP_User { * used. * * @since 2.1.0 + * @deprecated 4.9.0 Use WP_User::for_site() * * @global wpdb $wpdb WordPress database abstraction object. * @@ -441,15 +450,15 @@ class WP_User { protected function _init_caps( $cap_key = '' ) { global $wpdb; - if ( empty($cap_key) ) - $this->cap_key = $wpdb->get_blog_prefix() . 'capabilities'; - else + _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' ); + + if ( empty( $cap_key ) ) { + $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities'; + } else { $this->cap_key = $cap_key; + } - $this->caps = get_user_meta( $this->ID, $this->cap_key, true ); - - if ( ! is_array( $this->caps ) ) - $this->caps = array(); + $this->caps = $this->get_caps_data(); $this->get_role_caps(); } @@ -467,6 +476,13 @@ class WP_User { * @return array List of all capabilities for the user. */ public function get_role_caps() { + $switch_site = false; + if ( is_multisite() && $this->site_id != get_current_blog_id() ) { + $switch_site = true; + + switch_to_blog( $this->site_id ); + } + $wp_roles = wp_roles(); //Filter out caps that are not role names and assign to $this->roles @@ -481,6 +497,10 @@ class WP_User { } $this->allcaps = array_merge( (array) $this->allcaps, (array) $this->caps ); + if ( $switch_site ) { + restore_current_blog(); + } + return $this->allcaps; } @@ -754,17 +774,68 @@ class WP_User { * Set the site to operate on. Defaults to the current site. * * @since 3.0.0 + * @deprecated 4.9.0 Use WP_User::for_site() * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $blog_id Optional. Site ID, defaults to current site. */ public function for_blog( $blog_id = '' ) { + _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' ); + + $this->for_site( $blog_id ); + } + + /** + * Sets the site to operate on. Defaults to the current site. + * + * @since 4.9.0 + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param int $site_id Site ID to initialize user capabilities for. Default is the current site. + */ + public function for_site( $site_id = '' ) { global $wpdb; - if ( ! empty( $blog_id ) ) - $cap_key = $wpdb->get_blog_prefix( $blog_id ) . 'capabilities'; - else - $cap_key = ''; - $this->_init_caps( $cap_key ); + + if ( ! empty( $site_id ) ) { + $this->site_id = absint( $site_id ); + } else { + $this->site_id = get_current_blog_id(); + } + + $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities'; + + $this->caps = $this->get_caps_data(); + + $this->get_role_caps(); + } + + /** + * Gets the ID of the site for which the user's capabilities are currently initialized. + * + * @since 4.9.0 + * + * @return int Site ID. + */ + public function get_site_id() { + return $this->site_id; + } + + /** + * Gets the available user capabilities data. + * + * @since 4.9.0 + * + * @return array User capabilities array. + */ + private function get_caps_data() { + $caps = get_user_meta( $this->ID, $this->cap_key, true ); + + if ( ! is_array( $caps ) ) { + return array(); + } + + return $caps; } } diff --git a/src/wp-includes/ms-blogs.php b/src/wp-includes/ms-blogs.php index 34744c4eb1..721b8761ed 100644 --- a/src/wp-includes/ms-blogs.php +++ b/src/wp-includes/ms-blogs.php @@ -850,7 +850,7 @@ function switch_to_blog( $new_blog, $deprecated = null ) { if ( did_action( 'init' ) ) { $wp_roles = new WP_Roles(); $current_user = wp_get_current_user(); - $current_user->for_blog( $new_blog ); + $current_user->for_site( $new_blog ); } /** This filter is documented in wp-includes/ms-blogs.php */ @@ -924,7 +924,7 @@ function restore_current_blog() { if ( did_action( 'init' ) ) { $wp_roles = new WP_Roles(); $current_user = wp_get_current_user(); - $current_user->for_blog( $blog ); + $current_user->for_site( $blog ); } /** This filter is documented in wp-includes/ms-blogs.php */ diff --git a/tests/phpunit/tests/user.php b/tests/phpunit/tests/user.php index 3f5b99eccd..3e7cace00c 100644 --- a/tests/phpunit/tests/user.php +++ b/tests/phpunit/tests/user.php @@ -180,7 +180,7 @@ class Tests_User extends WP_UnitTestCase { $this->assertEquals( 'foo', $user->$key ); $this->assertEquals( 'foo', $user->data->$key ); // This will fail with WP < 3.3 - foreach ( (array) $user as $key => $value ) { + foreach ( get_object_vars( $user ) as $key => $value ) { $this->assertEquals( $value, $user->$key ); } } diff --git a/tests/phpunit/tests/user/capabilities.php b/tests/phpunit/tests/user/capabilities.php index 887b3372e7..0e2d8e4925 100644 --- a/tests/phpunit/tests/user/capabilities.php +++ b/tests/phpunit/tests/user/capabilities.php @@ -1848,4 +1848,95 @@ class Tests_User_Capabilities extends WP_UnitTestCase { $this->assertFalse( user_can( self::$users['contributor']->ID, 'remove_user', self::$users['contributor']->ID ) ); $this->assertFalse( user_can( self::$users['subscriber']->ID, 'remove_user', self::$users['subscriber']->ID ) ); } + + /** + * @ticket 36961 + * @group ms-required + */ + function test_init_user_caps_for_different_site() { + global $wpdb; + + $site_id = self::factory()->blog->create( array( 'user_id' => self::$users['administrator']->ID ) ); + + switch_to_blog( $site_id ); + + $role_name = 'uploader'; + add_role( $role_name, 'Uploader', array( + 'read' => true, + 'upload_files' => true, + ) ); + add_user_to_blog( $site_id, self::$users['subscriber']->ID, $role_name ); + + restore_current_blog(); + + $user = new WP_User( self::$users['subscriber']->ID, '', $site_id ); + $this->assertTrue( $user->has_cap( 'upload_files' ) ); + } + + /** + * @ticket 36961 + * @group ms-required + */ + function test_init_user_caps_for_different_site_by_user_switch() { + global $wpdb; + + $user = new WP_User( self::$users['subscriber']->ID ); + + $site_id = self::factory()->blog->create( array( 'user_id' => self::$users['administrator']->ID ) ); + + switch_to_blog( $site_id ); + + $role_name = 'uploader'; + add_role( $role_name, 'Uploader', array( + 'read' => true, + 'upload_files' => true, + ) ); + add_user_to_blog( $site_id, self::$users['subscriber']->ID, $role_name ); + + restore_current_blog(); + + $user->for_site( $site_id ); + $this->assertTrue( $user->has_cap( 'upload_files' ) ); + } + + /** + * @ticket 36961 + */ + function test_get_caps_data() { + global $wpdb; + + $custom_caps = array( + 'do_foo' => true, + 'do_bar' => false, + ); + + // Test `WP_User::get_caps_data()` by manually setting capabilities metadata. + update_user_meta( self::$users['subscriber']->ID, $wpdb->get_blog_prefix( get_current_blog_id() ) . 'capabilities', $custom_caps ); + + $user = new WP_User( self::$users['subscriber']->ID ); + $this->assertSame( $custom_caps, $user->caps ); + } + + /** + * @ticket 36961 + */ + function test_user_get_site_id_default() { + $user = new WP_User( self::$users['subscriber']->ID ); + $this->assertSame( get_current_blog_id(), $user->get_site_id() ); + } + + /** + * @ticket 36961 + */ + function test_user_get_site_id() { + global $wpdb; + + // Suppressing errors here allows to get around creating an actual site, + // which is unnecessary for this test. + $suppress = $wpdb->suppress_errors(); + $user = new WP_User( self::$users['subscriber']->ID, '', 333 ); + $wpdb->suppress_errors( $suppress ); + + $this->assertSame( 333, $user->get_site_id() ); + } }