Completed
Push — master ( ee9494...a1e9b4 )
by Mike
45:32
created

WC_REST_Product_Variations_Controller   F

Complexity

Total Complexity 79

Size/Duplication

Total Lines 840
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 513
dl 0
loc 840
rs 2.08
c 0
b 0
f 0
wmc 79

How to fix   Complexity   

Complex Class

Complex classes like WC_REST_Product_Variations_Controller 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 WC_REST_Product_Variations_Controller, 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
 * @since   3.0.0
9
 */
10
11
defined( 'ABSPATH' ) || exit;
12
13
/**
14
 * REST API variations controller class.
15
 *
16
 * @package WooCommerce/RestApi
17
 * @extends WC_REST_Product_Variations_V2_Controller
18
 */
19
class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V2_Controller {
20
21
	/**
22
	 * Endpoint namespace.
23
	 *
24
	 * @var string
25
	 */
26
	protected $namespace = 'wc/v3';
27
28
	/**
29
	 * Prepare a single variation output for response.
30
	 *
31
	 * @param  WC_Data         $object  Object data.
32
	 * @param  WP_REST_Request $request Request object.
33
	 * @return WP_REST_Response
34
	 */
35
	public function prepare_object_for_response( $object, $request ) {
36
		$data = array(
37
			'id'                    => $object->get_id(),
38
			'date_created'          => wc_rest_prepare_date_response( $object->get_date_created(), false ),
39
			'date_created_gmt'      => wc_rest_prepare_date_response( $object->get_date_created() ),
40
			'date_modified'         => wc_rest_prepare_date_response( $object->get_date_modified(), false ),
41
			'date_modified_gmt'     => wc_rest_prepare_date_response( $object->get_date_modified() ),
42
			'description'           => wc_format_content( $object->get_description() ),
43
			'permalink'             => $object->get_permalink(),
44
			'sku'                   => $object->get_sku(),
45
			'price'                 => $object->get_price(),
46
			'regular_price'         => $object->get_regular_price(),
47
			'sale_price'            => $object->get_sale_price(),
48
			'date_on_sale_from'     => wc_rest_prepare_date_response( $object->get_date_on_sale_from(), false ),
49
			'date_on_sale_from_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_from() ),
50
			'date_on_sale_to'       => wc_rest_prepare_date_response( $object->get_date_on_sale_to(), false ),
51
			'date_on_sale_to_gmt'   => wc_rest_prepare_date_response( $object->get_date_on_sale_to() ),
52
			'on_sale'               => $object->is_on_sale(),
53
			'status'                => $object->get_status(),
54
			'purchasable'           => $object->is_purchasable(),
55
			'virtual'               => $object->is_virtual(),
56
			'downloadable'          => $object->is_downloadable(),
57
			'downloads'             => $this->get_downloads( $object ),
58
			'download_limit'        => '' !== $object->get_download_limit() ? (int) $object->get_download_limit() : -1,
59
			'download_expiry'       => '' !== $object->get_download_expiry() ? (int) $object->get_download_expiry() : -1,
60
			'tax_status'            => $object->get_tax_status(),
61
			'tax_class'             => $object->get_tax_class(),
62
			'manage_stock'          => $object->managing_stock(),
63
			'stock_quantity'        => $object->get_stock_quantity(),
64
			'stock_status'          => $object->get_stock_status(),
65
			'backorders'            => $object->get_backorders(),
66
			'backorders_allowed'    => $object->backorders_allowed(),
67
			'backordered'           => $object->is_on_backorder(),
68
			'weight'                => $object->get_weight(),
69
			'dimensions'            => array(
70
				'length' => $object->get_length(),
71
				'width'  => $object->get_width(),
72
				'height' => $object->get_height(),
73
			),
74
			'shipping_class'        => $object->get_shipping_class(),
75
			'shipping_class_id'     => $object->get_shipping_class_id(),
76
			'image'                 => $this->get_image( $object ),
77
			'attributes'            => $this->get_attributes( $object ),
78
			'menu_order'            => $object->get_menu_order(),
79
			'meta_data'             => $object->get_meta_data(),
80
		);
81
82
		$context  = ! empty( $request['context'] ) ? $request['context'] : 'view';
83
		$data     = $this->add_additional_fields_to_object( $data, $request );
84
		$data     = $this->filter_response_by_context( $data, $context );
85
		$response = rest_ensure_response( $data );
86
		$response->add_links( $this->prepare_links( $object, $request ) );
87
88
		/**
89
		 * Filter the data for a response.
90
		 *
91
		 * The dynamic portion of the hook name, $this->post_type,
92
		 * refers to object type being prepared for the response.
93
		 *
94
		 * @param WP_REST_Response $response The response object.
95
		 * @param WC_Data          $object   Object data.
96
		 * @param WP_REST_Request  $request  Request object.
97
		 */
98
		return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request );
99
	}
