Passed
Push — master ( 85f980...1a98d2 )
by Mike
05:13 queued 16s
created

AbstractObjectsController::get_objects()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 2
nop 1
dl 0
loc 17
rs 9.8666
c 0
b 0
f 0
1
<?php
2
/**
3
 * Abstract Rest CRUD Controller Class
4
 *
5
 * @package WooCommerce/RestApi
6
 */
7
8
namespace WooCommerce\RestApi\Controllers\Version4;
9
10
defined( 'ABSPATH' ) || exit;
11
12
/**
13
 * CRUD Object Controller.
14
 */
15
abstract class AbstractObjectsController extends AbstractController {
16
17
	/**
18
	 * If object is hierarchical.
19
	 *
20
	 * @var bool
21
	 */
22
	protected $hierarchical = false;
23
24
	/**
25
	 * Post type.
26
	 *
27
	 * @var string
28
	 */
29
	protected $post_type = '';
30
31
	/**
32
	 * Get object.
33
	 *
34
	 * @param  int $id Object ID.
35
	 * @return \WC_Data
36
	 */
37
	abstract protected function get_object( $id );
38
39
	/**
40
	 * Check if a given request has access to read items.
41
	 *
42
	 * @param  \WP_REST_Request $request Full details about the request.
43
	 * @return \WP_Error|boolean
44
	 */
45
	public function get_items_permissions_check( $request ) {
46
		if ( ! wc_rest_check_post_permissions( $this->post_type, 'read' ) ) {
47
			return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
48
		}
49
50
		return true;
51
	}
52
53
	/**
54
	 * Check if a given request has access to read an item.
55
	 *
56
	 * @param  \WP_REST_Request $request Full details about the request.
57
	 * @return \WP_Error|boolean
58
	 */
59
	public function get_item_permissions_check( $request ) {
60
		$object = $this->get_object( (int) $request['id'] );
61
62
		if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'read', $object->get_id() ) ) {
63
			return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
64
		}
65
66
		return true;
67
	}
68
69
	/**
70
	 * Check if a given request has access to create an item.
71
	 *
72
	 * @param  \WP_REST_Request $request Full details about the request.
73
	 * @return \WP_Error|boolean
74
	 */
75
	public function create_item_permissions_check( $request ) {
76
		if ( ! wc_rest_check_post_permissions( $this->post_type, 'create' ) ) {
77
			return new \WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
78
		}
79
80
		return true;
81
	}
82
83
	/**
84
	 * Check if a given request has access to update an item.
85
	 *
86
	 * @param  \WP_REST_Request $request Full details about the request.
87
	 * @return \WP_Error|boolean
88
	 */
89
	public function update_item_permissions_check( $request ) {
90
		$object = $this->get_object( (int) $request['id'] );
91
92
		if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'edit', $object->get_id() ) ) {
93
			return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
94
		}
95
96
		return true;
97
	}
98
99
	/**
100
	 * Check if a given request has access to delete an item.
101
	 *
102
	 * @param  \WP_REST_Request $request Full details about the request.
103
	 * @return bool|\WP_Error
104
	 */
105
	public function delete_item_permissions_check( $request ) {
106
		$object = $this->get_object( (int) $request['id'] );
107
108
		if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'delete', $object->get_id() ) ) {
109
			return new \WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
110
		}
111
112
		return true;
113
	}
114
115
	/**
116
	 * Check if a given request has access batch create, update and delete items.
117
	 *
118
	 * @param  \WP_REST_Request $request Full details about the request.
119
	 *
120
	 * @return boolean|\WP_Error
121
	 */
