3 * REST API: WP_REST_Revisions_Controller class
11 * Core class used to access revisions via the REST API.
15 * @see WP_REST_Controller
17 class WP_REST_Revisions_Controller extends WP_REST_Controller {
26 private $parent_post_type;
33 * @var WP_REST_Controller
35 private $parent_controller;
38 * The base of the parent controller's route.
52 * @param string $parent_post_type Post type of the parent.
54 public function __construct( $parent_post_type ) {
55 $this->parent_post_type = $parent_post_type;
56 $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
57 $this->namespace = 'wp/v2';
58 $this->rest_base = 'revisions';
59 $post_type_object = get_post_type_object( $parent_post_type );
60 $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
64 * Registers routes for revisions based on post types supporting revisions.
69 * @see register_rest_route()
71 public function register_routes() {
73 register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array(
75 'methods' => WP_REST_Server::READABLE,
76 'callback' => array( $this, 'get_items' ),
77 'permission_callback' => array( $this, 'get_items_permissions_check' ),
78 'args' => $this->get_collection_params(),
80 'schema' => array( $this, 'get_public_item_schema' ),
83 register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array(
85 'methods' => WP_REST_Server::READABLE,
86 'callback' => array( $this, 'get_item' ),
87 'permission_callback' => array( $this, 'get_item_permissions_check' ),
89 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
93 'methods' => WP_REST_Server::DELETABLE,
94 'callback' => array( $this, 'delete_item' ),
95 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
100 'description' => __( 'Required to be true, as revisions do not support trashing.' ),
104 'schema' => array( $this, 'get_public_item_schema' ),
110 * Checks if a given request has access to get revisions.
115 * @param WP_REST_Request $request Full data about the request.
116 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
118 public function get_items_permissions_check( $request ) {
120 $parent = get_post( $request['parent'] );
124 $parent_post_type_obj = get_post_type_object( $parent->post_type );
125 if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) {
126 return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) );
133 * Gets a collection of revisions.
138 * @param WP_REST_Request $request Full data about the request.
139 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
141 public function get_items( $request ) {
142 $parent = get_post( $request['parent'] );
143 if ( ! $request['parent'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
144 return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) );
147 $revisions = wp_get_post_revisions( $request['parent'] );
150 foreach ( $revisions as $revision ) {
151 $data = $this->prepare_item_for_response( $revision, $request );
152 $response[] = $this->prepare_response_for_collection( $data );
154 return rest_ensure_response( $response );
158 * Checks if a given request has access to get a specific revision.
163 * @param WP_REST_Request $request Full data about the request.
164 * @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise.
166 public function get_item_permissions_check( $request ) {
167 return $this->get_items_permissions_check( $request );
171 * Retrieves one revision from the collection.
176 * @param WP_REST_Request $request Full data about the request.
177 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
179 public function get_item( $request ) {
180 $parent = get_post( $request['parent'] );
181 if ( ! $request['parent'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
182 return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) );
185 $revision = get_post( $request['id'] );
186 if ( ! $revision || 'revision' !== $revision->post_type ) {
187 return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision ID.' ), array( 'status' => 404 ) );
190 $response = $this->prepare_item_for_response( $revision, $request );
191 return rest_ensure_response( $response );
195 * Checks if a given request has access to delete a revision.
200 * @param WP_REST_Request $request Full details about the request.
201 * @return bool|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
203 public function delete_item_permissions_check( $request ) {
205 $response = $this->get_items_permissions_check( $request );
206 if ( ! $response || is_wp_error( $response ) ) {
210 $post = get_post( $request['id'] );
212 return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision ID.' ), array( 'status' => 404 ) );
214 $post_type = get_post_type_object( 'revision' );
215 return current_user_can( $post_type->cap->delete_post, $post->ID );
219 * Deletes a single revision.
224 * @param WP_REST_Request $request Full details about the request.
225 * @return true|WP_Error True on success, or WP_Error object on failure.
227 public function delete_item( $request ) {
228 $force = isset( $request['force'] ) ? (bool) $request['force'] : false;
230 // We don't support trashing for revisions.
232 return new WP_Error( 'rest_trash_not_supported', __( 'Revisions do not support trashing. Set force=true to delete.' ), array( 'status' => 501 ) );
235 $revision = get_post( $request['id'] );
236 $previous = $this->prepare_item_for_response( $revision, $request );
238 $result = wp_delete_post( $request['id'], true );
241 * Fires after a revision is deleted via the REST API.
245 * @param (mixed) $result The revision object (if it was deleted or moved to the trash successfully)
246 * or false (failure). If the revision was moved to to the trash, $result represents
247 * its new state; if it was deleted, $result represents its state before deletion.
248 * @param WP_REST_Request $request The request sent to the API.
250 do_action( 'rest_delete_revision', $result, $request );
253 return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
256 $response = new WP_REST_Response();
257 $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) );
262 * Prepares the revision for the REST response.
267 * @param WP_Post $post Post revision object.
268 * @param WP_REST_Request $request Request object.
269 * @return WP_REST_Response Response object.
271 public function prepare_item_for_response( $post, $request ) {
273 $schema = $this->get_item_schema();
277 if ( ! empty( $schema['properties']['author'] ) ) {
278 $data['author'] = $post->post_author;
281 if ( ! empty( $schema['properties']['date'] ) ) {
282 $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
285 if ( ! empty( $schema['properties']['date_gmt'] ) ) {
286 $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
289 if ( ! empty( $schema['properties']['id'] ) ) {
290 $data['id'] = $post->ID;
293 if ( ! empty( $schema['properties']['modified'] ) ) {
294 $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
297 if ( ! empty( $schema['properties']['modified_gmt'] ) ) {
298 $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
301 if ( ! empty( $schema['properties']['parent'] ) ) {
302 $data['parent'] = (int) $post->post_parent;
305 if ( ! empty( $schema['properties']['slug'] ) ) {
306 $data['slug'] = $post->post_name;
309 if ( ! empty( $schema['properties']['guid'] ) ) {
310 $data['guid'] = array(
311 /** This filter is documented in wp-includes/post-template.php */
312 'rendered' => apply_filters( 'get_the_guid', $post->guid ),
313 'raw' => $post->guid,
317 if ( ! empty( $schema['properties']['title'] ) ) {
318 $data['title'] = array(
319 'raw' => $post->post_title,
320 'rendered' => get_the_title( $post->ID ),
324 if ( ! empty( $schema['properties']['content'] ) ) {
326 $data['content'] = array(
327 'raw' => $post->post_content,
328 /** This filter is documented in wp-includes/post-template.php */
329 'rendered' => apply_filters( 'the_content', $post->post_content ),
333 if ( ! empty( $schema['properties']['excerpt'] ) ) {
334 $data['excerpt'] = array(
335 'raw' => $post->post_excerpt,
336 'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ),
340 $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
341 $data = $this->add_additional_fields_to_object( $data, $request );
342 $data = $this->filter_response_by_context( $data, $context );
343 $response = rest_ensure_response( $data );
345 if ( ! empty( $data['parent'] ) ) {
346 $response->add_link( 'parent', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) );
350 * Filters a revision returned from the API.
352 * Allows modification of the revision right before it is returned.
356 * @param WP_REST_Response $response The response object.
357 * @param WP_Post $post The original revision object.
358 * @param WP_REST_Request $request Request used to generate the response.
360 return apply_filters( 'rest_prepare_revision', $response, $post, $request );
364 * Checks the post_date_gmt or modified_gmt and prepare any post or
365 * modified date for single post output.
370 * @param string $date_gmt GMT publication time.
371 * @param string|null $date Optional. Local publication time. Default null.
372 * @return string|null ISO8601/RFC3339 formatted datetime, otherwise null.
374 protected function prepare_date_response( $date_gmt, $date = null ) {
375 if ( '0000-00-00 00:00:00' === $date_gmt ) {
379 if ( isset( $date ) ) {
380 return mysql_to_rfc3339( $date );
383 return mysql_to_rfc3339( $date_gmt );
387 * Retrieves the revision's schema, conforming to JSON Schema.
392 * @return array Item schema data.
394 public function get_item_schema() {
396 '$schema' => 'http://json-schema.org/schema#',
397 'title' => "{$this->parent_post_type}-revision",
399 // Base properties for every Revision.
400 'properties' => array(
402 'description' => __( 'The ID for the author of the object.' ),
404 'context' => array( 'view', 'edit', 'embed' ),
407 'description' => __( "The date the object was published, in the site's timezone." ),
409 'format' => 'date-time',
410 'context' => array( 'view', 'edit', 'embed' ),
413 'description' => __( 'The date the object was published, as GMT.' ),
415 'format' => 'date-time',
416 'context' => array( 'view', 'edit' ),
419 'description' => __( 'GUID for the object, as it exists in the database.' ),
421 'context' => array( 'view', 'edit' ),
424 'description' => __( 'Unique identifier for the object.' ),
426 'context' => array( 'view', 'edit', 'embed' ),
429 'description' => __( "The date the object was last modified, in the site's timezone." ),
431 'format' => 'date-time',
432 'context' => array( 'view', 'edit' ),
434 'modified_gmt' => array(
435 'description' => __( 'The date the object was last modified, as GMT.' ),
437 'format' => 'date-time',
438 'context' => array( 'view', 'edit' ),
441 'description' => __( 'The ID for the parent of the object.' ),
443 'context' => array( 'view', 'edit', 'embed' ),
446 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),
448 'context' => array( 'view', 'edit', 'embed' ),
453 $parent_schema = $this->parent_controller->get_item_schema();
455 if ( ! empty( $parent_schema['properties']['title'] ) ) {
456 $schema['properties']['title'] = $parent_schema['properties']['title'];
459 if ( ! empty( $parent_schema['properties']['content'] ) ) {
460 $schema['properties']['content'] = $parent_schema['properties']['content'];
463 if ( ! empty( $parent_schema['properties']['excerpt'] ) ) {
464 $schema['properties']['excerpt'] = $parent_schema['properties']['excerpt'];
467 if ( ! empty( $parent_schema['properties']['guid'] ) ) {
468 $schema['properties']['guid'] = $parent_schema['properties']['guid'];
471 return $this->add_additional_fields_schema( $schema );
475 * Retrieves the query params for collections.
480 * @return array Collection parameters.
482 public function get_collection_params() {
484 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
489 * Checks the post excerpt and prepare it for single post output.
494 * @param string $excerpt The post excerpt.
495 * @param WP_Post $post Post revision object.
496 * @return string Prepared excerpt or empty string.
498 protected function prepare_excerpt_response( $excerpt, $post ) {
500 /** This filter is documented in wp-includes/post-template.php */
501 $excerpt = apply_filters( 'the_excerpt', $excerpt, $post );
503 if ( empty( $excerpt ) ) {