Passed
Push — master ( 76ee42...cd04f7 )
by Mike
03:19
created

AbstractObjectsController::prepare_objects_query()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 51
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 25
nc 16
nop 1
dl 0
loc 51
rs 8.8977
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 \WP_Error|\WC_Data
0 ignored issues
show
Bug introduced by
The type WC_Data was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
36
	 */
37
	protected function get_object( $id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $id 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

37
	protected function get_object( /** @scrutinizer ignore-unused */ $id ) {

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...
38
		// translators: %s: Class method name.
39
		return new \WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
40
	}
41
42
	/**
43
	 * Check if a given request has access to read items.
44
	 *
45
	 * @param  \WP_REST_Request $request Full details about the request.
46
	 * @return \WP_Error|boolean
47
	 */
48
	public function get_items_permissions_check( $request ) {
49
		if ( ! wc_rest_check_post_permissions( $this->post_type, 'read' ) ) {
50
			return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
51
		}
52
53
		return true;
54
	}
55
56
	/**
57
	 * Check if a given request has access to read an item.
58
	 *
59
	 * @param  \WP_REST_Request $request Full details about the request.
60
	 * @return \WP_Error|boolean
61
	 */
62
	public function get_item_permissions_check( $request ) {
63
		$object = $this->get_object( (int) $request['id'] );
64
65
		if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'read', $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

65
		if ( $object && 0 !== $object->/** @scrutinizer ignore-call */ get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'read', $object->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...
66
			return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
67
		}
68
69
		return true;
70
	}
71
72
	/**
73
	 * Check if a given request has access to create an item.
74
	 *
75
	 * @param  \WP_REST_Request $request Full details about the request.
76
	 * @return \WP_Error|boolean
77
	 */
78
	public function create_item_permissions_check( $request ) {
79
		if ( ! wc_rest_check_post_permissions( $this->post_type, 'create' ) ) {
80
			return new \WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
81
		}
82
83
		return true;
84
	}
85
86
	/**
87
	 * Check if a given request has access to update an item.
88
	 *
89
	 * @param  \WP_REST_Request $request Full details about the request.
90
	 * @return \WP_Error|boolean
91
	 */
92
	public function update_item_permissions_check( $request ) {
93
		$object = $this->get_object( (int) $request['id'] );
94
95
		if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'edit', $object->get_id() ) ) {
96
			return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
97
		}
98
99
		return true;
100
	}
101
102
	/**
103
	 * Check if a given request has access to delete an item.
104
	 *
105
	 * @param  \WP_REST_Request $request Full details about the request.
106
	 * @return bool|\WP_Error
107
	 */
108
	public function delete_item_permissions_check( $request ) {
109
		$object = $this->get_object( (int) $request['id'] );
110
111
		if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'delete', $object->get_id() ) ) {
112
			return new \WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
113
		}
114
115
		return true;
116
	}
117
118
	/**
119
	 * Check if a given request has access batch create, update and delete items.
120
	 *
121
	 * @param  \WP_REST_Request $request Full details about the request.
122
	 *
123
	 * @return boolean|\WP_Error
124
	 */
125
	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

125
	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...
126
		if ( ! wc_rest_check_post_permissions( $this->post_type, 'batch' ) ) {
127
			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() ) );
128
		}
129
130
		return true;
131
	}
132
133
	/**
134
	 * Get object permalink.
135
	 *
136
	 * @param  object $object Object.
137
	 * @return string
138
	 */
139
	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

139
	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...
140
		return '';
141
	}
142
143
	/**
144
	 * Prepares the object for the REST response.
145
	 *
146
	 * @since  3.0.0
147
	 * @param  \WC_Data         $object  Object data.
148
	 * @param  \WP_REST_Request $request Request object.
149
	 * @return \WP_Error|\WP_REST_Response Response object on success, or \WP_Error object on failure.
150
	 */
151
	protected function prepare_object_for_response( $object, $request ) {
152
		// translators: %s: Class method name.
153
		return new \WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
154
	}
155
156
	/**
157
	 * Prepares one object for create or update operation.
158
	 *
159
	 * @since  3.0.0
160
	 * @param  \WP_REST_Request $request Request object.
161
	 * @param  bool             $creating If is creating a new object.
162
	 * @return \WP_Error|WC_Data The prepared item, or \WP_Error object on failure.
0 ignored issues
show
Bug introduced by
The type WooCommerce\RestApi\Controllers\Version4\WC_Data was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
163
	 */
164
	protected function prepare_object_for_database( $request, $creating = false ) {
165
		// translators: %s: Class method name.
166
		return new \WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
167
	}