122
	public function batch_items_permissions_check( $request ) {
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

122
	public function batch_items_permissions_check( /** @scrutinizer ignore-unused */ $request ) {

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...
123
		if ( ! wc_rest_check_post_permissions( $this->post_type, 'batch' ) ) {
124
			return new \WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
125
		}
126
127
		return true;
128
	}
129
130
	/**
131
	 * Get object permalink.
132
	 *
133
	 * @param  object $object Object.
134
	 * @return string
135
	 */
136
	protected function get_permalink( $object ) {
0 ignored issues
show
Unused Code introduced by
The parameter $object 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

136
	protected function get_permalink( /** @scrutinizer ignore-unused */ $object ) {

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...
137
		return '';
138
	}
139
140
	/**
141
	 * Prepares the object for the REST response.
142
	 *
143
	 * @since  3.0.0
144
	 * @param  \WC_Data         $object  Object data.
145
	 * @param  \WP_REST_Request $request Request object.
146
	 * @return \WP_REST_Response Response object on success, or \WP_Error object on failure.
147
	 */
148
	abstract protected function prepare_object_for_response( $object, $request );
149
150
	/**
151
	 * Prepares one object for create or update operation.
152
	 *
153
	 * @since  3.0.0
154
	 * @param  \WP_REST_Request $request Request object.
155
	 * @param  bool             $creating If is creating a new object.
156
	 * @return \WC_Data The prepared item, or \WP_Error object on failure.
157
	 */
158
	abstract protected function prepare_object_for_database( $request, $creating = false );
159
160
	/**
161
	 * Get a single item.
162
	 *
163
	 * @param \WP_REST_Request $request Full details about the request.
164
	 * @return \WP_Error|\WP_REST_Response
165
	 */
166
	public function get_item( $request ) {
167
		$object = $this->get_object( (int) $request['id'] );
168
169
		if ( ! $object || 0 === $object->get_id() ) {
0 ignored issues
show
introduced by
$object is of type WC_Data, thus it always evaluated to true.
Loading history...
170
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) );
171
		}
172
173
		$data     = $this->prepare_object_for_response( $object, $request );
174
		$response = rest_ensure_response( $data );
175
176
		return $response;
177
	}
178
179
	/**
180
	 * Save an object data.
181
	 *
182
	 * @since  3.0.0
183
	 * @param  \WP_REST_Request $request  Full details about the request.
184
	 * @param  bool             $creating If is creating a new object.
185
	 * @return \WC_Data|\WP_Error
186
	 */
187
	protected function save_object( $request, $creating = false ) {
188
		try {
189
			$object = $this->prepare_object_for_database( $request, $creating );
190
191
			if ( is_wp_error( $object ) ) {
192
				return $object;
193
			}
194
195
			$object->save();
196
197
			return $this->get_object( $object->get_id() );
198
		} catch ( \WC_Data_Exception $e ) {
199
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
200
		} catch ( \WC_REST_Exception $e ) {
201
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
202
		}
203
	}
204
205
	/**
206
	 * Create a single item.
207
	 *
208
	 * @param \WP_REST_Request $request Full details about the request.
209
	 * @return \WP_Error|\WP_REST_Response
210
	 */
211
	public function create_item( $request ) {
212
		if ( ! empty( $request['id'] ) ) {
213
			/* translators: %s: post type */
214
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) );
215
		}
216
217
		$object = $this->save_object( $request, true );
218
219
		if ( is_wp_error( $object ) ) {
220
			return $object;
221
		}
222
223
		try {
224
			$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

224
			$this->update_additional_fields_for_object( /** @scrutinizer ignore-type */ $object, $request );
Loading history...
225
		} catch ( \WC_Data_Exception $e ) {
226
			$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

226
			$object->/** @scrutinizer ignore-call */ 
227
            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...
227
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
228
		} catch ( \WC_REST_Exception $e ) {
229
			$object->delete();
230
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
231
		}
232
233
		/**
234
		 * Fires after a single object is created or updated via the REST API.
235
		 *
236
		 * @param \WC_Data         $object    Inserted object.
237
		 * @param \WP_REST_Request $request   Request object.
238
		 * @param boolean         $creating  True when creating object, false when updating.
239
		 */
240
		do_action( "woocommerce_rest_insert_{$this->post_type}_object", $object, $request, true );
241
242
		$request->set_param( 'context', 'edit' );
243
		$response = $this->prepare_object_for_response( $object, $request );
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type WP_Error; however, parameter $object of WooCommerce\RestApi\Cont...e_object_for_response() does only seem to accept WC_Data, 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

243
		$response = $this->prepare_object_for_response( /** @scrutinizer ignore-type */ $object, $request );
Loading history...
244
		$response = rest_ensure_response( $response );
245
		$response->set_status( 201 );
246
		$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

246
		$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...
247
248
		return $response;
249
	}
250
251
	/**
252
	 * Update a single post.
253
	 *
254
	 * @param \WP_REST_Request $request Full details about the request.
255
	 * @return \WP_Error|\WP_REST_Response
256
	 */
