Test Failed
Push — master ( a5e4c3...7495fa )
by Mike
43:00
created

ProductVariations::update_item_permissions_check()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 6
nc 3
nop 1
dl 0
loc 13
rs 8.8333
c 0
b 0
f 0
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
/**
15
 * REST API variations controller class.
16
 */
17
class ProductVariations extends Products {
18
19
	/**
20
	 * Route base.
21
	 *
22
	 * @var string
23
	 */
24
	protected $rest_base = 'products/(?P<product_id>[\d]+)/variations';
25
26
	/**
27
	 * Post type.
28
	 *
29
	 * @var string
30
	 */
31
	protected $post_type = 'product_variation';
32
33
	/**
34
	 * Initialize product actions (parent).
35
	 */
36
	public function __construct() {
37
		add_filter( "woocommerce_rest_{$this->post_type}_query", array( $this, 'add_product_id' ), 9, 2 );
38
		parent::__construct();
39
	}
40
41
	/**
42
	 * Register the routes for products.
43
	 */
44
	public function register_routes() {
45
		register_rest_route(
46
			$this->namespace,
47
			'/' . $this->rest_base,
48
			array(
49
				'args'   => array(
50
					'product_id' => array(
51
						'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
52
						'type'        => 'integer',
53
					),
54
				),
55
				array(
56
					'methods'             => \WP_REST_Server::READABLE,
0 ignored issues
show
Bug introduced by
The type WP_REST_Server 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...
57
					'callback'            => array( $this, 'get_items' ),
58
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
59
					'args'                => $this->get_collection_params(),
60
				),
61
				array(
62
					'methods'             => \WP_REST_Server::CREATABLE,
63
					'callback'            => array( $this, 'create_item' ),
64
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
65
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
66
				),
67
				'schema' => array( $this, 'get_public_item_schema' ),
68
			),
69
			true
70
		);
71
		register_rest_route(
72
			$this->namespace,
73
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
74
			array(
75
				'args'   => array(
76
					'product_id' => array(
77
						'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
78
						'type'        => 'integer',
79
					),
80
					'id'         => array(
81
						'description' => __( 'Unique identifier for the variation.', 'woocommerce' ),
82
						'type'        => 'integer',
83
					),
84
				),
85
				array(
86
					'methods'             => \WP_REST_Server::READABLE,
87
					'callback'            => array( $this, 'get_item' ),
88
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
89
					'args'                => array(
90
						'context' => $this->get_context_param(
91
							array(
92
								'default' => 'view',
93
							)
94
						),
95
					),
96
				),
97
				array(
98
					'methods'             => \WP_REST_Server::EDITABLE,
99
					'callback'            => array( $this, 'update_item' ),
100
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
101
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
102
				),
103
				array(
104
					'methods'             => \WP_REST_Server::DELETABLE,
105
					'callback'            => array( $this, 'delete_item' ),
106
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
107
					'args'                => array(
108
						'force' => array(
109
							'default'     => false,
110
							'type'        => 'boolean',
111
							'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
112
						),
113
					),
114
				),
115
				'schema' => array( $this, 'get_public_item_schema' ),
116
			),
117
			true
118
		);
119
		register_rest_route(
120
			$this->namespace, '/' . $this->rest_base . '/batch',
121
			array(
122
				'args'   => array(
123
					'product_id' => array(
124
						'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),
125
						'type'        => 'integer',
126
					),
127
				),
128
				array(
129
					'methods'             => \WP_REST_Server::EDITABLE,
130
					'callback'            => array( $this, 'batch_items' ),
131
					'permission_callback' => array( $this, 'batch_items_permissions_check' ),
132
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
133
				),
134
				'schema' => array( $this, 'get_public_batch_schema' ),
135
			),
136
			true
137
		);
138
	}
139
140
	/**
141
	 * Get object.
142
	 *
143
	 * @since  3.0.0
144
	 * @param  int $id Object ID.
145
	 * @return WC_Data
0 ignored issues
show
Bug introduced by
The type WooCommerce\RestApi\Controllers\Version4\WC_Data was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
146
	 */
147
	protected function get_object( $id ) {
148
		return wc_get_product( $id );
149
	}
150
151
	/**
152
	 * Check if a given request has access to update an item.
153
	 *
154
	 * @param  \WP_REST_Request $request Full details about the request.
0 ignored issues
show
Bug introduced by
The type WP_REST_Request 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...
155
	 * @return \WP_Error|boolean
156
	 */
157
	public function update_item_permissions_check( $request ) {
158
		$object = $this->get_object( (int) $request['id'] );
159
160
		if ( $object && 0 !== $object->get_id() && ! wc_rest_check_post_permissions( $this->post_type, 'edit', $object->get_id() ) ) {
161
			return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
0 ignored issues
show
Bug introduced by
The type WP_Error 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...
162
		}
163
164
		// Check if variation belongs to the correct parent product.
165
		if ( $object && 0 !== $object->get_parent_id() && absint( $request['product_id'] ) !== $object->get_parent_id() ) {
166
			return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Parent product does not match current variation.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
167
		}
168
169
		return true;
170
	}
171
172
	/**
173
	 * Prepare a single variation output for response.
174
	 *
175
	 * @param  WC_Data         $object  Object data.
176
	 * @param  \WP_REST_Request $request Request object.
177
	 * @return \WP_REST_Response
0 ignored issues
show
Bug introduced by
The type WP_REST_Response 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...
178
	 */
179
	public function prepare_object_for_response( $object, $request ) {
180
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
181
		$data    = array(
182
			'id'                    => $object->get_id(),
183
			'name'                  => $object->get_name( $context ),
184
			'type'                  => $object->get_type(),
185
			'parent_id'             => $object->get_parent_id( $context ),
186
			'date_created'          => wc_rest_prepare_date_response( $object->get_date_created(), false ),
187
			'date_created_gmt'      => wc_rest_prepare_date_response( $object->get_date_created() ),
188
			'date_modified'         => wc_rest_prepare_date_response( $object->get_date_modified(), false ),
189
			'date_modified_gmt'     => wc_rest_prepare_date_response( $object->get_date_modified() ),
190
			'description'           => wc_format_content( $object->get_description() ),
191
			'permalink'             => $object->get_permalink(),
192
			'sku'                   => $object->get_sku(),
193
			'price'                 => $object->get_price(),
194
			'regular_price'         => $object->get_regular_price(),
195
			'sale_price'            => $object->get_sale_price(),
196
			'date_on_sale_from'     => wc_rest_prepare_date_response( $object->get_date_on_sale_from(), false ),
197
			'date_on_sale_from_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_from() ),
198
			'date_on_sale_to'       => wc_rest_prepare_date_response( $object->get_date_on_sale_to(), false ),
199
			'date_on_sale_to_gmt'   => wc_rest_prepare_date_response( $object->get_date_on_sale_to() ),
200
			'on_sale'               => $object->is_on_sale(),
201
			'status'                => $object->get_status(),
202
			'purchasable'           => $object->is_purchasable(),
203
			'virtual'               => $object->is_virtual(),
204
			'downloadable'          => $object->is_downloadable(),
205
			'downloads'             => $this->get_downloads( $object ),
206
			'download_limit'        => '' !== $object->get_download_limit() ? (int) $object->get_download_limit() : -1,
207
			'download_expiry'       => '' !== $object->get_download_expiry() ? (int) $object->get_download_expiry() : -1,
208
			'tax_status'            => $object->get_tax_status(),
209
			'tax_class'             => $object->get_tax_class(),
210
			'manage_stock'          => $object->managing_stock(),
211
			'stock_quantity'        => $object->get_stock_quantity(),
212
			'stock_status'          => $object->get_stock_status(),
213
			'backorders'            => $object->get_backorders(),
214
			'backorders_allowed'    => $object->backorders_allowed(),
215
			'backordered'           => $object->is_on_backorder(),
216
			'weight'                => $object->get_weight(),
217
			'dimensions'            => array(
218
				'length' => $object->get_length(),
219
				'width'  => $object->get_width(),
220
				'height' => $object->get_height(),
221
			),
222
			'shipping_class'        => $object->get_shipping_class(),
223
			'shipping_class_id'     => $object->get_shipping_class_id(),
224
			'image'                 => $this->get_image( $object ),
225
			'attributes'            => $this->get_attributes( $object ),
226
			'menu_order'            => $object->get_menu_order(),
227
			'meta_data'             => $object->get_meta_data(),
228
		);
229
230
		$context  = ! empty( $request['context'] ) ? $request['context'] : 'view';
231
		$data     = $this->add_additional_fields_to_object( $data, $request );
232
		$data     = $this->filter_response_by_context( $data, $context );
233
		$response = rest_ensure_response( $data );
234
		$response->add_links( $this->prepare_links( $object, $request ) );
235
236
		/**
237
		 * Filter the data for a response.
238
		 *
239
		 * The dynamic portion of the hook name, $this->post_type,
240
		 * refers to object type being prepared for the response.
241
		 *
242
		 * @param \WP_REST_Response $response The response object.
243
		 * @param WC_Data          $object   Object data.
244
		 * @param \WP_REST_Request  $request  Request object.
245
		 */
246
		return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request );
247
	}
