63 * |
63 * |
64 * @see register_rest_route() |
64 * @see register_rest_route() |
65 */ |
65 */ |
66 public function register_routes() { |
66 public function register_routes() { |
67 |
67 |
68 register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array( |
68 register_rest_route( |
69 'args' => array( |
69 $this->namespace, |
70 'parent' => array( |
70 '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, |
71 'description' => __( 'The ID for the parent of the object.' ), |
|
72 'type' => 'integer', |
|
73 ), |
|
74 ), |
|
75 array( |
71 array( |
76 'methods' => WP_REST_Server::READABLE, |
72 'args' => array( |
77 'callback' => array( $this, 'get_items' ), |
73 'parent' => array( |
78 'permission_callback' => array( $this, 'get_items_permissions_check' ), |
74 'description' => __( 'The ID for the parent of the object.' ), |
79 'args' => $this->get_collection_params(), |
75 'type' => 'integer', |
80 ), |
76 ), |
81 'schema' => array( $this, 'get_public_item_schema' ), |
77 ), |
82 ) ); |
78 array( |
83 |
79 'methods' => WP_REST_Server::READABLE, |
84 register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array( |
80 'callback' => array( $this, 'get_items' ), |
85 'args' => array( |
81 'permission_callback' => array( $this, 'get_items_permissions_check' ), |
86 'parent' => array( |
82 'args' => $this->get_collection_params(), |
87 'description' => __( 'The ID for the parent of the object.' ), |
83 ), |
88 'type' => 'integer', |
84 'schema' => array( $this, 'get_public_item_schema' ), |
89 ), |
85 ) |
90 'id' => array( |
86 ); |
91 'description' => __( 'Unique identifier for the object.' ), |
87 |
92 'type' => 'integer', |
88 register_rest_route( |
93 ), |
89 $this->namespace, |
94 ), |
90 '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', |
95 array( |
91 array( |
96 'methods' => WP_REST_Server::READABLE, |
92 'args' => array( |
97 'callback' => array( $this, 'get_item' ), |
93 'parent' => array( |
98 'permission_callback' => array( $this, 'get_item_permissions_check' ), |
94 'description' => __( 'The ID for the parent of the object.' ), |
99 'args' => array( |
95 'type' => 'integer', |
100 'context' => $this->get_context_param( array( 'default' => 'view' ) ), |
|
101 ), |
|
102 ), |
|
103 array( |
|
104 'methods' => WP_REST_Server::DELETABLE, |
|
105 'callback' => array( $this, 'delete_item' ), |
|
106 'permission_callback' => array( $this, 'delete_item_permissions_check' ), |
|
107 'args' => array( |
|
108 'force' => array( |
|
109 'type' => 'boolean', |
|
110 'default' => false, |
|
111 'description' => __( 'Required to be true, as revisions do not support trashing.' ), |
|
112 ), |
96 ), |
113 ), |
97 'id' => array( |
114 ), |
98 'description' => __( 'Unique identifier for the object.' ), |
115 'schema' => array( $this, 'get_public_item_schema' ), |
99 'type' => 'integer', |
116 )); |
100 ), |
|
101 ), |
|
102 array( |
|
103 'methods' => WP_REST_Server::READABLE, |
|
104 'callback' => array( $this, 'get_item' ), |
|
105 'permission_callback' => array( $this, 'get_item_permissions_check' ), |
|
106 'args' => array( |
|
107 'context' => $this->get_context_param( array( 'default' => 'view' ) ), |
|
108 ), |
|
109 ), |
|
110 array( |
|
111 'methods' => WP_REST_Server::DELETABLE, |
|
112 'callback' => array( $this, 'delete_item' ), |
|
113 'permission_callback' => array( $this, 'delete_item_permissions_check' ), |
|
114 'args' => array( |
|
115 'force' => array( |
|
116 'type' => 'boolean', |
|
117 'default' => false, |
|
118 'description' => __( 'Required to be true, as revisions do not support trashing.' ), |
|
119 ), |
|
120 ), |
|
121 ), |
|
122 'schema' => array( $this, 'get_public_item_schema' ), |
|
123 ) |
|
124 ); |
117 |
125 |
118 } |
126 } |
119 |
127 |
120 /** |
128 /** |
121 * Get the parent post, if the ID is valid. |
129 * Get the parent post, if the ID is valid. |
195 $parent = $this->get_parent( $request['parent'] ); |
203 $parent = $this->get_parent( $request['parent'] ); |
196 if ( is_wp_error( $parent ) ) { |
204 if ( is_wp_error( $parent ) ) { |
197 return $parent; |
205 return $parent; |
198 } |
206 } |
199 |
207 |
200 $revisions = wp_get_post_revisions( $request['parent'] ); |
208 // Ensure a search string is set in case the orderby is set to 'relevance'. |
|
209 if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) { |
|
210 return new WP_Error( 'rest_no_search_term_defined', __( 'You need to define a search term to order by relevance.' ), array( 'status' => 400 ) ); |
|
211 } |
|
212 |
|
213 // Ensure an include parameter is set in case the orderby is set to 'include'. |
|
214 if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) { |
|
215 return new WP_Error( 'rest_orderby_include_missing_include', __( 'You need to define an include parameter to order by include.' ), array( 'status' => 400 ) ); |
|
216 } |
|
217 |
|
218 if ( wp_revisions_enabled( $parent ) ) { |
|
219 $registered = $this->get_collection_params(); |
|
220 $args = array( |
|
221 'post_parent' => $parent->ID, |
|
222 'post_type' => 'revision', |
|
223 'post_status' => 'inherit', |
|
224 'posts_per_page' => -1, |
|
225 'orderby' => 'date ID', |
|
226 'order' => 'DESC', |
|
227 'suppress_filters' => true, |
|
228 ); |
|
229 |
|
230 $parameter_mappings = array( |
|
231 'exclude' => 'post__not_in', |
|
232 'include' => 'post__in', |
|
233 'offset' => 'offset', |
|
234 'order' => 'order', |
|
235 'orderby' => 'orderby', |
|
236 'page' => 'paged', |
|
237 'per_page' => 'posts_per_page', |
|
238 'search' => 's', |
|
239 ); |
|
240 |
|
241 foreach ( $parameter_mappings as $api_param => $wp_param ) { |
|
242 if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { |
|
243 $args[ $wp_param ] = $request[ $api_param ]; |
|
244 } |
|
245 } |
|
246 |
|
247 // For backward-compatibility, 'date' needs to resolve to 'date ID'. |
|
248 if ( isset( $args['orderby'] ) && 'date' === $args['orderby'] ) { |
|
249 $args['orderby'] = 'date ID'; |
|
250 } |
|
251 |
|
252 /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ |
|
253 $args = apply_filters( 'rest_revision_query', $args, $request ); |
|
254 $query_args = $this->prepare_items_query( $args, $request ); |
|
255 |
|
256 $revisions_query = new WP_Query(); |
|
257 $revisions = $revisions_query->query( $query_args ); |
|
258 $offset = isset( $query_args['offset'] ) ? (int) $query_args['offset'] : 0; |
|
259 $page = (int) $query_args['paged']; |
|
260 $total_revisions = $revisions_query->found_posts; |
|
261 |
|
262 if ( $total_revisions < 1 ) { |
|
263 // Out-of-bounds, run the query again without LIMIT for total count. |
|
264 unset( $query_args['paged'], $query_args['offset'] ); |
|
265 |
|
266 $count_query = new WP_Query(); |
|
267 $count_query->query( $query_args ); |
|
268 |
|
269 $total_revisions = $count_query->found_posts; |
|
270 } |
|
271 |
|
272 if ( $revisions_query->query_vars['posts_per_page'] > 0 ) { |
|
273 $max_pages = ceil( $total_revisions / (int) $revisions_query->query_vars['posts_per_page'] ); |
|
274 } else { |
|
275 $max_pages = $total_revisions > 0 ? 1 : 0; |
|
276 } |
|
277 |
|
278 if ( $total_revisions > 0 ) { |
|
279 if ( $offset >= $total_revisions ) { |
|
280 return new WP_Error( 'rest_revision_invalid_offset_number', __( 'The offset number requested is larger than or equal to the number of available revisions.' ), array( 'status' => 400 ) ); |
|
281 } elseif ( ! $offset && $page > $max_pages ) { |
|
282 return new WP_Error( 'rest_revision_invalid_page_number', __( 'The page number requested is larger than the number of pages available.' ), array( 'status' => 400 ) ); |
|
283 } |
|
284 } |
|
285 } else { |
|
286 $revisions = array(); |
|
287 $total_revisions = 0; |
|
288 $max_pages = 0; |
|
289 $page = (int) $request['page']; |
|
290 } |
201 |
291 |
202 $response = array(); |
292 $response = array(); |
203 foreach ( $revisions as $revision ) { |
293 foreach ( $revisions as $revision ) { |
204 $data = $this->prepare_item_for_response( $revision, $request ); |
294 $data = $this->prepare_item_for_response( $revision, $request ); |
205 $response[] = $this->prepare_response_for_collection( $data ); |
295 $response[] = $this->prepare_response_for_collection( $data ); |
206 } |
296 } |
207 return rest_ensure_response( $response ); |
297 |
|
298 $response = rest_ensure_response( $response ); |
|
299 |
|
300 $response->header( 'X-WP-Total', (int) $total_revisions ); |
|
301 $response->header( 'X-WP-TotalPages', (int) $max_pages ); |
|
302 |
|
303 $request_params = $request->get_query_params(); |
|
304 $base = add_query_arg( urlencode_deep( $request_params ), rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) ) ); |
|
305 |
|
306 if ( $page > 1 ) { |
|
307 $prev_page = $page - 1; |
|
308 |
|
309 if ( $prev_page > $max_pages ) { |
|
310 $prev_page = $max_pages; |
|
311 } |
|
312 |
|
313 $prev_link = add_query_arg( 'page', $prev_page, $base ); |
|
314 $response->link_header( 'prev', $prev_link ); |
|
315 } |
|
316 if ( $max_pages > $page ) { |
|
317 $next_page = $page + 1; |
|
318 $next_link = add_query_arg( 'page', $next_page, $base ); |
|
319 |
|
320 $response->link_header( 'next', $next_link ); |
|
321 } |
|
322 |
|
323 return $response; |
208 } |
324 } |
209 |
325 |
210 /** |
326 /** |
211 * Checks if a given request has access to get a specific revision. |
327 * Checks if a given request has access to get a specific revision. |
212 * |
328 * |
311 if ( ! $result ) { |
427 if ( ! $result ) { |
312 return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); |
428 return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); |
313 } |
429 } |
314 |
430 |
315 $response = new WP_REST_Response(); |
431 $response = new WP_REST_Response(); |
316 $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) ); |
432 $response->set_data( |
|
433 array( |
|
434 'deleted' => true, |
|
435 'previous' => $previous->get_data(), |
|
436 ) |
|
437 ); |
317 return $response; |
438 return $response; |
|
439 } |
|
440 |
|
441 /** |
|
442 * Determines the allowed query_vars for a get_items() response and prepares |
|
443 * them for WP_Query. |
|
444 * |
|
445 * @since 5.0.0 |
|
446 * |
|
447 * @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array. |
|
448 * @param WP_REST_Request $request Optional. Full details about the request. |
|
449 * @return array Items query arguments. |
|
450 */ |
|
451 protected function prepare_items_query( $prepared_args = array(), $request = null ) { |
|
452 $query_args = array(); |
|
453 |
|
454 foreach ( $prepared_args as $key => $value ) { |
|
455 /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ |
|
456 $query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores |
|
457 } |
|
458 |
|
459 // Map to proper WP_Query orderby param. |
|
460 if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) { |
|
461 $orderby_mappings = array( |
|
462 'id' => 'ID', |
|
463 'include' => 'post__in', |
|
464 'slug' => 'post_name', |
|
465 'include_slugs' => 'post_name__in', |
|
466 ); |
|
467 |
|
468 if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) { |
|
469 $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ]; |
|
470 } |
|
471 } |
|
472 |
|
473 return $query_args; |
318 } |
474 } |
319 |
475 |
320 /** |
476 /** |
321 * Prepares the revision for the REST response. |
477 * Prepares the revision for the REST response. |
322 * |
478 * |
395 'raw' => $post->post_excerpt, |
551 'raw' => $post->post_excerpt, |
396 'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ), |
552 'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ), |
397 ); |
553 ); |
398 } |
554 } |
399 |
555 |
400 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; |
556 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; |
401 $data = $this->add_additional_fields_to_object( $data, $request ); |
557 $data = $this->add_additional_fields_to_object( $data, $request ); |
402 $data = $this->filter_response_by_context( $data, $context ); |
558 $data = $this->filter_response_by_context( $data, $context ); |
403 $response = rest_ensure_response( $data ); |
559 $response = rest_ensure_response( $data ); |
404 |
560 |
405 if ( ! empty( $data['parent'] ) ) { |
561 if ( ! empty( $data['parent'] ) ) { |
406 $response->add_link( 'parent', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) ); |
562 $response->add_link( 'parent', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) ); |
407 } |
563 } |
454 '$schema' => 'http://json-schema.org/draft-04/schema#', |
610 '$schema' => 'http://json-schema.org/draft-04/schema#', |
455 'title' => "{$this->parent_post_type}-revision", |
611 'title' => "{$this->parent_post_type}-revision", |
456 'type' => 'object', |
612 'type' => 'object', |
457 // Base properties for every Revision. |
613 // Base properties for every Revision. |
458 'properties' => array( |
614 'properties' => array( |
459 'author' => array( |
615 'author' => array( |
460 'description' => __( 'The ID for the author of the object.' ), |
616 'description' => __( 'The ID for the author of the object.' ), |
461 'type' => 'integer', |
617 'type' => 'integer', |
462 'context' => array( 'view', 'edit', 'embed' ), |
618 'context' => array( 'view', 'edit', 'embed' ), |
463 ), |
619 ), |
464 'date' => array( |
620 'date' => array( |
465 'description' => __( "The date the object was published, in the site's timezone." ), |
621 'description' => __( "The date the object was published, in the site's timezone." ), |
466 'type' => 'string', |
622 'type' => 'string', |
467 'format' => 'date-time', |
623 'format' => 'date-time', |
468 'context' => array( 'view', 'edit', 'embed' ), |
624 'context' => array( 'view', 'edit', 'embed' ), |
469 ), |
625 ), |
470 'date_gmt' => array( |
626 'date_gmt' => array( |
471 'description' => __( 'The date the object was published, as GMT.' ), |
627 'description' => __( 'The date the object was published, as GMT.' ), |
472 'type' => 'string', |
628 'type' => 'string', |
473 'format' => 'date-time', |
629 'format' => 'date-time', |
474 'context' => array( 'view', 'edit' ), |
630 'context' => array( 'view', 'edit' ), |
475 ), |
631 ), |
476 'guid' => array( |
632 'guid' => array( |
477 'description' => __( 'GUID for the object, as it exists in the database.' ), |
633 'description' => __( 'GUID for the object, as it exists in the database.' ), |
478 'type' => 'string', |
634 'type' => 'string', |
479 'context' => array( 'view', 'edit' ), |
635 'context' => array( 'view', 'edit' ), |
480 ), |
636 ), |
481 'id' => array( |
637 'id' => array( |
482 'description' => __( 'Unique identifier for the object.' ), |
638 'description' => __( 'Unique identifier for the object.' ), |
483 'type' => 'integer', |
639 'type' => 'integer', |
484 'context' => array( 'view', 'edit', 'embed' ), |
640 'context' => array( 'view', 'edit', 'embed' ), |
485 ), |
641 ), |
486 'modified' => array( |
642 'modified' => array( |
487 'description' => __( "The date the object was last modified, in the site's timezone." ), |
643 'description' => __( "The date the object was last modified, in the site's timezone." ), |
488 'type' => 'string', |
644 'type' => 'string', |
489 'format' => 'date-time', |
645 'format' => 'date-time', |
490 'context' => array( 'view', 'edit' ), |
646 'context' => array( 'view', 'edit' ), |
491 ), |
647 ), |
492 'modified_gmt' => array( |
648 'modified_gmt' => array( |
493 'description' => __( 'The date the object was last modified, as GMT.' ), |
649 'description' => __( 'The date the object was last modified, as GMT.' ), |
494 'type' => 'string', |
650 'type' => 'string', |
495 'format' => 'date-time', |
651 'format' => 'date-time', |
496 'context' => array( 'view', 'edit' ), |
652 'context' => array( 'view', 'edit' ), |
497 ), |
653 ), |
498 'parent' => array( |
654 'parent' => array( |
499 'description' => __( 'The ID for the parent of the object.' ), |
655 'description' => __( 'The ID for the parent of the object.' ), |
500 'type' => 'integer', |
656 'type' => 'integer', |
501 'context' => array( 'view', 'edit', 'embed' ), |
657 'context' => array( 'view', 'edit', 'embed' ), |
502 ), |
658 ), |
503 'slug' => array( |
659 'slug' => array( |
504 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ), |
660 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ), |
505 'type' => 'string', |
661 'type' => 'string', |
506 'context' => array( 'view', 'edit', 'embed' ), |
662 'context' => array( 'view', 'edit', 'embed' ), |
507 ), |
663 ), |
508 ), |
664 ), |
535 * @since 4.7.0 |
691 * @since 4.7.0 |
536 * |
692 * |
537 * @return array Collection parameters. |
693 * @return array Collection parameters. |
538 */ |
694 */ |
539 public function get_collection_params() { |
695 public function get_collection_params() { |
540 return array( |
696 $query_params = parent::get_collection_params(); |
541 'context' => $this->get_context_param( array( 'default' => 'view' ) ), |
697 |
542 ); |
698 $query_params['context']['default'] = 'view'; |
|
699 |
|
700 unset( $query_params['per_page']['default'] ); |
|
701 |
|
702 $query_params['exclude'] = array( |
|
703 'description' => __( 'Ensure result set excludes specific IDs.' ), |
|
704 'type' => 'array', |
|
705 'items' => array( |
|
706 'type' => 'integer', |
|
707 ), |
|
708 'default' => array(), |
|
709 ); |
|
710 |
|
711 $query_params['include'] = array( |
|
712 'description' => __( 'Limit result set to specific IDs.' ), |
|
713 'type' => 'array', |
|
714 'items' => array( |
|
715 'type' => 'integer', |
|
716 ), |
|
717 'default' => array(), |
|
718 ); |
|
719 |
|
720 $query_params['offset'] = array( |
|
721 'description' => __( 'Offset the result set by a specific number of items.' ), |
|
722 'type' => 'integer', |
|
723 ); |
|
724 |
|
725 $query_params['order'] = array( |
|
726 'description' => __( 'Order sort attribute ascending or descending.' ), |
|
727 'type' => 'string', |
|
728 'default' => 'desc', |
|
729 'enum' => array( 'asc', 'desc' ), |
|
730 ); |
|
731 |
|
732 $query_params['orderby'] = array( |
|
733 'description' => __( 'Sort collection by object attribute.' ), |
|
734 'type' => 'string', |
|
735 'default' => 'date', |
|
736 'enum' => array( |
|
737 'date', |
|
738 'id', |
|
739 'include', |
|
740 'relevance', |
|
741 'slug', |
|
742 'include_slugs', |
|
743 'title', |
|
744 ), |
|
745 ); |
|
746 |
|
747 return $query_params; |
543 } |
748 } |
544 |
749 |
545 /** |
750 /** |
546 * Checks the post excerpt and prepare it for single post output. |
751 * Checks the post excerpt and prepare it for single post output. |
547 * |
752 * |