100
101
	/**
102
	 * Prepare a single variation for create or update.
103
	 *
104
	 * @param  WP_REST_Request $request Request object.
105
	 * @param  bool            $creating If is creating a new object.
106
	 * @return WP_Error|WC_Data
107
	 */
108
	protected function prepare_object_for_database( $request, $creating = false ) {
109
		if ( isset( $request['id'] ) ) {
110
			$variation = wc_get_product( absint( $request['id'] ) );
111
		} else {
112
			$variation = new WC_Product_Variation();
113
		}
114
115
		$variation->set_parent_id( absint( $request['product_id'] ) );
116
117
		// Status.
118
		if ( isset( $request['status'] ) ) {
119
			$variation->set_status( get_post_status_object( $request['status'] ) ? $request['status'] : 'draft' );
120
		}
121
122
		// SKU.
123
		if ( isset( $request['sku'] ) ) {
124
			$variation->set_sku( wc_clean( $request['sku'] ) );
125
		}
126
127
		// Thumbnail.
128
		if ( isset( $request['image'] ) ) {
129
			if ( is_array( $request['image'] ) ) {
130
				$variation = $this->set_variation_image( $variation, $request['image'] );
131
			} else {
132
				$variation->set_image_id( '' );
133
			}
134
		}
135
136
		// Virtual variation.
137
		if ( isset( $request['virtual'] ) ) {
138
			$variation->set_virtual( $request['virtual'] );
139
		}
140
141
		// Downloadable variation.
142
		if ( isset( $request['downloadable'] ) ) {
143
			$variation->set_downloadable( $request['downloadable'] );
144
		}
145
146
		// Downloads.
147
		if ( $variation->get_downloadable() ) {
148
			// Downloadable files.
149
			if ( isset( $request['downloads'] ) && is_array( $request['downloads'] ) ) {
150
				$variation = $this->save_downloadable_files( $variation, $request['downloads'] );
151
			}
152
153
			// Download limit.
154
			if ( isset( $request['download_limit'] ) ) {
155
				$variation->set_download_limit( $request['download_limit'] );
156
			}
157
158
			// Download expiry.
159
			if ( isset( $request['download_expiry'] ) ) {
160
				$variation->set_download_expiry( $request['download_expiry'] );
161
			}
162
		}
163
164
		// Shipping data.
165
		$variation = $this->save_product_shipping_data( $variation, $request );
166
167
		// Stock handling.
168
		if ( isset( $request['manage_stock'] ) ) {
169
			$variation->set_manage_stock( $request['manage_stock'] );
170
		}
171
172
		if ( isset( $request['stock_status'] ) ) {
173
			$variation->set_stock_status( $request['stock_status'] );
174
		}
175
176
		if ( isset( $request['backorders'] ) ) {
177
			$variation->set_backorders( $request['backorders'] );
178
		}
179
180
		if ( $variation->get_manage_stock() ) {
181
			if ( isset( $request['stock_quantity'] ) ) {
182
				$variation->set_stock_quantity( $request['stock_quantity'] );
183
			} elseif ( isset( $request['inventory_delta'] ) ) {
184
				$stock_quantity  = wc_stock_amount( $variation->get_stock_quantity() );
185
				$stock_quantity += wc_stock_amount( $request['inventory_delta'] );
186
				$variation->set_stock_quantity( $stock_quantity );
187
			}
188
		} else {
189
			$variation->set_backorders( 'no' );
190
			$variation->set_stock_quantity( '' );
191
		}
192
193
		// Regular Price.
194
		if ( isset( $request['regular_price'] ) ) {
195
			$variation->set_regular_price( $request['regular_price'] );
196
		}
197
198
		// Sale Price.
199
		if ( isset( $request['sale_price'] ) ) {
200
			$variation->set_sale_price( $request['sale_price'] );
201
		}
202
203
		if ( isset( $request['date_on_sale_from'] ) ) {
204
			$variation->set_date_on_sale_from( $request['date_on_sale_from'] );
205
		}
206
207
		if ( isset( $request['date_on_sale_from_gmt'] ) ) {
208
			$variation->set_date_on_sale_from( $request['date_on_sale_from_gmt'] ? strtotime( $request['date_on_sale_from_gmt'] ) : null );
209
		}
210
211
		if ( isset( $request['date_on_sale_to'] ) ) {
212
			$variation->set_date_on_sale_to( $request['date_on_sale_to'] );
213
		}
214
215
		if ( isset( $request['date_on_sale_to_gmt'] ) ) {
216
			$variation->set_date_on_sale_to( $request['date_on_sale_to_gmt'] ? strtotime( $request['date_on_sale_to_gmt'] ) : null );
217
		}
218
219
		// Tax class.
220
		if ( isset( $request['tax_class'] ) ) {
221
			$variation->set_tax_class( $request['tax_class'] );
222
		}
223
224
		// Description.
225
		if ( isset( $request['description'] ) ) {
226
			$variation->set_description( wp_kses_post( $request['description'] ) );
227
		}
228
229
		// Update taxonomies.
230
		if ( isset( $request['attributes'] ) ) {
231
			$attributes = array();
232
			$parent     = wc_get_product( $variation->get_parent_id() );
233
234
			if ( ! $parent ) {
235
				return new WP_Error(
236
					// Translators: %d parent ID.
237
					"woocommerce_rest_{$this->post_type}_invalid_parent", sprintf( __( 'Cannot set attributes due to invalid parent product.', 'woocommerce' ), $variation->get_parent_id() ), array(
238
						'status' => 404,
239
					)
240
				);
241
			}
242
243
			$parent_attributes = $parent->get_attributes();
244
245
			foreach ( $request['attributes'] as $attribute ) {
246
				$attribute_id   = 0;
247
				$attribute_name = '';
248
249
				// Check ID for global attributes or name for product attributes.
250
				if ( ! empty( $attribute['id'] ) ) {
251
					$attribute_id   = absint( $attribute['id'] );
252
					$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
253
				} elseif ( ! empty( $attribute['name'] ) ) {
254
					$attribute_name = sanitize_title( $attribute['name'] );
255
				}
256
257
				if ( ! $attribute_id && ! $attribute_name ) {
258
					continue;
259
				}
260
261
				if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) {
262
					continue;
263
				}
264
265
				$attribute_key   = sanitize_title( $parent_attributes[ $attribute_name ]->get_name() );
266
				$attribute_value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : '';
267
268
				if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) {
269
					// If dealing with a taxonomy, we need to get the slug from the name posted to the API.
270
					$term = get_term_by( 'name', $attribute_value, $attribute_name );
271
272
					if ( $term && ! is_wp_error( $term ) ) {
273
						$attribute_value = $term->slug;
274
					} else {
275
						$attribute_value = sanitize_title( $attribute_value );
276
					}
277
				}
278
279
				$attributes[ $attribute_key ] = $attribute_value;
280
			}
