Passed
Push — master ( aa41b1...2b3c87 )
by Mike
04:52
created

ProductVariations::get_item_schema()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 372
Code Lines 295

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 295
nc 1
nop 0
dl 0
loc 372
rs 8
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
 * REST API variations controller
4
 *
5
 * Handles requests to the /products/<product_id>/variations endpoints.
6
 *
7
 * @package WooCommerce/RestApi
8
 */
9
10
namespace WooCommerce\RestApi\Controllers\Version4;
11
12
defined( 'ABSPATH' ) || exit;
13
14
use WooCommerce\RestApi\Controllers\Version4\Schema\ProductVariationRequest;
15
use WooCommerce\RestApi\Controllers\Version4\Schema\ProductVariationResponse;
16
17
/**
18
 * REST API variations controller class.
19
 */
20
class ProductVariations extends Products {
21
22
	/**
23
	 * Route base.
24
	 *
25
	 * @var string
26
	 */
27
	protected $rest_base = 'products/(?P<product_id>[\d]+)/variations';
28
29
	/**
30
	 * Post type.
31
	 *
32
	 * @var string
33
	 */
34
	protected $post_type = 'product_variation';
35
36
	/**
37
	 * Register the routes for products.
38
	 */
39
	public function register_routes() {
40
		register_rest_route(
41
			$this->namespace,
42
			'/' . $this->rest_base,
43
			array(
44
				'args'   => array(
45
					'product_id' => array(
46
						'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
47
						'type'        => 'integer',
48
					),
49
				),
50
				array(
51
					'methods'             => \WP_REST_Server::READABLE,
52
					'callback'            => array( $this, 'get_items' ),
53
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
54
					'args'                => $this->get_collection_params(),
55
				),
56
				array(
57
					'methods'             => \WP_REST_Server::CREATABLE,
58
					'callback'            => array( $this, 'create_item' ),
59
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
60
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
61
				),
62
				'schema' => array( $this, 'get_public_item_schema' ),
63
			),
64
			true
65
		);
66
		register_rest_route(
67
			$this->namespace,
68
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
69
			array(
70
				'args'   => array(
71
					'product_id' => array(
72
						'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
73
						'type'        => 'integer',
74
					),
75
					'id'         => array(
76
						'description' => __( 'Unique identifier for the variation.', 'woocommerce' ),
77
						'type'        => 'integer',
78
					),
79
				),
80
				array(
81
					'methods'             => \WP_REST_Server::READABLE,
82
					'callback'            => array( $this, 'get_item' ),
83
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
84
					'args'                => array(
85
						'context' => $this->get_context_param(
86
							array(
87
								'default' => 'view',
88
							)
89
						),
90
					),
91
				),
92
				array(
93
					'methods'             => \WP_REST_Server::EDITABLE,
94
					'callback'            => array( $this, 'update_item' ),
95
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
96
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
97
				),
98
				array(
99
					'methods'             => \WP_REST_Server::DELETABLE,
100
					'callback'            => array( $this, 'delete_item' ),
101
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
102
					'args'                => array(
103
						'force' => array(
104
							'default'     => false,
105
							'type'        => 'boolean',
106
							'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
107
						),
108
					),
109
				),
110
				'schema' => array( $this, 'get_public_item_schema' ),
111
			),
112
			true
113
		);
114
		register_rest_route(
115
			$this->namespace,
116
			'/' . $this->rest_base . '/batch',
117
			array(
118
				'args'   => array(
119
					'product_id' => array(
120
						'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
121
						'type'        => 'integer',
122
					),
123
				),
124
				array(
125
					'methods'             => \WP_REST_Server::EDITABLE,
126
					'callback'            => array( $this, 'batch_items' ),
127
					'permission_callback' => array( $this, 'batch_items_permissions_check' ),
128
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
129
				),
130
				'schema' => array( $this, 'get_public_batch_schema' ),
131
			),
132
			true
133
		);
134
	}
135
136
	/**
137
	 * Get the Variation's schema, conforming to JSON Schema.
138
	 *
139
	 * @return array
140
	 */
141
	public function get_item_schema() {
142
		$weight_unit    = get_option( 'woocommerce_weight_unit' );
143
		$dimension_unit = get_option( 'woocommerce_dimension_unit' );
144
		$schema         = array(
145
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
146
			'title'      => 'product_variation',
147
			'type'       => 'object',
148
			'properties' => array(
149
				'id'                    => array(
150
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
151
					'type'        => 'integer',
152
					'context'     => array( 'view', 'edit' ),
153
					'readonly'    => true,
154
				),
155
				'name'                  => array(
156
					'description' => __( 'Product parent name.', 'woocommerce' ),
157
					'type'        => 'string',
158
					'context'     => array( 'view', 'edit' ),
159
				),
160
				'type'                  => array(
161
					'description' => __( 'Product type.', 'woocommerce' ),
162
					'type'        => 'string',
163
					'default'     => 'variation',
164
					'enum'        => array( 'variation' ),
165
					'context'     => array( 'view', 'edit' ),
166
				),
167
				'parent_id'             => array(
168
					'description' => __( 'Product parent ID.', 'woocommerce' ),
169
					'type'        => 'integer',
170
					'context'     => array( 'view', 'edit' ),
171
				),
172
				'date_created'          => array(
173
					'description' => __( "The date the variation was created, in the site's timezone.", 'woocommerce' ),
174
					'type'        => 'date-time',
175
					'context'     => array( 'view', 'edit' ),
176
					'readonly'    => true,
177
				),
178
				'date_modified'         => array(
179
					'description' => __( "The date the variation was last modified, in the site's timezone.", 'woocommerce' ),
180
					'type'        => 'date-time',
181
					'context'     => array( 'view', 'edit' ),
182
					'readonly'    => true,
183
				),
184
				'description'           => array(
185
					'description' => __( 'Variation description.', 'woocommerce' ),
186
					'type'        => 'string',
187
					'context'     => array( 'view', 'edit' ),
188
					'arg_options' => array(
189
						'sanitize_callback' => 'wp_filter_post_kses',
190
					),
191
				),
192
				'permalink'             => array(
193
					'description' => __( 'Variation URL.', 'woocommerce' ),
194
					'type'        => 'string',
195
					'format'      => 'uri',
196
					'context'     => array( 'view', 'edit' ),
197
					'readonly'    => true,
198
				),
199
				'sku'                   => array(
200
					'description' => __( 'Unique identifier.', 'woocommerce' ),
201
					'type'        => 'string',
202
					'context'     => array( 'view', 'edit' ),
203
					'arg_options' => array(
204
						'sanitize_callback' => 'wc_clean',
205
					),
206
				),
207
				'price'                 => array(
208
					'description' => __( 'Current variation price.', 'woocommerce' ),
209
					'type'        => 'string',
210
					'context'     => array( 'view', 'edit' ),
211
					'readonly'    => true,
212
				),
213
				'regular_price'         => array(
214
					'description' => __( 'Variation regular price.', 'woocommerce' ),
215
					'type'        => 'string',
216
					'context'     => array( 'view', 'edit' ),
217
				),
218
				'sale_price'            => array(
219
					'description' => __( 'Variation sale price.', 'woocommerce' ),
220
					'type'        => 'string',
221
					'context'     => array( 'view', 'edit' ),
222
				),
223
				'date_on_sale_from'     => array(
224
					'description' => __( "Start date of sale price, in the site's timezone.", 'woocommerce' ),
225
					'type'        => 'date-time',
226
					'context'     => array( 'view', 'edit' ),
227
				),
228
				'date_on_sale_from_gmt' => array(
229
					'description' => __( 'Start date of sale price, as GMT.', 'woocommerce' ),
230
					'type'        => 'date-time',
231
					'context'     => array( 'view', 'edit' ),
232
				),
233
				'date_on_sale_to'       => array(
234
					'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
235
					'type'        => 'date-time',
236
					'context'     => array( 'view', 'edit' ),
237
				),
238
				'date_on_sale_to_gmt'   => array(
239
					'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
240
					'type'        => 'date-time',
241
					'context'     => array( 'view', 'edit' ),
242
				),
243
				'on_sale'               => array(
244
					'description' => __( 'Shows if the variation is on sale.', 'woocommerce' ),
245
					'type'        => 'boolean',
246
					'context'     => array( 'view', 'edit' ),
247
					'readonly'    => true,
248
				),
249
				'status'                => array(
250
					'description' => __( 'Variation status.', 'woocommerce' ),
251
					'type'        => 'string',
252
					'default'     => 'publish',
253
					'enum'        => array_keys( get_post_statuses() ),
254
					'context'     => array( 'view', 'edit' ),
255
				),
256
				'purchasable'           => array(
257
					'description' => __( 'Shows if the variation can be bought.', 'woocommerce' ),
258
					'type'        => 'boolean',
259
					'context'     => array( 'view', 'edit' ),
260
					'readonly'    => true,
261
				),
262
				'virtual'               => array(
263
					'description' => __( 'If the variation is virtual.', 'woocommerce' ),
264
					'type'        => 'boolean',
265
					'default'     => false,
266
					'context'     => array( 'view', 'edit' ),
267
				),
268
				'downloadable'          => array(
269
					'description' => __( 'If the variation is downloadable.', 'woocommerce' ),
270
					'type'        => 'boolean',
271
					'default'     => false,
272
					'context'     => array( 'view', 'edit' ),
273
				),
274
				'downloads'             => array(
275
					'description' => __( 'List of downloadable files.', 'woocommerce' ),
276
					'type'        => 'array',
277
					'context'     => array( 'view', 'edit' ),
278
					'items'       => array(
279
						'type'       => 'object',
280
						'properties' => array(
281
							'id'   => array(
282
								'description' => __( 'File ID.', 'woocommerce' ),
283
								'type'        => 'string',
284
								'context'     => array( 'view', 'edit' ),
285
							),
286
							'name' => array(
287
								'description' => __( 'File name.', 'woocommerce' ),
288
								'type'        => 'string',
289
								'context'     => array( 'view', 'edit' ),
290
							),
291
							'file' => array(
292
								'description' => __( 'File URL.', 'woocommerce' ),
293
								'type'        => 'string',
294
								'context'     => array( 'view', 'edit' ),
295
							),
296
						),
297
					),
298
				),
299
				'download_limit'        => array(
300
					'description' => __( 'Number of times downloadable files can be downloaded after purchase.', 'woocommerce' ),
301
					'type'        => 'integer',
302
					'default'     => -1,
303
					'context'     => array( 'view', 'edit' ),
304
				),
305
				'download_expiry'       => array(
306
					'description' => __( 'Number of days until access to downloadable files expires.', 'woocommerce' ),
307
					'type'        => 'integer',
308
					'default'     => -1,
309
					'context'     => array( 'view', 'edit' ),
310
				),
311
				'tax_status'            => array(
312
					'description' => __( 'Tax status.', 'woocommerce' ),
313
					'type'        => 'string',
314
					'default'     => 'taxable',
315
					'enum'        => array( 'taxable', 'shipping', 'none' ),
316
					'context'     => array( 'view', 'edit' ),
317
				),
318
				'tax_class'             => array(
319
					'description' => __( 'Tax class.', 'woocommerce' ),
320
					'type'        => 'string',
321
					'context'     => array( 'view', 'edit' ),
322
				),
323
				'manage_stock'          => array(
324
					'description' => __( 'Stock management at variation level.', 'woocommerce' ),
325
					'type'        => 'boolean',
326
					'default'     => false,
327
					'context'     => array( 'view', 'edit' ),
328
				),
329
				'stock_quantity'        => array(
330
					'description' => __( 'Stock quantity.', 'woocommerce' ),
331
					'type'        => 'integer',
332
					'context'     => array( 'view', 'edit' ),
333
				),
334
				'stock_status'          => array(
335
					'description' => __( 'Controls the stock status of the product.', 'woocommerce' ),
336
					'type'        => 'string',
337
					'default'     => 'instock',
338
					'enum'        => array_keys( wc_get_product_stock_status_options() ),
339
					'context'     => array( 'view', 'edit' ),
340
				),
341
				'backorders'            => array(
342
					'description' => __( 'If managing stock, this controls if backorders are allowed.', 'woocommerce' ),
343
					'type'        => 'string',
344
					'default'     => 'no',
345
					'enum'        => array( 'no', 'notify', 'yes' ),
346
					'context'     => array( 'view', 'edit' ),
347
				),
348
				'backorders_allowed'    => array(
349
					'description' => __( 'Shows if backorders are allowed.', 'woocommerce' ),
350
					'type'        => 'boolean',
351
					'context'     => array( 'view', 'edit' ),
352
					'readonly'    => true,
353
				),
354
				'backordered'           => array(
355
					'description' => __( 'Shows if the variation is on backordered.', 'woocommerce' ),
356
					'type'        => 'boolean',
357
					'context'     => array( 'view', 'edit' ),
358
					'readonly'    => true,
359
				),
360
				'weight'                => array(
361
					/* translators: %s: weight unit */
362
					'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), $weight_unit ),
0 ignored issues
show
Bug introduced by
It seems like $weight_unit can also be of type false; however, parameter $args of sprintf() does only seem to accept string, 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

362
					'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), /** @scrutinizer ignore-type */ $weight_unit ),
Loading history...
363
					'type'        => 'string',
364
					'context'     => array( 'view', 'edit' ),
365
				),
366
				'dimensions'            => array(
367
					'description' => __( 'Variation dimensions.', 'woocommerce' ),
368
					'type'        => 'object',
369
					'context'     => array( 'view', 'edit' ),
370
					'properties'  => array(
371
						'length' => array(
372
							/* translators: %s: dimension unit */
373
							'description' => sprintf( __( 'Variation length (%s).', 'woocommerce' ), $dimension_unit ),
374
							'type'        => 'string',
375
							'context'     => array( 'view', 'edit' ),
376
						),
377
						'width'  => array(
378
							/* translators: %s: dimension unit */
379
							'description' => sprintf( __( 'Variation width (%s).', 'woocommerce' ), $dimension_unit ),
380
							'type'        => 'string',
381
							'context'     => array( 'view', 'edit' ),
382
						),
383
						'height' => array(
384
							/* translators: %s: dimension unit */
385
							'description' => sprintf( __( 'Variation height (%s).', 'woocommerce' ), $dimension_unit ),
386
							'type'        => 'string',
387
							'context'     => array( 'view', 'edit' ),
388
						),
389
					),
390
				),
391
				'shipping_class'        => array(
392
					'description' => __( 'Shipping class slug.', 'woocommerce' ),
393
					'type'        => 'string',
394
					'context'     => array( 'view', 'edit' ),
395
				),
396
				'shipping_class_id'     => array(
397
					'description' => __( 'Shipping class ID.', 'woocommerce' ),
398
					'type'        => 'string',
399
					'context'     => array( 'view', 'edit' ),
400
					'readonly'    => true,
401
				),
402
				'image'                 => array(
403
					'description' => __( 'Variation image data.', 'woocommerce' ),
404
					'type'        => 'object',
405
					'context'     => array( 'view', 'edit' ),
406
					'properties'  => array(
407
						'id'                => array(
408
							'description' => __( 'Image ID.', 'woocommerce' ),
409
							'type'        => 'integer',
410
							'context'     => array( 'view', 'edit' ),
411
						),
412
						'date_created'      => array(
413
							'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),
414
							'type'        => 'date-time',
415
							'context'     => array( 'view', 'edit' ),
416
							'readonly'    => true,
417
						),
418
						'date_created_gmt'  => array(
419
							'description' => __( 'The date the image was created, as GMT.', 'woocommerce' ),
420
							'type'        => 'date-time',
421
							'context'     => array( 'view', 'edit' ),
422
							'readonly'    => true,
423
						),
424
						'date_modified'     => array(
425
							'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),
426
							'type'        => 'date-time',
427
							'context'     => array( 'view', 'edit' ),
428
							'readonly'    => true,
429
						),
430
						'date_modified_gmt' => array(
431
							'description' => __( 'The date the image was last modified, as GMT.', 'woocommerce' ),
432
							'type'        => 'date-time',
433
							'context'     => array( 'view', 'edit' ),
434
							'readonly'    => true,
435
						),
436
						'src'               => array(
437
							'description' => __( 'Image URL.', 'woocommerce' ),
438
							'type'        => 'string',
439
							'format'      => 'uri',
440
							'context'     => array( 'view', 'edit' ),
441
						),
442
						'name'              => array(
443
							'description' => __( 'Image name.', 'woocommerce' ),
444
							'type'        => 'string',
445
							'context'     => array( 'view', 'edit' ),
446
						),
447
						'alt'               => array(
448
							'description' => __( 'Image alternative text.', 'woocommerce' ),
449
							'type'        => 'string',
450
							'context'     => array( 'view', 'edit' ),
451
						),
452
					),
453
				),
454
				'attributes'            => array(
455
					'description' => __( 'List of attributes.', 'woocommerce' ),
456
					'type'        => 'array',
457
					'context'     => array( 'view', 'edit' ),
458
					'items'       => array(
459
						'type'       => 'object',
460
						'properties' => array(
461
							'id'     => array(
462
								'description' => __( 'Attribute ID.', 'woocommerce' ),
463
								'type'        => 'integer',
464
								'context'     => array( 'view', 'edit' ),
465
							),
466
							'name'   => array(
467
								'description' => __( 'Attribute name.', 'woocommerce' ),
468
								'type'        => 'string',
469
								'context'     => array( 'view', 'edit' ),
470
							),
471
							'option' => array(
472
								'description' => __( 'Selected attribute term name.', 'woocommerce' ),
473
								'type'        => 'string',
474
								'context'     => array( 'view', 'edit' ),
475
							),
476
						),
477
					),
478
				),
479
				'menu_order'            => array(
480
					'description' => __( 'Menu order, used to custom sort products.', 'woocommerce' ),
481
					'type'        => 'integer',
482
					'context'     => array( 'view', 'edit' ),
483
				),
484
				'meta_data'             => array(
485
					'description' => __( 'Meta data.', 'woocommerce' ),
486
					'type'        => 'array',
487
					'context'     => array( 'view', 'edit' ),
488
					'items'       => array(
489
						'type'       => 'object',
490
						'properties' => array(
491
							'id'    => array(
492
								'description' => __( 'Meta ID.', 'woocommerce' ),
493
								'type'        => 'integer',
494
								'context'     => array( 'view', 'edit' ),
495
								'readonly'    => true,
496
							),
497
							'key'   => array(
498
								'description' => __( 'Meta key.', 'woocommerce' ),
499
								'type'        => 'string',
500
								'context'     => array( 'view', 'edit' ),
501
							),
502
							'value' => array(
503
								'description' => __( 'Meta value.', 'woocommerce' ),
504
								'type'        => 'mixed',
505
								'context'     => array( 'view', 'edit' ),
506
							),
507
						),
508
					),
509
				),
510
			),
511
		);
512
		return $this->add_additional_fields_schema( $schema );
513
	}
514
515
	/**
516
	 * Get the query params for collections of attachments.
517
	 *
518
	 * @return array
519
	 */
520
	public function get_collection_params() {
521
		$params = parent::get_collection_params();
522
523
		unset(
524
			$params['in_stock'],
525
			$params['type'],
526
			$params['featured'],
527
			$params['category'],
528
			$params['tag'],
529
			$params['shipping_class'],
530
			$params['attribute'],
531
			$params['attribute_term']
532
		);
533
534
		$params['stock_status'] = array(
535
			'description'       => __( 'Limit result set to products with specified stock status.', 'woocommerce' ),
536
			'type'              => 'string',
537
			'enum'              => array_keys( wc_get_product_stock_status_options() ),
538
			'sanitize_callback' => 'sanitize_text_field',
539
			'validate_callback' => 'rest_validate_request_arg',
540
		);
541
542
		$params['search'] = array(
543
			'description'       => __( 'Search by similar product name or sku.', 'woocommerce' ),
544
			'type'              => 'string',
545
			'validate_callback' => 'rest_validate_request_arg',
546
		);
547
548
		return $params;
549
	}
550
551
	/**
552
	 * Get object.
553
	 *
554
	 * @since  3.0.0
555
	 * @param  int $id Object ID.
556
	 * @return \WC_Data|bool
557
	 */
558
	protected function get_object( $id ) {
559
		return wc_get_product( $id );
560
	}
561
562
	/**
563
	 * Check if a given request has access to update an item.
564
	 *
565
	 * @param  \WP_REST_Request $request Full details about the request.
566
	 * @return \WP_Error|boolean
567
	 */
568
	public function update_item_permissions_check( $request ) {
569
		$object = $this->get_object( (int) $request['id'] );
570
571
		if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'edit', $object->get_id() ) ) {
572
			return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
573
		}