257
	public function update_item( $request ) {
258
		$object = $this->get_object( (int) $request['id'] );
259
260
		if ( ! $object || 0 === $object->get_id() ) {
0 ignored issues
show
introduced by
$object is of type WC_Data, thus it always evaluated to true.
Loading history...
261
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) );
262
		}
263
264
		$object = $this->save_object( $request, false );
265
266
		if ( is_wp_error( $object ) ) {
267
			return $object;
268
		}
269
270
		try {
271
			$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

271
			$this->update_additional_fields_for_object( /** @scrutinizer ignore-type */ $object, $request );
Loading history...
272
		} catch ( \WC_Data_Exception $e ) {
273
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
274
		} catch ( \WC_REST_Exception $e ) {
275
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
276
		}
277
278
		/**
279
		 * Fires after a single object is created or updated via the REST API.
280
		 *
281
		 * @param \WC_Data         $object    Inserted object.
282
		 * @param \WP_REST_Request $request   Request object.
283
		 * @param boolean         $creating  True when creating object, false when updating.
284
		 */
285
		do_action( "woocommerce_rest_insert_{$this->post_type}_object", $object, $request, false );
286
287
		$request->set_param( 'context', 'edit' );
288
		$response = $this->prepare_object_for_response( $object, $request );
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type WP_Error; however, parameter $object of WooCommerce\RestApi\Cont...e_object_for_response() does only seem to accept WC_Data, 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

288
		$response = $this->prepare_object_for_response( /** @scrutinizer ignore-type */ $object, $request );
Loading history...
289
		return rest_ensure_response( $response );
290
	}
291
292
	/**
293
	 * Prepare objects query.
294
	 *
295
	 * @since  3.0.0
296
	 * @param  \WP_REST_Request $request Full details about the request.
297
	 * @return array
298
	 */
299
	protected function prepare_objects_query( $request ) {
300
		$args                        = array();
301
		$args['offset']              = $request['offset'];
302
		$args['order']               = $request['order'];
303
		$args['orderby']             = $request['orderby'];
304
		$args['paged']               = $request['page'];
305
		$args['post__in']            = $request['include'];
306
		$args['post__not_in']        = $request['exclude'];
307
		$args['posts_per_page']      = $request['per_page'];
308
		$args['name']                = $request['slug'];
309
		$args['post_parent__in']     = $request['parent'];
310
		$args['post_parent__not_in'] = $request['parent_exclude'];
311
		$args['s']                   = $request['search'];
312
		$args['fields']              = 'ids';
313
314
		if ( 'date' === $args['orderby'] ) {
315
			$args['orderby'] = 'date ID';
316
		}
317
318
		$args['date_query'] = array();
319
320
		// Set before into date query. Date query must be specified as an array of an array.
321
		if ( isset( $request['before'] ) ) {
322
			$args['date_query'][0]['before'] = $request['before'];
323
		}
324
325
		// Set after into date query. Date query must be specified as an array of an array.
326
		if ( isset( $request['after'] ) ) {
327
			$args['date_query'][0]['after'] = $request['after'];
328
		}
329
330
		// Set date query colummn. Defaults to post_date.
331
		if ( isset( $request['date_column'] ) && ! empty( $args['date_query'][0] ) ) {
332
			$args['date_query'][0]['column'] = 'post_' . $request['date_column'];
333
		}
334
335
		// Force the post_type argument, since it's not a user input variable.
336
		$args['post_type'] = $this->post_type;
337
338
		/**
339
		 * Filter the query arguments for a request.
340
		 *
341
		 * Enables adding extra arguments or setting defaults for a post
342
		 * collection request.
343
		 *
344
		 * @param array            $args    Key value array of query var to query value.
345
		 * @param \WP_REST_Request $request The request used.
346
		 */
347
		$args = apply_filters( "woocommerce_rest_{$this->post_type}_object_query", $args, $request );
348
349
		return $this->prepare_items_query( $args, $request );
350
	}
351
352
	/**
353
	 * Get objects.
354
	 *
355
	 * @since  3.0.0
356
	 * @param  array $query_args Query args.
357
	 * @return array
358
	 */