248
249
	/**
250
	 * Get the image for a product variation.
251
	 *
252
	 * @param WC_Product_Variation $variation Variation data.
0 ignored issues
show
Bug introduced by
The type WooCommerce\RestApi\Cont...n4\WC_Product_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...
253
	 * @return array
254
	 */
255
	protected function get_image( $variation ) {
256
		if ( ! $variation->get_image_id() ) {
257
			return;
258
		}
259
260
		$attachment_id   = $variation->get_image_id();
261
		$attachment_post = get_post( $attachment_id );
262
		if ( is_null( $attachment_post ) ) {
263
			return;
264
		}
265
266
		$attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
267
		if ( ! is_array( $attachment ) ) {
268
			return;
269
		}
270
271
		if ( ! isset( $image ) ) {
272
			return array(
273
				'id'                => (int) $attachment_id,
274
				'date_created'      => wc_rest_prepare_date_response( $attachment_post->post_date, false ),
275
				'date_created_gmt'  => wc_rest_prepare_date_response( strtotime( $attachment_post->post_date_gmt ) ),
276
				'date_modified'     => wc_rest_prepare_date_response( $attachment_post->post_modified, false ),
277
				'date_modified_gmt' => wc_rest_prepare_date_response( strtotime( $attachment_post->post_modified_gmt ) ),
278
				'src'               => current( $attachment ),
279
				'name'              => get_the_title( $attachment_id ),
280
				'alt'               => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
281
			);
282
		}
283
	}
284
285
	/**
286
	 * Set variation image.
287
	 *
288
	 * @throws \WC_REST_Exception REST API exceptions.
289
	 *
290
	 * @param  \WC_Product_Variation $variation Variation instance.
0 ignored issues
show
Bug introduced by
The type WC_Product_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...
291
	 * @param  array                 $image     Image data.
292
	 * @return WC_Product_Variation
293
	 */
294
	protected function set_variation_image( $variation, $image ) {
295
		$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
296
297
		if ( 0 === $attachment_id && isset( $image['src'] ) ) {
298
			$upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) );
299
300
			if ( is_wp_error( $upload ) ) {
301
				if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $variation->get_id(), array( $image ) ) ) {
302
					throw new \WC_REST_Exception( 'woocommerce_variation_image_upload_error', $upload->get_error_message(), 400 );
0 ignored issues
show
Bug introduced by
The type WC_REST_Exception was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
303
				}
304
			}
305
306
			$attachment_id = wc_rest_set_uploaded_image_as_attachment( $upload, $variation->get_id() );
307
		}