281
282
			$variation->set_attributes( $attributes );
283
		}
284
285
		// Menu order.
286
		if ( $request['menu_order'] ) {
287
			$variation->set_menu_order( $request['menu_order'] );
288
		}
289
290
		// Meta data.
291
		if ( is_array( $request['meta_data'] ) ) {
292
			foreach ( $request['meta_data'] as $meta ) {
293
				$variation->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
294
			}
295
		}
296
297
		/**
298
		 * Filters an object before it is inserted via the REST API.
299
		 *
300
		 * The dynamic portion of the hook name, `$this->post_type`,
301
		 * refers to the object type slug.
302
		 *
303
		 * @param WC_Data         $variation Object object.
304
		 * @param WP_REST_Request $request   Request object.
305
		 * @param bool            $creating  If is creating a new object.
306
		 */
307
		return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $variation, $request, $creating );
308
	}
309
310
	/**
311
	 * Get the image for a product variation.
312
	 *
313
	 * @param WC_Product_Variation $variation Variation data.
314
	 * @return array
315
	 */
316
	protected function get_image( $variation ) {
317
		if ( ! $variation->get_image_id() ) {
318
			return;
319
		}
320
321
		$attachment_id   = $variation->get_image_id();
322
		$attachment_post = get_post( $attachment_id );
323
		if ( is_null( $attachment_post ) ) {
324
			return;
325
		}
326
327
		$attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
328
		if ( ! is_array( $attachment ) ) {
329
			return;
330
		}
331
332
		if ( ! isset( $image ) ) {
333
			return array(
334
				'id'                => (int) $attachment_id,
335
				'date_created'      => wc_rest_prepare_date_response( $attachment_post->post_date, false ),
336
				'date_created_gmt'  => wc_rest_prepare_date_response( strtotime( $attachment_post->post_date_gmt ) ),
337
				'date_modified'     => wc_rest_prepare_date_response( $attachment_post->post_modified, false ),
338
				'date_modified_gmt' => wc_rest_prepare_date_response( strtotime( $attachment_post->post_modified_gmt ) ),
339
				'src'               => current( $attachment ),
340
				'name'              => get_the_title( $attachment_id ),
341
				'alt'               => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
342
			);
343
		}
344
	}