359
	protected function get_objects( $query_args ) {
360
		$query  = new \WP_Query();
361
		$result = $query->query( $query_args );
362
363
		$total_posts = $query->found_posts;
364
		if ( $total_posts < 1 ) {
365
			// Out-of-bounds, run the query again without LIMIT for total count.
366
			unset( $query_args['paged'] );
367
			$count_query = new \WP_Query();
368
			$count_query->query( $query_args );
369
			$total_posts = $count_query->found_posts;
370
		}
371
372
		return array(
373
			'objects' => array_map( array( $this, 'get_object' ), $result ),
374
			'total'   => (int) $total_posts,
375
			'pages'   => (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ),
376
		);
377
	}
378
379
	/**
380
	 * Get a collection of posts.
381
	 *
382
	 * @param \WP_REST_Request $request Full details about the request.
383
	 * @return \WP_REST_Response
384
	 */
385
	public function get_items( $request ) {
386
		$query_args    = $this->prepare_objects_query( $request );
387
		$query_results = $this->get_objects( $query_args );
388
389
		$objects = array();
390
		foreach ( $query_results['objects'] as $object ) {
391
			if ( ! wc_rest_check_post_permissions( $this->post_type, 'read', $object->get_id() ) ) {
392
				continue;
393
			}
394
395
			$data      = $this->prepare_object_for_response( $object, $request );
396
			$objects[] = $this->prepare_response_for_collection( $data );
397
		}
398
399
		$page      = (int) $query_args['paged'];
400
		$max_pages = $query_results['pages'];
401
402
		$response = rest_ensure_response( $objects );
403
		$response->header( 'X-WP-Total', $query_results['total'] );
404
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
405
406
		$base          = $this->rest_base;
407
		$attrib_prefix = '(?P<';
408
		if ( strpos( $base, $attrib_prefix ) !== false ) {
409
			$attrib_names = array();
410
			preg_match( '/\(\?P<[^>]+>.*\)/', $base, $attrib_names, PREG_OFFSET_CAPTURE );
411
			foreach ( $attrib_names as $attrib_name_match ) {
412
				$beginning_offset = strlen( $attrib_prefix );
413
				$attrib_name_end  = strpos( $attrib_name_match[0], '>', $attrib_name_match[1] );
414
				$attrib_name      = substr( $attrib_name_match[0], $beginning_offset, $attrib_name_end - $beginning_offset );
415
				if ( isset( $request[ $attrib_name ] ) ) {
416
					$base = str_replace( "(?P<$attrib_name>[\d]+)", $request[ $attrib_name ], $base );
417
				}
418
			}
419
		}
420
		$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ) );
421
422
		if ( $page > 1 ) {
423
			$prev_page = $page - 1;
424
			if ( $prev_page > $max_pages ) {
425
				$prev_page = $max_pages;
426
			}
427
			$prev_link = add_query_arg( 'page', $prev_page, $base );
428
			$response->link_header( 'prev', $prev_link );
429
		}
430
		if ( $max_pages > $page ) {
431
			$next_page = $page + 1;
432
			$next_link = add_query_arg( 'page', $next_page, $base );
433
			$response->link_header( 'next', $next_link );
434
		}
435
436
		return $response;
437
	}
438
439
	/**
440
	 * Delete a single item.
441
	 *
442
	 * @param \WP_REST_Request $request Full details about the request.
443
	 * @return \WP_REST_Response|\WP_Error
444
	 */
445
	public function delete_item( $request ) {
446
		$force  = (bool) $request['force'];
447
		$object = $this->get_object( (int) $request['id'] );
448
		$result = false;
449
450
		if ( ! $object || 0 === $object->get_id() ) {
0 ignored issues
show
introduced by
$object is of type WC_Data, thus it always evaluated to true.
Loading history...
451
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) );
452
		}
453
454
		$supports_trash = $this->supports_trash( $object );
455
456
		if ( ! wc_rest_check_post_permissions( $this->post_type, 'delete', $object->get_id() ) ) {
457
			/* translators: %s: post type */
458
			return new \WP_Error( "woocommerce_rest_user_cannot_delete_{$this->post_type}", sprintf( __( 'Sorry, you are not allowed to delete %s.', 'woocommerce' ), $this->post_type ), array( 'status' => rest_authorization_required_code() ) );
459
		}
460
461
		$request->set_param( 'context', 'edit' );
