<!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>[52342] trunk: REST API: Improve permission handling in global style endpoint.</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 { white-space: pre-line; 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/52342">52342</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/52342","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>spacedmonkey</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2021-12-07 20:56:18 +0000 (Tue, 07 Dec 2021)</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'>REST API: Improve permission handling in global style endpoint. 

The new wp_global_styles post type is registered to use edit_theme_options in the capability settings. The WP_REST_Global_Styles_Controller class's permission checks methods use the capability in a hard coded form rather than looking up the capability via the post type object. Changing the permission callbacks to lookup capabilities via the post type object, allows theme and plugin developers to modify the capability used for editing global styles via a filter and these values to be respected via the Global Styles REST API.

Props Spacedmonkey, peterwilsoncc, hellofromTonya , antonvlasenko, TimothyBlynJacobs, costdev, zieladam.
Fixes <a href="https://core.trac.wordpress.org/ticket/54516">#54516</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesrestapiendpointsclasswprestglobalstylescontrollerphp">trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestglobalstylescontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-global-styles-controller.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesrestapiendpointsclasswprestglobalstylescontrollerphp"></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/rest-api/endpoints/class-wp-rest-global-styles-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php       2021-12-07 20:05:27 UTC (rev 52341)
+++ trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php 2021-12-07 20:56:18 UTC (rev 52342)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -11,7 +11,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Base Global Styles REST API Controller.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
</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><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Post type.
+        *
+        * @since 5.9.0
+        * @var string
+        */
+       protected $post_type;
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Constructor.
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 5.9.0
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -18,6 +27,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function __construct() {
</span><span class="cx" style="display: block; padding: 0 10px">                $this->namespace = 'wp/v2';
</span><span class="cx" style="display: block; padding: 0 10px">                $this->rest_base = 'global-styles';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $this->post_type = 'wp_global_styles';
</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">@@ -75,36 +85,48 @@
</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">-         * Checks if the user has permissions to make the request.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Checks if a given request has access to read a single global style.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 5.9.0
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @param WP_REST_Request $request Full details about the request.
</ins><span class="cx" style="display: block; padding: 0 10px">          * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        protected function permissions_check() {
-               // Verify if the current user has edit_theme_options capability.
-               // This capability is required to edit/view/delete templates.
-               if ( ! current_user_can( 'edit_theme_options' ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function get_item_permissions_check( $request ) {
+               $post = $this->get_post( $request['id'] );
+               if ( is_wp_error( $post ) ) {
+                       return $post;
+               }
+
+               if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         return new WP_Error(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'rest_cannot_manage_global_styles',
-                               __( 'Sorry, you are not allowed to access the global styles on this site.' ),
-                               array(
-                                       'status' => rest_authorization_required_code(),
-                               )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'rest_forbidden_context',
+                               __( 'Sorry, you are not allowed to edit this global style.' ),
+                               array( 'status' => rest_authorization_required_code() )
</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><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( ! $this->check_read_permission( $post ) ) {
+                       return new WP_Error(
+                               'rest_cannot_view',
+                               __( 'Sorry, you are not allowed to view this global style.' ),
+                               array( 'status' => rest_authorization_required_code() )
+                       );
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 return true;
</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">-         * Checks if a given request has access to read a single global styles config.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Checks if a global style can be read.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param WP_REST_Request $request Full details about the request.
-        * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @since 5.9.0
+        *
+        * @param WP_Post $post Post object.
+        * @return bool Whether the post can be read.
</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 get_item_permissions_check( $request ) {
-               return $this->permissions_check( $request );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ protected function check_read_permission( $post ) {
+               return current_user_can( 'read_post', $post->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">@@ -117,9 +139,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return WP_REST_Response|WP_Error
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function get_item( $request ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $post = get_post( $request['id'] );
-               if ( ! $post || 'wp_global_styles' !== $post->post_type ) {
-                       return new WP_Error( 'rest_global_styles_not_found', __( 'No global styles config exist with that id.' ), array( 'status' => 404 ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $post = $this->get_post( $request['id'] );
+               if ( is_wp_error( $post ) ) {
+                       return $post;
</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">                return $this->prepare_item_for_response( $post, $request );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -134,10 +156,35 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function update_item_permissions_check( $request ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return $this->permissions_check( $request );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $post = $this->get_post( $request['id'] );
+               if ( is_wp_error( $post ) ) {
+                       return $post;
+               }
+
+               if ( $post && ! $this->check_update_permission( $post ) ) {
+                       return new WP_Error(
+                               'rest_cannot_edit',
+                               __( 'Sorry, you are not allowed to edit this global style.' ),
+                               array( 'status' => rest_authorization_required_code() )
+                       );
+               }
+
+               return true;
</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><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Checks if a global style can be edited.
+        *
+        * @since 5.9.0
+        *
+        * @param WP_Post $post Post object.
+        * @return bool Whether the post can be edited.
+        */
+       protected function check_update_permission( $post ) {
+               return current_user_can( 'edit_post', $post->ID );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Updates a single global style config.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 5.9.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -146,9 +193,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function update_item( $request ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $post_before = get_post( $request['id'] );
-               if ( ! $post_before || 'wp_global_styles' !== $post_before->post_type ) {
-                       return new WP_Error( 'rest_global_styles_not_found', __( 'No global styles config exist with that id.' ), array( 'status' => 404 ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $post_before = $this->get_post( $request['id'] );
+               if ( is_wp_error( $post_before ) ) {
+                       return $post_before;
</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">                $changes = $this->prepare_item_for_database( $request );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -289,7 +336,35 @@
</span><span class="cx" style="display: block; padding: 0 10px">                return $response;
</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">+        /**
+        * Get the post, if the ID is valid.
+        *
+        * @since 5.9.0
+        *
+        * @param int $id Supplied ID.
+        * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
+        */
+       protected function get_post( $id ) {
+               $error = new WP_Error(
+                       'rest_global_styles_not_found',
+                       __( 'No global styles config exist with that id.' ),
+                       array( 'status' => 404 )
+               );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $id = (int) $id;
+               if ( $id <= 0 ) {
+                       return $error;
+               }
+
+               $post = get_post( $id );
+               if ( empty( $post ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
+                       return $error;
+               }
+
+               return $post;
+       }
+
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Prepares links for the request.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -323,7 +398,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        protected function get_available_actions() {
</span><span class="cx" style="display: block; padding: 0 10px">                $rels = array();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $post_type = get_post_type_object( 'wp_global_styles' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $post_type = get_post_type_object( $this->post_type );
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( current_user_can( $post_type->cap->publish_posts ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $rels[] = 'https://api.w.org/action-publish';
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -371,7 +446,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $schema = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'title'      => 'wp_global_styles',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'title'      => $this->post_type,
</ins><span class="cx" style="display: block; padding: 0 10px">                         'type'       => 'object',
</span><span class="cx" style="display: block; padding: 0 10px">                        'properties' => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'id'       => array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -426,7 +501,19 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function get_theme_item_permissions_check( $request ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return $this->permissions_check( $request );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Verify if the current user has edit_theme_options capability.
+               // This capability is required to edit/view/delete templates.
+               if ( ! current_user_can( 'edit_theme_options' ) ) {
+                       return new WP_Error(
+                               'rest_cannot_manage_global_styles',
+                               __( 'Sorry, you are not allowed to access the global styles on this site.' ),
+                               array(
+                                       'status' => rest_authorization_required_code(),
+                               )
+                       );
+               }
+
+               return true;
</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></pre></div>
<a id="trunktestsphpunittestsrestapirestglobalstylescontrollerphp"></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/rest-api/rest-global-styles-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-global-styles-controller.php      2021-12-07 20:05:27 UTC (rev 52341)
+++ trunk/tests/phpunit/tests/rest-api/rest-global-styles-controller.php        2021-12-07 20:56:18 UTC (rev 52342)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -7,6 +7,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"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @covers WP_REST_Global_Styles_Controller
</ins><span class="cx" style="display: block; padding: 0 10px">  * @group restapi-global-styles
</span><span class="cx" style="display: block; padding: 0 10px">  * @group restapi
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -19,8 +20,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * @var int
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        protected static $subscriber_id;
+
+       /**
+        * @var int
+        */
</ins><span class="cx" style="display: block; padding: 0 10px">         protected static $global_styles_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">+        /**
+        * @var int
+        */
+       protected static $post_id;
+
</ins><span class="cx" style="display: block; padding: 0 10px">         private function find_and_normalize_global_styles_by_id( $global_styles, $id ) {
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $global_styles as $style ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( $style['id'] === $id ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -48,8 +59,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'role' => 'administrator',
</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">+
+               self::$subscriber_id = $factory->user->create(
+                       array(
+                               'role' => 'subscriber',
+                       )
+               );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 // This creates the global styles for the current theme.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                self::$global_styles_id = wp_insert_post(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         self::$global_styles_id = $factory->post->create(
</ins><span class="cx" style="display: block; padding: 0 10px">                         array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }',
</span><span class="cx" style="display: block; padding: 0 10px">                                'post_status'  => 'publish',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -59,25 +77,147 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'tax_input'    => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        'wp_theme' => 'tt1-blocks',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        ),
-                       true
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 )
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               self::$post_id = $factory->post->create();
</ins><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">+        /**
+        *
+        */
+       public static function wpTearDownAfterClass() {
+               self::delete_user( self::$admin_id );
+               self::delete_user( self::$subscriber_id );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::register_routes
+        */
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_register_routes() {
</span><span class="cx" style="display: block; padding: 0 10px">                $routes = rest_get_server()->get_routes();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertArrayHasKey( '/wp/v2/global-styles/(?P<id>[\/\w-]+)', $routes );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $this->assertCount( 2, $routes['/wp/v2/global-styles/(?P<id>[\/\w-]+)'] );
+               $this->assertArrayHasKey( '/wp/v2/global-styles/themes/(?P<stylesheet>[^.\/]+(?:\/[^.\/]+)?)', $routes );
+               $this->assertCount( 1, $routes['/wp/v2/global-styles/themes/(?P<stylesheet>[^.\/]+(?:\/[^.\/]+)?)'] );
</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">        public function test_context_param() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // TODO: Implement test_context_param() method.
-               $this->markTestIncomplete();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->markTestSkipped( 'Controller does not implement context_param().' );
</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">        public function test_get_items() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->markTestIncomplete();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->markTestSkipped( 'Controller does not implement get_items().' );
</ins><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">+        /**
+        * @covers WP_REST_Global_Styles_Controller::get_theme_item
+        * @ticket 54516
+        */
+       public function test_get_theme_item_no_user() {
+               wp_set_current_user( 0 );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/tt1-blocks' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_global_styles', $response, 401 );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::get_theme_item
+        * @ticket 54516
+        */
+       public function test_get_theme_item_permission_check() {
+               wp_set_current_user( self::$subscriber_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/tt1-blocks' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_global_styles', $response, 403 );
+       }
+
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::get_theme_item
+        * @ticket 54516
+        */
+       public function test_get_theme_item_invalid() {
+               wp_set_current_user( self::$admin_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/invalid' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_theme_not_found', $response, 404 );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::get_theme_item
+        */
+       public function test_get_theme_item() {
+               wp_set_current_user( self::$admin_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/tt1-blocks' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               unset( $data['_links'] );
+
+               $this->assertArrayHasKey( 'settings', $data );
+               $this->assertArrayHasKey( 'styles', $data );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::get_item
+        * @ticket 54516
+        */
+       public function test_get_item_no_user() {
+               wp_set_current_user( 0 );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_view', $response, 401 );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::get_item
+        * @ticket 54516
+        */
+       public function test_get_item_invalid_post() {
+               wp_set_current_user( self::$admin_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$post_id );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_global_styles_not_found', $response, 404 );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::get_item
+        * @ticket 54516
+        */
+       public function test_get_item_permission_check() {
+               wp_set_current_user( self::$subscriber_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_view', $response, 403 );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::get_item
+        * @ticket 54516
+        */
+       public function test_get_item_no_user_edit() {
+               wp_set_current_user( 0 );
+               $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id );
+               $request->set_param( 'context', 'edit' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::get_item
+        * @ticket 54516
+        */
+       public function test_get_item_permission_check_edit() {
+               wp_set_current_user( self::$subscriber_id );
+               $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id );
+               $request->set_param( 'context', 'edit' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_forbidden_context', $response, 403 );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::get_item
+        */
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_get_item() {
</span><span class="cx" style="display: block; padding: 0 10px">                wp_set_current_user( self::$admin_id );
</span><span class="cx" style="display: block; padding: 0 10px">                $request  = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -100,9 +240,13 @@
</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">        public function test_create_item() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->markTestIncomplete();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->markTestSkipped( 'Controller does not implement create_item().' );
</ins><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">+        /**
+        * @covers WP_REST_Global_Styles_Controller::update_item
+        * @ticket 54516
+        */
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_update_item() {
</span><span class="cx" style="display: block; padding: 0 10px">                wp_set_current_user( self::$admin_id );
</span><span class="cx" style="display: block; padding: 0 10px">                $request = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -116,17 +260,61 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( 'My new global styles title', $data['title']['raw'] );
</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">+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::update_item
+        * @ticket 54516
+        */
+       public function test_update_item_no_user() {
+               wp_set_current_user( 0 );
+               $request  = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_edit', $response, 401 );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::update_item
+        * @ticket 54516
+        */
+       public function test_update_item_invalid_post() {
+               wp_set_current_user( self::$admin_id );
+               $request  = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$post_id );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_global_styles_not_found', $response, 404 );
+       }
+
+       /**
+        * @covers WP_REST_Global_Styles_Controller::update_item
+        * @ticket 54516
+        */
+       public function test_update_item_permission_check() {
+               wp_set_current_user( self::$subscriber_id );
+               $request  = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_delete_item() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->markTestIncomplete();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->markTestSkipped( 'Controller does not implement delete_item().' );
</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">        public function test_prepare_item() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // TODO: Implement test_prepare_item() method.
-               $this->markTestIncomplete();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->markTestSkipped( 'Controller does not implement prepare_item().' );
</ins><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">+        /**
+        * @covers WP_REST_Global_Styles_Controller::get_item_schema
+        * @ticket 54516
+        */
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_get_item_schema() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // TODO: Implement test_get_item_schema() method.
-               $this->markTestIncomplete();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $request    = new WP_REST_Request( 'OPTIONS', '/wp/v2/global-styles/' . self::$global_styles_id );
+               $response   = rest_get_server()->dispatch( $request );
+               $data       = $response->get_data();
+               $properties = $data['schema']['properties'];
+               $this->assertCount( 4, $properties, 'Schema properties array does not have exactly 4 elements' );
+               $this->assertArrayHasKey( 'id', $properties, 'Schema properties array does not have "id" key' );
+               $this->assertArrayHasKey( 'styles', $properties, 'Schema properties array does not have "styles" key' );
+               $this->assertArrayHasKey( 'settings', $properties, 'Schema properties array does not have "settings" key' );
+               $this->assertArrayHasKey( 'title', $properties, 'Schema properties array does not have "title" key' );
</ins><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>