168
169
	/**
170
	 * Get a single item.
171
	 *
172
	 * @param \WP_REST_Request $request Full details about the request.
173
	 * @return \WP_Error|\WP_REST_Response
174
	 */
175
	public function get_item( $request ) {
176
		$object = $this->get_object( (int) $request['id'] );
177
178
		if ( ! $object || 0 === $object->get_id() ) {
0 ignored issues
show
introduced by
$object is of type WP_Error, thus it always evaluated to true.
Loading history...
179
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) );
180
		}
181
182
		$data     = $this->prepare_object_for_response( $object, $request );
183
		$response = rest_ensure_response( $data );
184
185
		return $response;
186
	}
187
188
	/**
189
	 * Save an object data.
190
	 *
191
	 * @since  3.0.0
192
	 * @param  \WP_REST_Request $request  Full details about the request.
193
	 * @param  bool             $creating If is creating a new object.
194
	 * @return WC_Data|\WP_Error
195
	 */
196
	protected function save_object( $request, $creating = false ) {
197
		try {
198
			$object = $this->prepare_object_for_database( $request, $creating );
199
200
			if ( is_wp_error( $object ) ) {
201
				return $object;
202
			}
203
204
			$object->save();
0 ignored issues
show
Bug introduced by
The method save() 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

204
			$object->/** @scrutinizer ignore-call */ 
205
            save();

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...
205
206
			return $this->get_object( $object->get_id() );
207
		} catch ( \WC_Data_Exception $e ) {
0 ignored issues
show
Bug introduced by
The type WC_Data_Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
208
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
209
		} catch ( \WC_REST_Exception $e ) {
0 ignored issues
show
Bug introduced by
The type WC_REST_Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
210
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
211
		}
212
	}
213
214
	/**
215
	 * Create a single item.
216
	 *
217
	 * @param \WP_REST_Request $request Full details about the request.
218
	 * @return \WP_Error|\WP_REST_Response
219
	 */
220
	public function create_item( $request ) {
221
		if ( ! empty( $request['id'] ) ) {
222
			/* translators: %s: post type */
223
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) );
224
		}
225
226
		$object = $this->save_object( $request, true );
227
228
		if ( is_wp_error( $object ) ) {
229
			return $object;
230
		}
231
232
		try {
233
			$this->update_additional_fields_for_object( $object, $request );
0 ignored issues
show
Bug introduced by
$object of type 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

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

235
			$object->/** @scrutinizer ignore-call */ 
236
            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...
236
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
237
		} catch ( \WC_REST_Exception $e ) {
238
			$object->delete();
239
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
240
		}
241
242
		/**
243
		 * Fires after a single object is created or updated via the REST API.
244
		 *
245
		 * @param \WC_Data         $object    Inserted object.
246
		 * @param \WP_REST_Request $request   Request object.
247
		 * @param boolean         $creating  True when creating object, false when updating.
248
		 */
249
		do_action( "woocommerce_rest_insert_{$this->post_type}_object", $object, $request, true );
250
251
		$request->set_param( 'context', 'edit' );
252
		$response = $this->prepare_object_for_response( $object, $request );
253
		$response = rest_ensure_response( $response );
254
		$response->set_status( 201 );
0 ignored issues
show
Bug introduced by
The method set_status() 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

254
		$response->/** @scrutinizer ignore-call */ 
255
             set_status( 201 );

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...
255
		$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 header() 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

255
		$response->/** @scrutinizer ignore-call */ 
256
             header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->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...
256
257
		return $response;
258
	}
259
260
	/**
261
	 * Update a single post.
262
	 *
263
	 * @param \WP_REST_Request $request Full details about the request.
264
	 * @return \WP_Error|\WP_REST_Response
265
	 */
266
	public function update_item( $request ) {
267
		$object = $this->get_object( (int) $request['id'] );
268
269
		if ( ! $object || 0 === $object->get_id() ) {
0 ignored issues
show
introduced by
$object is of type WP_Error, thus it always evaluated to true.
Loading history...
270
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) );
271
		}
272
273
		$object = $this->save_object( $request, false );
274
275
		if ( is_wp_error( $object ) ) {
276
			return $object;
277
		}
278
279
		try {
280
			$this->update_additional_fields_for_object( $object, $request );
0 ignored issues
show
Bug introduced by
$object of type 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

280
			$this->update_additional_fields_for_object( /** @scrutinizer ignore-type */ $object, $request );
Loading history...
281
		} catch ( \WC_Data_Exception $e ) {
282
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
283
		} catch ( \WC_REST_Exception $e ) {
284
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
285
		}
