PluginProbe
AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI / 6.0.2
AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI v6.0.2
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 5.6.2 All 33 releases
automatorwp / libraries / ct / includes / class-ct-rest-controller.php

class-ct-rest-controller.php in AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI 6.0.2, at libraries/ct/includes/class-ct-rest-controller.php

1,091 lines 39.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Rest Controller class
4 *
5 * Based on WP_REST_Posts_Controller class
6 *
7 * @author GamiPress <contact@gamipress.com>, Ruben Garcia <rubengcdev@gamil.com>
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 }
1091