PluginProbe
Gutenberg / 23.2.2
Gutenberg v23.2.2
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / experimental / guidelines / class-gutenberg-content-guidelines-revisions-controller.php

class-gutenberg-content-guidelines-revisions-controller.php in Gutenberg 23.2.2, at lib/experimental/guidelines/class-gutenberg-content-guidelines-revisions-controller.php

330 lines 10.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Content Guidelines Revisions REST API Controller.
4 *
5 * Specialized revisions controller mounted under /wp/v2/content-guidelines.
6 * Inherits standard WordPress revision list/get behavior and adds
7 * guideline_categories to responses plus a restore endpoint that returns the
8 * parent post in the singleton response shape. The standard
9 * /wp/v2/guidelines/{id}/revisions route is served by the default
10 * WP_REST_Revisions_Controller.
11 *
12 * @package gutenberg
13 */
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 /**
20 * REST API controller for content guidelines revisions.
21 */
22 class Gutenberg_Content_Guidelines_Revisions_Controller extends WP_REST_Revisions_Controller {
23
24 /**
25 * The base of the parent controller's route.
26 *
27 * Re-declared (not inherited) because the parent class keeps its own copy
28 * private; the override is what lets register_routes() mount under
29 * /wp/v2/content-guidelines instead of the post type's standard rest_base.
30 *
31 * @var string
32 */
33 protected $parent_base;
34
35 /**
36 * Parent post type.
37 *
38 * Re-declared for the same reason as $parent_base — used by
39 * restore_revision_permissions_check() in this class.
40 *
41 * @var string
42 */
43 protected $parent_post_type;
44
45 /**
46 * Constructor.
47 */
48 public function __construct() {
49 parent::__construct( Gutenberg_Guidelines_Post_Type::POST_TYPE );
50
51 $this->parent_post_type = Gutenberg_Guidelines_Post_Type::POST_TYPE;
52 $this->parent_base = Gutenberg_Content_Guidelines_REST_Controller::REST_BASE;
53 }
54
55 /**
56 * Registers the routes for guideline revisions.
57 *
58 * Mirrors the route shape of WP_REST_Revisions_Controller::register_routes()
59 * but uses this controller's $parent_base so the same class can be mounted
60 * under multiple parent bases (e.g. /content-guidelines and /guidelines).
61 * The parent's $parent_base is private, so calling parent::register_routes()
62 * would always register under the post type's rest_base regardless of any
63 * override done here.
64 */
65 public function register_routes() {
66 register_rest_route(
67 $this->namespace,
68 '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base,
69 array(
70 'args' => array(
71 'parent' => array(
72 'description' => __( 'The ID for the parent of the revision.', 'gutenberg' ),
73 'type' => 'integer',
74 ),
75 ),
76 array(
77 'methods' => WP_REST_Server::READABLE,
78 'callback' => array( $this, 'get_items' ),
79 'permission_callback' => array( $this, 'get_items_permissions_check' ),
80 'args' => $this->get_collection_params(),
81 ),
82 'schema' => array( $this, 'get_public_item_schema' ),
83 )
84 );
85
86 register_rest_route(
87 $this->namespace,
88 '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)',
89 array(
90 'args' => array(
91 'parent' => array(
92 'description' => __( 'The ID for the parent of the revision.', 'gutenberg' ),
93 'type' => 'integer',
94 ),
95 'id' => array(
96 'description' => __( 'Unique identifier for the revision.', 'gutenberg' ),
97 'type' => 'integer',
98 ),
99 ),
100 array(
101 'methods' => WP_REST_Server::READABLE,
102 'callback' => array( $this, 'get_item' ),
103 'permission_callback' => array( $this, 'get_item_permissions_check' ),
104 'args' => array(
105 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
106 ),
107 ),
108 array(
109 'methods' => WP_REST_Server::DELETABLE,
110 'callback' => array( $this, 'delete_item' ),
111 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
112 'args' => array(
113 'force' => array(
114 'type' => 'boolean',
115 'default' => false,
116 'description' => __( 'Required to be true, as revisions do not support trashing.', 'gutenberg' ),
117 ),
118 ),
119 ),
120 'schema' => array( $this, 'get_public_item_schema' ),
121 )
122 );
123
124 register_rest_route(
125 $this->namespace,
126 '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)/restore',
127 array(
128 array(
129 'methods' => WP_REST_Server::CREATABLE,
130 'callback' => array( $this, 'restore_revision' ),
131 'permission_callback' => array( $this, 'restore_revision_permissions_check' ),
132 'args' => array(
133 'parent' => array(
134 'description' => __( 'The ID for the parent of the revision.', 'gutenberg' ),
135 'type' => 'integer',
136 ),
137 'id' => array(
138 'description' => __( 'Unique identifier for the revision to restore.', 'gutenberg' ),
139 'type' => 'integer',
140 ),
141 ),
142 ),
143 )
144 );
145 }
146
147 /**
148 * Resolves a parent post ID to a content-typed guideline post.
149 *
150 * Restricts /wp/v2/content-guidelines/{parent}/revisions to parents tagged
151 * with the `content` term. Revisions of other guideline types are
152 * addressable only via the standard /wp/v2/guidelines collection.
153 *
154 * @param int $parent_post_id Supplied ID.
155 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
156 */
157 protected function get_parent( $parent_post_id ) {
158 $parent = parent::get_parent( $parent_post_id );
159 if ( is_wp_error( $parent ) ) {
160 return $parent;
161 }
162
163 if ( ! Gutenberg_Guidelines_Post_Type::is_content_guideline( $parent->ID ) ) {
164 return new WP_Error(
165 'rest_post_invalid_parent',
166 __( 'Invalid post parent ID.', 'gutenberg' ),
167 array( 'status' => 404 )
168 );
169 }
170
171 return $parent;
172 }
173
174 /**
175 * Prepares the revision for the REST response.
176 *
177 * Adds guideline_categories from revision meta to the standard revision response.
178 *
179 * @param WP_Post $item Post revision object.
180 * @param WP_REST_Request $request Request object.
181 * @return WP_REST_Response Response object.
182 */
183 public function prepare_item_for_response( $item, $request ) {
184 $response = parent::prepare_item_for_response( $item, $request );
185
186 if ( is_wp_error( $response ) ) {
187 return $response;
188 }
189
190 $fields = $this->get_fields_for_response( $request );
191
192 if ( rest_is_field_included( 'guideline_categories', $fields ) ) {
193 $data = $response->get_data();
194 $guideline_categories = Gutenberg_Guidelines_Post_Type::get_guideline_categories_from_meta( $item->ID );
195 $data['guideline_categories'] = ! empty( $guideline_categories ) ? $guideline_categories : new stdClass();
196 $response->set_data( $data );
197 }
198
199 // Add embeddable author link to get author name in revision history screen
200 if ( ! empty( $item->post_author ) ) {
201 $response->add_link(
202 'author',
203 rest_url( 'wp/v2/users/' . $item->post_author ),
204 array( 'embeddable' => true )
205 );
206 }
207
208 return $response;
209 }
210
211 /**
212 * Retrieves the revision's schema, conforming to JSON Schema.
213 *
214 * Adds guideline_categories to the standard revision schema.
215 *
216 * @return array Item schema data.
217 */
218 public function get_item_schema() {
219 $schema = parent::get_item_schema();
220
221 $schema['properties']['guideline_categories'] = array(
222 'description' => __( 'The guideline categories and their content.', 'gutenberg' ),
223 'type' => 'object',
224 'context' => array( 'view', 'edit' ),
225 'readonly' => true,
226 );
227
228 return $schema;
229 }
230
231 /**
232 * Restricts revision deletion to administrators.
233 *
234 * The inherited check only requires `delete_post` on the parent and the
235 * revision. The singleton route is admin-managed for every other write,
236 * so deleting revisions follows the same rule.
237 *
238 * @param WP_REST_Request $request Full details about the request.
239 * @return true|WP_Error True if the request has access, WP_Error object otherwise.
240 */
241 public function delete_item_permissions_check( $request ) {
242 $parent = $this->get_parent( $request['parent'] );
243 if ( is_wp_error( $parent ) ) {
244 return $parent;
245 }
246
247 if ( ! current_user_can( 'manage_options' ) ) {
248 return new WP_Error(
249 'rest_cannot_delete',
250 __( 'Sorry, you are not allowed to delete revisions.', 'gutenberg' ),
251 array( 'status' => rest_authorization_required_code() )
252 );
253 }
254
255 return parent::delete_item_permissions_check( $request );
256 }
257
258 /**
259 * Checks if a given request has access to restore a revision.
260 *
261 * @param WP_REST_Request $request Full details about the request.
262 * @return true|WP_Error True if the request has access, WP_Error object otherwise.
263 */
264 public function restore_revision_permissions_check( $request ) {
265 $parent = $this->get_parent( $request['parent'] );
266 if ( is_wp_error( $parent ) ) {
267 return $parent;
268 }
269
270 if ( ! current_user_can( 'manage_options' ) ) {
271 return new WP_Error(
272 'rest_cannot_restore',
273 __( 'Sorry, you are not allowed to restore revisions.', 'gutenberg' ),
274 array( 'status' => rest_authorization_required_code() )
275 );
276 }
277
278 return true;
279 }
280
281 /**
282 * Restores a revision to the main guidelines post.
283 *
284 * Uses WordPress's native wp_restore_post_revision() which restores all
285 * revision fields, sets _edit_last meta, fires hooks, and creates a new
286 * revision for audit trail.
287 *
288 * @param WP_REST_Request $request Full details about the request.
289 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error on failure.
290 */
291 public function restore_revision( $request ) {
292 $parent = $this->get_parent( $request['parent'] );
293 if ( is_wp_error( $parent ) ) {
294 return $parent;
295 }
296
297 $revision = get_post( $request['id'] );
298 if ( ! $revision || 'revision' !== $revision->post_type || (int) $revision->post_parent !== (int) $parent->ID ) {
299 return new WP_Error(
300 'rest_revision_not_found',
301 __( 'Revision not found.', 'gutenberg' ),
302 array( 'status' => 404 )
303 );
304 }
305
306 $result = wp_restore_post_revision( $revision->ID );
307
308 if ( is_wp_error( $result ) ) {
309 return $result;
310 }
311
312 if ( ! $result ) {
313 return new WP_Error(
314 'rest_cannot_restore',
315 __( 'Could not restore revision.', 'gutenberg' ),
316 array( 'status' => 500 )
317 );
318 }
319
320 // Shape the restore response with the singleton controller so callers
321 // get the same payload as a regular GET /wp/v2/content-guidelines/{id}.
322 // The post type's registered REST controller is the standard
323 // WP_REST_Posts_Controller, which would return a different shape.
324 $post = get_post( $parent->ID );
325 $singleton_controller = new Gutenberg_Content_Guidelines_REST_Controller();
326
327 return $singleton_controller->prepare_item_for_response( $post, $request );
328 }
329 }
330