574
575
		// Check if variation belongs to the correct parent product.
576
		if ( $object && 0 !== $object->get_parent_id() && absint( $request['product_id'] ) !== $object->get_parent_id() ) {
577
			return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Parent product does not match current variation.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
578
		}
579
580
		return true;
581
	}
582
583
	/**
584
	 * Prepare a single variation output for response.
585
	 *
586
	 * @param  \WC_Data         $object  Object data.
587
	 * @param  \WP_REST_Request $request Request object.
588
	 * @return \WP_REST_Response
589
	 */
590
	public function prepare_object_for_response( $object, $request ) {
591
		$context            = ! empty( $request['context'] ) ? $request['context'] : 'view';
592
		$variation_response = new ProductVariationResponse();
593
		$data               = $variation_response->prepare_response( $object, $context );
594
		$data               = $this->add_additional_fields_to_object( $data, $request );
595
		$data               = $this->filter_response_by_context( $data, $context );
596
		$response           = rest_ensure_response( $data );
597
		$response->add_links( $this->prepare_links( $object, $request ) );
598
599
		/**
600
		 * Filter the data for a response.
601
		 *
602
		 * The dynamic portion of the hook name, $this->post_type,
603
		 * refers to object type being prepared for the response.
604
		 *
605
		 * @param \WP_REST_Response $response The response object.
606
		 * @param \WC_Data          $object   Object data.
607
		 * @param \WP_REST_Request  $request  Request object.
608
		 */
609
		return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request );