345
346
	/**
347
	 * Set variation image.
348
	 *
349
	 * @throws WC_REST_Exception REST API exceptions.
350
	 * @param  WC_Product_Variation $variation Variation instance.
351
	 * @param  array                $image    Image data.
352
	 * @return WC_Product_Variation
353
	 */
354
	protected function set_variation_image( $variation, $image ) {
355
		$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
356
357
		if ( 0 === $attachment_id && isset( $image['src'] ) ) {
358
			$upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) );
359
360
			if ( is_wp_error( $upload ) ) {
361
				if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $variation->get_id(), array( $image ) ) ) {
362
					throw new WC_REST_Exception( 'woocommerce_variation_image_upload_error', $upload->get_error_message(), 400 );
363
				}
364
			}
365
366
			$attachment_id = wc_rest_set_uploaded_image_as_attachment( $upload, $variation->get_id() );
367
		}
368
369
		if ( ! wp_attachment_is_image( $attachment_id ) ) {
370
			/* translators: %s: attachment ID */
371
			throw new WC_REST_Exception( 'woocommerce_variation_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'woocommerce' ), $attachment_id ), 400 );
372
		}
373
374
		$variation->set_image_id( $attachment_id );
375
376
		// Set the image alt if present.
377
		if ( ! empty( $image['alt'] ) ) {
378
			update_post_meta( $attachment_id, '_wp_attachment_image_alt', wc_clean( $image['alt'] ) );
379
		}
380
381
		// Set the image name if present.
382
		if ( ! empty( $image['name'] ) ) {
383
			wp_update_post(
384
				array(
385
					'ID'         => $attachment_id,
386
					'post_title' => $image['name'],
387
				)
388
			);
389
		}
390
391
		return $variation;
392
	}
393
394
	/**
395
	 * Get the Variation's schema, conforming to JSON Schema.
396
	 *
397
	 * @return array
398
	 */
