AbstractObjectsController   F
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 748
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 305
c 1
b 0
f 0
dl 0
loc 748
rs 3.28
wmc 64

19 Methods

Rating   Name   Duplication   Size   Complexity  
A supports_trash() 0 12 1
B prepare_objects_query() 0 51 6
A prepare_links() 0 11 1
A delete_item_permissions_check() 0 9 3
A register_routes() 0 4 1
A get_item() 0 11 3
B delete_item() 0 63 10
A get_hook_suffix() 0 2 1
A get_item_permissions_check() 0 9 3
A update_item_permissions_check() 0 9 3
B get_collection_params() 0 151 2
A save_object() 0 15 4
A get_items() 0 21 3
A prepare_items_query() 0 27 6
A get_allowed_query_vars() 0 66 2
A add_meta_query() 0 8 2
A get_objects() 0 17 2
A update_item() 0 33 6
A create_item() 0 38 5

How to fix   Complexity   

Complex Class

Complex classes like AbstractObjectsController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractObjectsController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Abstract Rest CRUD Controller Class
4
 *
5
 * @package Automattic/WooCommerce/RestApi
6
 */
7
8
namespace Automattic\WooCommerce\RestApi\Controllers\Version4;
9
10
defined( 'ABSPATH' ) || exit;
11
12
use Automattic\WooCommerce\RestApi\Controllers\Version4\Utilities\Permissions;
13
use Automattic\WooCommerce\RestApi\Controllers\Version4\Utilities\Pagination;
14
15
/**
16
 * CRUD Object Controller.
17
 */
18
abstract class AbstractObjectsController extends AbstractController {
19
20
	/**
21
	 * If object is hierarchical.
22
	 *
23
	 * @var bool
24
	 */
25
	protected $hierarchical = false;
26
27
	/**
28
	 * Post type.
29
	 *
30
	 * @var string
31
	 */
32
	protected $post_type = '';
33
34
	/**
35
	 * Get object.
36
	 *
37
	 * @param  int $id Object ID.
38
	 * @return \WC_Data|bool
39
	 */
40
	abstract protected function get_object( $id );
41
42
	/**
43
	 * Prepares one object for create or update operation.
44
	 *
45
	 * @since  3.0.0
46
	 * @param  \WP_REST_Request $request Request object.
47
	 * @param  bool             $creating If is creating a new object.
48
	 * @return \WC_Data The prepared item, or \WP_Error object on failure.
49
	 */
50
	abstract protected function prepare_object_for_database( $request, $creating = false );
51
52
	/**
53
	 * Register the routes for products.
54
	 */
55
	public function register_routes() {
56
		$this->register_items_route();
57
		$this->register_item_route();
58
		$this->register_batch_route();
59
	}
60
61
	/**
62
	 * Get a single item.
63
	 *
64
	 * @param \WP_REST_Request $request Full details about the request.
65
	 * @return \WP_Error|\WP_REST_Response
66
	 */
67
	public function get_item( $request ) {
68
		$object = $this->get_object( (int) $request['id'] );
69
70
		if ( ! $object || 0 === $object->get_id() ) {
71
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
72
		}
73
74
		$data     = $this->prepare_item_for_response( $object, $request );
75
		$response = rest_ensure_response( $data );
76
77
		return $response;
78
	}
79
80
	/**
81
	 * Save an object data.
82
	 *
83
	 * @since  3.0.0
84
	 * @param  \WP_REST_Request $request  Full details about the request.
85
	 * @param  bool             $creating If is creating a new object.
86
	 * @return \WC_Data|\WP_Error
87
	 */
88
	protected function save_object( $request, $creating = false ) {
89
		try {
90
			$object = $this->prepare_object_for_database( $request, $creating );
91
92
			if ( is_wp_error( $object ) ) {
93
				return $object;
94
			}
95
96
			$object->save();
97
98
			return $this->get_object( $object->get_id() );
99
		} catch ( \WC_Data_Exception $e ) {
100
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
101
		} catch ( \WC_REST_Exception $e ) {
102
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
103
		}
104
	}
105
106
	/**
107
	 * Create a single item.
108
	 *
109
	 * @param \WP_REST_Request $request Full details about the request.
110
	 * @return \WP_Error|\WP_REST_Response
111
	 */
112
	public function create_item( $request ) {
113
		if ( ! empty( $request['id'] ) ) {
114
			/* translators: %s: post type */
115
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce-rest-api' ), $this->post_type ), array( 'status' => 400 ) );
116
		}
117
118
		$object = $this->save_object( $request, true );
119
120
		if ( is_wp_error( $object ) ) {
121
			return $object;
122
		}
123
124
		try {
125
			$this->update_additional_fields_for_object( $object, $request );
0 ignored issues
show
Bug introduced by
$object of type WC_Data|WP_Error is incompatible with the type array expected by parameter $object of WP_REST_Controller::upda...nal_fields_for_object(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

125
			$this->update_additional_fields_for_object( /** @scrutinizer ignore-type */ $object, $request );
Loading history...
126
127
			/**
128
			 * Fires after a single object is created or updated via the REST API.
129
			 *
130
			 * @param \WC_Data         $object    Inserted object.
131
			 * @param \WP_REST_Request $request   Request object.
132
			 * @param boolean         $creating  True when creating object, false when updating.
133
			 */
134
			do_action( "woocommerce_rest_insert_{$this->post_type}_object", $object, $request, true );
135
		} catch ( \WC_Data_Exception $e ) {
136
			$object->delete();
0 ignored issues
show
Bug introduced by
The method delete() does not exist on WP_Error. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

136
			$object->/** @scrutinizer ignore-call */ 
137
            delete();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
137
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
138
		} catch ( \WC_REST_Exception $e ) {
139
			$object->delete();
140
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
141
		}
142
143
		$request->set_param( 'context', 'edit' );
144
		$response = $this->prepare_item_for_response( $object, $request );
145
		$response = rest_ensure_response( $response );
146
		$response->set_status( 201 );
147
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ) );
0 ignored issues
show
Bug introduced by
The method get_id() does not exist on WP_Error. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

147
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->/** @scrutinizer ignore-call */ get_id() ) ) );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
148
149
		return $response;