308
309
		if ( ! wp_attachment_is_image( $attachment_id ) ) {
310
			/* translators: %s: attachment ID */
311
			throw new \WC_REST_Exception( 'woocommerce_variation_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'woocommerce' ), $attachment_id ), 400 );
312
		}
313
314
		$variation->set_image_id( $attachment_id );
315
316
		// Set the image alt if present.
317
		if ( ! empty( $image['alt'] ) ) {
318
			update_post_meta( $attachment_id, '_wp_attachment_image_alt', wc_clean( $image['alt'] ) );
319
		}
320
321
		// Set the image name if present.
322
		if ( ! empty( $image['name'] ) ) {
323
			wp_update_post(
324
				array(
325
					'ID'         => $attachment_id,
326
					'post_title' => $image['name'],
327
				)
328
			);
329
		}
330
331
		return $variation;
332
	}
333
334
	/**
335
	 * Prepare objects query.
336
	 *
337
	 * @since  3.0.0
338
	 * @param  \WP_REST_Request $request Full details about the request.
339
	 * @return array
340
	 */
341
	protected function prepare_objects_query( $request ) {
342
		$args = parent::prepare_objects_query( $request );
343
344
		// Set post_status.
345
		$args['post_status'] = $request['status'];
346
347
		// Filter by sku.
348
		if ( ! empty( $request['sku'] ) ) {
349
			$skus = explode( ',', $request['sku'] );
350
			// Include the current string as a SKU too.
351
			if ( 1 < count( $skus ) ) {
352
				$skus[] = $request['sku'];
353
			}
354
355
			$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
356
				$args,
357
				array(
358
					'key'     => '_sku',
359
					'value'   => $skus,
360
					'compare' => 'IN',
361
				)
362
			);
363
		}
364
365
		// Filter by tax class.
366
		if ( ! empty( $request['tax_class'] ) ) {
367
			$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
368
				$args,
369
				array(
370
					'key'   => '_tax_class',
371
					'value' => 'standard' !== $request['tax_class'] ? $request['tax_class'] : '',
372
				)
373
			);
374
		}
375
376
		// Price filter.
377
		if ( ! empty( $request['min_price'] ) || ! empty( $request['max_price'] ) ) {
378
			$args['meta_query'] = $this->add_meta_query( $args, wc_get_min_max_price_meta_query( $request ) );  // WPCS: slow query ok.
379
		}
380
381
		// Filter product based on stock_status.
382
		if ( ! empty( $request['stock_status'] ) ) {
383
			$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
384
				$args,
385
				array(
386
					'key'   => '_stock_status',
387
					'value' => $request['stock_status'],
388
				)
389
			);
390
		}
391
392
		// Filter by on sale products.
393
		if ( is_bool( $request['on_sale'] ) ) {
394
			$on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in';
395
			$on_sale_ids = wc_get_product_ids_on_sale();
396
397
			// Use 0 when there's no on sale products to avoid return all products.
398
			$on_sale_ids = empty( $on_sale_ids ) ? array( 0 ) : $on_sale_ids;
399
400
			$args[ $on_sale_key ] += $on_sale_ids;
401
		}
402
403
		// Force the post_type argument, since it's not a user input variable.
404
		if ( ! empty( $request['sku'] ) ) {
405
			$args['post_type'] = array( 'product', 'product_variation' );
406
		} else {
407
			$args['post_type'] = $this->post_type;
408
		}
409
410
		$args['post_parent'] = $request['product_id'];
411
412
		if ( ! empty( $request['search'] ) ) {
413
			$args['search'] = $request['search'];
414
			unset( $args['s'] );
415
		}
416
417
		return $args;
418
	}
419
420
	/**
421
	 * Prepare a single variation for create or update.
422
	 *
423
	 * @param  \WP_REST_Request $request Request object.
424
	 * @param  bool            $creating If is creating a new object.
425
	 * @return \WP_Error|WC_Data
426
	 */