610
	}
611
612
	/**
613
	 * Prepare objects query.
614
	 *
615
	 * @since  3.0.0
616
	 * @param  \WP_REST_Request $request Full details about the request.
617
	 * @return array
618
	 */
619
	protected function prepare_objects_query( $request ) {
620
		$args = parent::prepare_objects_query( $request );
621
622
		// Set post_status.
623
		$args['post_status'] = $request['status'];
624
625
		// Set custom args to handle later during clauses.
626
		$custom_keys = array(
627
			'sku',
628
			'min_price',
629
			'max_price',
630
			'stock_status',
631
			'low_in_stock',
632
		);
633
		foreach ( $custom_keys as $key ) {
634
			if ( ! empty( $request[ $key ] ) ) {
635
				$args[ $key ] = $request[ $key ];
636
			}
637
		}
638
639
		// Filter by tax class.
640
		if ( ! empty( $request['tax_class'] ) ) {
641
			$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
642
				$args,
643
				array(
644
					'key'   => '_tax_class',
645
					'value' => 'standard' !== $request['tax_class'] ? $request['tax_class'] : '',
646
				)
647
			);
648
		}
649
650
		// Filter by on sale products.
651
		if ( is_bool( $request['on_sale'] ) ) {
652
			$on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in';
653
			$on_sale_ids = wc_get_product_ids_on_sale();
654
655
			// Use 0 when there's no on sale products to avoid return all products.
656
			$on_sale_ids = empty( $on_sale_ids ) ? array( 0 ) : $on_sale_ids;
657
658
			$args[ $on_sale_key ] += $on_sale_ids;
659
		}