462
		$previous = $this->prepare_object_for_response( $object, $request );
463
464
		// If we're forcing, then delete permanently.
465
		if ( $force ) {
466
			$object->delete( true );
467
			$result = 0 === $object->get_id();
468
		} else {
469
			// If we don't support trashing for this type, error out.
470
			if ( ! $supports_trash ) {
471
				/* translators: %s: post type */
472
				return new \WP_Error( 'woocommerce_rest_trash_not_supported', sprintf( __( 'The %s does not support trashing.', 'woocommerce' ), $this->post_type ), array( 'status' => 501 ) );
473
			}
474
475
			// Otherwise, only trash if we haven't already.
476
			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

476
			if ( is_callable( array( $object, 'get_status' ) ) && 'trash' === $object->/** @scrutinizer ignore-call */ get_status() ) {
Loading history...
477
				/* translators: %s: post type */
478
				return new \WP_Error( 'woocommerce_rest_already_trashed', sprintf( __( 'The %s has already been deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 410 ) );
479
			} else {
480
				$object->delete();
481
				$result = is_callable( array( $object, 'get_status' ) ) ? 'trash' === $object->get_status() : true;
482
			}
483
		}
484
485
		if ( ! $result ) {
486
			/* translators: %s: post type */
487
			return new \WP_Error( 'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 500 ) );
488
		}
489
490
		$response = new \WP_REST_Response();
491
		$response->set_data(
492
			array(
493
				'deleted'  => true,
494
				'previous' => $previous->get_data(),
495
			)
496
		);
497
498
		/**
499
		 * Fires after a single object is deleted or trashed via the REST API.
500
		 *
501
		 * @param \WC_Data          $object   The deleted or trashed object.
502
		 * @param \WP_REST_Response $response The response data.
503
		 * @param \WP_REST_Request  $request  The request sent to the API.
504
		 */
505
		do_action( "woocommerce_rest_delete_{$this->post_type}_object", $object, $response, $request );
506
507
		return $response;
508
	}
509
510
	/**
511
	 * Can this object be trashed?
512
	 *
513
	 * @param  object $object Object to check.
514
	 * @return boolean
515
	 */
516
	protected function supports_trash( $object ) {
517
		$supports_trash = EMPTY_TRASH_DAYS > 0;
518
519
		/**
520
		 * Filter whether an object is trashable.
521
		 *
522
		 * Return false to disable trash support for the object.
523
		 *
524
		 * @param boolean $supports_trash Whether the object type support trashing.
525
		 * @param \WC_Data $object         The object being considered for trashing support.
526
		 */
527
		return apply_filters( "woocommerce_rest_{$this->post_type}_object_trashable", $supports_trash, $object );
528
	}
529
530
	/**
531
	 * Prepare links for the request.
532
	 *
533
	 * @param \WC_Data         $object  Object data.
534
	 * @param \WP_REST_Request $request Request object.
535
	 * @return array                   Links for the given post.
536
	 */
537
	protected function prepare_links( $object, $request ) {
538
		$links = array(
539
			'self'       => array(
540
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ),
541
			),
542
			'collection' => array(
543
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
544
			),
545
		);
546
547
		return $links;
548
	}
549
550
	/**
551
	 * Get the query params for collections of attachments.
552
	 *
553
	 * @return array
554
	 */
555
	public function get_collection_params() {
556
		$params                       = array();
557
		$params['context']            = $this->get_context_param();
558
		$params['context']['default'] = 'view';
559
560
		$params['page'] = array(
561
			'description'       => __( 'Current page of the collection.', 'woocommerce' ),
562
			'type'              => 'integer',
563
			'default'           => 1,
564
			'sanitize_callback' => 'absint',
565
			'validate_callback' => 'rest_validate_request_arg',
566
			'minimum'           => 1,
567
		);
568
569
		$params['per_page'] = array(
570
			'description'       => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ),
571
			'type'              => 'integer',
572
			'default'           => 10,
573
			'minimum'           => 1,
574
			'maximum'           => 100,
575
			'sanitize_callback' => 'absint',
576
			'validate_callback' => 'rest_validate_request_arg',
577
		);
578
579
		$params['search'] = array(
580
			'description'       => __( 'Limit results to those matching a string.', 'woocommerce' ),
581
			'type'              => 'string',
582
			'sanitize_callback' => 'sanitize_text_field',
583
			'validate_callback' => 'rest_validate_request_arg',
584
		);
585
586
		$params['after'] = array(
587
			'description'       => __( 'Limit response to resources created after a given ISO8601 compliant date.', 'woocommerce' ),
588
			'type'              => 'string',
589
			'format'            => 'date-time',
590
			'validate_callback' => 'rest_validate_request_arg',
591
		);
592
593
		$params['before'] = array(
594
			'description'       => __( 'Limit response to resources created before a given ISO8601 compliant date.', 'woocommerce' ),
595
			'type'              => 'string',
596
			'format'            => 'date-time',
597
			'validate_callback' => 'rest_validate_request_arg',
598
		);
599
600
		$params['date_column'] = array(
601
			'description'       => __( 'When limiting response using after/before, which date column to compare against.', 'woocommerce' ),
602
			'type'              => 'string',
603
			'default'           => 'date',
604
			'enum'              => array(
605
				'date',
606
				'date_gmt',
607
				'modified',
608
				'modified_gmt',
609
			),
610
			'validate_callback' => 'rest_validate_request_arg',
611
		);
612
613
		$params['modified_before'] = array(
614
			'description'       => __( 'Limit response to resources modified before a given ISO8601 compliant date.', 'woocommerce' ),
615
			'type'              => 'string',
616
			'format'            => 'date-time',
617
			'validate_callback' => 'rest_validate_request_arg',
618
		);
619
620
		$params['exclude'] = array(
621
			'description'       => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ),
622
			'type'              => 'array',
623
			'items'             => array(
624
				'type' => 'integer',
625
			),
626
			'default'           => array(),
627
			'sanitize_callback' => 'wp_parse_id_list',
628
		);
629
630
		$params['include'] = array(
631
			'description'       => __( 'Limit result set to specific ids.', 'woocommerce' ),
632
			'type'              => 'array',
633
			'items'             => array(
634
				'type' => 'integer',
635
			),
636
			'default'           => array(),
637
			'sanitize_callback' => 'wp_parse_id_list',
638
		);
639
640
		$params['offset'] = array(
641
			'description'       => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
642
			'type'              => 'integer',
643
			'sanitize_callback' => 'absint',
644
			'validate_callback' => 'rest_validate_request_arg',
645
		);
646
647
		$params['order'] = array(
648
			'description'       => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
649
			'type'              => 'string',
650
			'default'           => 'desc',
651
			'enum'              => array( 'asc', 'desc' ),
652
			'validate_callback' => 'rest_validate_request_arg',
653
		);
654
655
		$params['orderby'] = array(
656
			'description'       => __( 'Sort collection by object attribute.', 'woocommerce' ),
657
			'type'              => 'string',
658
			'default'           => 'date',
659
			'enum'              => array(
660
				'date',
661
				'modified',
662
				'id',
663
				'include',
664
				'title',
665
				'slug',
666
			),
667
			'validate_callback' => 'rest_validate_request_arg',
668
		);
669
670
		if ( $this->hierarchical ) {
671
			$params['parent'] = array(
672
				'description'       => __( 'Limit result set to those of particular parent IDs.', 'woocommerce' ),
673
				'type'              => 'array',
674
				'items'             => array(
675
					'type' => 'integer',
676
				),
677
				'sanitize_callback' => 'wp_parse_id_list',
678
				'default'           => array(),
679
			);
680
681
			$params['parent_exclude'] = array(
682
				'description'       => __( 'Limit result set to all items except those of a particular parent ID.', 'woocommerce' ),
683
				'type'              => 'array',
684
				'items'             => array(
685
					'type' => 'integer',
686
				),
687
				'sanitize_callback' => 'wp_parse_id_list',
688
				'default'           => array(),
689
			);
690
		}
691
692
		/**
693
		 * Filter collection parameters for the posts controller.
694
		 *
695
		 * The dynamic part of the filter `$this->post_type` refers to the post
696
		 * type slug for the controller.
697
		 *
698
		 * This filter registers the collection parameter, but does not map the
699
		 * collection parameter to an internal \WP_Query parameter. Use the
700
		 * `rest_{$this->post_type}_query` filter to set \WP_Query parameters.
701
		 *
702
		 * @param array        $query_params JSON Schema-formatted collection parameters.
703
		 * @param \WP_Post_Type $post_type    Post type object.
704
		 */
705
		return apply_filters( "rest_{$this->post_type}_collection_params", $params, $this->post_type );
706
	}
