Passed
Push — master ( 5bd17a...71a32c )
by Mike
04:53
created

ProductVariations   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 880
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 513
dl 0
loc 880
rs 8.8
c 0
b 0
f 0
wmc 45

14 Methods

Rating   Name   Duplication   Size   Complexity  
A get_object() 0 2 1
A get_collection_params() 0 29 1
B get_item_schema() 0 372 1
B register_routes() 0 94 1
A prepare_links() 0 15 1
A prepare_object_for_database() 0 19 2
A check_valid_variation_id() 0 13 6
B prepare_objects_query() 0 57 10
B delete_item() 0 98 10
A get_item_permissions_check() 0 8 2
A delete_item_permissions_check() 0 8 2
A update_item_permissions_check() 0 8 2
A get_batch_of_items_from_request() 0 23 5
A get_data_for_response() 0 4 1

How to fix   Complexity   

Complex Class

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

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

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

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

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