150
	}
151
152
	/**
153
	 * Update a single post.
154
	 *
155
	 * @param \WP_REST_Request $request Full details about the request.
156
	 * @return \WP_Error|\WP_REST_Response
157
	 */
158
	public function update_item( $request ) {
159
		$object = $this->get_object( (int) $request['id'] );
160
161
		if ( ! $object || 0 === $object->get_id() ) {
162
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
163
		}
164
165
		$object = $this->save_object( $request, false );
166
167
		if ( is_wp_error( $object ) ) {
168
			return $object;
169
		}
170
171
		try {
172
			$this->update_additional_fields_for_object( $object, $request );
0 ignored issues
show
Bug introduced by
$object of type WC_Data|WP_Error is incompatible with the type array expected by parameter $object of WP_REST_Controller::upda...nal_fields_for_object(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

172
			$this->update_additional_fields_for_object( /** @scrutinizer ignore-type */ $object, $request );
Loading history...
173
174
			/**
175
			 * Fires after a single object is created or updated via the REST API.
176
			 *
177
			 * @param \WC_Data         $object    Inserted object.
178
			 * @param \WP_REST_Request $request   Request object.
179
			 * @param boolean         $creating  True when creating object, false when updating.
180
			 */
181
			do_action( "woocommerce_rest_insert_{$this->post_type}_object", $object, $request, false );
182
		} catch ( \WC_Data_Exception $e ) {
183
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
184
		} catch ( \WC_REST_Exception $e ) {
185
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
186
		}
187
188
		$request->set_param( 'context', 'edit' );
189
		$response = $this->prepare_item_for_response( $object, $request );
190
		return rest_ensure_response( $response );
191
	}
192
193
	/**
194
	 * Prepare objects query.
195
	 *
196
	 * @since  3.0.0
197
	 * @param  \WP_REST_Request $request Full details about the request.
198
	 * @return array
199
	 */
200
	protected function prepare_objects_query( $request ) {
201
		$args                        = array();
202
		$args['offset']              = $request['offset'];
203
		$args['order']               = $request['order'];
204
		$args['orderby']             = $request['orderby'];
205
		$args['paged']               = $request['page'];
206
		$args['post__in']            = $request['include'];
207
		$args['post__not_in']        = $request['exclude'];
208
		$args['posts_per_page']      = $request['per_page'];
209
		$args['name']                = $request['slug'];
210
		$args['post_parent__in']     = $request['parent'];
211
		$args['post_parent__not_in'] = $request['parent_exclude'];
212
		$args['s']                   = $request['search'];
213
		$args['fields']              = 'ids';
214
215
		if ( 'date' === $args['orderby'] ) {
216
			$args['orderby'] = 'date ID';
217
		}
218
219
		$args['date_query'] = array();
220
221
		// Set before into date query. Date query must be specified as an array of an array.
222
		if ( isset( $request['before'] ) ) {
223
			$args['date_query'][0]['before'] = $request['before'];
224
		}
225
226
		// Set after into date query. Date query must be specified as an array of an array.
227
		if ( isset( $request['after'] ) ) {
228
			$args['date_query'][0]['after'] = $request['after'];
229
		}
230
231
		// Set date query colummn. Defaults to post_date.
232
		if ( isset( $request['date_column'] ) && ! empty( $args['date_query'][0] ) ) {
233
			$args['date_query'][0]['column'] = 'post_' . $request['date_column'];
234
		}
235
236
		// Force the post_type argument, since it's not a user input variable.
237
		$args['post_type'] = $this->post_type;
238
239
		/**
240
		 * Filter the query arguments for a request.
241
		 *
242
		 * Enables adding extra arguments or setting defaults for a post
243
		 * collection request.
244
		 *
245
		 * @param array            $args    Key value array of query var to query value.
246
		 * @param \WP_REST_Request $request The request used.
247
		 */
248
		$args = apply_filters( "woocommerce_rest_{$this->post_type}_object_query", $args, $request );
249
250
		return $this->prepare_items_query( $args, $request );
251
	}
252
253
	/**
254
	 * Get objects.
255
	 *
256
	 * @since  3.0.0
257
	 * @param  array $query_args Query args.
258
	 * @return array
259
	 */
260
	protected function get_objects( $query_args ) {
261
		$query  = new \WP_Query();
262
		$result = $query->query( $query_args );
263
264
		$total_posts = $query->found_posts;
265
		if ( $total_posts < 1 ) {
266
			// Out-of-bounds, run the query again without LIMIT for total count.
267
			unset( $query_args['paged'] );
268
			$count_query = new \WP_Query();
269
			$count_query->query( $query_args );
270
			$total_posts = $count_query->found_posts;
271
		}
272
273
		return array(
274
			'objects' => array_map( array( $this, 'get_object' ), $result ),
275
			'total'   => (int) $total_posts,
276
			'pages'   => (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ),
277
		);
278
	}
279
280
	/**
281
	 * Get a collection of posts.
282
	 *
283
	 * @param \WP_REST_Request $request Full details about the request.
284
	 * @return \WP_REST_Response
285
	 */
286
	public function get_items( $request ) {
287
		$query_args    = $this->prepare_objects_query( $request );
288
		$query_results = $this->get_objects( $query_args );
289
290
		$objects = array();
291
		foreach ( $query_results['objects'] as $object ) {
292
			if ( ! Permissions::user_can_read( $this->post_type, $object->get_id() ) ) {
293
				continue;
294
			}
295
296
			$data      = $this->prepare_item_for_response( $object, $request );
297
			$objects[] = $this->prepare_response_for_collection( $data );
298
		}
299
300
		$total     = $query_results['total'];
301
		$max_pages = $query_results['pages'];
302
303
		$response = rest_ensure_response( $objects );
304
		$response = Pagination::add_pagination_headers( $response, $request, $total, $max_pages );
305
306
		return $response;
307
	}
308
309
	/**
310
	 * Delete a single item.
311
	 *
312
	 * @param \WP_REST_Request $request Full details about the request.
313
	 * @return \WP_REST_Response|\WP_Error
314
	 */
315
	public function delete_item( $request ) {
316
		$force  = (bool) $request['force'];
317
		$object = $this->get_object( (int) $request['id'] );
318
		$result = false;
319
320
		if ( ! $object || 0 === $object->get_id() ) {
321
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
322
		}
323
324
		$supports_trash = $this->supports_trash( $object );
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type true; however, parameter $object of Automattic\WooCommerce\R...oller::supports_trash() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

324
		$supports_trash = $this->supports_trash( /** @scrutinizer ignore-type */ $object );
Loading history...
325
326
		if ( ! Permissions::user_can_delete( $this->post_type, $object->get_id() ) ) {
327
			/* translators: %s: post type */
328
			return new \WP_Error( "woocommerce_rest_user_cannot_delete_{$this->post_type}", sprintf( __( 'Sorry, you are not allowed to delete %s.', 'woocommerce-rest-api' ), $this->post_type ), array( 'status' => rest_authorization_required_code() ) );
329
		}
330
331
		$request->set_param( 'context', 'edit' );
332
		$previous = $this->prepare_item_for_response( $object, $request );
333
334
		// If we're forcing, then delete permanently.
335
		if ( $force ) {
336
			$object->delete( true );
337
			$result = 0 === $object->get_id();
338
		} else {
339
			// If we don't support trashing for this type, error out.
340
			if ( ! $supports_trash ) {
341
				/* translators: %s: post type */
342
				return new \WP_Error( 'woocommerce_rest_trash_not_supported', sprintf( __( 'The %s does not support trashing.', 'woocommerce-rest-api' ), $this->post_type ), array( 'status' => 501 ) );
343
			}
344
345
			// Otherwise, only trash if we haven't already.
346
			if ( is_callable( array( $object, 'get_status' ) ) && 'trash' === $object->get_status() ) {
0 ignored issues
show
Bug introduced by
The method get_status() does not exist on WC_Data. It seems like you code against a sub-type of WC_Data such as WC_Product or WC_Abstract_Order or WC_Webhook. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

346
			if ( is_callable( array( $object, 'get_status' ) ) && 'trash' === $object->/** @scrutinizer ignore-call */ get_status() ) {
Loading history...
347
				/* translators: %s: post type */
348
				return new \WP_Error( 'woocommerce_rest_already_trashed', sprintf( __( 'The %s has already been deleted.', 'woocommerce-rest-api' ), $this->post_type ), array( 'status' => 410 ) );
349
			} else {
350
				$object->delete();
351
				$result = is_callable( array( $object, 'get_status' ) ) ? 'trash' === $object->get_status() : true;
352
			}
353
		}
354
355
		if ( ! $result ) {
356
			/* translators: %s: post type */
357
			return new \WP_Error( 'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce-rest-api' ), $this->post_type ), array( 'status' => 500 ) );
358
		}
359
360
		$response = new \WP_REST_Response();
361
		$response->set_data(
362
			array(
363
				'deleted'  => true,
364
				'previous' => $previous->get_data(),
365
			)
366
		);
367
368
		/**
369
		 * Fires after a single object is deleted or trashed via the REST API.
370
		 *
371
		 * @param \WC_Data          $object   The deleted or trashed object.
372
		 * @param \WP_REST_Response $response The response data.
373
		 * @param \WP_REST_Request  $request  The request sent to the API.
374
		 */
375
		do_action( "woocommerce_rest_delete_{$this->post_type}_object", $object, $response, $request );
376
377
		return $response;
378
	}