399
	public function get_item_schema() {
400
		$weight_unit    = get_option( 'woocommerce_weight_unit' );
401
		$dimension_unit = get_option( 'woocommerce_dimension_unit' );
402
		$schema         = array(
403
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
404
			'title'      => $this->post_type,
405
			'type'       => 'object',
406
			'properties' => array(
407
				'id'                    => array(
408
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
409
					'type'        => 'integer',
410
					'context'     => array( 'view', 'edit' ),
411
					'readonly'    => true,
412
				),
413
				'date_created'          => array(
414
					'description' => __( "The date the variation was created, in the site's timezone.", 'woocommerce' ),
415
					'type'        => 'date-time',
416
					'context'     => array( 'view', 'edit' ),
417
					'readonly'    => true,
418
				),
419
				'date_modified'         => array(
420
					'description' => __( "The date the variation was last modified, in the site's timezone.", 'woocommerce' ),
421
					'type'        => 'date-time',
422
					'context'     => array( 'view', 'edit' ),
423
					'readonly'    => true,
424
				),
425
				'description'           => array(
426
					'description' => __( 'Variation description.', 'woocommerce' ),
427
					'type'        => 'string',
428
					'context'     => array( 'view', 'edit' ),
429
				),
430
				'permalink'             => array(
431
					'description' => __( 'Variation URL.', 'woocommerce' ),
432
					'type'        => 'string',
433
					'format'      => 'uri',
434
					'context'     => array( 'view', 'edit' ),
435
					'readonly'    => true,
436
				),
437
				'sku'                   => array(
438
					'description' => __( 'Unique identifier.', 'woocommerce' ),
439
					'type'        => 'string',
440
					'context'     => array( 'view', 'edit' ),
441
				),
442
				'price'                 => array(
443
					'description' => __( 'Current variation price.', 'woocommerce' ),
444
					'type'        => 'string',
445
					'context'     => array( 'view', 'edit' ),
446
					'readonly'    => true,
447
				),
448
				'regular_price'         => array(
449
					'description' => __( 'Variation regular price.', 'woocommerce' ),
450
					'type'        => 'string',
451
					'context'     => array( 'view', 'edit' ),
452
				),
453
				'sale_price'            => array(
454
					'description' => __( 'Variation sale price.', 'woocommerce' ),
455
					'type'        => 'string',
456
					'context'     => array( 'view', 'edit' ),
457
				),
458
				'date_on_sale_from'     => array(
459
					'description' => __( "Start date of sale price, in the site's timezone.", 'woocommerce' ),
460
					'type'        => 'date-time',
461
					'context'     => array( 'view', 'edit' ),
462
				),
463
				'date_on_sale_from_gmt' => array(
464
					'description' => __( 'Start date of sale price, as GMT.', 'woocommerce' ),
465
					'type'        => 'date-time',
466
					'context'     => array( 'view', 'edit' ),
467
				),
468
				'date_on_sale_to'       => array(
469
					'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
470
					'type'        => 'date-time',
471
					'context'     => array( 'view', 'edit' ),
472
				),
473
				'date_on_sale_to_gmt'   => array(
474
					'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
475
					'type'        => 'date-time',
476
					'context'     => array( 'view', 'edit' ),
477
				),
478
				'on_sale'               => array(
479
					'description' => __( 'Shows if the variation is on sale.', 'woocommerce' ),
480
					'type'        => 'boolean',
481
					'context'     => array( 'view', 'edit' ),
482
					'readonly'    => true,
483
				),
484
				'status'                => array(
485
					'description' => __( 'Variation status.', 'woocommerce' ),
486
					'type'        => 'string',
487
					'default'     => 'publish',
488
					'enum'        => array_keys( get_post_statuses() ),
489
					'context'     => array( 'view', 'edit' ),
490
				),
491
				'purchasable'           => array(
492
					'description' => __( 'Shows if the variation can be bought.', 'woocommerce' ),
493
					'type'        => 'boolean',
494
					'context'     => array( 'view', 'edit' ),
495
					'readonly'    => true,
496
				),
497
				'virtual'               => array(
498
					'description' => __( 'If the variation is virtual.', 'woocommerce' ),
499
					'type'        => 'boolean',
500
					'default'     => false,
501
					'context'     => array( 'view', 'edit' ),
502
				),
503
				'downloadable'          => array(
504
					'description' => __( 'If the variation is downloadable.', 'woocommerce' ),
505
					'type'        => 'boolean',
506
					'default'     => false,
507
					'context'     => array( 'view', 'edit' ),
508
				),
509
				'downloads'             => array(
510
					'description' => __( 'List of downloadable files.', 'woocommerce' ),
511
					'type'        => 'array',
512
					'context'     => array( 'view', 'edit' ),
513
					'items'       => array(
514
						'type'       => 'object',
515
						'properties' => array(
516
							'id'   => array(
517
								'description' => __( 'File ID.', 'woocommerce' ),
518
								'type'        => 'string',
519
								'context'     => array( 'view', 'edit' ),
520
							),
521
							'name' => array(
522
								'description' => __( 'File name.', 'woocommerce' ),
523
								'type'        => 'string',
524
								'context'     => array( 'view', 'edit' ),
525
							),
526
							'file' => array(
527
								'description' => __( 'File URL.', 'woocommerce' ),
528
								'type'        => 'string',
529
								'context'     => array( 'view', 'edit' ),
530
							),
531
						),
532
					),
533
				),
534
				'download_limit'        => array(
535
					'description' => __( 'Number of times downloadable files can be downloaded after purchase.', 'woocommerce' ),
536
					'type'        => 'integer',
537
					'default'     => -1,
538
					'context'     => array( 'view', 'edit' ),
539
				),
540
				'download_expiry'       => array(
541
					'description' => __( 'Number of days until access to downloadable files expires.', 'woocommerce' ),
542
					'type'        => 'integer',
543
					'default'     => -1,
544
					'context'     => array( 'view', 'edit' ),
545
				),
546
				'tax_status'            => array(
547
					'description' => __( 'Tax status.', 'woocommerce' ),
548
					'type'        => 'string',
549
					'default'     => 'taxable',
550
					'enum'        => array( 'taxable', 'shipping', 'none' ),
551
					'context'     => array( 'view', 'edit' ),
552
				),
553
				'tax_class'             => array(
554
					'description' => __( 'Tax class.', 'woocommerce' ),
555
					'type'        => 'string',
556
					'context'     => array( 'view', 'edit' ),
557
				),
558
				'manage_stock'          => array(
559
					'description' => __( 'Stock management at variation level.', 'woocommerce' ),
560
					'type'        => 'boolean',
561
					'default'     => false,
562
					'context'     => array( 'view', 'edit' ),
563
				),
564
				'stock_quantity'        => array(
565
					'description' => __( 'Stock quantity.', 'woocommerce' ),
566
					'type'        => 'integer',
567
					'context'     => array( 'view', 'edit' ),
568
				),
569
				'stock_status'          => array(
570
					'description' => __( 'Controls the stock status of the product.', 'woocommerce' ),
571
					'type'        => 'string',
572
					'default'     => 'instock',
573
					'enum'        => array_keys( wc_get_product_stock_status_options() ),
574
					'context'     => array( 'view', 'edit' ),
575
				),
576
				'backorders'            => array(
577
					'description' => __( 'If managing stock, this controls if backorders are allowed.', 'woocommerce' ),
578
					'type'        => 'string',
579
					'default'     => 'no',
580
					'enum'        => array( 'no', 'notify', 'yes' ),
581
					'context'     => array( 'view', 'edit' ),
582
				),
583
				'backorders_allowed'    => array(
584
					'description' => __( 'Shows if backorders are allowed.', 'woocommerce' ),
585
					'type'        => 'boolean',
586
					'context'     => array( 'view', 'edit' ),
587
					'readonly'    => true,
588
				),
589
				'backordered'           => array(
590
					'description' => __( 'Shows if the variation is on backordered.', 'woocommerce' ),
591
					'type'        => 'boolean',
592
					'context'     => array( 'view', 'edit' ),
593
					'readonly'    => true,
594
				),
595
				'weight'                => array(
596
					/* translators: %s: weight unit */
597
					'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), $weight_unit ),
598
					'type'        => 'string',
599
					'context'     => array( 'view', 'edit' ),
600
				),
601
				'dimensions'            => array(
602
					'description' => __( 'Variation dimensions.', 'woocommerce' ),
603
					'type'        => 'object',
604
					'context'     => array( 'view', 'edit' ),
605
					'properties'  => array(
606
						'length' => array(
607
							/* translators: %s: dimension unit */
608
							'description' => sprintf( __( 'Variation length (%s).', 'woocommerce' ), $dimension_unit ),
609
							'type'        => 'string',
610
							'context'     => array( 'view', 'edit' ),
611
						),
612
						'width'  => array(
613
							/* translators: %s: dimension unit */
614
							'description' => sprintf( __( 'Variation width (%s).', 'woocommerce' ), $dimension_unit ),
615
							'type'        => 'string',
616
							'context'     => array( 'view', 'edit' ),
617
						),
618
						'height' => array(
619
							/* translators: %s: dimension unit */
620
							'description' => sprintf( __( 'Variation height (%s).', 'woocommerce' ), $dimension_unit ),
621
							'type'        => 'string',
622
							'context'     => array( 'view', 'edit' ),
623
						),
624
					),
625
				),
626
				'shipping_class'        => array(
627
					'description' => __( 'Shipping class slug.', 'woocommerce' ),
628
					'type'        => 'string',
629
					'context'     => array( 'view', 'edit' ),
630
				),
631
				'shipping_class_id'     => array(
632
					'description' => __( 'Shipping class ID.', 'woocommerce' ),
633
					'type'        => 'string',
634
					'context'     => array( 'view', 'edit' ),
635
					'readonly'    => true,
636
				),
637
				'image'                 => array(
638
					'description' => __( 'Variation image data.', 'woocommerce' ),
639
					'type'        => 'object',
640
					'context'     => array( 'view', 'edit' ),
641
					'properties'  => array(
642
						'id'                => array(
643
							'description' => __( 'Image ID.', 'woocommerce' ),
644
							'type'        => 'integer',
645
							'context'     => array( 'view', 'edit' ),
646
						),
647
						'date_created'      => array(
648
							'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),
649
							'type'        => 'date-time',
650
							'context'     => array( 'view', 'edit' ),
651
							'readonly'    => true,
652
						),
653
						'date_created_gmt'  => array(
654
							'description' => __( 'The date the image was created, as GMT.', 'woocommerce' ),
655
							'type'        => 'date-time',
656
							'context'     => array( 'view', 'edit' ),
657
							'readonly'    => true,
658
						),
659
						'date_modified'     => array(
660
							'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),
661
							'type'        => 'date-time',
662
							'context'     => array( 'view', 'edit' ),
663
							'readonly'    => true,
664
						),
665
						'date_modified_gmt' => array(
666
							'description' => __( 'The date the image was last modified, as GMT.', 'woocommerce' ),
667
							'type'        => 'date-time',
668
							'context'     => array( 'view', 'edit' ),
669
							'readonly'    => true,
670
						),
671
						'src'               => array(
672
							'description' => __( 'Image URL.', 'woocommerce' ),
673
							'type'        => 'string',
674
							'format'      => 'uri',
675
							'context'     => array( 'view', 'edit' ),
676
						),
677
						'name'              => array(
678
							'description' => __( 'Image name.', 'woocommerce' ),
679
							'type'        => 'string',
680
							'context'     => array( 'view', 'edit' ),
681
						),
682
						'alt'               => array(
683
							'description' => __( 'Image alternative text.', 'woocommerce' ),
684
							'type'        => 'string',
685
							'context'     => array( 'view', 'edit' ),
686
						),
687
					),
688
				),
689
				'attributes'            => array(
690
					'description' => __( 'List of attributes.', 'woocommerce' ),
691
					'type'        => 'array',
692
					'context'     => array( 'view', 'edit' ),
693
					'items'       => array(
694
						'type'       => 'object',
695
						'properties' => array(
696
							'id'     => array(
697
								'description' => __( 'Attribute ID.', 'woocommerce' ),
698
								'type'        => 'integer',
699
								'context'     => array( 'view', 'edit' ),
700
							),
701
							'name'   => array(
702
								'description' => __( 'Attribute name.', 'woocommerce' ),
703
								'type'        => 'string',
704
								'context'     => array( 'view', 'edit' ),
705
							),
706
							'option' => array(
707
								'description' => __( 'Selected attribute term name.', 'woocommerce' ),
708
								'type'        => 'string',
709
								'context'     => array( 'view', 'edit' ),
710
							),
711
						),
712
					),
713
				),
714
				'menu_order'            => array(
715
					'description' => __( 'Menu order, used to custom sort products.', 'woocommerce' ),
716
					'type'        => 'integer',
717
					'context'     => array( 'view', 'edit' ),
718
				),
719
				'meta_data'             => array(
720
					'description' => __( 'Meta data.', 'woocommerce' ),
721
					'type'        => 'array',
722
					'context'     => array( 'view', 'edit' ),
723
					'items'       => array(
724
						'type'       => 'object',
725
						'properties' => array(
726
							'id'    => array(
727
								'description' => __( 'Meta ID.', 'woocommerce' ),
728
								'type'        => 'integer',
729
								'context'     => array( 'view', 'edit' ),
730
								'readonly'    => true,
731
							),
732
							'key'   => array(
733
								'description' => __( 'Meta key.', 'woocommerce' ),
734
								'type'        => 'string',
735
								'context'     => array( 'view', 'edit' ),
736
							),
737
							'value' => array(
738
								'description' => __( 'Meta value.', 'woocommerce' ),
739
								'type'        => 'mixed',
740
								'context'     => array( 'view', 'edit' ),
741
							),
742
						),
743
					),
744
				),
745
			),
746
		);
747
		return $this->add_additional_fields_schema( $schema );
748
	}