286
287
		/**
288
		 * Fires after a single object is created or updated via the REST API.
289
		 *
290
		 * @param \WC_Data         $object    Inserted object.
291
		 * @param \WP_REST_Request $request   Request object.
292
		 * @param boolean         $creating  True when creating object, false when updating.
293
		 */
294
		do_action( "woocommerce_rest_insert_{$this->post_type}_object", $object, $request, false );
295
296
		$request->set_param( 'context', 'edit' );
297
		$response = $this->prepare_object_for_response( $object, $request );
298
		return rest_ensure_response( $response );
299
	}
300
301
	/**
302
	 * Prepare objects query.
303
	 *
304
	 * @since  3.0.0
305
	 * @param  \WP_REST_Request $request Full details about the request.
306
	 * @return array
307
	 */
308
	protected function prepare_objects_query( $request ) {
309
		$args                        = array();
310
		$args['offset']              = $request['offset'];
311
		$args['order']               = $request['order'];
312
		$args['orderby']             = $request['orderby'];
313
		$args['paged']               = $request['page'];
314
		$args['post__in']            = $request['include'];
315
		$args['post__not_in']        = $request['exclude'];
316
		$args['posts_per_page']      = $request['per_page'];
317
		$args['name']                = $request['slug'];
318
		$args['post_parent__in']     = $request['parent'];
319
		$args['post_parent__not_in'] = $request['parent_exclude'];
320
		$args['s']                   = $request['search'];
321
		$args['fields']              = 'ids';
322
323
		if ( 'date' === $args['orderby'] ) {
324
			$args['orderby'] = 'date ID';
325
		}
326
327
		$args['date_query'] = array();
328
329
		// Set before into date query. Date query must be specified as an array of an array.
330
		if ( isset( $request['before'] ) ) {
331
			$args['date_query'][0]['before'] = $request['before'];
332
		}
333
334
		// Set after into date query. Date query must be specified as an array of an array.
335
		if ( isset( $request['after'] ) ) {
336
			$args['date_query'][0]['after'] = $request['after'];
337
		}
338
339
		// Set date query colummn. Defaults to post_date.
340
		if ( isset( $request['date_column'] ) && ! empty( $args['date_query'][0] ) ) {
341
			$args['date_query'][0]['column'] = 'post_' . $request['date_column'];
342
		}
343
344
		// Force the post_type argument, since it's not a user input variable.
345
		$args['post_type'] = $this->post_type;
346
347
		/**
348
		 * Filter the query arguments for a request.
349
		 *
350
		 * Enables adding extra arguments or setting defaults for a post
351
		 * collection request.
352
		 *
353
		 * @param array            $args    Key value array of query var to query value.
354
		 * @param \WP_REST_Request $request The request used.
355
		 */
356
		$args = apply_filters( "woocommerce_rest_{$this->post_type}_object_query", $args, $request );
357
358
		return $this->prepare_items_query( $args, $request );
359
	}
360
361
	/**
362
	 * Get objects.
363
	 *
364
	 * @since  3.0.0
365
	 * @param  array $query_args Query args.
366
	 * @return array
367
	 */
368
	protected function get_objects( $query_args ) {
369
		$query  = new \WP_Query();
370
		$result = $query->query( $query_args );
371
372
		$total_posts = $query->found_posts;
373
		if ( $total_posts < 1 ) {
374
			// Out-of-bounds, run the query again without LIMIT for total count.
375
			unset( $query_args['paged'] );
376
			$count_query = new \WP_Query();
377
			$count_query->query( $query_args );
378
			$total_posts = $count_query->found_posts;
379
		}
380
381
		return array(
382
			'objects' => array_map( array( $this, 'get_object' ), $result ),
383
			'total'   => (int) $total_posts,
384
			'pages'   => (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ),
385
		);
386
	}
387
388
	/**
389
	 * Get a collection of posts.
390
	 *
391
	 * @param \WP_REST_Request $request Full details about the request.
392
	 * @return \WP_Error|\WP_REST_Response
393
	 */