379
380
	/**
381
	 * Can this object be trashed?
382
	 *
383
	 * @param  object $object Object to check.
384
	 * @return boolean
385
	 */
386
	protected function supports_trash( $object ) {
387
		$supports_trash = EMPTY_TRASH_DAYS > 0;
388
389
		/**
390
		 * Filter whether an object is trashable.
391
		 *
392
		 * Return false to disable trash support for the object.
393
		 *
394
		 * @param boolean $supports_trash Whether the object type support trashing.
395
		 * @param \WC_Data $object         The object being considered for trashing support.
396
		 */
397
		return apply_filters( "woocommerce_rest_{$this->post_type}_object_trashable", $supports_trash, $object );
398
	}
399
400
	/**
401
	 * Prepare links for the request.
402
	 *
403
	 * @param mixed            $item Object to prepare.
404
	 * @param \WP_REST_Request $request Request object.
405
	 * @return array
406
	 */
407
	protected function prepare_links( $item, $request ) {
408
		$links = array(
409
			'self'       => array(
410
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $item->get_id() ) ),
411
			),
412
			'collection' => array(
413
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
414
			),
415
		);
416
417
		return $links;
418
	}
419
420
	/**
421
	 * Get the query params for collections of attachments.
422
	 *
423
	 * @return array
424
	 */