427
	protected function prepare_object_for_database( $request, $creating = false ) {
428
		if ( isset( $request['id'] ) ) {
429
			$variation = wc_get_product( absint( $request['id'] ) );
430
		} else {
431
			$variation = new \WC_Product_Variation();
432
		}
433
434
		$variation->set_parent_id( absint( $request['product_id'] ) );
435
436
		// Status.
437
		if ( isset( $request['status'] ) ) {
438
			$variation->set_status( get_post_status_object( $request['status'] ) ? $request['status'] : 'draft' );
439
		}
440
441
		// SKU.
442
		if ( isset( $request['sku'] ) ) {
443
			$variation->set_sku( wc_clean( $request['sku'] ) );
444
		}
445
446
		// Thumbnail.
447
		if ( isset( $request['image'] ) ) {
448
			if ( is_array( $request['image'] ) ) {
449
				$variation = $this->set_variation_image( $variation, $request['image'] );
450
			} else {
451
				$variation->set_image_id( '' );
452
			}
453
		}
454
455
		// Virtual variation.
456
		if ( isset( $request['virtual'] ) ) {
457
			$variation->set_virtual( $request['virtual'] );
458
		}
459
460
		// Downloadable variation.
461
		if ( isset( $request['downloadable'] ) ) {
462
			$variation->set_downloadable( $request['downloadable'] );
463
		}
464
465
		// Downloads.
466
		if ( $variation->get_downloadable() ) {
467
			// Downloadable files.
468
			if ( isset( $request['downloads'] ) && is_array( $request['downloads'] ) ) {
469
				$variation = $this->save_downloadable_files( $variation, $request['downloads'] );
470
			}
471
472
			// Download limit.
473
			if ( isset( $request['download_limit'] ) ) {
474
				$variation->set_download_limit( $request['download_limit'] );
475
			}
476
477
			// Download expiry.
478
			if ( isset( $request['download_expiry'] ) ) {
479
				$variation->set_download_expiry( $request['download_expiry'] );
480
			}
481
		}
482
483
		// Shipping data.
484
		$variation = $this->save_product_shipping_data( $variation, $request );
485
486
		// Stock handling.
487
		if ( isset( $request['manage_stock'] ) ) {
488
			$variation->set_manage_stock( $request['manage_stock'] );
489
		}
490
491
		if ( isset( $request['stock_status'] ) ) {
492
			$variation->set_stock_status( $request['stock_status'] );
493
		}
494
495
		if ( isset( $request['backorders'] ) ) {
496
			$variation->set_backorders( $request['backorders'] );
497
		}
498
499
		if ( $variation->get_manage_stock() ) {
500
			if ( isset( $request['stock_quantity'] ) ) {
501
				$variation->set_stock_quantity( $request['stock_quantity'] );
502
			} elseif ( isset( $request['inventory_delta'] ) ) {
503
				$stock_quantity  = wc_stock_amount( $variation->get_stock_quantity() );
504
				$stock_quantity += wc_stock_amount( $request['inventory_delta'] );
505
				$variation->set_stock_quantity( $stock_quantity );
506
			}
507
		} else {
508
			$variation->set_backorders( 'no' );
509
			$variation->set_stock_quantity( '' );
510
		}
511
512
		// Regular Price.
513
		if ( isset( $request['regular_price'] ) ) {
514
			$variation->set_regular_price( $request['regular_price'] );
515
		}
516
517
		// Sale Price.
518
		if ( isset( $request['sale_price'] ) ) {
519
			$variation->set_sale_price( $request['sale_price'] );
520
		}
521
522
		if ( isset( $request['date_on_sale_from'] ) ) {
523
			$variation->set_date_on_sale_from( $request['date_on_sale_from'] );
524
		}
525
526
		if ( isset( $request['date_on_sale_from_gmt'] ) ) {
527
			$variation->set_date_on_sale_from( $request['date_on_sale_from_gmt'] ? strtotime( $request['date_on_sale_from_gmt'] ) : null );
528
		}
529
530
		if ( isset( $request['date_on_sale_to'] ) ) {
531
			$variation->set_date_on_sale_to( $request['date_on_sale_to'] );
532
		}
533
534
		if ( isset( $request['date_on_sale_to_gmt'] ) ) {
535
			$variation->set_date_on_sale_to( $request['date_on_sale_to_gmt'] ? strtotime( $request['date_on_sale_to_gmt'] ) : null );
536
		}
537
538
		// Tax class.
539
		if ( isset( $request['tax_class'] ) ) {
540
			$variation->set_tax_class( $request['tax_class'] );
541
		}
542
543
		// Description.
544
		if ( isset( $request['description'] ) ) {
545
			$variation->set_description( wp_kses_post( $request['description'] ) );
546
		}
547
548
		// Update taxonomies.
549
		if ( isset( $request['attributes'] ) ) {
550
			$attributes = array();
551
			$parent     = wc_get_product( $variation->get_parent_id() );
552
553
			if ( ! $parent ) {
554
				return new \WP_Error(
555
					// Translators: %d parent ID.
556
					"woocommerce_rest_{$this->post_type}_invalid_parent", sprintf( __( 'Cannot set attributes due to invalid parent product.', 'woocommerce' ), $variation->get_parent_id() ), array(
557
						'status' => 404,
558
					)
559
				);
560
			}
561
562
			$parent_attributes = $parent->get_attributes();
563
564
			foreach ( $request['attributes'] as $attribute ) {
565
				$attribute_id   = 0;
566
				$attribute_name = '';
567
568
				// Check ID for global attributes or name for product attributes.
569
				if ( ! empty( $attribute['id'] ) ) {
570
					$attribute_id   = absint( $attribute['id'] );
571
					$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
572
				} elseif ( ! empty( $attribute['name'] ) ) {
573
					$attribute_name = sanitize_title( $attribute['name'] );
574
				}
575
576
				if ( ! $attribute_id && ! $attribute_name ) {
577
					continue;
578
				}
579
580
				if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) {
581
					continue;
582
				}
583
584
				$attribute_key   = sanitize_title( $parent_attributes[ $attribute_name ]->get_name() );
585
				$attribute_value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : '';
586
587
				if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) {
588
					// If dealing with a taxonomy, we need to get the slug from the name posted to the API.
589
					$term = get_term_by( 'name', $attribute_value, $attribute_name );
590
591
					if ( $term && ! is_wp_error( $term ) ) {
592
						$attribute_value = $term->slug;
593
					} else {
594
						$attribute_value = sanitize_title( $attribute_value );
595
					}
596
				}
597
598
				$attributes[ $attribute_key ] = $attribute_value;
599
			}
600
601
			$variation->set_attributes( $attributes );
602
		}
603
604
		// Menu order.
605
		if ( $request['menu_order'] ) {
606
			$variation->set_menu_order( $request['menu_order'] );
607
		}
608
609
		// Meta data.
610
		if ( is_array( $request['meta_data'] ) ) {
611
			foreach ( $request['meta_data'] as $meta ) {
612
				$variation->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
613
			}
614
		}
615
616
		/**
617
		 * Filters an object before it is inserted via the REST API.
618
		 *
619
		 * The dynamic portion of the hook name, `$this->post_type`,
620
		 * refers to the object type slug.
621
		 *
622
		 * @param WC_Data         $variation Object object.
623
		 * @param \WP_REST_Request $request   Request object.
624
		 * @param bool            $creating  If is creating a new object.
625
		 */
626
		return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $variation, $request, $creating );
627
	}
628
629
	/**
630
	 * Clear caches here so in sync with any new variations.
631
	 *
632
	 * @param WC_Data $object Object data.
633
	 */
634
	public function clear_transients( $object ) {
635
		wc_delete_product_transients( $object->get_parent_id() );
636
		wp_cache_delete( 'product-' . $object->get_parent_id(), 'products' );
637
	}