660
661
		// Force the post_type argument, since it's not a user input variable.
662
		if ( ! empty( $request['sku'] ) ) {
663
			$args['post_type'] = array( 'product', 'product_variation' );
664
		} else {
665
			$args['post_type'] = $this->post_type;
666
		}
667
668
		$args['post_parent'] = $request['product_id'];
669
670
		if ( ! empty( $request['search'] ) ) {
671
			$args['search'] = $request['search'];
672
			unset( $args['s'] );
673
		}
674
675
		return $args;
676
	}
677
678
	/**
679
	 * Prepare a single variation for create or update.
680
	 *
681
	 * @param  \WP_REST_Request $request Request object.
682
	 * @param  bool             $creating If is creating a new object.
683
	 * @return \WP_Error|\WC_Data
684
	 */
685
	protected function prepare_object_for_database( $request, $creating = false ) {
686
		try {
687
			$variation_request = new ProductVariationRequest( $request );
688
			$variation         = $variation_request->prepare_object();
689
		} catch ( \WC_REST_Exception $e ) {
690
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
691
		}
692
693
		/**
694
		 * Filters an object before it is inserted via the REST API.
695
		 *
696
		 * The dynamic portion of the hook name, `$this->post_type`,
697
		 * refers to the object type slug.
698
		 *
699
		 * @param \WC_Data         $variation Object object.
700
		 * @param \WP_REST_Request $request   Request object.
701
		 * @param bool            $creating  If is creating a new object.
702
		 */
703
		return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $variation, $request, $creating );