425
	public function get_collection_params() {
426
		$params                       = array();
427
		$params['context']            = $this->get_context_param();
428
		$params['context']['default'] = 'view';
429
430
		$params['page'] = array(
431
			'description'       => __( 'Current page of the collection.', 'woocommerce-rest-api' ),
432
			'type'              => 'integer',
433
			'default'           => 1,
434
			'sanitize_callback' => 'absint',
435
			'validate_callback' => 'rest_validate_request_arg',
436
			'minimum'           => 1,
437
		);
438
439
		$params['per_page'] = array(
440
			'description'       => __( 'Maximum number of items to be returned in result set.', 'woocommerce-rest-api' ),
441
			'type'              => 'integer',
442
			'default'           => 10,
443
			'minimum'           => 1,
444
			'maximum'           => 100,
445
			'sanitize_callback' => 'absint',
446
			'validate_callback' => 'rest_validate_request_arg',
447
		);
448
449
		$params['search'] = array(
450
			'description'       => __( 'Limit results to those matching a string.', 'woocommerce-rest-api' ),
451
			'type'              => 'string',
452
			'sanitize_callback' => 'sanitize_text_field',
453
			'validate_callback' => 'rest_validate_request_arg',
454
		);
455
456
		$params['after'] = array(
457
			'description'       => __( 'Limit response to resources created after a given ISO8601 compliant date.', 'woocommerce-rest-api' ),
458
			'type'              => 'string',
459
			'format'            => 'date-time',
460
			'validate_callback' => 'rest_validate_request_arg',
461
		);
462
463
		$params['before'] = array(
464
			'description'       => __( 'Limit response to resources created before a given ISO8601 compliant date.', 'woocommerce-rest-api' ),
465
			'type'              => 'string',
466
			'format'            => 'date-time',
467
			'validate_callback' => 'rest_validate_request_arg',
468
		);
469
470
		$params['date_column'] = array(
471
			'description'       => __( 'When limiting response using after/before, which date column to compare against.', 'woocommerce-rest-api' ),
472
			'type'              => 'string',
473
			'default'           => 'date',
474
			'enum'              => array(
475
				'date',
476
				'date_gmt',
477
				'modified',
478
				'modified_gmt',
479
			),
480
			'validate_callback' => 'rest_validate_request_arg',
481
		);
482
483
		$params['modified_before'] = array(
484
			'description'       => __( 'Limit response to resources modified before a given ISO8601 compliant date.', 'woocommerce-rest-api' ),
485
			'type'              => 'string',
486
			'format'            => 'date-time',
487
			'validate_callback' => 'rest_validate_request_arg',
488
		);
489
490
		$params['exclude'] = array(
491
			'description'       => __( 'Ensure result set excludes specific IDs.', 'woocommerce-rest-api' ),
492
			'type'              => 'array',
493
			'items'             => array(
494
				'type' => 'integer',
495
			),
496
			'default'           => array(),
497
			'sanitize_callback' => 'wp_parse_id_list',
498
		);
499
500
		$params['include'] = array(
501
			'description'       => __( 'Limit result set to specific ids.', 'woocommerce-rest-api' ),
502
			'type'              => 'array',
503
			'items'             => array(
504
				'type' => 'integer',
505
			),
506
			'default'           => array(),
507
			'sanitize_callback' => 'wp_parse_id_list',
508
		);
509
510
		$params['offset'] = array(
511
			'description'       => __( 'Offset the result set by a specific number of items.', 'woocommerce-rest-api' ),
512
			'type'              => 'integer',
513
			'sanitize_callback' => 'absint',
514
			'validate_callback' => 'rest_validate_request_arg',
515
		);
516
517
		$params['order'] = array(
518
			'description'       => __( 'Order sort attribute ascending or descending.', 'woocommerce-rest-api' ),
519
			'type'              => 'string',
520
			'default'           => 'desc',
521
			'enum'              => array( 'asc', 'desc' ),
522
			'validate_callback' => 'rest_validate_request_arg',
523
		);
524
525
		$params['orderby'] = array(
526
			'description'       => __( 'Sort collection by object attribute.', 'woocommerce-rest-api' ),
527
			'type'              => 'string',
528
			'default'           => 'date',
529
			'enum'              => array(
530
				'date',
531
				'modified',
532
				'id',
533
				'include',
534
				'title',
535
				'slug',
536
			),
537
			'validate_callback' => 'rest_validate_request_arg',
538
		);
539
540
		if ( $this->hierarchical ) {
541
			$params['parent'] = array(
542
				'description'       => __( 'Limit result set to those of particular parent IDs.', 'woocommerce-rest-api' ),
543
				'type'              => 'array',
544
				'items'             => array(
545
					'type' => 'integer',
546
				),
547
				'sanitize_callback' => 'wp_parse_id_list',
548
				'default'           => array(),
549
			);
550
551
			$params['parent_exclude'] = array(
552
				'description'       => __( 'Limit result set to all items except those of a particular parent ID.', 'woocommerce-rest-api' ),
553
				'type'              => 'array',
554
				'items'             => array(
555
					'type' => 'integer',
556
				),
557
				'sanitize_callback' => 'wp_parse_id_list',
558
				'default'           => array(),
559
			);
560
		}
561
562
		/**
563
		 * Filter collection parameters for the posts controller.
564
		 *
565
		 * The dynamic part of the filter `$this->post_type` refers to the post
566
		 * type slug for the controller.
567
		 *
568
		 * This filter registers the collection parameter, but does not map the
569
		 * collection parameter to an internal \WP_Query parameter. Use the
570
		 * `rest_{$this->post_type}_query` filter to set \WP_Query parameters.
571
		 *
572
		 * @param array        $query_params JSON Schema-formatted collection parameters.
573
		 * @param \WP_Post_Type $post_type    Post type object.
574
		 */
575
		return apply_filters( "rest_{$this->post_type}_collection_params", $params, $this->post_type );
576
	}