749
750
	/**
751
	 * Prepare objects query.
752
	 *
753
	 * @since  3.0.0
754
	 * @param  WP_REST_Request $request Full details about the request.
755
	 * @return array
756
	 */
757
	protected function prepare_objects_query( $request ) {
758
		$args = WC_REST_CRUD_Controller::prepare_objects_query( $request );
759
760
		// Set post_status.
761
		$args['post_status'] = $request['status'];
762
763
		// Filter by sku.
764
		if ( ! empty( $request['sku'] ) ) {
765
			$skus = explode( ',', $request['sku'] );
766
			// Include the current string as a SKU too.
767
			if ( 1 < count( $skus ) ) {
768
				$skus[] = $request['sku'];
769
			}
770
771
			$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
772
				$args,
773
				array(
774
					'key'     => '_sku',
775
					'value'   => $skus,
776
					'compare' => 'IN',
777
				)
778
			);
779
		}
780
781
		// Filter by tax class.
782
		if ( ! empty( $request['tax_class'] ) ) {
783
			$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
784
				$args,
785
				array(
786
					'key'   => '_tax_class',
787
					'value' => 'standard' !== $request['tax_class'] ? $request['tax_class'] : '',
788
				)
789
			);
790
		}
791
792
		// Price filter.
793
		if ( ! empty( $request['min_price'] ) || ! empty( $request['max_price'] ) ) {
794
			$args['meta_query'] = $this->add_meta_query( $args, wc_get_min_max_price_meta_query( $request ) );  // WPCS: slow query ok.
795
		}