704
	}
705
706
707
	/**
708
	 * Delete a variation.
709
	 *
710
	 * @param \WP_REST_Request $request Full details about the request.
711
	 *
712
	 * @return bool|\WP_Error|\WP_REST_Response
713
	 */
714
	public function delete_item( $request ) {
715
		$force  = (bool) $request['force'];
716
		$object = $this->get_object( (int) $request['id'] );
717
		$result = false;
718
719
		if ( ! $object || 0 === $object->get_id() ) {
0 ignored issues
show
introduced by
$object is of type WC_Product, thus it always evaluated to true.
Loading history...
720
			return new \WP_Error(
721
				"woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array(
722
					'status' => 404,
723
				)
724
			);
725
		}
726
727
		$supports_trash = EMPTY_TRASH_DAYS > 0 && is_callable( array( $object, 'get_status' ) );
728
729
		/**
730
		 * Filter whether an object is trashable.
731
		 *
732
		 * Return false to disable trash support for the object.
733
		 *
734
		 * @param boolean  $supports_trash Whether the object type support trashing.
735
		 * @param \WC_Data $object         The object being considered for trashing support.
736
		 */
737
		$supports_trash = apply_filters( "woocommerce_rest_{$this->post_type}_object_trashable", $supports_trash, $object );
738
739
		if ( ! wc_rest_check_post_permissions( $this->post_type, 'delete', $object->get_id() ) ) {
740
			return new \WP_Error(
741
				/* translators: %s: post type */
742
				"woocommerce_rest_user_cannot_delete_{$this->post_type}", sprintf( __( 'Sorry, you are not allowed to delete %s.', 'woocommerce' ), $this->post_type ), array(
743
					'status' => rest_authorization_required_code(),
744
				)
745
			);
746
		}