577
578
	/**
579
	 * Determine the allowed query_vars for a get_items() response and
580
	 * prepare for \WP_Query.
581
	 *
582
	 * @param array            $prepared_args Prepared arguments.
583
	 * @param \WP_REST_Request $request Request object.
584
	 * @return array           $query_args
585
	 */
586
	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

586
	protected function prepare_items_query( $prepared_args = array(), /** @scrutinizer ignore-unused */ $request = null ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
587
		$valid_vars = array_flip( $this->get_allowed_query_vars() );
588
		$query_args = array();
589
		foreach ( $valid_vars as $var => $index ) {
590
			if ( isset( $prepared_args[ $var ] ) ) {
591
				/**
592
				 * Filter the query_vars used in `get_items` for the constructed query.
593
				 *
594
				 * The dynamic portion of the hook name, $var, refers to the query_var key.
595
				 *
596
				 * @param mixed $prepared_args[ $var ] The query_var value.
597
				 */
598
				$query_args[ $var ] = apply_filters( "woocommerce_rest_query_var_{$var}", $prepared_args[ $var ] );
599
			}
600
		}
601
602
		$query_args['ignore_sticky_posts'] = true;
603
604
		if ( 'include' === $query_args['orderby'] ) {
605
			$query_args['orderby'] = 'post__in';
606
		} elseif ( 'id' === $query_args['orderby'] ) {
607
			$query_args['orderby'] = 'ID'; // ID must be capitalized.
608
		} elseif ( 'slug' === $query_args['orderby'] ) {
609
			$query_args['orderby'] = 'name';
610
		}
611
612
		return $query_args;
613
	}