638
639
	/**
640
	 * Delete a variation.
641
	 *
642
	 * @param \WP_REST_Request $request Full details about the request.
643
	 *
644
	 * @return bool|\WP_Error\WP_REST_Response
0 ignored issues
show
Bug introduced by
The type WP_Error\WP_REST_Response 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...
645
	 */
646
	public function delete_item( $request ) {
647
		$force  = (bool) $request['force'];
648
		$object = $this->get_object( (int) $request['id'] );
649
		$result = false;
650
651
		if ( ! $object || 0 === $object->get_id() ) {
0 ignored issues
show
introduced by
$object is of type WooCommerce\RestApi\Controllers\Version4\WC_Data, thus it always evaluated to true.
Loading history...
652
			return new \WP_Error(
653
				"woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array(
654
					'status' => 404,
655
				)
656
			);
657
		}
658
659
		$supports_trash = EMPTY_TRASH_DAYS > 0 && is_callable( array( $object, 'get_status' ) );
0 ignored issues
show
Bug introduced by
The constant WooCommerce\RestApi\Cont...rsion4\EMPTY_TRASH_DAYS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
660
661
		/**
662
		 * Filter whether an object is trashable.
663
		 *
664
		 * Return false to disable trash support for the object.
665
		 *
666
		 * @param boolean $supports_trash Whether the object type support trashing.
667
		 * @param WC_Data $object         The object being considered for trashing support.
668
		 */
669
		$supports_trash = apply_filters( "woocommerce_rest_{$this->post_type}_object_trashable", $supports_trash, $object );
670
671
		if ( ! wc_rest_check_post_permissions( $this->post_type, 'delete', $object->get_id() ) ) {
672
			return new \WP_Error(
673
				/* translators: %s: post type */
674
				"woocommerce_rest_user_cannot_delete_{$this->post_type}", sprintf( __( 'Sorry, you are not allowed to delete %s.', 'woocommerce' ), $this->post_type ), array(
675
					'status' => rest_authorization_required_code(),
676
				)
677
			);
678
		}
679
680
		$request->set_param( 'context', 'edit' );
681
682
		// If we're forcing, then delete permanently.
683
		if ( $force ) {
684
			$previous = $this->prepare_object_for_response( $object, $request );
685
686
			$object->delete( true );
687
688
			$result   = 0 === $object->get_id();
689
			$response = new \WP_REST_Response();
690
			$response->set_data(
691
				array(
692
					'deleted'  => true,
693
					'previous' => $previous->get_data(),
694
				)
695
			);
696
		} else {
697
			// If we don't support trashing for this type, error out.
698
			if ( ! $supports_trash ) {
699
				return new \WP_Error(
700
					/* translators: %s: post type */
701
					'woocommerce_rest_trash_not_supported', sprintf( __( 'The %s does not support trashing.', 'woocommerce' ), $this->post_type ), array(
702
						'status' => 501,
703
					)
704
				);
705
			}
706
707
			// Otherwise, only trash if we haven't already.
708
			if ( is_callable( array( $object, 'get_status' ) ) ) {
709
				if ( 'trash' === $object->get_status() ) {
710
					return new \WP_Error(
711
						/* translators: %s: post type */
712
						'woocommerce_rest_already_trashed', sprintf( __( 'The %s has already been deleted.', 'woocommerce' ), $this->post_type ), array(
713
							'status' => 410,
714
						)
715
					);
716
				}
717
718
				$object->delete();
719
				$result = 'trash' === $object->get_status();
720
			}
721
722
			$response = $this->prepare_object_for_response( $object, $request );
723
		}
724
725
		if ( ! $result ) {
726
			return new \WP_Error(
727
				/* translators: %s: post type */
728
				'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array(
729
					'status' => 500,
730
				)
731
			);
732
		}
733
734
		/**
735
		 * Fires after a single object is deleted or trashed via the REST API.
736
		 *
737
		 * @param WC_Data          $object   The deleted or trashed object.
738
		 * @param \WP_REST_Response $response The response data.
739
		 * @param \WP_REST_Request  $request  The request sent to the API.
740
		 */
741
		do_action( "woocommerce_rest_delete_{$this->post_type}_object", $object, $response, $request );
742
743
		return $response;
744
	}
745
746
	/**
747
	 * Bulk create, update and delete items.
748
	 *
749
	 * @since  3.0.0
750
	 * @param \WP_REST_Request $request Full details about the request.
751
	 * @return array Of \WP_Error or \WP_REST_Response.
752
	 */
753
	public function batch_items( $request ) {
754
		$items       = array_filter( $request->get_params() );
755
		$params      = $request->get_url_params();
756
		$product_id  = $params['product_id'];
757
		$body_params = array();
758
759
		foreach ( array( 'update', 'create', 'delete' ) as $batch_type ) {
760
			if ( ! empty( $items[ $batch_type ] ) ) {
761
				$injected_items = array();
762
				foreach ( $items[ $batch_type ] as $item ) {
763
					$injected_items[] = is_array( $item ) ? array_merge(
764
						array(
765
							'product_id' => $product_id,
766
						), $item
767
					) : $item;
768
				}
769
				$body_params[ $batch_type ] = $injected_items;
770
			}
771
		}
772
773
		$request = new \WP_REST_Request( $request->get_method() );
774
		$request->set_body_params( $body_params );
775
776
		return parent::batch_items( $request );
777
	}
778
779
	/**
780
	 * Prepare links for the request.
781
	 *
782
	 * @param WC_Data         $object  Object data.
783
	 * @param \WP_REST_Request $request Request object.
784
	 * @return array                   Links for the given post.
785
	 */
786
	protected function prepare_links( $object, $request ) {
787
		$product_id = (int) $request['product_id'];
788
		$base       = str_replace( '(?P<product_id>[\d]+)', $product_id, $this->rest_base );
789
		$links      = array(
790
			'self'       => array(
791
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $object->get_id() ) ),
792
			),
793
			'collection' => array(
794
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),
795
			),
796
			'up'         => array(
797
				'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product_id ) ),
798
			),