707
708
	/**
709
	 * Determine the allowed query_vars for a get_items() response and
710
	 * prepare for \WP_Query.
711
	 *
712
	 * @param array            $prepared_args Prepared arguments.
713
	 * @param \WP_REST_Request $request Request object.
714
	 * @return array           $query_args
715
	 */
716
	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

716
	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...
717
		$valid_vars = array_flip( $this->get_allowed_query_vars() );
718
		$query_args = array();
719
		foreach ( $valid_vars as $var => $index ) {
720
			if ( isset( $prepared_args[ $var ] ) ) {
721
				/**
722
				 * Filter the query_vars used in `get_items` for the constructed query.
723
				 *
724
				 * The dynamic portion of the hook name, $var, refers to the query_var key.
725
				 *
726
				 * @param mixed $prepared_args[ $var ] The query_var value.
727
				 */
728
				$query_args[ $var ] = apply_filters( "woocommerce_rest_query_var_{$var}", $prepared_args[ $var ] );
729
			}
730
		}
731
732
		$query_args['ignore_sticky_posts'] = true;
733
734
		if ( 'include' === $query_args['orderby'] ) {
735
			$query_args['orderby'] = 'post__in';
736
		} elseif ( 'id' === $query_args['orderby'] ) {
737
			$query_args['orderby'] = 'ID'; // ID must be capitalized.
738
		} elseif ( 'slug' === $query_args['orderby'] ) {
739
			$query_args['orderby'] = 'name';
740
		}