614
615
	/**
616
	 * Get all the WP Query vars that are allowed for the API request.
617
	 *
618
	 * @return array
619
	 */
620
	protected function get_allowed_query_vars() {
621
		global $wp;
622
623
		/**
624
		 * Filter the publicly allowed query vars.
625
		 *
626
		 * Allows adjusting of the default query vars that are made public.
627
		 *
628
		 * @param array  Array of allowed \WP_Query query vars.
629
		 */
630
		$valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );
631
632
		$post_type_obj = get_post_type_object( $this->post_type );
633
		if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
634
			/**
635
			 * Filter the allowed 'private' query vars for authorized users.
636
			 *
637
			 * If the user has the `edit_posts` capability, we also allow use of
638
			 * private query parameters, which are only undesirable on the
639
			 * frontend, but are safe for use in query strings.
640
			 *
641
			 * To disable anyway, use
642
			 * `add_filter( 'woocommerce_rest_private_query_vars', '__return_empty_array' );`
643
			 *
644
			 * @param array $private_query_vars Array of allowed query vars for authorized users.
645
			 * }
646
			 */
647
			$private    = apply_filters( 'woocommerce_rest_private_query_vars', $wp->private_query_vars );
648
			$valid_vars = array_merge( $valid_vars, $private );
649
		}