799
		);
800
		return $links;
801
	}
802
803
	/**
804
	 * Get the Variation's schema, conforming to JSON Schema.
805
	 *
806
	 * @return array
807
	 */
808
	public function get_item_schema() {
809
		$weight_unit    = get_option( 'woocommerce_weight_unit' );
810
		$dimension_unit = get_option( 'woocommerce_dimension_unit' );
811
		$schema         = array(
812
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
813
			'title'      => $this->post_type,
814
			'type'       => 'object',
815
			'properties' => array(
816
				'id'                    => array(
817
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
818
					'type'        => 'integer',
819
					'context'     => array( 'view', 'edit' ),
820
					'readonly'    => true,
821
				),
822
				'name'                  => array(
823
					'description' => __( 'Product parent name.', 'woocommerce' ),
824
					'type'        => 'string',
825
					'context'     => array( 'view', 'edit' ),
826
				),
827
				'type'                  => array(
828
					'description' => __( 'Product type.', 'woocommerce' ),
829
					'type'        => 'string',
830
					'default'     => 'variation',
831
					'enum'        => array( 'variation' ),
832
					'context'     => array( 'view', 'edit' ),
833
				),
834
				'parent_id'             => array(
835
					'description' => __( 'Product parent ID.', 'woocommerce' ),
836
					'type'        => 'integer',
837
					'context'     => array( 'view', 'edit' ),
838
				),
839
				'date_created'          => array(
840
					'description' => __( "The date the variation was created, in the site's timezone.", 'woocommerce' ),
841
					'type'        => 'date-time',
842
					'context'     => array( 'view', 'edit' ),
843
					'readonly'    => true,
844
				),
845
				'date_modified'         => array(
846
					'description' => __( "The date the variation was last modified, in the site's timezone.", 'woocommerce' ),
847
					'type'        => 'date-time',
848
					'context'     => array( 'view', 'edit' ),
849
					'readonly'    => true,
850
				),
851
				'description'           => array(
852
					'description' => __( 'Variation description.', 'woocommerce' ),
853
					'type'        => 'string',
854
					'context'     => array( 'view', 'edit' ),
855
				),
856
				'permalink'             => array(
857
					'description' => __( 'Variation URL.', 'woocommerce' ),
858
					'type'        => 'string',
859
					'format'      => 'uri',
860
					'context'     => array( 'view', 'edit' ),
861
					'readonly'    => true,
862
				),
863
				'sku'                   => array(
864
					'description' => __( 'Unique identifier.', 'woocommerce' ),
865
					'type'        => 'string',
866
					'context'     => array( 'view', 'edit' ),
867
				),
868
				'price'                 => array(
869
					'description' => __( 'Current variation price.', 'woocommerce' ),
870
					'type'        => 'string',
871
					'context'     => array( 'view', 'edit' ),
872
					'readonly'    => true,
873
				),
874
				'regular_price'         => array(
875
					'description' => __( 'Variation regular price.', 'woocommerce' ),
876
					'type'        => 'string',
877
					'context'     => array( 'view', 'edit' ),
878
				),
879
				'sale_price'            => array(
880
					'description' => __( 'Variation sale price.', 'woocommerce' ),
881
					'type'        => 'string',
882
					'context'     => array( 'view', 'edit' ),
883
				),
884
				'date_on_sale_from'     => array(
885
					'description' => __( "Start date of sale price, in the site's timezone.", 'woocommerce' ),
886
					'type'        => 'date-time',
887
					'context'     => array( 'view', 'edit' ),
888
				),
889
				'date_on_sale_from_gmt' => array(
890
					'description' => __( 'Start date of sale price, as GMT.', 'woocommerce' ),
891
					'type'        => 'date-time',
892
					'context'     => array( 'view', 'edit' ),
893
				),
894
				'date_on_sale_to'       => array(
895
					'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
896
					'type'        => 'date-time',
897
					'context'     => array( 'view', 'edit' ),
898
				),
899
				'date_on_sale_to_gmt'   => array(
900
					'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
901
					'type'        => 'date-time',
902
					'context'     => array( 'view', 'edit' ),
903
				),
904
				'on_sale'               => array(
905
					'description' => __( 'Shows if the variation is on sale.', 'woocommerce' ),
906
					'type'        => 'boolean',
907
					'context'     => array( 'view', 'edit' ),
908
					'readonly'    => true,
909
				),
910
				'status'                => array(
911
					'description' => __( 'Variation status.', 'woocommerce' ),
912
					'type'        => 'string',
913
					'default'     => 'publish',
914
					'enum'        => array_keys( get_post_statuses() ),
915
					'context'     => array( 'view', 'edit' ),
916
				),
917
				'purchasable'           => array(
918
					'description' => __( 'Shows if the variation can be bought.', 'woocommerce' ),
919
					'type'        => 'boolean',
920
					'context'     => array( 'view', 'edit' ),
921
					'readonly'    => true,
922
				),
923
				'virtual'               => array(
924
					'description' => __( 'If the variation is virtual.', 'woocommerce' ),
925
					'type'        => 'boolean',
926
					'default'     => false,
927
					'context'     => array( 'view', 'edit' ),
928
				),
929
				'downloadable'          => array(
930
					'description' => __( 'If the variation is downloadable.', 'woocommerce' ),
931
					'type'        => 'boolean',
932
					'default'     => false,
933
					'context'     => array( 'view', 'edit' ),
934
				),
935
				'downloads'             => array(
936
					'description' => __( 'List of downloadable files.', 'woocommerce' ),
937
					'type'        => 'array',
938
					'context'     => array( 'view', 'edit' ),
939
					'items'       => array(
940
						'type'       => 'object',
941
						'properties' => array(
942
							'id'   => array(
943
								'description' => __( 'File ID.', 'woocommerce' ),
944
								'type'        => 'string',
945
								'context'     => array( 'view', 'edit' ),
946
							),
947
							'name' => array(
948
								'description' => __( 'File name.', 'woocommerce' ),
949
								'type'        => 'string',
950
								'context'     => array( 'view', 'edit' ),
951
							),
952
							'file' => array(
953
								'description' => __( 'File URL.', 'woocommerce' ),
954
								'type'        => 'string',
955
								'context'     => array( 'view', 'edit' ),
956
							),
957
						),
958
					),
959
				),
960
				'download_limit'        => array(
961
					'description' => __( 'Number of times downloadable files can be downloaded after purchase.', 'woocommerce' ),
962
					'type'        => 'integer',
963
					'default'     => -1,
964
					'context'     => array( 'view', 'edit' ),
965
				),
966
				'download_expiry'       => array(
967
					'description' => __( 'Number of days until access to downloadable files expires.', 'woocommerce' ),
968
					'type'        => 'integer',
969
					'default'     => -1,
970
					'context'     => array( 'view', 'edit' ),
971
				),
972
				'tax_status'            => array(
973
					'description' => __( 'Tax status.', 'woocommerce' ),
974
					'type'        => 'string',
975
					'default'     => 'taxable',
976
					'enum'        => array( 'taxable', 'shipping', 'none' ),
977
					'context'     => array( 'view', 'edit' ),
978
				),
979
				'tax_class'             => array(
980
					'description' => __( 'Tax class.', 'woocommerce' ),
981
					'type'        => 'string',
982
					'context'     => array( 'view', 'edit' ),
983
				),
984
				'manage_stock'          => array(
985
					'description' => __( 'Stock management at variation level.', 'woocommerce' ),
986
					'type'        => 'boolean',
987
					'default'     => false,
988
					'context'     => array( 'view', 'edit' ),
989
				),
990
				'stock_quantity'        => array(
991
					'description' => __( 'Stock quantity.', 'woocommerce' ),
992
					'type'        => 'integer',
993
					'context'     => array( 'view', 'edit' ),
994
				),
995
				'stock_status'          => array(
996
					'description' => __( 'Controls the stock status of the product.', 'woocommerce' ),
997
					'type'        => 'string',
998
					'default'     => 'instock',
999
					'enum'        => array_keys( wc_get_product_stock_status_options() ),
1000
					'context'     => array( 'view', 'edit' ),
1001
				),
1002
				'backorders'            => array(
1003
					'description' => __( 'If managing stock, this controls if backorders are allowed.', 'woocommerce' ),
1004
					'type'        => 'string',
1005
					'default'     => 'no',
1006
					'enum'        => array( 'no', 'notify', 'yes' ),
1007
					'context'     => array( 'view', 'edit' ),
1008
				),
1009
				'backorders_allowed'    => array(
1010
					'description' => __( 'Shows if backorders are allowed.', 'woocommerce' ),
1011
					'type'        => 'boolean',
1012
					'context'     => array( 'view', 'edit' ),
1013
					'readonly'    => true,
1014
				),
1015
				'backordered'           => array(
1016
					'description' => __( 'Shows if the variation is on backordered.', 'woocommerce' ),
1017
					'type'        => 'boolean',
1018
					'context'     => array( 'view', 'edit' ),
1019
					'readonly'    => true,
1020
				),
1021
				'weight'                => array(
1022
					/* translators: %s: weight unit */
1023
					'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), $weight_unit ),
1024
					'type'        => 'string',
1025
					'context'     => array( 'view', 'edit' ),
1026
				),
1027
				'dimensions'            => array(
1028
					'description' => __( 'Variation dimensions.', 'woocommerce' ),
1029
					'type'        => 'object',
1030
					'context'     => array( 'view', 'edit' ),
1031
					'properties'  => array(
1032
						'length' => array(
1033
							/* translators: %s: dimension unit */
1034
							'description' => sprintf( __( 'Variation length (%s).', 'woocommerce' ), $dimension_unit ),
1035
							'type'        => 'string',
1036
							'context'     => array( 'view', 'edit' ),
1037
						),
1038
						'width'  => array(
1039
							/* translators: %s: dimension unit */
1040
							'description' => sprintf( __( 'Variation width (%s).', 'woocommerce' ), $dimension_unit ),
1041
							'type'        => 'string',
1042
							'context'     => array( 'view', 'edit' ),
1043
						),
1044
						'height' => array(
1045
							/* translators: %s: dimension unit */
1046
							'description' => sprintf( __( 'Variation height (%s).', 'woocommerce' ), $dimension_unit ),
1047
							'type'        => 'string',
1048
							'context'     => array( 'view', 'edit' ),
1049
						),
1050
					),
1051
				),
1052
				'shipping_class'        => array(
1053
					'description' => __( 'Shipping class slug.', 'woocommerce' ),
1054
					'type'        => 'string',
1055
					'context'     => array( 'view', 'edit' ),
1056
				),
1057
				'shipping_class_id'     => array(
1058
					'description' => __( 'Shipping class ID.', 'woocommerce' ),
1059
					'type'        => 'string',
1060
					'context'     => array( 'view', 'edit' ),
1061
					'readonly'    => true,
1062
				),
1063
				'image'                 => array(
1064
					'description' => __( 'Variation image data.', 'woocommerce' ),
1065
					'type'        => 'object',
1066
					'context'     => array( 'view', 'edit' ),
1067
					'properties'  => array(
1068
						'id'                => array(
1069
							'description' => __( 'Image ID.', 'woocommerce' ),
1070
							'type'        => 'integer',
1071
							'context'     => array( 'view', 'edit' ),
1072
						),
1073
						'date_created'      => array(
1074
							'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),
1075
							'type'        => 'date-time',
1076
							'context'     => array( 'view', 'edit' ),
1077
							'readonly'    => true,
1078
						),
1079
						'date_created_gmt'  => array(
1080
							'description' => __( 'The date the image was created, as GMT.', 'woocommerce' ),
1081
							'type'        => 'date-time',
1082
							'context'     => array( 'view', 'edit' ),
1083
							'readonly'    => true,
1084
						),
1085
						'date_modified'     => array(
1086
							'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),
1087
							'type'        => 'date-time',
1088
							'context'     => array( 'view', 'edit' ),
1089
							'readonly'    => true,
1090
						),
1091
						'date_modified_gmt' => array(
1092
							'description' => __( 'The date the image was last modified, as GMT.', 'woocommerce' ),
1093
							'type'        => 'date-time',
1094
							'context'     => array( 'view', 'edit' ),
1095
							'readonly'    => true,
1096
						),
1097
						'src'               => array(
1098
							'description' => __( 'Image URL.', 'woocommerce' ),
1099
							'type'        => 'string',
1100
							'format'      => 'uri',
1101
							'context'     => array( 'view', 'edit' ),
1102
						),
1103
						'name'              => array(
1104
							'description' => __( 'Image name.', 'woocommerce' ),
1105
							'type'        => 'string',
1106
							'context'     => array( 'view', 'edit' ),
1107
						),
1108
						'alt'               => array(
1109
							'description' => __( 'Image alternative text.', 'woocommerce' ),
1110
							'type'        => 'string',
1111
							'context'     => array( 'view', 'edit' ),
1112
						),
1113
					),
1114
				),
1115
				'attributes'            => array(
1116
					'description' => __( 'List of attributes.', 'woocommerce' ),
1117
					'type'        => 'array',
1118
					'context'     => array( 'view', 'edit' ),
1119
					'items'       => array(
1120
						'type'       => 'object',
1121
						'properties' => array(
1122
							'id'     => array(
1123
								'description' => __( 'Attribute ID.', 'woocommerce' ),
1124
								'type'        => 'integer',
1125
								'context'     => array( 'view', 'edit' ),
1126
							),
1127
							'name'   => array(
1128
								'description' => __( 'Attribute name.', 'woocommerce' ),
1129
								'type'        => 'string',
1130
								'context'     => array( 'view', 'edit' ),
1131
							),
1132
							'option' => array(
1133
								'description' => __( 'Selected attribute term name.', 'woocommerce' ),
1134
								'type'        => 'string',
1135
								'context'     => array( 'view', 'edit' ),
1136
							),
1137
						),
1138
					),
1139
				),
1140
				'menu_order'            => array(
1141
					'description' => __( 'Menu order, used to custom sort products.', 'woocommerce' ),
1142
					'type'        => 'integer',
1143
					'context'     => array( 'view', 'edit' ),
1144
				),
1145
				'meta_data'             => array(
1146
					'description' => __( 'Meta data.', 'woocommerce' ),
1147
					'type'        => 'array',
1148
					'context'     => array( 'view', 'edit' ),
1149
					'items'       => array(
1150
						'type'       => 'object',
1151
						'properties' => array(
1152
							'id'    => array(
1153
								'description' => __( 'Meta ID.', 'woocommerce' ),
1154
								'type'        => 'integer',
1155
								'context'     => array( 'view', 'edit' ),
1156
								'readonly'    => true,
1157
							),
1158
							'key'   => array(
1159
								'description' => __( 'Meta key.', 'woocommerce' ),
1160
								'type'        => 'string',
1161
								'context'     => array( 'view', 'edit' ),
1162
							),
1163
							'value' => array(
1164
								'description' => __( 'Meta value.', 'woocommerce' ),
1165
								'type'        => 'mixed',
1166
								'context'     => array( 'view', 'edit' ),
1167
							),
1168
						),
1169
					),
1170
				),
1171
			),
1172
		);
1173
		return $this->add_additional_fields_schema( $schema );
1174
	}
