<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[41624] trunk: Multisite: Initialize a user's roles correctly when setting them up for a different site.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/41624">41624</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/41624","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>flixos90</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2017-09-27 21:09:11 +0000 (Wed, 27 Sep 2017)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>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 <a href="https://core.trac.wordpress.org/ticket/36961">#36961</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpuserphp">trunk/src/wp-includes/class-wp-user.php</a></li>
<li><a href="#trunksrcwpincludesmsblogsphp">trunk/src/wp-includes/ms-blogs.php</a></li>
<li><a href="#trunktestsphpunittestsusercapabilitiesphp">trunk/tests/phpunit/tests/user/capabilities.php</a></li>
<li><a href="#trunktestsphpunittestsuserphp">trunk/tests/phpunit/tests/user.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswpuserphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-user.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-user.php   2017-09-27 17:41:31 UTC (rev 41623)
+++ trunk/src/wp-includes/class-wp-user.php     2017-09-27 21:09:11 UTC (rev 41624)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -93,6 +93,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public $filter = null;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * The site ID the capabilities of this user are initialized for.
+        *
+        * @since 4.9.0
+        * @var int
+        */
+       private $site_id = 0;
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * @static
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 3.3.0
</span><span class="cx" style="display: block; padding: 0 10px">         * @var array
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -110,9 +118,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param int|string|stdClass|WP_User $id User's ID, a WP_User object, or a user object from the DB.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string $name Optional. User's username
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param int $blog_id Optional Site ID, defaults to current site.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param int $site_id Optional Site ID, defaults to current site.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public function __construct( $id = 0, $name = '', $blog_id = '' ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function __construct( $id = 0, $name = '', $site_id = '' ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( ! isset( self::$back_compat_keys ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $prefix = $GLOBALS['wpdb']->prefix;
</span><span class="cx" style="display: block; padding: 0 10px">                        self::$back_compat_keys = array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -126,10 +134,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $id instanceof WP_User ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $this->init( $id->data, $blog_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $this->init( $id->data, $site_id );
</ins><span class="cx" style="display: block; padding: 0 10px">                         return;
</span><span class="cx" style="display: block; padding: 0 10px">                } elseif ( is_object( $id ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $this->init( $id, $blog_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $this->init( $id, $site_id );
</ins><span class="cx" style="display: block; padding: 0 10px">                         return;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -145,7 +153,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $data ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $this->init( $data, $blog_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $this->init( $data, $site_id );
</ins><span class="cx" style="display: block; padding: 0 10px">                 } else {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->data = new stdClass;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -157,13 +165,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @since  3.3.0
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param object $data    User DB row object.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param int    $blog_id Optional. The site ID to initialize for.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param int    $site_id Optional. The site ID to initialize for.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public function init( $data, $blog_id = '' ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function init( $data, $site_id = '' ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->data = $data;
</span><span class="cx" style="display: block; padding: 0 10px">                $this->ID = (int) $data->ID;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->for_blog( $blog_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->for_site( $site_id );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -241,22 +249,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * 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;
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Magic method for checking the existence of a certain custom field.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 3.3.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -425,6 +417,22 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * 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;
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Set up capability object properties.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * Will set the value for the 'cap_key' property to current database table
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -433,6 +441,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * used.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 2.1.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @deprecated 4.9.0 Use WP_User::for_site()
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @global wpdb $wpdb WordPress database abstraction object.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -441,16 +450,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">        protected function _init_caps( $cap_key = '' ) {
</span><span class="cx" style="display: block; padding: 0 10px">                global $wpdb;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( empty($cap_key) )
-                       $this->cap_key = $wpdb->get_blog_prefix() . 'capabilities';
-               else
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         _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 {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $this->cap_key = $cap_key;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->caps = get_user_meta( $this->ID, $this->cap_key, true );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->caps = $this->get_caps_data();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! is_array( $this->caps ) )
-                       $this->caps = array();
-
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->get_role_caps();
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -467,6 +476,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array List of all capabilities for the user.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function get_role_caps() {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $switch_site = false;
+               if ( is_multisite() && $this->site_id != get_current_blog_id() ) {
+                       $switch_site = true;
+
+                       switch_to_blog( $this->site_id );
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $wp_roles = wp_roles();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                //Filter out caps that are not role names and assign to $this->roles
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -481,6 +497,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">                $this->allcaps = array_merge( (array) $this->allcaps, (array) $this->caps );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( $switch_site ) {
+                       restore_current_blog();
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 return $this->allcaps;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -754,17 +774,68 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Set the site to operate on. Defaults to the current site.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 3.0.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @deprecated 4.9.0 Use WP_User::for_site()
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @global wpdb $wpdb WordPress database abstraction object.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param int $blog_id Optional. Site ID, defaults to current site.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function for_blog( $blog_id = '' ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                _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 = '' ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 global $wpdb;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! empty( $blog_id ) )
-                       $cap_key = $wpdb->get_blog_prefix( $blog_id ) . 'capabilities';
-               else
-                       $cap_key = '';
-               $this->_init_caps( $cap_key );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               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();
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * 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;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcwpincludesmsblogsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/ms-blogs.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/ms-blogs.php        2017-09-27 17:41:31 UTC (rev 41623)
+++ trunk/src/wp-includes/ms-blogs.php  2017-09-27 21:09:11 UTC (rev 41624)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -850,7 +850,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        if ( did_action( 'init' ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $wp_roles = new WP_Roles();
</span><span class="cx" style="display: block; padding: 0 10px">                $current_user = wp_get_current_user();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $current_user->for_blog( $new_blog );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $current_user->for_site( $new_blog );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /** This filter is documented in wp-includes/ms-blogs.php */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -924,7 +924,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        if ( did_action( 'init' ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $wp_roles = new WP_Roles();
</span><span class="cx" style="display: block; padding: 0 10px">                $current_user = wp_get_current_user();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $current_user->for_blog( $blog );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $current_user->for_site( $blog );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /** This filter is documented in wp-includes/ms-blogs.php */
</span></span></pre></div>
<a id="trunktestsphpunittestsusercapabilitiesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/user/capabilities.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/user/capabilities.php   2017-09-27 17:41:31 UTC (rev 41623)
+++ trunk/tests/phpunit/tests/user/capabilities.php     2017-09-27 21:09:11 UTC (rev 41624)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1848,4 +1848,95 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertFalse( user_can( self::$users['contributor']->ID,   'remove_user', self::$users['contributor']->ID ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertFalse( user_can( self::$users['subscriber']->ID,    'remove_user', self::$users['subscriber']->ID ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @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() );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsuserphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/user.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/user.php        2017-09-27 17:41:31 UTC (rev 41623)
+++ trunk/tests/phpunit/tests/user.php  2017-09-27 21:09:11 UTC (rev 41624)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -180,7 +180,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( 'foo', $user->$key );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( 'foo', $user->data->$key );  // This will fail with WP < 3.3
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                foreach ( (array) $user as $key => $value ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         foreach ( get_object_vars( $user ) as $key => $value ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $this->assertEquals( $value, $user->$key );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre>
</div>
</div>

</body>
</html>