747
748
		$request->set_param( 'context', 'edit' );
749
750
		// If we're forcing, then delete permanently.
751
		if ( $force ) {
752
			$previous = $this->prepare_object_for_response( $object, $request );
753
754
			$object->delete( true );
755
756
			$result   = 0 === $object->get_id();
757
			$response = new \WP_REST_Response();
758
			$response->set_data(
759
				array(
760
					'deleted'  => true,
761
					'previous' => $previous->get_data(),
762
				)
763
			);
764
		} else {
765
			// If we don't support trashing for this type, error out.
766
			if ( ! $supports_trash ) {
767
				return new \WP_Error(
768
					/* translators: %s: post type */
769
					'woocommerce_rest_trash_not_supported', sprintf( __( 'The %s does not support trashing.', 'woocommerce' ), $this->post_type ), array(
770
						'status' => 501,
771
					)
772
				);
773
			}
774
775
			// Otherwise, only trash if we haven't already.
776
			if ( is_callable( array( $object, 'get_status' ) ) ) {
777
				if ( 'trash' === $object->get_status() ) {
778
					return new \WP_Error(
779
						/* translators: %s: post type */
780
						'woocommerce_rest_already_trashed', sprintf( __( 'The %s has already been deleted.', 'woocommerce' ), $this->post_type ), array(
781
							'status' => 410,
782
						)
783
					);
784
				}
785
786
				$object->delete();
787
				$result = 'trash' === $object->get_status();
788
			}
789
790
			$response = $this->prepare_object_for_response( $object, $request );
791
		}