1175
1176
	/**
1177
	 * Get the query params for collections of attachments.
1178
	 *
1179
	 * @return array
1180
	 */
1181
	public function get_collection_params() {
1182
		$params = parent::get_collection_params();
1183
1184
		unset(
1185
			$params['in_stock'],
1186
			$params['type'],
1187
			$params['featured'],
1188
			$params['category'],
1189
			$params['tag'],
1190
			$params['shipping_class'],
1191
			$params['attribute'],
1192
			$params['attribute_term']
1193
		);
1194
1195
		$params['stock_status'] = array(
1196
			'description'       => __( 'Limit result set to products with specified stock status.', 'woocommerce' ),
1197
			'type'              => 'string',
1198
			'enum'              => array_keys( wc_get_product_stock_status_options() ),
1199
			'sanitize_callback' => 'sanitize_text_field',
1200
			'validate_callback' => 'rest_validate_request_arg',
1201
		);
1202
1203
		$params['search'] = array(
1204
			'description'       => __( 'Search by similar product name or sku.', 'woocommerce' ),
1205
			'type'              => 'string',
1206
			'validate_callback' => 'rest_validate_request_arg',
1207
		);
1208
1209
		return $params;
1210
	}
1211
1212
	/**
1213
	 * Get a collection of posts and add the post title filter option to \WP_Query.
1214
	 *
1215
	 * @param \WP_REST_Request $request Full details about the request.
1216
	 * @return \WP_Error\WP_REST_Response
1217
	 */
1218
	public function get_items( $request ) {
1219
		add_filter( 'posts_where', array( __CLASS__, 'add_wp_query_filter' ), 10, 2 );
1220
		add_filter( 'posts_join', array( __CLASS__, 'add_wp_query_join' ), 10, 2 );
1221
		add_filter( 'posts_groupby', array( __CLASS__, 'add_wp_query_group_by' ), 10, 2 );
1222
		$response = parent::get_items( $request );
1223
		remove_filter( 'posts_where', array( __CLASS__, 'add_wp_query_filter' ), 10 );
1224
		remove_filter( 'posts_join', array( __CLASS__, 'add_wp_query_join' ), 10 );
1225
		remove_filter( 'posts_groupby', array( __CLASS__, 'add_wp_query_group_by' ), 10 );
1226
		return $response;
1227
	}
1228
}
1229