650
		// Define our own in addition to WP's normal vars.
651
		$rest_valid = array(
652
			'date_query',
653
			'ignore_sticky_posts',
654
			'offset',
655
			'post__in',
656
			'post__not_in',
657
			'post_parent',
658
			'post_parent__in',
659
			'post_parent__not_in',
660
			'posts_per_page',
661
			'meta_query',
662
			'tax_query',
663
			'meta_key',
664
			'meta_value',
665
			'meta_compare',
666
			'meta_value_num',
667
		);
668
		$valid_vars = array_merge( $valid_vars, $rest_valid );
669
670
		/**
671
		 * Filter allowed query vars for the REST API.
672
		 *
673
		 * This filter allows you to add or remove query vars from the final allowed
674
		 * list for all requests, including unauthenticated ones. To alter the
675
		 * vars for editors only.
676
		 *
677
		 * @param array {
678
		 *    Array of allowed \WP_Query query vars.
679
		 *
680
		 *    @param string $allowed_query_var The query var to allow.
681
		 * }
682
		 */
683
		$valid_vars = apply_filters( 'woocommerce_rest_query_vars', $valid_vars );
684
685
		return $valid_vars;
686
	}
687
688
	/**
689
	 * Add meta query.
690
	 *
691
	 * @since 3.0.0
692
	 * @param array $args       Query args.
693
	 * @param array $meta_query Meta query.
694
	 * @return array
695
	 */
696
	protected function add_meta_query( $args, $meta_query ) {
697
		if ( empty( $args['meta_query'] ) ) {
698
			$args['meta_query'] = []; // phpcs:ignore
699
		}
700
701
		$args['meta_query'][] = $meta_query;
702
703
		return $args['meta_query'];
704
	}
705
706
	/**
707
	 * Return suffix for item action hooks.
708
	 *
709
	 * @return string
710
	 */
711
	protected function get_hook_suffix() {
712
		return $this->post_type . '_object';
713
	}
714
715
	/**
716
	 * Check if a given request has access to read a webhook.
717
	 *
718
	 * @param  \WP_REST_Request $request Full details about the request.
719
	 * @return \WP_Error|boolean
720
	 */
721
	public function get_item_permissions_check( $request ) {
722
		$id     = $request->get_param( 'id' );
723
		$object = $this->get_object( $id );
724
725
		if ( ! $object || 0 === $object->get_id() ) {
726
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
727
		}
728
729
		return parent::get_item_permissions_check( $request );
730
	}
731
732
	/**
733
	 * Check if a given request has access update a webhook.
734
	 *
735
	 * @param  \WP_REST_Request $request Full details about the request.
736
	 *
737
	 * @return bool|\WP_Error
738
	 */
739
	public function update_item_permissions_check( $request ) {
740
		$id     = $request->get_param( 'id' );
741
		$object = $this->get_object( $id );
742
743
		if ( ! $object || 0 === $object->get_id() ) {
744
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
745
		}
746
747
		return parent::update_item_permissions_check( $request );
748
	}
749
750
	/**
751
	 * Check if a given request has access delete a webhook.
752
	 *
753
	 * @param  \WP_REST_Request $request Full details about the request.
754
	 *
755
	 * @return bool|\WP_Error
756
	 */
757
	public function delete_item_permissions_check( $request ) {
758
		$id     = $request->get_param( 'id' );
759
		$object = $this->get_object( $id );
760
761
		if ( ! $object || 0 === $object->get_id() ) {
762
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
763
		}
764
765
		return parent::delete_item_permissions_check( $request );
766
	}
767
}
768