PluginProbe
AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI / 6.0.3
AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI v6.0.3
6.0.3 6.0.2 6.0.1 6.0.0 5.8.6 5.8.5 5.8.4 5.8.3 5.8.2 5.8.1 5.8.0 5.7.9.2 5.7.9.1 5.7.8 5.7.9 5.7.6 5.7.7 5.7.5 5.7.4 5.7.3 5.7.2 5.7.1 trunk 5.6.0 5.6.1 All 34 releases
← All changes | libraries/ct/includes/class-ct-rest-controller.php +1090 -0 5.7.3 → 6.0.3 View file →
@@ -1,0 +1,1090 @@
1 +<?php
2 +/**
3 + * Rest Controller class
4 + *
5 + * Based on WP_REST_Posts_Controller class
6 + *
7 + * @author GamiPress <[email protected]>, Ruben Garcia <[email protected]>
8 + *
9 + * @since 1.0.0
10 + */
11 +// Exit if accessed directly
12 +defined( 'ABSPATH' ) || exit;
13 +
14 +/**
15 + * Core class to access posts via the REST API.
16 + *
17 + * @since 1.0.0
18 + *
19 + * @see WP_REST_Controller
20 + */
21 +class CT_REST_Controller extends WP_REST_Controller {
22 +
23 + /**
24 + * Table name.
25 + *
26 + * @since 1.0.0
27 + * @var string
28 + */
29 + protected $name;
30 +
31 + /**
32 + * Table Meta table object.
33 + *
34 + * @since 1.0.0
35 + * @access public
36 + * @var CT_Table $table
37 + */
38 + public $table;
39 +
40 + /**
41 + * Instance of a post meta fields object.
42 + *
43 + * @since 1.0.0
44 + * @var CT_REST_Meta_Fields
45 + */
46 + protected $meta;
47 +
48 + /**
49 + * Constructor.
50 + *
51 + * @since 1.0.0
52 + *
53 + * @param string $name Custom table name.
54 + */
55 + public function __construct( $name ) {
56 + $this->name = $name;
57 + $this->namespace = 'wp/v2';
58 + $this->table = ct_get_table_object( $name );
59 + $this->rest_base = ! empty( $this->table->rest_base ) ? $this->table->rest_base : $this->name;
60 +
61 + if( in_array( 'meta', $this->table->supports ) ) {
62 + $this->meta = new CT_REST_Meta_Fields( $name );
63 + }
64 + }
65 +
66 + /**
67 + * Registers the routes for the objects of the controller.
68 + *
69 + * @since 1.0.0
70 + *
71 + * @see register_rest_route()
72 + */
73 + public function register_routes() {
74 +
75 + register_rest_route( $this->namespace, '/' . $this->rest_base, array(
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 + array(
83 + 'methods' => WP_REST_Server::CREATABLE,
84 + 'callback' => array( $this, 'create_item' ),
85 + 'permission_callback' => array( $this, 'create_item_permissions_check' ),
86 + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
87 + ),
88 + 'schema' => array( $this, 'get_public_item_schema' ),
89 + ) );
90 +
91 + register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
92 + 'args' => array(
93 + 'id' => array(
94 + 'description' => __( 'Unique identifier for the object.' ),
95 + 'type' => 'integer',
96 + ),
97 + ),
98 + array(
99 + 'methods' => WP_REST_Server::READABLE,
100 + 'callback' => array( $this, 'get_item' ),
101 + 'permission_callback' => array( $this, 'get_item_permissions_check' ),
102 + 'args' => array(
103 + 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
104 + ),
105 + ),
106 + array(
107 + 'methods' => WP_REST_Server::EDITABLE,
108 + 'callback' => array( $this, 'update_item' ),
109 + 'permission_callback' => array( $this, 'update_item_permissions_check' ),
110 + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
111 + ),
112 + array(
113 + 'methods' => WP_REST_Server::DELETABLE,
114 + 'callback' => array( $this, 'delete_item' ),
115 + 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
116 + 'args' => array(
117 + // TODO: There is no support for trash functionality, so let's remove it temporally
118 + /*'force' => array(
119 + 'type' => 'boolean',
120 + 'default' => false,
121 + 'description' => __( 'Whether to bypass trash and force deletion.' ),
122 + ),*/
123 + ),
124 + ),
125 + 'schema' => array( $this, 'get_public_item_schema' ),
126 + ) );
127 + }
128 +
129 + /**
130 + * Checks if a given request has access to read posts.
131 + *
132 + * @since 1.0.0
133 + *
134 + * @param WP_REST_Request $request Full details about the request.
135 + * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
136 + */
137 + public function get_items_permissions_check( $request ) {
138 +
139 + if ( 'edit' === $request['context'] && ! current_user_can( $this->table->cap->edit_items ) ) {
140 + return new WP_Error( 'rest_forbidden_context', ct_get_table_label( $this->table->name, 'edit_items_not_allowed' ), array( 'status' => rest_authorization_required_code() ) );
141 + }
142 +
143 + return true;
144 + }
145 +
146 + /**
147 + * Retrieves a collection of posts.
148 + *
149 + * @since 1.0.0
150 + *
151 + * @param WP_REST_Request $request Full details about the request.
152 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
153 + */
154 + public function get_items( $request ) {
155 +
156 + // Ensure a search string is set in case the orderby is set to 'relevance'.
157 + if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) {
158 + return new WP_Error( 'rest_no_search_term_defined', __( 'You need to define a search term to order by relevance.' ), array( 'status' => 400 ) );
159 + }
160 +
161 + // Ensure an include parameter is set in case the orderby is set to 'include'.
162 + if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) {
163 + return new WP_Error( 'rest_orderby_include_missing_include', __( 'You need to define an include parameter to order by include.' ), array( 'status' => 400 ) );
164 + }
165 +
166 + // Retrieve the list of registered collection query parameters.
167 + $registered = $this->get_collection_params();
168 + $args = array();
169 +
170 + $ct_table = ct_setup_table( $this->name );
171 +
172 + /*
173 + * This array defines mappings between public API query parameters whose
174 + * values are accepted as-passed, and their internal CT_Query parameter
175 + * name equivalents (some are the same). Only values which are also
176 + * present in $registered will be set.
177 + */
178 + $parameter_mappings = array(
179 + // WP_REST_Controller fields
180 + 'page' => 'paged',
181 + 'search' => 's',
182 + // CT_REST_Controller fields
183 + 'offset' => 'offset',
184 + 'order' => 'order',
185 + 'orderby' => 'orderby',
186 + );
187 +
188 + /**
189 + * Filter parameters mappings for the rest controller.
190 + *
191 + * The dynamic part of the filter `$this->name` refers to the custom table name.
192 + *
193 + * @since 1.0.0
194 + *
195 + * @param array $parameter_mappings Array of parameters to map.
196 + * @param CT_Table $ct_table Table object.
197 + * @param WP_REST_Request $request The request given.
198 + */
199 + $parameter_mappings = apply_filters( "ct_rest_{$this->name}_parameter_mappings", $parameter_mappings, $ct_table, $request );
200 +
201 + /*
202 + * For each known parameter which is both registered and present in the request,
203 + * set the parameter's value on the query $args.
204 + */
205 + foreach ( $parameter_mappings as $api_param => $wp_param ) {
206 + if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
207 + $args[ $wp_param ] = $request[ $api_param ];
208 + }
209 + }
210 +
211 + // Ensure our per_page parameter overrides any provided items_per_page filter.
212 + if ( isset( $registered['per_page'] ) ) {
213 + $args['items_per_page'] = $request['per_page'];
214 + }
215 +
216 + /**
217 + * Filters the query arguments for a request.
218 + *
219 + * Enables adding extra arguments or setting defaults for a post collection request.
220 + *
221 + * @since 1.0.0
222 + *
223 + * @link https://developer.wordpress.org/reference/classes/wp_query/
224 + *
225 + * @param array $args Key value array of query var to query value.
226 + * @param WP_REST_Request $request The request used.
227 + */
228 + $args = apply_filters( "ct_rest_{$this->name}_query", $args, $request );
229 + $query_args = $this->prepare_items_query( $args, $request );
230 +
231 + $ct_query = new CT_Query();
232 + $query_result = $ct_query->query( $query_args );
233 +
234 + $items = array();
235 +
236 + foreach ( $query_result as $item ) {
237 + if ( ! $this->check_read_permission( $item ) ) {
238 + continue;
239 + }
240 +
241 + $data = $this->prepare_item_for_response( $item, $request );
242 + $items[] = $this->prepare_response_for_collection( $data );
243 + }
244 +
245 + $page = (int) $query_args['paged'];
246 + $total_items = $ct_query->found_results;
247 +
248 + if ( $total_items < 1 ) {
249 + // Out-of-bounds, run the query again without LIMIT for total count.
250 + unset( $query_args['paged'] );
251 +
252 + $count_query = new CT_Query();
253 + $count_query->query( $query_args );
254 + $total_items = $count_query->found_results;
255 + }
256 +
257 + $max_pages = ceil( $total_items / (int) $ct_query->query_vars['items_per_page'] );
258 +
259 + if ( $page > $max_pages && $total_items > 0 ) {
260 + ct_reset_setup_table();
261 +
262 + return new WP_Error( 'rest_item_invalid_page_number', __( 'The page number requested is larger than the number of pages available.' ), array( 'status' => 400 ) );
263 + }
264 +
265 + $response = rest_ensure_response( $items );
266 +
267 + $response->header( 'X-WP-Total', (int) $total_items );
268 + $response->header( 'X-WP-TotalPages', (int) $max_pages );
269 +
270 + $request_params = $request->get_query_params();
271 + $base = add_query_arg( $request_params, rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
272 +
273 + if ( $page > 1 ) {
274 + $prev_page = $page - 1;
275 +
276 + if ( $prev_page > $max_pages ) {
277 + $prev_page = $max_pages;
278 + }
279 +
280 + $prev_link = add_query_arg( 'page', $prev_page, $base );
281 + $response->link_header( 'prev', $prev_link );
282 + }
283 + if ( $max_pages > $page ) {
284 + $next_page = $page + 1;
285 + $next_link = add_query_arg( 'page', $next_page, $base );
286 +
287 + $response->link_header( 'next', $next_link );
288 + }
289 +
290 + ct_reset_setup_table();
291 +
292 + return $response;
293 + }
294 +
295 + /**
296 + * Get the object, if the ID is valid.
297 + *
298 + * @since 4.7.2
299 + *
300 + * @param int $id Supplied ID.
301 + * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
302 + */
303 + protected function get_object( $id ) {
304 + $error = new WP_Error( 'rest_item_invalid_id', __( 'Invalid item ID.' ), array( 'status' => 404 ) );
305 + if ( (int) $id <= 0 ) {
306 + return $error;
307 + }
308 +
309 + ct_setup_table( $this->name );
310 +
311 + $object = ct_get_object( (int) $id );
312 + $primary_key = $this->table->db->primary_key;
313 +
314 + ct_reset_setup_table();
315 +
316 + if ( empty( $object ) || empty( $object->$primary_key ) ) {
317 + return $error;
318 + }
319 +
320 + return $object;
321 + }
322 +
323 + /**
324 + * Checks if a given request has access to read a post.
325 + *
326 + * @since 1.0.0
327 + *
328 + * @param WP_REST_Request $request Full details about the request.
329 + * @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise.
330 + */
331 + public function get_item_permissions_check( $request ) {
332 + $object = $this->get_object( $request['id'] );
333 + if ( is_wp_error( $object ) ) {
334 + return $object;
335 + }
336 +
337 + if ( 'edit' === $request['context'] && $object && ! $this->check_update_permission( $object ) ) {
338 + return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this item.' ), array( 'status' => rest_authorization_required_code() ) );
339 + }
340 +
341 + if ( $object ) {
342 + return $this->check_read_permission( $object );
343 + }
344 +
345 + return true;
346 + }
347 +
348 + /**
349 + * Retrieves a single post.
350 + *
351 + * @since 1.0.0
352 + *
353 + * @param WP_REST_Request $request Full details about the request.
354 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
355 + */
356 + public function get_item( $request ) {
357 + $object = $this->get_object( $request['id'] );
358 + if ( is_wp_error( $object ) ) {
359 + return $object;
360 + }
361 +
362 + $data = $this->prepare_item_for_response( $object, $request );
363 + $response = rest_ensure_response( $data );
364 +
365 + return $response;
366 + }
367 +
368 + /**
369 + * Checks if a given request has access to create a post.
370 + *
371 + * @since 1.0.0
372 + *
373 + * @param WP_REST_Request $request Full details about the request.
374 + * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
375 + */
376 + public function create_item_permissions_check( $request ) {
377 + if ( ! empty( $request['id'] ) ) {
378 + return new WP_Error( 'rest_item_exists', __( 'Cannot create existing item.', 'ct' ), array( 'status' => 400 ) );
379 + }
380 +
381 + if ( ! current_user_can( $this->table->cap->create_items ) ) {
382 + return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create items as this user.', 'ct' ), array( 'status' => rest_authorization_required_code() ) );
383 + }
384 +
385 + return true;
386 + }
387 +
388 + /**
389 + * Creates a single post.
390 + *
391 + * @since 1.0.0
392 + *
393 + * @param WP_REST_Request $request Full details about the request.
394 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
395 + */
396 + public function create_item( $request ) {
397 + if ( ! empty( $request['id'] ) ) {
398 + return new WP_Error( 'rest_item_exists', __( 'Cannot create existing item.', 'ct' ), array( 'status' => 400 ) );
399 + }
400 +
401 + ct_setup_table( $this->name );
402 +
403 + $prepared_object = $this->prepare_item_for_database( $request );
404 +
405 + if ( is_wp_error( $prepared_object ) ) {
406 + return $prepared_object;
407 + }
408 +
409 + $object_id = ct_insert_object( wp_slash( (array) $prepared_object ), true );
410 +
411 + if ( is_wp_error( $object_id ) ) {
412 +
413 + if ( 'db_insert_error' === $object_id->get_error_code() ) {
414 + $object_id->add_data( array( 'status' => 500 ) );
415 + } else {
416 + $object_id->add_data( array( 'status' => 400 ) );
417 + }
418 +
419 + return $object_id;
420 + }
421 +
422 + $object = ct_get_object( $object_id );
423 +
424 + /**
425 + * Fires after a single object is created or updated via the REST API.
426 + *
427 + * The dynamic portion of the hook name, `$this->name`, refers to the post type slug.
428 + *
429 + * @since 1.0.0
430 + *
431 + * @param WP_Post $object Inserted or updated object.
432 + * @param WP_REST_Request $request Request object.
433 + * @param bool $creating True when creating a post, false when updating.
434 + */
435 + do_action( "ct_rest_insert_{$this->name}", $object, $request, true );
436 +
437 + $schema = $this->get_item_schema();
438 +
439 + if ( in_array( 'meta', $this->table->supports ) && ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
440 +
441 + $meta_update = $this->meta->update_value( $request['meta'], $object_id );
442 +
443 + if ( is_wp_error( $meta_update ) ) {
444 + return $meta_update;
445 + }
446 +
447 + }
448 +
449 + $object = ct_get_object( $object_id );
450 + $fields_update = $this->update_additional_fields_for_object( $object, $request );
451 +
452 + if ( is_wp_error( $fields_update ) ) {
453 + return $fields_update;
454 + }
455 +
456 + $request->set_param( 'context', 'edit' );
457 +
458 + /**
459 + * Fires after a single object is completely created or updated via the REST API.
460 + *
461 + * The dynamic portion of the hook name, `$this->name`, refers to the custom table name.
462 + *
463 + * @since 1.0.0
464 + *
465 + * @param WP_Post $object Inserted or updated object.
466 + * @param WP_REST_Request $request Request object.
467 + * @param bool $creating True when creating a post, false when updating.
468 + */
469 + do_action( "ct_rest_after_insert_{$this->name}", $object, $request, true );
470 +
471 + $response = $this->prepare_item_for_response( $object, $request );
472 + $response = rest_ensure_response( $response );
473 +
474 + $response->set_status( 201 );
475 + $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $object_id ) ) );
476 +
477 + ct_reset_setup_table();
478 +
479 + return $response;
480 + }
481 +
482 + /**
483 + * Checks if a given request has access to update a post.
484 + *
485 + * @since 1.0.0
486 + *
487 + * @param WP_REST_Request $request Full details about the request.
488 + * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
489 + */
490 + public function update_item_permissions_check( $request ) {
491 + $object = $this->get_object( $request['id'] );
492 + if ( is_wp_error( $object ) ) {
493 + return $object;
494 + }
495 +
496 + if ( $object && ! $this->check_update_permission( $object ) ) {
497 + return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this item.' ), array( 'status' => rest_authorization_required_code() ) );
498 + }
499 +
500 + return true;
501 + }
502 +
503 + /**
504 + * Updates a single post.
505 + *
506 + * @since 1.0.0
507 + *
508 + * @param WP_REST_Request $request Full details about the request.
509 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
510 + */
511 + public function update_item( $request ) {
512 + $valid_check = $this->get_object( $request['id'] );
513 + if ( is_wp_error( $valid_check ) ) {
514 + return $valid_check;
515 + }
516 +
517 + ct_setup_table( $this->name );
518 +
519 + $object = $this->prepare_item_for_database( $request );
520 +
521 + if ( is_wp_error( $object ) ) {
522 + return $object;
523 + }
524 +
525 + // Convert the object to an array, otherwise ct_update_object will expect non-escaped input.
526 + $object_id = ct_update_object( wp_slash( (array) $object ), true );
527 +
528 + if ( is_wp_error( $object_id ) ) {
529 + if ( 'db_update_error' === $object_id->get_error_code() ) {
530 + $object_id->add_data( array( 'status' => 500 ) );
531 + } else {
532 + $object_id->add_data( array( 'status' => 400 ) );
533 + }
534 + return $object_id;
535 + }
536 +
537 + $object = ct_get_object( $object_id );
538 +
539 + /**
540 + * Fires after a single object is created or updated via the REST API.
541 + *
542 + * The dynamic portion of the hook name, `$this->name`, refers to the post type slug.
543 + *
544 + * @since 1.0.0
545 + *
546 + * @param WP_Post $object Inserted or updated object.
547 + * @param WP_REST_Request $request Request object.
548 + * @param bool $creating True when creating a post, false when updating.
549 + */
550 + do_action( "ct_rest_insert_{$this->name}", $object, $request, false );
551 +
552 + $schema = $this->get_item_schema();
553 +
554 + if ( in_array( 'meta', $this->table->supports ) && ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
555 +
556 + $meta_update = $this->meta->update_value( $request['meta'], $object_id );
557 +
558 + if ( is_wp_error( $meta_update ) ) {
559 + return $meta_update;
560 + }
561 +
562 + }
563 +
564 + $object = ct_get_object( $object_id );
565 + $fields_update = $this->update_additional_fields_for_object( $object, $request );
566 +
567 + if ( is_wp_error( $fields_update ) ) {
568 + return $fields_update;
569 + }
570 +
571 + $request->set_param( 'context', 'edit' );
572 +
573 + /**
574 + * Fires after a single object is completely created or updated via the REST API.
575 + *
576 + * The dynamic portion of the hook name, `$this->name`, refers to the custom table name.
577 + *
578 + * @since 1.0.0
579 + *
580 + * @param WP_Post $object Inserted or updated object.
581 + * @param WP_REST_Request $request Request object.
582 + * @param bool $creating True when creating a post, false when updating.
583 + */
584 + do_action( "ct_rest_after_insert_{$this->name}", $object, $request, false );
585 +
586 + $response = $this->prepare_item_for_response( $object, $request );
587 +
588 + ct_reset_setup_table();
589 +
590 + return rest_ensure_response( $response );
591 + }
592 +
593 + /**
594 + * Checks if a given request has access to delete a post.
595 + *
596 + * @since 1.0.0
597 + *
598 + * @param WP_REST_Request $request Full details about the request.
599 + * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
600 + */
601 + public function delete_item_permissions_check( $request ) {
602 + $object = $this->get_object( $request['id'] );
603 + if ( is_wp_error( $object ) ) {
604 + return $object;
605 + }
606 +
607 + if ( $object && ! $this->check_delete_permission( $object ) ) {
608 + return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this item.' ), array( 'status' => rest_authorization_required_code() ) );
609 + }
610 +
611 + return true;
612 + }
613 +
614 + /**
615 + * Deletes a single item.
616 + *
617 + * @since 1.0.0
618 + *
619 + * @param WP_REST_Request $request Full details about the request.
620 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
621 + */
622 + public function delete_item( $request ) {
623 + $object = $this->get_object( $request['id'] );
624 + if ( is_wp_error( $object ) ) {
625 + return $object;
626 + }
627 +
628 + $id = $request['id'];
629 + $force = (bool) $request['force'];
630 +
631 + $supports_trash = ( EMPTY_TRASH_DAYS > 0 );
632 +
633 + /**
634 + * Filters whether a post is trashable.
635 + *
636 + * The dynamic portion of the hook name, `$this->name`, refers to the custom table name.
637 + *
638 + * Pass false to disable trash support for the post.
639 + *
640 + * @since 1.0.0
641 + *
642 + * @param bool $supports_trash Whether the post type support trashing.
643 + * @param WP_Post $post The Post object being considered for trashing support.
644 + */
645 + $supports_trash = apply_filters( "ct_rest_{$this->name}_trashable", $supports_trash, $object );
646 +
647 + if ( ! $this->check_delete_permission( $object ) ) {
648 + return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => rest_authorization_required_code() ) );
649 + }
650 +
651 + $request->set_param( 'context', 'edit' );
652 +
653 + // TODO: There is no support for trash functionality, so let's force deletion
654 + $force = true;
655 +
656 + // If we're forcing, then delete permanently.
657 + if ( $force ) {
658 + $previous = $this->prepare_item_for_response( $object, $request );
659 + $result = ct_delete_object( $id, true );
660 + $response = new WP_REST_Response();
661 + $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) );
662 + } else {
663 + // If we don't support trashing for this type, error out.
664 + if ( ! $supports_trash ) {
665 + /* translators: %s: force=true */
666 + return new WP_Error( 'rest_trash_not_supported', sprintf( __( "The post does not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) );
667 + }
668 +
669 + // Otherwise, only trash if we haven't already.
670 + //if ( 'trash' === $post->post_status ) {
671 + //return new WP_Error( 'rest_already_trashed', __( 'The post has already been deleted.' ), array( 'status' => 410 ) );
672 + //}
673 +
674 + // (Note that internally this falls through to `wp_delete_post` if the trash is disabled.)
675 + //$result = wp_trash_post( $id );
676 + $object = ct_get_object( $id );
677 + $response = $this->prepare_item_for_response( $object, $request );
678 + }
679 +
680 + if ( ! $result ) {
681 + return new WP_Error( 'rest_cannot_delete', __( 'The item cannot be deleted.', 'ct' ), array( 'status' => 500 ) );
682 + }
683 +
684 + /**
685 + * Fires immediately after a single post is deleted or trashed via the REST API.
686 + *
687 + * They dynamic portion of the hook name, `$this->name`, refers to the custom table name.
688 + *
689 + * @since 1.0.0
690 + *
691 + * @param object $post The deleted or trashed post.
692 + * @param WP_REST_Response $response The response data.
693 + * @param WP_REST_Request $request The request sent to the API.
694 + */
695 + do_action( "ct_rest_delete_{$this->name}", $object, $response, $request );
696 +
697 + return $response;
698 + }
699 +
700 + /**
701 + * Determines the allowed query_vars for a get_items() response and prepares
702 + * them for CT_Query.
703 + *
704 + * @since 1.0.0
705 + *
706 + * @param array $prepared_args Optional. Prepared CT_Query arguments. Default empty array.
707 + * @param WP_REST_Request $request Optional. Full details about the request.
708 + * @return array Items query arguments.
709 + */
710 + protected function prepare_items_query( $prepared_args = array(), $request = null ) {
711 +
712 + $ct_table = $this->table;
713 + $query_args = array();
714 +
715 + foreach ( $prepared_args as $key => $value ) {
716 + /**
717 + * Filters the query_vars used in get_items() for the constructed query.
718 + *
719 + * The dynamic portion of the hook name, `$key`, refers to the query_var key.
720 + *
721 + * @since 1.0.0
722 + *
723 + * @param string $value The query_var value.
724 + */
725 + $query_args[ $key ] = apply_filters( "ct_rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
726 + }
727 +
728 + // Map to proper CT_Query orderby param.
729 + if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) {
730 + $orderby_mappings = array();
731 +
732 + /**
733 + * Filter orderby parameters mappings for the rest controller.
734 + *
735 + * The dynamic part of the filter `$this->name` refers to the custom table for the controller.
736 + *
737 + * @since 1.0.0
738 + *
739 + * @param array $orderby_mappings Array of parameters to map (for the orderby clause).
740 + * @param CT_Table $ct_table Table object.
741 + * @param array $prepared_args Prepared CT_Query arguments. Default empty array.
742 + * @param WP_REST_Request $request The request given.
743 + */
744 + $orderby_mappings = apply_filters( "ct_rest_{$this->name}_orderby_mappings", $orderby_mappings, $ct_table, $prepared_args, $request );
745 +
746 + if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
747 + $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
748 + }
749 + }
750 +
751 + return $query_args;
752 + }
753 +
754 + /**
755 + * Checks the post_date_gmt or modified_gmt and prepare any post or
756 + * modified date for single post output.
757 + *
758 + * @since 1.0.0
759 + *
760 + * @param string $date_gmt GMT publication time.
761 + * @param string|null $date Optional. Local publication time. Default null.
762 + * @return string|null ISO8601/RFC3339 formatted datetime.
763 + */
764 + protected function prepare_date_response( $date_gmt, $date = null ) {
765 + // Use the date if passed.
766 + if ( isset( $date ) ) {
767 + return mysql_to_rfc3339( $date );
768 + }
769 +
770 + // Return null if $date_gmt is empty/zeros.
771 + if ( '0000-00-00 00:00:00' === $date_gmt ) {
772 + return null;
773 + }
774 +
775 + // Return the formatted datetime.
776 + return mysql_to_rfc3339( $date_gmt );
777 + }
778 +
779 + /**
780 + * Prepares a single post for create or update.
781 + *
782 + * @since 1.0.0
783 + *
784 + * @param WP_REST_Request $request Request object.
785 + * @return stdClass|WP_Error Post object or WP_Error.
786 + */
787 + protected function prepare_item_for_database( $request ) {
788 + $prepared_object = new stdClass;
789 + $primary_key = $this->table->db->primary_key;
790 + $table_fields = $this->table->db->schema->fields;
791 +
792 + // Parse object primary key as ID
793 + if( isset( $request[$primary_key] ) ) {
794 + $request['id'] = $request[$primary_key];
795 + }
796 +
797 + // Object ID.
798 + if ( isset( $request['id'] ) ) {
799 + $existing_object = $this->get_object( $request['id'] );
800 + if ( is_wp_error( $existing_object ) ) {
801 + return $existing_object;
802 + }
803 +
804 + $prepared_object->$primary_key = $existing_object->$primary_key;
805 + }
806 +
807 + $schema = $this->get_item_schema();
808 +
809 + if( isset( $schema['properties'] ) && is_array( $schema['properties'] ) ) {
810 + foreach( $schema['properties'] as $field => $field_args ) {
811 +
812 + // Check if field is on request and also if is a table field
813 + if( isset( $request[$field] ) && isset( $table_fields[$field] ) ) {
814 +
815 + $value = $request[$field];
816 +
817 + /**
818 + * Filters a post before it is inserted via the REST API.
819 + *
820 + * The dynamic portion of the hook name, `$this->name`, refers to the custom table name.
821 + *
822 + * @since 1.0.0
823 + *
824 + * @param mixed $value The field value given.
825 + * @param string $field The field name.
826 + * @param WP_REST_Request $request Request object.
827 + *
828 + * @return mixed|WP_Error Return the field value sanitized or a WP_Error if for some reason field value is not correct
829 + */
830 + $value = apply_filters( "ct_rest_{$this->name}_sanitize_field_value", $value, $field, $request );
831 +
832 + // Bail if value filtered returns an error
833 + if( is_wp_error( $value ) ) {
834 + return $value;
835 + }
836 +
837 + $prepared_object->$field = $request[$field];
838 +
839 + }
840 +
841 + }
842 + }
843 +
844 + /**
845 + * Filters an object before it is inserted via the REST API.
846 + *
847 + * The dynamic portion of the hook name, `$this->name`, refers to the custom table name.
848 + *
849 + * @since 1.0.0
850 + *
851 + * @param stdClass $prepared_post An object representing a single post prepared
852 + * for inserting or updating the database.
853 + * @param WP_REST_Request $request Request object.
854 + */
855 + return apply_filters( "ct_rest_pre_insert_{$this->name}", $prepared_object, $request );
856 +
857 + }
858 +
859 + /**
860 + * Checks if an item can be read.
861 + *
862 + * Correctly handles posts with the inherit status.
863 + *
864 + * @since 1.0.0
865 + *
866 + * @param object $item Item object.
867 + * @return bool Whether the item can be read.
868 + */
869 + public function check_read_permission( $item ) {
870 + $primary_key = $this->table->db->primary_key;
871 +
872 + // Is the item readable?
873 + if ( current_user_can( $this->table->cap->read_item, $item->$primary_key ) ) {
874 + return true;
875 + }
876 +
877 + return false;
878 + }
879 +
880 + /**
881 + * Checks if an item can be edited.
882 + *
883 + * @since 1.0.0
884 + *
885 + * @param object $item Item object.
886 + * @return bool Whether the item can be edited.
887 + */
888 + protected function check_update_permission( $item ) {
889 + $primary_key = $this->table->db->primary_key;
890 +
891 + // Is the item editable?
892 + if ( current_user_can( $this->table->cap->edit_item, $item->$primary_key ) ) {
893 + return true;
894 + }
895 +
896 + return false;
897 + }
898 +
899 + /**
900 + * Checks if an item can be created.
901 + *
902 + * @since 1.0.0
903 + *
904 + * @param object $item Item object.
905 + * @return bool Whether the item can be created.
906 + */
907 + protected function check_create_permission( $item ) {
908 + return current_user_can( $this->table->cap->create_items );
909 + }
910 +
911 + /**
912 + * Checks if an item can be deleted.
913 + *
914 + * @since 1.0.0
915 + *
916 + * @param object $item Item object.
917 + * @return bool Whether the item can be deleted.
918 + */
919 + protected function check_delete_permission( $item ) {
920 + $primary_key = $this->table->db->primary_key;
921 +
922 + return current_user_can( $this->table->cap->delete_item, $item->$primary_key );
923 + }
924 +
925 + /**
926 + * Prepares a single post output for response.
927 + *
928 + * @since 1.0.0
929 + *
930 + * @param stdClass $object Object.
931 + * @param WP_REST_Request $request Request object.
932 + * @return WP_REST_Response Response object.
933 + */
934 + public function prepare_item_for_response( $object, $request ) {
935 +
936 + $fields = $this->get_fields_for_response( $request );
937 + $primary_key = $this->table->db->primary_key;
938 + $data = array();
939 +
940 + foreach( $fields as $field ) {
941 +
942 + $value = isset( $object->$field ) ? $object->$field : '';
943 +
944 + if( $field === 'id' ) {
945 + $value = $object->$primary_key;
946 + }
947 +
948 + /**
949 + * Filters the object field value for a response.
950 + *
951 + * The dynamic portion of the hook name, `$this->name`, refers to the custom table name.
952 + *
953 + * @since 1.0.0
954 + *
955 + * @param mixed $value The field value.
956 + * @param string $field The field key.
957 + * @param stdClass $object Object.
958 + * @param WP_REST_Request $request Request object.
959 + * @param array $fields Fields defined to being returned.
960 + */
961 + $data[$field] = apply_filters( "ct_rest_prepare_{$this->name}_field_value", $value, $field, $object, $request, $fields );
962 + }
963 +
964 + if ( in_array( 'meta', $this->table->supports ) && in_array( 'meta', $fields, true ) ) {
965 + $data['meta'] = $this->meta->get_value( $object->$primary_key, $request );
966 + }
967 +
968 + $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
969 + $data = $this->add_additional_fields_to_object( $data, $request );
970 + $data = $this->filter_response_by_context( $data, $context );
971 +
972 + // Wrap the data in a response object.
973 + $response = rest_ensure_response( $data );
974 +
975 + /**
976 + * Filters the object data for a response.
977 + *
978 + * The dynamic portion of the hook name, `$this->name`, refers to the custom table name.
979 + *
980 + * @since 1.0.0
981 + *
982 + * @param WP_REST_Response $response The response object.
983 + * @param stdClass $object Object.
984 + * @param WP_REST_Request $request Request object.
985 + */
986 + return apply_filters( "ct_rest_prepare_{$this->name}", $response, $object, $request );
987 + }
988 +
989 + /**
990 + * Retrieves the post's schema, conforming to JSON Schema.
991 + *
992 + * @since 1.0.0
993 + *
994 + * @return array Item schema data.
995 + */
996 + public function get_item_schema() {
997 +
998 + $schema = array(
999 + '$schema' => 'http://json-schema.org/draft-04/schema#',
1000 + 'title' => $this->name,
1001 + 'type' => 'object',
1002 + // Properties are the fields that will be returned through rest request.
1003 + 'properties' => array(
1004 + // id is common to all registered tables
1005 + 'id' => array(
1006 + 'description' => __( 'Unique identifier for the object.' ),
1007 + 'type' => 'integer',
1008 + 'context' => array( 'view', 'edit', 'embed' ),
1009 + ),
1010 + ),
1011 + );
1012 +
1013 + // Add meta property if table has support for it
1014 + if( in_array( 'meta', $this->table->supports ) ) {
1015 + $schema['properties']['meta'] = $this->meta->get_field_schema();
1016 + }
1017 +
1018 + /**
1019 + * Filter item schema for the rest controller.
1020 + *
1021 + * The dynamic part of the filter `$this->name` refers to the custom table name.
1022 + *
1023 + * @since 1.0.0
1024 + *
1025 + * @param array $schema
1026 + */
1027 + $schema = apply_filters( "ct_rest_{$this->name}_schema", $schema );
1028 +
1029 + return $this->add_additional_fields_schema( $schema );
1030 + }
1031 +
1032 + /**
1033 + * Retrieves the query params for the posts collection.
1034 + *
1035 + * @since 1.0.0
1036 + *
1037 + * @return array Collection parameters.
1038 + */
1039 + public function get_collection_params() {
1040 +
1041 + $query_params = parent::get_collection_params();
1042 +
1043 + $query_params['context']['default'] = 'view';
1044 +
1045 + $ct_table = $this->table;
1046 +
1047 + // Offset
1048 + $query_params['offset'] = array(
1049 + 'description' => __( 'Offset the result set by a specific number of items.' ),
1050 + 'type' => 'integer',
1051 + );
1052 +
1053 + // Order
1054 + $query_params['order'] = array(
1055 + 'description' => __( 'Order sort attribute ascending or descending.' ),
1056 + 'type' => 'string',
1057 + 'default' => 'desc',
1058 + 'enum' => array( 'asc', 'desc' ),
1059 + );
1060 +
1061 + // Order By
1062 + $query_params['orderby'] = array(
1063 + 'description' => __( 'Sort collection by object attribute.' ),
1064 + 'type' => 'string',
1065 + 'default' => $ct_table->db->primary_key,
1066 + 'enum' => array_merge(
1067 + // Allow order by table fields
1068 + array_keys( $ct_table->db->schema->fields ),
1069 + // Allow order by custom order by clauses
1070 + array( 'include', 'relevance' )
1071 + ),
1072 + );
1073 +
1074 + /**
1075 + * Filter collection parameters for the rest controller.
1076 + *
1077 + * The dynamic part of the filter `$this->name` refers to the custom table for the controller.
1078 + *
1079 + * This filter registers the collection parameter, but does not map the
1080 + * collection parameter to an internal CT_Query parameter. Use the
1081 + * `ct_rest_{$this->name}_query` filter to set CT_Query parameters.
1082 + *
1083 + * @since 1.0.0
1084 + *
1085 + * @param array $query_params JSON Schema-formatted collection parameters.
1086 + * @param CT_Table $ct_table Table object.
1087 + */
1088 + return apply_filters( "ct_rest_{$this->name}_collection_params", $query_params, $ct_table );
1089 + }
1090 +}