796
797
		// Filter product based on stock_status.
798
		if ( ! empty( $request['stock_status'] ) ) {
799
			$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
800
				$args,
801
				array(
802
					'key'   => '_stock_status',
803
					'value' => $request['stock_status'],
804
				)
805
			);
806
		}
807
808
		// Filter by on sale products.
809
		if ( is_bool( $request['on_sale'] ) ) {
810
			$on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in';
811
			$on_sale_ids = wc_get_product_ids_on_sale();
812
813
			// Use 0 when there's no on sale products to avoid return all products.
814
			$on_sale_ids = empty( $on_sale_ids ) ? array( 0 ) : $on_sale_ids;
815
816
			$args[ $on_sale_key ] += $on_sale_ids;
817
		}
818
819
		// Force the post_type argument, since it's not a user input variable.
820
		if ( ! empty( $request['sku'] ) ) {
821
			$args['post_type'] = array( 'product', 'product_variation' );
822
		} else {
823
			$args['post_type'] = $this->post_type;
824
		}
825
826
		$args['post_parent'] = $request['product_id'];
827
828
		return $args;
829
	}
830
831
	/**
832
	 * Get the query params for collections of attachments.
833
	 *
834
	 * @return array
835
	 */
836
	public function get_collection_params() {
837
		$params = parent::get_collection_params();
838
839
		unset(
840
			$params['in_stock'],
841
			$params['type'],
842
			$params['featured'],
843
			$params['category'],
844
			$params['tag'],
845
			$params['shipping_class'],
846
			$params['attribute'],
847
			$params['attribute_term']
848
		);
849
850
		$params['stock_status'] = array(
851
			'description'       => __( 'Limit result set to products with specified stock status.', 'woocommerce' ),
852
			'type'              => 'string',
853
			'enum'              => array_keys( wc_get_product_stock_status_options() ),
854
			'sanitize_callback' => 'sanitize_text_field',
855
			'validate_callback' => 'rest_validate_request_arg',
856
		);
857
858
		return $params;
859
	}
860
}
861