792
793
		if ( ! $result ) {
794
			return new \WP_Error(
795
				/* translators: %s: post type */
796
				'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array(
797
					'status' => 500,
798
				)
799
			);
800
		}
801
802
		/**
803
		 * Fires after a single object is deleted or trashed via the REST API.
804
		 *
805
		 * @param \WC_Data          $object   The deleted or trashed object.
806
		 * @param \WP_REST_Response $response The response data.
807
		 * @param \WP_REST_Request  $request  The request sent to the API.
808
		 */
809
		do_action( "woocommerce_rest_delete_{$this->post_type}_object", $object, $response, $request );
810
811
		return $response;
812
	}
813
814
	/**
815
	 * Get batch of items from requst.
816
	 *
817
	 * @param \WP_REST_Request $request Full details about the request.
818
	 * @param string           $batch_type Batch type; one of create, update, delete.
819
	 * @return array
820
	 */
821
	protected function get_batch_of_items_from_request( $request, $batch_type ) {
822
		$params     = $request->get_params();
823
		$url_params = $request->get_url_params();
824
		$product_id = $url_params['product_id'];
825
826
		if ( ! isset( $params[ $batch_type ] ) ) {
827
			return array();
828
		}
829
830
		$items = array_filter( $params[ $batch_type ] );
831
832
		if ( 'update' === $batch_type || 'create' === $batch_type ) {
833
			foreach ( $items as $key => $item ) {
834
				$items[ $key ] = array_merge(
835
					array(
836
						'product_id' => $product_id,
837
					),
838
					$item
839
				);
840
			}
841
		}
842
843
		return array_filter( $items );
844
	}
845
846
	/**
847
	 * Prepare links for the request.
848
	 *
849
	 * @param \WC_Data         $object  Object data.
850
	 * @param \WP_REST_Request $request Request object.
851
	 * @return array                   Links for the given post.
852
	 */
853
	protected function prepare_links( $object, $request ) {
854
		$product_id = (int) $request['product_id'];
855
		$base       = str_replace( '(?P<product_id>[\d]+)', $product_id, $this->rest_base );
856
		$links      = array(
857
			'self'       => array(
858
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $object->get_id() ) ),
859
			),
860
			'collection' => array(
861
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),
862
			),
863
			'up'         => array(
864
				'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product_id ) ),
865
			),
866
		);
867
		return $links;
868
	}
869
}
870