11<?php
22/**
3- * Guidelines REST API Controller.
3+ * Content Guidelines REST API Controller.
44 *
5- * Extends WP_REST_Posts_Controller to inherit standard WordPress CRUD behavior,
6- * permission checks, and response formatting. Follows the pattern used by
7- * WP_REST_Global_Styles_Controller.
5+ * Specialized controller for the site-wide "content" guideline singleton.
6+ * Exposes a flat `/wp/v2/content-guidelines` endpoint that always reads,
7+ * creates, and updates a single post tagged with the `content` term in
8+ * the `wp_guideline_type` taxonomy. Other guideline posts (artifacts) are
9+ * served by the standard `/wp/v2/guidelines` collection.
810 *
911 * @package gutenberg
1012 */
1416}
1517
1618/**
17- * REST API controller for Guidelines .
19+ * REST API controller for the site-wide content guidelines singleton .
1820 */
19- class Gutenberg_Guidelines_REST_Controller extends WP_REST_Posts_Controller {
21+ class Gutenberg_Content_Guidelines_REST_Controller extends WP_REST_Posts_Controller {
2022
2123 /**
2224 * Maximum length for guideline text strings.
@@ -32,15 +34,50 @@ class Gutenberg_Guidelines_REST_Controller extends WP_REST_Posts_Controller {
3234 */
3335 const MAX_LABEL_LENGTH = 200 ;
3436
37+ /**
38+ * REST base for the singleton route.
39+ *
40+ * @var string
41+ */
42+ const REST_BASE = 'content-guidelines ' ;
43+
3544 /**
3645 * Constructor.
3746 */
3847 public function __construct () {
3948 parent ::__construct ( Gutenberg_Guidelines_Post_Type::POST_TYPE );
49+ $ this ->rest_base = self ::REST_BASE ;
4050 }
4151
4252 /**
43- * Registers the routes for guidelines.
53+ * Resolves a post ID to a content-typed guideline post.
54+ *
55+ * Restricts /wp/v2/content-guidelines/{id} to posts tagged with the
56+ * `content` term. Other guideline types are addressable only via the
57+ * standard /wp/v2/guidelines collection.
58+ *
59+ * @param int $id Post ID.
60+ * @return WP_Post|WP_Error Post object on success, WP_Error on failure.
61+ */
62+ protected function get_post ( $ id ) {
63+ $ post = parent ::get_post ( $ id );
64+ if ( is_wp_error ( $ post ) ) {
65+ return $ post ;
66+ }
67+
68+ if ( ! Gutenberg_Guidelines_Post_Type::is_content_guideline ( $ post ->ID ) ) {
69+ return new WP_Error (
70+ 'rest_post_invalid_id ' ,
71+ __ ( 'Invalid post ID. ' , 'gutenberg ' ),
72+ array ( 'status ' => 404 )
73+ );
74+ }
75+
76+ return $ post ;
77+ }
78+
79+ /**
80+ * Registers the routes for the content guidelines singleton.
4481 *
4582 * Calls parent to register standard /{id} CRUD routes, then overrides the
4683 * collection route with a singleton GET endpoint.
@@ -106,7 +143,7 @@ public function get_collection_params() {
106143 * @param WP_REST_Request $request Full details about the request.
107144 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
108145 */
109- public function get_guidelines_permissions_check ( $ request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
146+ public function get_guidelines_permissions_check ( WP_REST_Request $ request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
110147 $ post_type = get_post_type_object ( $ this ->post_type );
111148 if ( ! current_user_can ( $ post_type ->cap ->read ) ) {
112149 return new WP_Error (
@@ -119,6 +156,63 @@ public function get_guidelines_permissions_check( $request ) { // phpcs:ignore V
119156 return true ;
120157 }
121158
159+ /**
160+ * Restricts guideline creation to administrators.
161+ *
162+ * Defers to the parent controller for per-post checks (status validation,
163+ * sticky support, etc.) once the admin gate passes.
164+ *
165+ * @param WP_REST_Request $request Full details about the request.
166+ * @return true|WP_Error True if the request has access, WP_Error object otherwise.
167+ */
168+ public function create_item_permissions_check ( $ request ) {
169+ if ( ! current_user_can ( 'manage_options ' ) ) {
170+ return new WP_Error (
171+ 'rest_cannot_create ' ,
172+ __ ( 'Sorry, you are not allowed to create guidelines. ' , 'gutenberg ' ),
173+ array ( 'status ' => rest_authorization_required_code () )
174+ );
175+ }
176+
177+ return parent ::create_item_permissions_check ( $ request );
178+ }
179+
180+ /**
181+ * Restricts guideline updates to administrators.
182+ *
183+ * @param WP_REST_Request $request Full details about the request.
184+ * @return true|WP_Error True if the request has access, WP_Error object otherwise.
185+ */
186+ public function update_item_permissions_check ( $ request ) {
187+ if ( ! current_user_can ( 'manage_options ' ) ) {
188+ return new WP_Error (
189+ 'rest_cannot_edit ' ,
190+ __ ( 'Sorry, you are not allowed to edit guidelines. ' , 'gutenberg ' ),
191+ array ( 'status ' => rest_authorization_required_code () )
192+ );
193+ }
194+
195+ return parent ::update_item_permissions_check ( $ request );
196+ }
197+
198+ /**
199+ * Restricts guideline deletion to administrators.
200+ *
201+ * @param WP_REST_Request $request Full details about the request.
202+ * @return true|WP_Error True if the request has access, WP_Error object otherwise.
203+ */
204+ public function delete_item_permissions_check ( $ request ) {
205+ if ( ! current_user_can ( 'manage_options ' ) ) {
206+ return new WP_Error (
207+ 'rest_cannot_delete ' ,
208+ __ ( 'Sorry, you are not allowed to delete guidelines. ' , 'gutenberg ' ),
209+ array ( 'status ' => rest_authorization_required_code () )
210+ );
211+ }
212+
213+ return parent ::delete_item_permissions_check ( $ request );
214+ }
215+
122216 /**
123217 * Gets the singleton guidelines.
124218 *
@@ -130,7 +224,7 @@ public function get_guidelines_permissions_check( $request ) { // phpcs:ignore V
130224 * @param WP_REST_Request $request Full details about the request.
131225 * @return WP_REST_Response Response object.
132226 */
133- public function get_guidelines ( $ request ) {
227+ public function get_guidelines ( WP_REST_Request $ request ) {
134228 $ status_filter = $ request ->get_param ( 'status ' );
135229 $ post = $ this ->get_guidelines_post ( $ status_filter );
136230
@@ -149,9 +243,10 @@ public function get_guidelines( $request ) {
149243 }
150244
151245 /**
152- * Creates guidelines.
246+ * Creates the content guidelines singleton .
153247 *
154- * Enforces singleton pattern — only one guidelines post per site.
248+ * Enforces the singleton constraint — only one post tagged with the
249+ * `content` term may exist.
155250 *
156251 * @param WP_REST_Request $request Full details about the request.
157252 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error on failure.
@@ -208,7 +303,7 @@ public function create_item( $request ) {
208303 }
209304
210305 /**
211- * Updates guidelines.
306+ * Updates the content guidelines singleton .
212307 *
213308 * Saves guideline categories to meta before updating the post so that
214309 * the revision captures the updated meta values.
@@ -397,7 +492,7 @@ protected function prepare_links( $id ) {
397492 * @param int $post_id Post ID.
398493 * @param array $categories Sanitized guideline categories.
399494 */
400- protected function save_guideline_categories_to_meta ( $ post_id , $ categories ) {
495+ protected function save_guideline_categories_to_meta ( int $ post_id , array $ categories ): void {
401496 // Save standard categories.
402497 foreach ( Gutenberg_Guidelines_Post_Type::CATEGORY_META_KEYS as $ category ) {
403498 if ( isset ( $ categories [ $ category ] ) ) {
@@ -428,7 +523,7 @@ protected function save_guideline_categories_to_meta( $post_id, $categories ) {
428523 * @param mixed $categories Raw guideline categories from the request.
429524 * @return array Sanitized guideline categories.
430525 */
431- protected function sanitize_guideline_categories ( $ categories ) {
526+ protected function sanitize_guideline_categories ( $ categories ): array {
432527 if ( ! is_array ( $ categories ) ) {
433528 return array ();
434529 }
@@ -459,7 +554,7 @@ protected function sanitize_guideline_categories( $categories ) {
459554 * @param array $category Raw category data.
460555 * @return array Sanitized category data.
461556 */
462- private function sanitize_standard_category ( $ category ) {
557+ private function sanitize_standard_category ( array $ category ): array {
463558 $ sanitized = array_intersect_key ( $ category , array_flip ( array ( 'label ' , 'guidelines ' ) ) );
464559
465560 foreach ( $ sanitized as $ key => &$ value ) {
@@ -480,7 +575,7 @@ private function sanitize_standard_category( $category ) {
480575 * @param array $blocks Raw blocks category data.
481576 * @return array Sanitized blocks category data.
482577 */
483- private function sanitize_blocks_category ( $ blocks ) {
578+ private function sanitize_blocks_category ( array $ blocks ): array {
484579 $ sanitized = array ();
485580
486581 foreach ( $ blocks as $ block_name => $ block_data ) {
@@ -511,12 +606,12 @@ private function sanitize_blocks_category( $blocks ) {
511606 }
512607
513608 /**
514- * Gets the single guidelines post.
609+ * Gets the single content guidelines post.
515610 *
516611 * @param string|null $status_filter Optional. Filter by status ('publish' or 'draft').
517612 * @return WP_Post|null The guidelines post or null if not found.
518613 */
519- protected function get_guidelines_post ( $ status_filter = null ) {
614+ protected function get_guidelines_post ( ? string $ status_filter = null ): ? WP_Post {
520615 $ post_status = array ( 'publish ' , 'draft ' );
521616
522617 if ( $ status_filter ) {
@@ -556,7 +651,7 @@ public function get_item_schema() {
556651
557652 $ this ->schema = array (
558653 '$schema ' => 'http://json-schema.org/draft-04/schema# ' ,
559- 'title ' => 'guidelines ' ,
654+ 'title ' => 'content- guidelines ' ,
560655 'type ' => 'object ' ,
561656 'properties ' => array (
562657 'id ' => array (
0 commit comments