741
742
		return $query_args;
743
	}
744
745
	/**
746
	 * Get all the WP Query vars that are allowed for the API request.
747
	 *
748
	 * @return array
749
	 */
750
	protected function get_allowed_query_vars() {
751
		global $wp;
752
753
		/**
754
		 * Filter the publicly allowed query vars.
755
		 *
756
		 * Allows adjusting of the default query vars that are made public.
757
		 *
758
		 * @param array  Array of allowed \WP_Query query vars.
759
		 */
760
		$valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );
761
762
		$post_type_obj = get_post_type_object( $this->post_type );
763
		if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
764
			/**
765
			 * Filter the allowed 'private' query vars for authorized users.
766
			 *
767
			 * If the user has the `edit_posts` capability, we also allow use of
768
			 * private query parameters, which are only undesirable on the
769
			 * frontend, but are safe for use in query strings.
770
			 *
771
			 * To disable anyway, use
772
			 * `add_filter( 'woocommerce_rest_private_query_vars', '__return_empty_array' );`
773
			 *
774
			 * @param array $private_query_vars Array of allowed query vars for authorized users.
775
			 * }
776
			 */
777
			$private    = apply_filters( 'woocommerce_rest_private_query_vars', $wp->private_query_vars );
778
			$valid_vars = array_merge( $valid_vars, $private );
779
		}
780
		// Define our own in addition to WP's normal vars.
781
		$rest_valid = array(
782
			'date_query',
783
			'ignore_sticky_posts',
784
			'offset',
785
			'post__in',
786
			'post__not_in',
787
			'post_parent',
788
			'post_parent__in',
789
			'post_parent__not_in',
790
			'posts_per_page',
791
			'meta_query',
792
			'tax_query',
793
			'meta_key',
794
			'meta_value',
795
			'meta_compare',
796
			'meta_value_num',
797
		);
798
		$valid_vars = array_merge( $valid_vars, $rest_valid );
799
800
		/**
801
		 * Filter allowed query vars for the REST API.
802
		 *
803
		 * This filter allows you to add or remove query vars from the final allowed
804
		 * list for all requests, including unauthenticated ones. To alter the
805
		 * vars for editors only.
806
		 *
807
		 * @param array {
808
		 *    Array of allowed \WP_Query query vars.
809
		 *
810
		 *    @param string $allowed_query_var The query var to allow.
811
		 * }
812
		 */
813
		$valid_vars = apply_filters( 'woocommerce_rest_query_vars', $valid_vars );
814
815
		return $valid_vars;
816
	}
817
}
818