394
	public function get_items( $request ) {
395
		$query_args    = $this->prepare_objects_query( $request );
396
		$query_results = $this->get_objects( $query_args );
397
398
		$objects = array();
399
		foreach ( $query_results['objects'] as $object ) {
400
			if ( ! wc_rest_check_post_permissions( $this->post_type, 'read', $object->get_id() ) ) {
401
				continue;
402
			}
403
404
			$data      = $this->prepare_object_for_response( $object, $request );
405
			$objects[] = $this->prepare_response_for_collection( $data );
0 ignored issues
show
Bug introduced by
$data of type WP_Error is incompatible with the type WP_REST_Response expected by parameter $response of WP_REST_Controller::prep...sponse_for_collection(). ( Ignorable by Annotation )

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

405
			$objects[] = $this->prepare_response_for_collection( /** @scrutinizer ignore-type */ $data );
Loading history...
406
		}
407
408
		$page      = (int) $query_args['paged'];
409
		$max_pages = $query_results['pages'];
410
411
		$response = rest_ensure_response( $objects );
412
		$response->header( 'X-WP-Total', $query_results['total'] );
413
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
414
415
		$base          = $this->rest_base;
416
		$attrib_prefix = '(?P<';
417
		if ( strpos( $base, $attrib_prefix ) !== false ) {
418
			$attrib_names = array();
419
			preg_match( '/\(\?P<[^>]+>.*\)/', $base, $attrib_names, PREG_OFFSET_CAPTURE );
420
			foreach ( $attrib_names as $attrib_name_match ) {
421
				$beginning_offset = strlen( $attrib_prefix );
422
				$attrib_name_end  = strpos( $attrib_name_match[0], '>', $attrib_name_match[1] );
423
				$attrib_name      = substr( $attrib_name_match[0], $beginning_offset, $attrib_name_end - $beginning_offset );
424
				if ( isset( $request[ $attrib_name ] ) ) {
425
					$base = str_replace( "(?P<$attrib_name>[\d]+)", $request[ $attrib_name ], $base );
426
				}
427
			}
428
		}
429
		$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ) );
430
431
		if ( $page > 1 ) {
432
			$prev_page = $page - 1;
433
			if ( $prev_page > $max_pages ) {
434
				$prev_page = $max_pages;
435
			}
436
			$prev_link = add_query_arg( 'page', $prev_page, $base );
437
			$response->link_header( 'prev', $prev_link );
438
		}
439
		if ( $max_pages > $page ) {
440
			$next_page = $page + 1;
441
			$next_link = add_query_arg( 'page', $next_page, $base );
442
			$response->link_header( 'next', $next_link );
443
		}
444
445
		return $response;
446
	}
447
448
	/**
449
	 * Delete a single item.
450
	 *
451
	 * @param \WP_REST_Request $request Full details about the request.
452
	 * @return \WP_REST_Response|\WP_Error
453
	 */
454
	public function delete_item( $request ) {
455
		$force  = (bool) $request['force'];
456
		$object = $this->get_object( (int) $request['id'] );
457
		$result = false;
458
459
		if ( ! $object || 0 === $object->get_id() ) {
0 ignored issues
show
introduced by
$object is of type WP_Error, thus it always evaluated to true.
Loading history...
460
			return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) );
461
		}
462
463
		$supports_trash = $this->supports_trash( $object );
464
465
		if ( ! wc_rest_check_post_permissions( $this->post_type, 'delete', $object->get_id() ) ) {
466
			/* translators: %s: post type */
467
			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() ) );
468
		}
469
470
		$request->set_param( 'context', 'edit' );
471
		$previous = $this->prepare_object_for_response( $object, $request );
472
473
		// If we're forcing, then delete permanently.
474
		if ( $force ) {
475
			$object->delete( true );
476
			$result = 0 === $object->get_id();
477
		} else {
478
			// If we don't support trashing for this type, error out.
479
			if ( ! $supports_trash ) {
480
				/* translators: %s: post type */
481
				return new \WP_Error( 'woocommerce_rest_trash_not_supported', sprintf( __( 'The %s does not support trashing.', 'woocommerce' ), $this->post_type ), array( 'status' => 501 ) );
482
			}
483
484
			// Otherwise, only trash if we haven't already.
485
			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 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

485
			if ( is_callable( array( $object, 'get_status' ) ) && 'trash' === $object->/** @scrutinizer ignore-call */ get_status() ) {

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...
486
				/* translators: %s: post type */
487
				return new \WP_Error( 'woocommerce_rest_already_trashed', sprintf( __( 'The %s has already been deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 410 ) );
488
			} else {
489
				$object->delete();
490
				$result = is_callable( array( $object, 'get_status' ) ) ? 'trash' === $object->get_status() : true;
491
			}
492
		}
493
494
		if ( ! $result ) {
495
			/* translators: %s: post type */
496
			return new \WP_Error( 'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 500 ) );
497
		}
498
499
		$response = new \WP_REST_Response();
500
		$response->set_data(
501
			array(
502
				'deleted'  => true,
503
				'previous' => $previous->get_data(),
0 ignored issues
show
Bug introduced by
The method get_data() 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

503
				'previous' => $previous->/** @scrutinizer ignore-call */ get_data(),

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

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