Completed
Push — master ( 7f2ea5...5630d2 )
by Mike
09:46
created

ProductSchema::set_object_data()   F

Complexity

Conditions 25
Paths > 20000

Size

Total Lines 112
Code Lines 67

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 25
eloc 67
nc 55296
nop 2
dl 0
loc 112
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Product schema.
4
 *
5
 * @package WooCommerce/RestApi
6
 */
7
8
namespace WooCommerce\RestApi\Controllers\Version4\Schema;
9
10
defined( 'ABSPATH' ) || exit;
11
12
/**
13
 * ProductSchema class.
14
 */
15
class ProductSchema {
16
17
	/**
18
	 * Return schema for products.
19
	 *
20
	 * @return array
21
	 */
22
	public static function get_schema() {
23
		$weight_unit    = get_option( 'woocommerce_weight_unit' );
24
		$dimension_unit = get_option( 'woocommerce_dimension_unit' );
25
		$schema         = array(
26
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
27
			'title'      => 'product',
28
			'type'       => 'object',
29
			'properties' => array(
30
				'id'                    => array(
31
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
32
					'type'        => 'integer',
33
					'context'     => array( 'view', 'edit', 'embed' ),
34
					'readonly'    => true,
35
				),
36
				'name'                  => array(
37
					'description' => __( 'Product name.', 'woocommerce' ),
38
					'type'        => 'string',
39
					'context'     => array( 'view', 'edit', 'embed' ),
40
					'arg_options' => array(
41
						'sanitize_callback' => 'wp_filter_post_kses',
42
					),
43
				),
44
				'slug'                  => array(
45
					'description' => __( 'Product slug.', 'woocommerce' ),
46
					'type'        => 'string',
47
					'context'     => array( 'view', 'edit', 'embed' ),
48
				),
49
				'permalink'             => array(
50
					'description' => __( 'Product URL.', 'woocommerce' ),
51
					'type'        => 'string',
52
					'format'      => 'uri',
53
					'context'     => array( 'view', 'edit', 'embed' ),
54
					'readonly'    => true,
55
				),
56
				'date_created'          => array(
57
					'description' => __( "The date the product was created, in the site's timezone.", 'woocommerce' ),
58
					'type'        => 'date-time',
59
					'context'     => array( 'view', 'edit' ),
60
				),
61
				'date_created_gmt'      => array(
62
					'description' => __( 'The date the product was created, as GMT.', 'woocommerce' ),
63
					'type'        => 'date-time',
64
					'context'     => array( 'view', 'edit' ),
65
				),
66
				'date_modified'         => array(
67
					'description' => __( "The date the product was last modified, in the site's timezone.", 'woocommerce' ),
68
					'type'        => 'date-time',
69
					'context'     => array( 'view', 'edit' ),
70
					'readonly'    => true,
71
				),
72
				'date_modified_gmt'     => array(
73
					'description' => __( 'The date the product was last modified, as GMT.', 'woocommerce' ),
74
					'type'        => 'date-time',
75
					'context'     => array( 'view', 'edit' ),
76
					'readonly'    => true,
77
				),
78
				'type'                  => array(
79
					'description' => __( 'Product type.', 'woocommerce' ),
80
					'type'        => 'string',
81
					'default'     => 'simple',
82
					'enum'        => array_keys( wc_get_product_types() ),
83
					'context'     => array( 'view', 'edit' ),
84
				),
85
				'status'                => array(
86
					'description' => __( 'Product status (post status).', 'woocommerce' ),
87
					'type'        => 'string',
88
					'default'     => 'publish',
89
					'enum'        => array_merge( array_keys( get_post_statuses() ), array( 'future' ) ),
90
					'context'     => array( 'view', 'edit' ),
91
				),
92
				'featured'              => array(
93
					'description' => __( 'Featured product.', 'woocommerce' ),
94
					'type'        => 'boolean',
95
					'default'     => false,
96
					'context'     => array( 'view', 'edit' ),
97
				),
98
				'catalog_visibility'    => array(
99
					'description' => __( 'Catalog visibility.', 'woocommerce' ),
100
					'type'        => 'string',
101
					'default'     => 'visible',
102
					'enum'        => array( 'visible', 'catalog', 'search', 'hidden' ),
103
					'context'     => array( 'view', 'edit' ),
104
				),
105
				'description'           => array(
106
					'description' => __( 'Product description.', 'woocommerce' ),
107
					'type'        => 'string',
108
					'context'     => array( 'view', 'edit', 'embed' ),
109
					'arg_options' => array(
110
						'sanitize_callback' => 'wp_filter_post_kses',
111
					),
112
				),
113
				'short_description'     => array(
114
					'description' => __( 'Product short description.', 'woocommerce' ),
115
					'type'        => 'string',
116
					'context'     => array( 'view', 'edit', 'embed' ),
117
					'arg_options' => array(
118
						'sanitize_callback' => 'wp_filter_post_kses',
119
					),
120
				),
121
				'sku'                   => array(
122
					'description' => __( 'Unique identifier.', 'woocommerce' ),
123
					'type'        => 'string',
124
					'context'     => array( 'view', 'edit' ),
125
					'arg_options' => array(
126
						'sanitize_callback' => 'wc_clean',
127
					),
128
				),
129
				'price'                 => array(
130
					'description' => __( 'Current product price.', 'woocommerce' ),
131
					'type'        => 'string',
132
					'context'     => array( 'view', 'edit' ),
133
					'readonly'    => true,
134
				),
135
				'regular_price'         => array(
136
					'description' => __( 'Product regular price.', 'woocommerce' ),
137
					'type'        => 'string',
138
					'context'     => array( 'view', 'edit' ),
139
				),
140
				'sale_price'            => array(
141
					'description' => __( 'Product sale price.', 'woocommerce' ),
142
					'type'        => 'string',
143
					'context'     => array( 'view', 'edit' ),
144
				),
145
				'date_on_sale_from'     => array(
146
					'description' => __( "Start date of sale price, in the site's timezone.", 'woocommerce' ),
147
					'type'        => 'date-time',
148
					'context'     => array( 'view', 'edit' ),
149
				),
150
				'date_on_sale_from_gmt' => array(
151
					'description' => __( 'Start date of sale price, as GMT.', 'woocommerce' ),
152
					'type'        => 'date-time',
153
					'context'     => array( 'view', 'edit' ),
154
				),
155
				'date_on_sale_to'       => array(
156
					'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
157
					'type'        => 'date-time',
158
					'context'     => array( 'view', 'edit' ),
159
				),
160
				'date_on_sale_to_gmt'   => array(
161
					'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),
162
					'type'        => 'date-time',
163
					'context'     => array( 'view', 'edit' ),
164
				),
165
				'price_html'            => array(
166
					'description' => __( 'Price formatted in HTML.', 'woocommerce' ),
167
					'type'        => 'string',
168
					'context'     => array( 'view', 'edit' ),
169
					'readonly'    => true,
170
				),
171
				'on_sale'               => array(
172
					'description' => __( 'Shows if the product is on sale.', 'woocommerce' ),
173
					'type'        => 'boolean',
174
					'context'     => array( 'view', 'edit' ),
175
					'readonly'    => true,
176
				),
177
				'purchasable'           => array(
178
					'description' => __( 'Shows if the product can be bought.', 'woocommerce' ),
179
					'type'        => 'boolean',
180
					'context'     => array( 'view', 'edit' ),
181
					'readonly'    => true,
182
				),
183
				'total_sales'           => array(
184
					'description' => __( 'Amount of sales.', 'woocommerce' ),
185
					'type'        => 'integer',
186
					'context'     => array( 'view', 'edit' ),
187
					'readonly'    => true,
188
				),
189
				'virtual'               => array(
190
					'description' => __( 'If the product is virtual.', 'woocommerce' ),
191
					'type'        => 'boolean',
192
					'default'     => false,
193
					'context'     => array( 'view', 'edit' ),
194
				),
195
				'downloadable'          => array(
196
					'description' => __( 'If the product is downloadable.', 'woocommerce' ),
197
					'type'        => 'boolean',
198
					'default'     => false,
199
					'context'     => array( 'view', 'edit' ),
200
				),
201
				'downloads'             => array(
202
					'description' => __( 'List of downloadable files.', 'woocommerce' ),
203
					'type'        => 'array',
204
					'context'     => array( 'view', 'edit' ),
205
					'items'       => array(
206
						'type'       => 'object',
207
						'properties' => array(
208
							'id'   => array(
209
								'description' => __( 'File ID.', 'woocommerce' ),
210
								'type'        => 'string',
211
								'context'     => array( 'view', 'edit' ),
212
							),
213
							'name' => array(
214
								'description' => __( 'File name.', 'woocommerce' ),
215
								'type'        => 'string',
216
								'context'     => array( 'view', 'edit' ),
217
							),
218
							'file' => array(
219
								'description' => __( 'File URL.', 'woocommerce' ),
220
								'type'        => 'string',
221
								'context'     => array( 'view', 'edit' ),
222
							),
223
						),
224
					),
225
				),
226
				'download_limit'        => array(
227
					'description' => __( 'Number of times downloadable files can be downloaded after purchase.', 'woocommerce' ),
228
					'type'        => 'integer',
229
					'default'     => -1,
230
					'context'     => array( 'view', 'edit' ),
231
				),
232
				'download_expiry'       => array(
233
					'description' => __( 'Number of days until access to downloadable files expires.', 'woocommerce' ),
234
					'type'        => 'integer',
235
					'default'     => -1,
236
					'context'     => array( 'view', 'edit' ),
237
				),
238
				'external_url'          => array(
239
					'description' => __( 'Product external URL. Only for external products.', 'woocommerce' ),
240
					'type'        => 'string',
241
					'format'      => 'uri',
242
					'context'     => array( 'view', 'edit' ),
243
				),
244
				'button_text'           => array(
245
					'description' => __( 'Product external button text. Only for external products.', 'woocommerce' ),
246
					'type'        => 'string',
247
					'context'     => array( 'view', 'edit' ),
248
				),
249
				'tax_status'            => array(
250
					'description' => __( 'Tax status.', 'woocommerce' ),
251
					'type'        => 'string',
252
					'default'     => 'taxable',
253
					'enum'        => array( 'taxable', 'shipping', 'none' ),
254
					'context'     => array( 'view', 'edit' ),
255
				),
256
				'tax_class'             => array(
257
					'description' => __( 'Tax class.', 'woocommerce' ),
258
					'type'        => 'string',
259
					'context'     => array( 'view', 'edit' ),
260
				),
261
				'manage_stock'          => array(
262
					'description' => __( 'Stock management at product level.', 'woocommerce' ),
263
					'type'        => 'boolean',
264
					'default'     => false,
265
					'context'     => array( 'view', 'edit' ),
266
				),
267
				'stock_quantity'        => array(
268
					'description' => __( 'Stock quantity.', 'woocommerce' ),
269
					'type'        => 'integer',
270
					'context'     => array( 'view', 'edit' ),
271
				),
272
				'stock_status'          => array(
273
					'description' => __( 'Controls the stock status of the product.', 'woocommerce' ),
274
					'type'        => 'string',
275
					'default'     => 'instock',
276
					'enum'        => array_keys( wc_get_product_stock_status_options() ),
277
					'context'     => array( 'view', 'edit' ),
278
				),
279
				'backorders'            => array(
280
					'description' => __( 'If managing stock, this controls if backorders are allowed.', 'woocommerce' ),
281
					'type'        => 'string',
282
					'default'     => 'no',
283
					'enum'        => array( 'no', 'notify', 'yes' ),
284
					'context'     => array( 'view', 'edit' ),
285
				),
286
				'backorders_allowed'    => array(
287
					'description' => __( 'Shows if backorders are allowed.', 'woocommerce' ),
288
					'type'        => 'boolean',
289
					'context'     => array( 'view', 'edit' ),
290
					'readonly'    => true,
291
				),
292
				'backordered'           => array(
293
					'description' => __( 'Shows if the product is on backordered.', 'woocommerce' ),
294
					'type'        => 'boolean',
295
					'context'     => array( 'view', 'edit' ),
296
					'readonly'    => true,
297
				),
298
				'sold_individually'     => array(
299
					'description' => __( 'Allow one item to be bought in a single order.', 'woocommerce' ),
300
					'type'        => 'boolean',
301
					'default'     => false,
302
					'context'     => array( 'view', 'edit' ),
303
				),
304
				'weight'                => array(
305
					/* translators: %s: weight unit */
306
					'description' => sprintf( __( 'Product 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

306
					'description' => sprintf( __( 'Product weight (%s).', 'woocommerce' ), /** @scrutinizer ignore-type */ $weight_unit ),
Loading history...
307
					'type'        => 'string',
308
					'context'     => array( 'view', 'edit' ),
309
				),
310
				'dimensions'            => array(
311
					'description' => __( 'Product dimensions.', 'woocommerce' ),
312
					'type'        => 'object',
313
					'context'     => array( 'view', 'edit' ),
314
					'properties'  => array(
315
						'length' => array(
316
							/* translators: %s: dimension unit */
317
							'description' => sprintf( __( 'Product length (%s).', 'woocommerce' ), $dimension_unit ),
318
							'type'        => 'string',
319
							'context'     => array( 'view', 'edit' ),
320
						),
321
						'width'  => array(
322
							/* translators: %s: dimension unit */
323
							'description' => sprintf( __( 'Product width (%s).', 'woocommerce' ), $dimension_unit ),
324
							'type'        => 'string',
325
							'context'     => array( 'view', 'edit' ),
326
						),
327
						'height' => array(
328
							/* translators: %s: dimension unit */
329
							'description' => sprintf( __( 'Product height (%s).', 'woocommerce' ), $dimension_unit ),
330
							'type'        => 'string',
331
							'context'     => array( 'view', 'edit' ),
332
						),
333
					),
334
				),
335
				'shipping_required'     => array(
336
					'description' => __( 'Shows if the product need to be shipped.', 'woocommerce' ),
337
					'type'        => 'boolean',
338
					'context'     => array( 'view', 'edit' ),
339
					'readonly'    => true,
340
				),
341
				'shipping_taxable'      => array(
342
					'description' => __( 'Shows whether or not the product shipping is taxable.', 'woocommerce' ),
343
					'type'        => 'boolean',
344
					'context'     => array( 'view', 'edit' ),
345
					'readonly'    => true,
346
				),
347
				'shipping_class'        => array(
348
					'description' => __( 'Shipping class slug.', 'woocommerce' ),
349
					'type'        => 'string',
350
					'context'     => array( 'view', 'edit' ),
351
				),
352
				'shipping_class_id'     => array(
353
					'description' => __( 'Shipping class ID.', 'woocommerce' ),
354
					'type'        => 'string',
355
					'context'     => array( 'view', 'edit' ),
356
					'readonly'    => true,
357
				),
358
				'reviews_allowed'       => array(
359
					'description' => __( 'Allow reviews.', 'woocommerce' ),
360
					'type'        => 'boolean',
361
					'default'     => true,
362
					'context'     => array( 'view', 'edit' ),
363
				),
364
				'average_rating'        => array(
365
					'description' => __( 'Reviews average rating.', 'woocommerce' ),
366
					'type'        => 'string',
367
					'context'     => array( 'view', 'edit' ),
368
					'readonly'    => true,
369
				),
370
				'rating_count'          => array(
371
					'description' => __( 'Amount of reviews that the product have.', 'woocommerce' ),
372
					'type'        => 'integer',
373
					'context'     => array( 'view', 'edit' ),
374
					'readonly'    => true,
375
				),
376
				'related_ids'           => array(
377
					'description' => __( 'List of related products IDs.', 'woocommerce' ),
378
					'type'        => 'array',
379
					'items'       => array(
380
						'type' => 'integer',
381
					),
382
					'context'     => array( 'view', 'edit' ),
383
					'readonly'    => true,
384
				),
385
				'upsell_ids'            => array(
386
					'description' => __( 'List of up-sell products IDs.', 'woocommerce' ),
387
					'type'        => 'array',
388
					'items'       => array(
389
						'type' => 'integer',
390
					),
391
					'context'     => array( 'view', 'edit' ),
392
				),
393
				'cross_sell_ids'        => array(
394
					'description' => __( 'List of cross-sell products IDs.', 'woocommerce' ),
395
					'type'        => 'array',
396
					'items'       => array(
397
						'type' => 'integer',
398
					),
399
					'context'     => array( 'view', 'edit' ),
400
				),
401
				'parent_id'             => array(
402
					'description' => __( 'Product parent ID.', 'woocommerce' ),
403
					'type'        => 'integer',
404
					'context'     => array( 'view', 'edit' ),
405
				),
406
				'purchase_note'         => array(
407
					'description' => __( 'Optional note to send the customer after purchase.', 'woocommerce' ),
408
					'type'        => 'string',
409
					'context'     => array( 'view', 'edit' ),
410
					'arg_options' => array(
411
						'sanitize_callback' => 'wp_kses_post',
412
					),
413
				),
414
				'categories'            => array(
415
					'description' => __( 'List of categories.', 'woocommerce' ),
416
					'type'        => 'array',
417
					'context'     => array( 'view', 'edit' ),
418
					'items'       => array(
419
						'type'       => 'object',
420
						'properties' => array(
421
							'id'   => array(
422
								'description' => __( 'Category ID.', 'woocommerce' ),
423
								'type'        => 'integer',
424
								'context'     => array( 'view', 'edit' ),
425
							),
426
							'name' => array(
427
								'description' => __( 'Category name.', 'woocommerce' ),
428
								'type'        => 'string',
429
								'context'     => array( 'view', 'edit' ),
430
								'readonly'    => true,
431
							),
432
							'slug' => array(
433
								'description' => __( 'Category slug.', 'woocommerce' ),
434
								'type'        => 'string',
435
								'context'     => array( 'view', 'edit' ),
436
								'readonly'    => true,
437
							),
438
						),
439
					),
440
				),
441
				'tags'                  => array(
442
					'description' => __( 'List of tags.', 'woocommerce' ),
443
					'type'        => 'array',
444
					'context'     => array( 'view', 'edit' ),
445
					'items'       => array(
446
						'type'       => 'object',
447
						'properties' => array(
448
							'id'   => array(
449
								'description' => __( 'Tag ID.', 'woocommerce' ),
450
								'type'        => 'integer',
451
								'context'     => array( 'view', 'edit' ),
452
							),
453
							'name' => array(
454
								'description' => __( 'Tag name.', 'woocommerce' ),
455
								'type'        => 'string',
456
								'context'     => array( 'view', 'edit' ),
457
								'readonly'    => true,
458
							),
459
							'slug' => array(
460
								'description' => __( 'Tag slug.', 'woocommerce' ),
461
								'type'        => 'string',
462
								'context'     => array( 'view', 'edit' ),
463
								'readonly'    => true,
464
							),
465
						),
466
					),
467
				),
468
				'images'                => array(
469
					'description' => __( 'List of images.', 'woocommerce' ),
470
					'type'        => 'object',
471
					'context'     => array( 'view', 'edit', 'embed' ),
472
					'items'       => array(
473
						'type'       => 'object',
474
						'properties' => array(
475
							'id'                => array(
476
								'description' => __( 'Image ID.', 'woocommerce' ),
477
								'type'        => 'integer',
478
								'context'     => array( 'view', 'edit' ),
479
							),
480
							'date_created'      => array(
481
								'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),
482
								'type'        => 'date-time',
483
								'context'     => array( 'view', 'edit' ),
484
								'readonly'    => true,
485
							),
486
							'date_created_gmt'  => array(
487
								'description' => __( 'The date the image was created, as GMT.', 'woocommerce' ),
488
								'type'        => 'date-time',
489
								'context'     => array( 'view', 'edit' ),
490
								'readonly'    => true,
491
							),
492
							'date_modified'     => array(
493
								'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),
494
								'type'        => 'date-time',
495
								'context'     => array( 'view', 'edit' ),
496
								'readonly'    => true,
497
							),
498
							'date_modified_gmt' => array(
499
								'description' => __( 'The date the image was last modified, as GMT.', 'woocommerce' ),
500
								'type'        => 'date-time',
501
								'context'     => array( 'view', 'edit' ),
502
								'readonly'    => true,
503
							),
504
							'src'               => array(
505
								'description' => __( 'Image URL.', 'woocommerce' ),
506
								'type'        => 'string',
507
								'format'      => 'uri',
508
								'context'     => array( 'view', 'edit' ),
509
							),
510
							'name'              => array(
511
								'description' => __( 'Image name.', 'woocommerce' ),
512
								'type'        => 'string',
513
								'context'     => array( 'view', 'edit' ),
514
							),
515
							'alt'               => array(
516
								'description' => __( 'Image alternative text.', 'woocommerce' ),
517
								'type'        => 'string',
518
								'context'     => array( 'view', 'edit' ),
519
							),
520
						),
521
					),
522
				),
523
				'attributes'            => array(
524
					'description' => __( 'List of attributes.', 'woocommerce' ),
525
					'type'        => 'array',
526
					'context'     => array( 'view', 'edit' ),
527
					'items'       => array(
528
						'type'       => 'object',
529
						'properties' => array(
530
							'id'        => array(
531
								'description' => __( 'Attribute ID.', 'woocommerce' ),
532
								'type'        => 'integer',
533
								'context'     => array( 'view', 'edit' ),
534
							),
535
							'name'      => array(
536
								'description' => __( 'Attribute name.', 'woocommerce' ),
537
								'type'        => 'string',
538
								'context'     => array( 'view', 'edit' ),
539
							),
540
							'position'  => array(
541
								'description' => __( 'Attribute position.', 'woocommerce' ),
542
								'type'        => 'integer',
543
								'context'     => array( 'view', 'edit' ),
544
							),
545
							'visible'   => array(
546
								'description' => __( "Define if the attribute is visible on the \"Additional information\" tab in the product's page.", 'woocommerce' ),
547
								'type'        => 'boolean',
548
								'default'     => false,
549
								'context'     => array( 'view', 'edit' ),
550
							),
551
							'variation' => array(
552
								'description' => __( 'Define if the attribute can be used as variation.', 'woocommerce' ),
553
								'type'        => 'boolean',
554
								'default'     => false,
555
								'context'     => array( 'view', 'edit' ),
556
							),
557
							'options'   => array(
558
								'description' => __( 'List of available term names of the attribute.', 'woocommerce' ),
559
								'type'        => 'array',
560
								'items'       => array(
561
									'type' => 'string',
562
								),
563
								'context'     => array( 'view', 'edit' ),
564
							),
565
						),
566
					),
567
				),
568
				'default_attributes'    => array(
569
					'description' => __( 'Defaults variation attributes.', 'woocommerce' ),
570
					'type'        => 'array',
571
					'context'     => array( 'view', 'edit' ),
572
					'items'       => array(
573
						'type'       => 'object',
574
						'properties' => array(
575
							'id'     => array(
576
								'description' => __( 'Attribute ID.', 'woocommerce' ),
577
								'type'        => 'integer',
578
								'context'     => array( 'view', 'edit' ),
579
							),
580
							'name'   => array(
581
								'description' => __( 'Attribute name.', 'woocommerce' ),
582
								'type'        => 'string',
583
								'context'     => array( 'view', 'edit' ),
584
							),
585
							'option' => array(
586
								'description' => __( 'Selected attribute term name.', 'woocommerce' ),
587
								'type'        => 'string',
588
								'context'     => array( 'view', 'edit' ),
589
							),
590
						),
591
					),
592
				),
593
				'variations'            => array(
594
					'description' => __( 'List of variations IDs.', 'woocommerce' ),
595
					'type'        => 'array',
596
					'context'     => array( 'view', 'edit' ),
597
					'items'       => array(
598
						'type' => 'integer',
599
					),
600
					'readonly'    => true,
601
				),
602
				'grouped_products'      => array(
603
					'description' => __( 'List of grouped products ID.', 'woocommerce' ),
604
					'type'        => 'array',
605
					'items'       => array(
606
						'type' => 'integer',
607
					),
608
					'context'     => array( 'view', 'edit' ),
609
					'readonly'    => true,
610
				),
611
				'menu_order'            => array(
612
					'description' => __( 'Menu order, used to custom sort products.', 'woocommerce' ),
613
					'type'        => 'integer',
614
					'context'     => array( 'view', 'edit' ),
615
				),
616
				'meta_data'             => array(
617
					'description' => __( 'Meta data.', 'woocommerce' ),
618
					'type'        => 'array',
619
					'context'     => array( 'view', 'edit' ),
620
					'items'       => array(
621
						'type'       => 'object',
622
						'properties' => array(
623
							'id'    => array(
624
								'description' => __( 'Meta ID.', 'woocommerce' ),
625
								'type'        => 'integer',
626
								'context'     => array( 'view', 'edit' ),
627
								'readonly'    => true,
628
							),
629
							'key'   => array(
630
								'description' => __( 'Meta key.', 'woocommerce' ),
631
								'type'        => 'string',
632
								'context'     => array( 'view', 'edit' ),
633
							),
634
							'value' => array(
635
								'description' => __( 'Meta value.', 'woocommerce' ),
636
								'type'        => 'mixed',
637
								'context'     => array( 'view', 'edit' ),
638
							),
639
						),
640
					),
641
				),
642
			),
643
		);
644
		return $schema;
645
	}
646
647
	/**
648
	 * Convert object to match data in the schema.
649
	 *
650
	 * @param \WC_Product $object Product instance.
651
	 * @param string      $context Request context. Options: 'view' and 'edit'.
652
	 * @return array
653
	 */
654
	public static function object_to_schema( $object, $context ) {
655
		$data = array(
656
			'id'                    => $object->get_id(),
657
			'name'                  => $object->get_name( $context ),
658
			'slug'                  => $object->get_slug( $context ),
659
			'permalink'             => $object->get_permalink(),
660
			'date_created'          => wc_rest_prepare_date_response( $object->get_date_created( $context ), false ),
661
			'date_created_gmt'      => wc_rest_prepare_date_response( $object->get_date_created( $context ) ),
662
			'date_modified'         => wc_rest_prepare_date_response( $object->get_date_modified( $context ), false ),
663
			'date_modified_gmt'     => wc_rest_prepare_date_response( $object->get_date_modified( $context ) ),
664
			'type'                  => $object->get_type(),
665
			'status'                => $object->get_status( $context ),
666
			'featured'              => $object->is_featured(),
667
			'catalog_visibility'    => $object->get_catalog_visibility( $context ),
668
			'description'           => 'view' === $context ? wpautop( do_shortcode( $object->get_description() ) ) : $object->get_description( $context ),
669
			'short_description'     => 'view' === $context ? apply_filters( 'woocommerce_short_description', $object->get_short_description() ) : $object->get_short_description( $context ),
670
			'sku'                   => $object->get_sku( $context ),
671
			'price'                 => $object->get_price( $context ),
672
			'regular_price'         => $object->get_regular_price( $context ),
673
			'sale_price'            => $object->get_sale_price( $context ) ? $object->get_sale_price( $context ) : '',
674
			'date_on_sale_from'     => wc_rest_prepare_date_response( $object->get_date_on_sale_from( $context ), false ),
675
			'date_on_sale_from_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_from( $context ) ),
676
			'date_on_sale_to'       => wc_rest_prepare_date_response( $object->get_date_on_sale_to( $context ), false ),
677
			'date_on_sale_to_gmt'   => wc_rest_prepare_date_response( $object->get_date_on_sale_to( $context ) ),
678
			'price_html'            => $object->get_price_html(),
679
			'on_sale'               => $object->is_on_sale( $context ),
680
			'purchasable'           => $object->is_purchasable(),
681
			'total_sales'           => $object->get_total_sales( $context ),
682
			'virtual'               => $object->is_virtual(),
683
			'downloadable'          => $object->is_downloadable(),
684
			'downloads'             => self::get_downloads( $object ),
685
			'download_limit'        => $object->get_download_limit( $context ),
686
			'download_expiry'       => $object->get_download_expiry( $context ),
687
			'external_url'          => $object->is_type( 'external' ) ? $object->get_product_url( $context ) : '',
0 ignored issues
show
Bug introduced by
The method get_product_url() does not exist on WC_Product. It seems like you code against a sub-type of WC_Product such as WC_Product_External. ( Ignorable by Annotation )

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

687
			'external_url'          => $object->is_type( 'external' ) ? $object->/** @scrutinizer ignore-call */ get_product_url( $context ) : '',
Loading history...
688
			'button_text'           => $object->is_type( 'external' ) ? $object->get_button_text( $context ) : '',
0 ignored issues
show
Bug introduced by
The method get_button_text() does not exist on WC_Product. It seems like you code against a sub-type of WC_Product such as WC_Product_External. ( Ignorable by Annotation )

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

688
			'button_text'           => $object->is_type( 'external' ) ? $object->/** @scrutinizer ignore-call */ get_button_text( $context ) : '',
Loading history...
689
			'tax_status'            => $object->get_tax_status( $context ),
690
			'tax_class'             => $object->get_tax_class( $context ),
691
			'manage_stock'          => $object->managing_stock(),
692
			'stock_quantity'        => $object->get_stock_quantity( $context ),
693
			'stock_status'          => $object->get_stock_status( $context ),
694
			'backorders'            => $object->get_backorders( $context ),
695
			'backorders_allowed'    => $object->backorders_allowed(),
696
			'backordered'           => $object->is_on_backorder(),
697
			'sold_individually'     => $object->is_sold_individually(),
698
			'weight'                => $object->get_weight( $context ),
699
			'dimensions'            => array(
700
				'length' => $object->get_length( $context ),
701
				'width'  => $object->get_width( $context ),
702
				'height' => $object->get_height( $context ),
703
			),
704
			'shipping_required'     => $object->needs_shipping(),
705
			'shipping_taxable'      => $object->is_shipping_taxable(),
706
			'shipping_class'        => $object->get_shipping_class(),
707
			'shipping_class_id'     => $object->get_shipping_class_id( $context ),
708
			'reviews_allowed'       => $object->get_reviews_allowed( $context ),
709
			'average_rating'        => 'view' === $context ? wc_format_decimal( $object->get_average_rating(), 2 ) : $object->get_average_rating( $context ),
710
			'rating_count'          => $object->get_rating_count(),
711
			'related_ids'           => array_map( 'absint', array_values( wc_get_related_products( $object->get_id() ) ) ),
712
			'upsell_ids'            => array_map( 'absint', $object->get_upsell_ids( $context ) ),
713
			'cross_sell_ids'        => array_map( 'absint', $object->get_cross_sell_ids( $context ) ),
714
			'parent_id'             => $object->get_parent_id( $context ),
715
			'purchase_note'         => 'view' === $context ? wpautop( do_shortcode( wp_kses_post( $object->get_purchase_note() ) ) ) : $object->get_purchase_note( $context ),
716
			'categories'            => self::get_taxonomy_terms( $object ),
717
			'tags'                  => self::get_taxonomy_terms( $object, 'tag' ),
718
			'images'                => self::get_images( $object ),
719
			'attributes'            => self::get_attributes( $object ),
720
			'default_attributes'    => self::get_default_attributes( $object ),
721
			'variations'            => array(),
722
			'grouped_products'      => array(),
723
			'menu_order'            => $object->get_menu_order( $context ),
724
			'meta_data'             => $object->get_meta_data(),
725
		);
726
727
		// Add variations to variable products.
728
		if ( $object->is_type( 'variable' ) && $object->has_child() ) {
729
			$data['variations'] = $object->get_children();
730
		}
731
732
		// Add grouped products data.
733
		if ( $object->is_type( 'grouped' ) && $object->has_child() ) {
734
			$data['grouped_products'] = $object->get_children();
735
		}
736
737
		return $data;
738
	}
739
740
	/**
741
	 * Take data in the format of the schema and convert to a product object.
742
	 *
743
	 * @param  \WP_REST_Request $request Request object.
744
	 * @return \WP_Error|\WC_Product
745
	 */
746
	public static function schema_to_object( $request ) {
747
		$id = isset( $request['id'] ) ? (int) $request['id'] : 0;
748
749
		if ( isset( $request['type'] ) ) {
750
			$classname = '\\' . \WC_Product_Factory::get_classname_from_product_type( $request['type'] );
0 ignored issues
show
Bug introduced by
Are you sure WC_Product_Factory::get_..._type($request['type']) of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

750
			$classname = '\\' . /** @scrutinizer ignore-type */ \WC_Product_Factory::get_classname_from_product_type( $request['type'] );
Loading history...
751
752
			if ( ! class_exists( $classname ) ) {
753
				$classname = '\\WC_Product_Simple';
754
			}
755
756
			$object = new $classname( $id );
757
		} elseif ( isset( $request['id'] ) ) {
758
			$object = wc_get_product( $id );
759
		} else {
760
			$object = new \WC_Product_Simple();
761
		}
762
763
		if ( $object->is_type( 'variation' ) ) {
764
			return new \WP_Error(
765
				'woocommerce_rest_invalid_product_id',
766
				__( 'To manipulate product variations you should use the /products/&lt;product_id&gt;/variations/&lt;id&gt; endpoint.', 'woocommerce' ),
767
				array(
768
					'status' => 404,
769
				)
770
			);
771
		}
772
773
		self::set_object_data( $object, $request );
774
775
		return $object;
776
	}
777
778
	/**
779
	 * Set object data from a request.
780
	 *
781
	 * @param \WC_Product      $object Product object.
782
	 * @param \WP_REST_Request $request Request object.
783
	 */
784
	protected static function set_object_data( &$object, $request ) {
785
		$props_to_set = [
786
			'name',
787
			'sku',
788
			'description',
789
			'short_description',
790
			'slug',
791
			'menu_order',
792
			'reviews_allowed',
793
			'virtual',
794
			'tax_status',
795
			'tax_class',
796
			'catalog_visibility',
797
			'purchase_note',
798
			'status',
799
			'featured',
800
			'regular_price',
801
			'sale_price',
802
			'date_on_sale_from',
803
			'date_on_sale_from_gmt',
804
			'date_on_sale_to',
805
			'date_on_sale_to_gmt',
806
			'parent_id',
807
			'sold_individually',
808
			'manage_stock',
809
			'backorders',
810
			'stock_status',
811
			'stock_quantity',
812
			'downloadable',
813
			'button_text',
814
			'download_limit',
815
			'download_expiry',
816
		];
817
818
		foreach ( $props_to_set as $prop ) {
819
			if ( isset( $request[ $prop ] ) && is_callable( array( $object, "set_$prop" ) ) ) {
820
				$object->{"set_$prop"}( $request[ $prop ] );
821
			}
822
		}
823
824
		if ( isset( $request['external_url'] ) && is_callable( array( $object, 'set_product_url' ) ) ) {
825
			$object->set_product_url( $request['external_url'] );
0 ignored issues
show
Bug introduced by
The method set_product_url() does not exist on WC_Product. It seems like you code against a sub-type of WC_Product such as WC_Product_External. ( Ignorable by Annotation )

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

825
			$object->/** @scrutinizer ignore-call */ 
826
            set_product_url( $request['external_url'] );
Loading history...
826
		}
827
828
		if ( ! empty( $request['date_created'] ) ) {
829
			$date = rest_parse_date( $request['date_created'] );
830
831
			if ( $date ) {
832
				$object->set_date_created( $date );
833
			}
834
		}
835
836
		if ( ! empty( $request['date_created_gmt'] ) ) {
837
			$date = rest_parse_date( $request['date_created_gmt'], true );
838
839
			if ( $date ) {
840
				$object->set_date_created( $date );
841
			}
842
		}
843
844
		if ( isset( $request['upsell_ids'] ) ) {
845
			$object->set_upsell_ids( wp_parse_id_list( $request['upsell_ids'] ) );
846
		}
847
848
		if ( isset( $request['cross_sell_ids'] ) ) {
849
			$object->set_cross_sell_ids( wp_parse_id_list( $request['cross_sell_ids'] ) );
850
		}
851
852
		// Set children for a grouped product.
853
		if ( $object->is_type( 'grouped' ) && isset( $request['grouped_products'] ) ) {
854
			$object->set_children( $request['grouped_products'] );
0 ignored issues
show
Bug introduced by
The method set_children() does not exist on WC_Product. It seems like you code against a sub-type of WC_Product such as WC_Product_Grouped or WC_Product_Variable. ( Ignorable by Annotation )

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

854
			$object->/** @scrutinizer ignore-call */ 
855
            set_children( $request['grouped_products'] );
Loading history...
855
		}
856
857
		// Allow set meta_data.
858
		if ( isset( $request['meta_data'] ) ) {
859
			foreach ( $request['meta_data'] as $meta ) {
860
				$object->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? $meta['id'] : '' can also be of type string; however, parameter $meta_id of WC_Data::update_meta_data() does only seem to accept integer, 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

860
				$object->update_meta_data( $meta['key'], $meta['value'], /** @scrutinizer ignore-type */ isset( $meta['id'] ) ? $meta['id'] : '' );
Loading history...
861
			}
862
		}
863
864
		// Save default attributes for variable products.
865
		if ( $object->is_type( 'variable' ) && isset( $request['default_attributes'] ) ) {
866
			self::set_default_attributes( $object, $request['default_attributes'] );
867
		}
868
869
		// Check for featured/gallery images, upload it and set it.
870
		if ( isset( $request['images'] ) ) {
871
			self::set_images( $object, $request['images'] );
872
		}
873
874
		// Product categories.
875
		if ( isset( $request['categories'] ) ) {
876
			self::set_taxonomy_terms( $object, $request['categories'] );
877
		}
878
879
		// Product tags.
880
		if ( isset( $request['tags'] ) ) {
881
			self::set_taxonomy_terms( $object, $request['tags'], 'tag' );
882
		}
883
884
		// Downloadable files.
885
		if ( isset( $request['downloads'] ) && is_array( $request['downloads'] ) ) {
886
			self::set_downloadable_files( $object, $request['downloads'] );
887
		}
888
889
		if ( isset( $request['attributes'] ) ) {
890
			self::set_attributes( $object, $request['attributes'] );
891
		}
892
893
		self::set_shipping_data( $object, $request );
894
895
		return $object;
896
	}
897
898
	/**
899
	 * Get the downloads for a product or product variation.
900
	 *
901
	 * @param \WC_Product|\WC_Product_Variation $object Product instance.
902
	 *
903
	 * @return array
904
	 */
905
	protected static function get_downloads( $object ) {
906
		$downloads = array();
907
908
		if ( $object->is_downloadable() ) {
909
			foreach ( $object->get_downloads() as $file_id => $file ) {
910
				$downloads[] = array(
911
					'id'   => $file_id, // MD5 hash.
912
					'name' => $file['name'],
913
					'file' => $file['file'],
914
				);
915
			}
916
		}
917
918
		return $downloads;
919
	}
920
921
	/**
922
	 * Get taxonomy terms.
923
	 *
924
	 * @param \WC_Product $object  Product instance.
925
	 * @param string      $taxonomy Taxonomy slug.
926
	 *
927
	 * @return array
928
	 */
929
	protected static function get_taxonomy_terms( $object, $taxonomy = 'cat' ) {
930
		$terms = array();
931
932
		foreach ( wc_get_object_terms( $object->get_id(), 'product_' . $taxonomy ) as $term ) {
933
			$terms[] = array(
934
				'id'   => $term->term_id,
935
				'name' => $term->name,
936
				'slug' => $term->slug,
937
			);
938
		}
939
940
		return $terms;
941
	}
942
943
	/**
944
	 * Get the images for a product or product variation.
945
	 *
946
	 * @param \WC_Product|\WC_Product_Variation $object Product instance.
947
	 * @return array
948
	 */
949
	protected static function get_images( $object ) {
950
		$images         = array();
951
		$attachment_ids = array();
952
953
		// Add featured image.
954
		if ( $object->get_image_id() ) {
955
			$attachment_ids[] = $object->get_image_id();
956
		}
957
958
		// Add gallery images.
959
		$attachment_ids = array_merge( $attachment_ids, $object->get_gallery_image_ids() );
960
961
		// Build image data.
962
		foreach ( $attachment_ids as $attachment_id ) {
963
			$attachment_post = get_post( $attachment_id );
964
			if ( is_null( $attachment_post ) ) {
965
				continue;
966
			}
967
968
			$attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
969
			if ( ! is_array( $attachment ) ) {
970
				continue;
971
			}
972
973
			$images[] = array(
974
				'id'                => (int) $attachment_id,
975
				'date_created'      => wc_rest_prepare_date_response( $attachment_post->post_date, false ),
976
				'date_created_gmt'  => wc_rest_prepare_date_response( strtotime( $attachment_post->post_date_gmt ) ),
977
				'date_modified'     => wc_rest_prepare_date_response( $attachment_post->post_modified, false ),
978
				'date_modified_gmt' => wc_rest_prepare_date_response( strtotime( $attachment_post->post_modified_gmt ) ),
979
				'src'               => current( $attachment ),
980
				'name'              => get_the_title( $attachment_id ),
981
				'alt'               => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
982
			);
983
		}
984
985
		return $images;
986
	}
987
988
	/**
989
	 * Get product attribute taxonomy name.
990
	 *
991
	 * @param string      $slug   Taxonomy name.
992
	 * @param \WC_Product $object Product data.
993
	 *
994
	 * @since  3.0.0
995
	 * @return string
996
	 */
997
	protected static function get_attribute_taxonomy_name( $slug, $object ) {
998
		// Format slug so it matches attributes of the product.
999
		$slug       = wc_attribute_taxonomy_slug( $slug );
1000
		$attributes = $object->get_attributes();
1001
		$attribute  = false;
1002
1003
		// pa_ attributes.
1004
		if ( isset( $attributes[ wc_attribute_taxonomy_name( $slug ) ] ) ) {
1005
			$attribute = $attributes[ wc_attribute_taxonomy_name( $slug ) ];
1006
		} elseif ( isset( $attributes[ $slug ] ) ) {
1007
			$attribute = $attributes[ $slug ];
1008
		}
1009
1010
		if ( ! $attribute ) {
1011
			return $slug;
1012
		}
1013
1014
		// Taxonomy attribute name.
1015
		if ( $attribute->is_taxonomy() ) {
1016
			$taxonomy = $attribute->get_taxonomy_object();
1017
			return $taxonomy->attribute_label;
1018
		}
1019
1020
		// Custom product attribute name.
1021
		return $attribute->get_name();
1022
	}
1023
1024
	/**
1025
	 * Get default attributes.
1026
	 *
1027
	 * @param \WC_Product $object Product instance.
1028
	 *
1029
	 * @return array
1030
	 */
1031
	protected static function get_default_attributes( $object ) {
1032
		$default = array();
1033
1034
		if ( $object->is_type( 'variable' ) ) {
1035
			foreach ( array_filter( (array) $object->get_default_attributes(), 'strlen' ) as $key => $value ) {
1036
				if ( 0 === strpos( $key, 'pa_' ) ) {
1037
					$default[] = array(
1038
						'id'     => wc_attribute_taxonomy_id_by_name( $key ),
1039
						'name'   => self::get_attribute_taxonomy_name( $key, $object ),
1040
						'option' => $value,
1041
					);
1042
				} else {
1043
					$default[] = array(
1044
						'id'     => 0,
1045
						'name'   => self::get_attribute_taxonomy_name( $key, $object ),
1046
						'option' => $value,
1047
					);
1048
				}
1049
			}
1050
		}
1051
1052
		return $default;
1053
	}
1054
1055
	/**
1056
	 * Get attribute options.
1057
	 *
1058
	 * @param int   $object_id Product ID.
1059
	 * @param array $attribute  Attribute data.
1060
	 *
1061
	 * @return array
1062
	 */
1063
	protected static function get_attribute_options( $object_id, $attribute ) {
1064
		if ( isset( $attribute['is_taxonomy'] ) && $attribute['is_taxonomy'] ) {
1065
			return wc_get_product_terms(
1066
				$object_id,
1067
				$attribute['name'],
1068
				array(
1069
					'fields' => 'names',
1070
				)
1071
			);
1072
		} elseif ( isset( $attribute['value'] ) ) {
1073
			return array_map( 'trim', explode( '|', $attribute['value'] ) );
1074
		}
1075
1076
		return array();
1077
	}
1078
1079
	/**
1080
	 * Get the attributes for a product or product variation.
1081
	 *
1082
	 * @param \WC_Product|\WC_Product_Variation $object Product instance.
1083
	 *
1084
	 * @return array
1085
	 */
1086
	protected static function get_attributes( $object ) {
1087
		$attributes = array();
1088
1089
		if ( $object->is_type( 'variation' ) ) {
1090
			$_product = wc_get_product( $object->get_parent_id() );
1091
			foreach ( $object->get_variation_attributes() as $attribute_name => $attribute ) {
0 ignored issues
show
Bug introduced by
The method get_variation_attributes() does not exist on WC_Product. Did you maybe mean get_variation_id()? ( Ignorable by Annotation )

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

1091
			foreach ( $object->/** @scrutinizer ignore-call */ get_variation_attributes() as $attribute_name => $attribute ) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1092
				$name = str_replace( 'attribute_', '', $attribute_name );
1093
1094
				if ( empty( $attribute ) && '0' !== $attribute ) {
1095
					continue;
1096
				}
1097
1098
				// Taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_`.
1099
				if ( 0 === strpos( $attribute_name, 'attribute_pa_' ) ) {
1100
					$option_term  = get_term_by( 'slug', $attribute, $name );
1101
					$attributes[] = array(
1102
						'id'     => wc_attribute_taxonomy_id_by_name( $name ),
1103
						'name'   => self::get_attribute_taxonomy_name( $name, $_product ),
1104
						'option' => $option_term && ! is_wp_error( $option_term ) ? $option_term->name : $attribute,
1105
					);
1106
				} else {
1107
					$attributes[] = array(
1108
						'id'     => 0,
1109
						'name'   => self::get_attribute_taxonomy_name( $name, $_product ),
1110
						'option' => $attribute,
1111
					);
1112
				}
1113
			}
1114
		} else {
1115
			foreach ( $object->get_attributes() as $attribute ) {
1116
				$attributes[] = array(
1117
					'id'        => $attribute['is_taxonomy'] ? wc_attribute_taxonomy_id_by_name( $attribute['name'] ) : 0,
1118
					'name'      => self::get_attribute_taxonomy_name( $attribute['name'], $object ),
1119
					'position'  => (int) $attribute['position'],
1120
					'visible'   => (bool) $attribute['is_visible'],
1121
					'variation' => (bool) $attribute['is_variation'],
1122
					'options'   => self::get_attribute_options( $object->get_id(), $attribute ),
1123
				);
1124
			}
1125
		}
1126
1127
		return $attributes;
1128
	}
1129
1130
	/**
1131
	 * Set product object's attributes.
1132
	 *
1133
	 * @param \WC_Product $object Product object.
1134
	 * @param array       $raw_attributes Attribute data from request.
1135
	 */
1136
	protected static function set_attributes( &$object, $raw_attributes ) {
1137
		$attributes = array();
1138
1139
		foreach ( $raw_attributes as $attribute ) {
1140
			$attribute_id   = 0;
1141
			$attribute_name = '';
1142
1143
			// Check ID for global attributes or name for product attributes.
1144
			if ( ! empty( $attribute['id'] ) ) {
1145
				$attribute_id   = absint( $attribute['id'] );
1146
				$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
1147
			} elseif ( ! empty( $attribute['name'] ) ) {
1148
				$attribute_name = wc_clean( $attribute['name'] );
1149
			}
1150
1151
			if ( ! $attribute_id && ! $attribute_name ) {
1152
				continue;
1153
			}
1154
1155
			if ( $attribute_id ) {
1156
1157
				if ( isset( $attribute['options'] ) ) {
1158
					$options = $attribute['options'];
1159
1160
					if ( ! is_array( $attribute['options'] ) ) {
1161
						// Text based attributes - Posted values are term names.
1162
						$options = explode( WC_DELIMITER, $options );
0 ignored issues
show
Bug introduced by
The constant WooCommerce\RestApi\Cont...on4\Schema\WC_DELIMITER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1163
					}
1164
1165
					$values = array_map( 'wc_sanitize_term_text_based', $options );
1166
					$values = array_filter( $values, 'strlen' );
1167
				} else {
1168
					$values = array();
1169
				}
1170
1171
				if ( ! empty( $values ) ) {
1172
					// Add attribute to array, but don't set values.
1173
					$attribute_object = new \WC_Product_Attribute();
1174
					$attribute_object->set_id( $attribute_id );
1175
					$attribute_object->set_name( $attribute_name );
0 ignored issues
show
Bug introduced by
$attribute_name of type array|string is incompatible with the type integer expected by parameter $value of WC_Product_Attribute::set_name(). ( Ignorable by Annotation )

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

1175
					$attribute_object->set_name( /** @scrutinizer ignore-type */ $attribute_name );
Loading history...
1176
					$attribute_object->set_options( $values );
1177
					$attribute_object->set_position( isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0' );
0 ignored issues
show
Bug introduced by
IssetNode ? (string)absi...bute['position']) : '0' of type string is incompatible with the type integer expected by parameter $value of WC_Product_Attribute::set_position(). ( Ignorable by Annotation )

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

1177
					$attribute_object->set_position( /** @scrutinizer ignore-type */ isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0' );
Loading history...
1178
					$attribute_object->set_visible( ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 );
0 ignored issues
show
Bug introduced by
IssetNode && $attribute['visible'] ? 1 : 0 of type integer is incompatible with the type boolean expected by parameter $value of WC_Product_Attribute::set_visible(). ( Ignorable by Annotation )

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

1178
					$attribute_object->set_visible( /** @scrutinizer ignore-type */ ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 );
Loading history...
1179
					$attribute_object->set_variation( ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 );
0 ignored issues
show
Bug introduced by
IssetNode && $attribute['variation'] ? 1 : 0 of type integer is incompatible with the type boolean expected by parameter $value of WC_Product_Attribute::set_variation(). ( Ignorable by Annotation )

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

1179
					$attribute_object->set_variation( /** @scrutinizer ignore-type */ ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 );
Loading history...
1180
					$attributes[] = $attribute_object;
1181
				}
1182
			} elseif ( isset( $attribute['options'] ) ) {
1183
				// Custom attribute - Add attribute to array and set the values.
1184
				if ( is_array( $attribute['options'] ) ) {
1185
					$values = $attribute['options'];
1186
				} else {
1187
					$values = explode( WC_DELIMITER, $attribute['options'] );
1188
				}
1189
				$attribute_object = new \WC_Product_Attribute();
1190
				$attribute_object->set_name( $attribute_name );
1191
				$attribute_object->set_options( $values );
1192
				$attribute_object->set_position( isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0' );
1193
				$attribute_object->set_visible( ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 );
1194
				$attribute_object->set_variation( ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 );
1195
				$attributes[] = $attribute_object;
1196
			}
1197
		}
1198
		$object->set_attributes( $attributes );
1199
	}
1200
1201
	/**
1202
	 * Set product images.
1203
	 *
1204
	 * @throws \WC_REST_Exception REST API exceptions.
1205
	 *
1206
	 * @param \WC_Product $object Product instance.
1207
	 * @param array       $images  Images data.
1208
	 */
1209
	protected static function set_images( &$object, $images ) {
1210
		$images = is_array( $images ) ? array_filter( $images ) : array();
0 ignored issues
show
introduced by
The condition is_array($images) is always true.
Loading history...
1211
1212
		if ( ! empty( $images ) ) {
1213
			$gallery = array();
1214
1215
			foreach ( $images as $index => $image ) {
1216
				$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
1217
1218
				if ( 0 === $attachment_id && isset( $image['src'] ) ) {
1219
					$upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) );
1220
1221
					if ( is_wp_error( $upload ) ) {
1222
						if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $object->get_id(), $images ) ) {
1223
							throw new \WC_REST_Exception( 'woocommerce_product_image_upload_error', $upload->get_error_message(), 400 );
1224
						} else {
1225
							continue;
1226
						}
1227
					}
1228
1229
					$attachment_id = wc_rest_set_uploaded_image_as_attachment( $upload, $object->get_id() );
0 ignored issues
show
Bug introduced by
It seems like $upload can also be of type WP_Error; however, parameter $upload of wc_rest_set_uploaded_image_as_attachment() does only seem to accept array, 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

1229
					$attachment_id = wc_rest_set_uploaded_image_as_attachment( /** @scrutinizer ignore-type */ $upload, $object->get_id() );
Loading history...
1230
				}
1231
1232
				if ( ! wp_attachment_is_image( $attachment_id ) ) {
1233
					/* translators: %s: image ID */
1234
					throw new \WC_REST_Exception( 'woocommerce_product_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'woocommerce' ), $attachment_id ), 400 );
1235
				}
1236
1237
				$featured_image = $object->get_image_id();
1238
1239
				if ( 0 === $index ) {
1240
					$object->set_image_id( $attachment_id );
1241
				} else {
1242
					$gallery[] = $attachment_id;
1243
				}
1244
1245
				// Set the image alt if present.
1246
				if ( ! empty( $image['alt'] ) ) {
1247
					update_post_meta( $attachment_id, '_wp_attachment_image_alt', wc_clean( $image['alt'] ) );
1248
				}
1249
1250
				// Set the image name if present.
1251
				if ( ! empty( $image['name'] ) ) {
1252
					wp_update_post(
1253
						array(
1254
							'ID'         => $attachment_id,
1255
							'post_title' => $image['name'],
1256
						)
1257
					);
1258
				}
1259
			}
1260
1261
			$object->set_gallery_image_ids( $gallery );
1262
		} else {
1263
			$object->set_image_id( '' );
1264
			$object->set_gallery_image_ids( array() );
1265
		}
1266
	}
1267
1268
	/**
1269
	 * Set product shipping data.
1270
	 *
1271
	 * @param \WC_Product $object Product instance.
1272
	 * @param array       $data    Shipping data.
1273
	 */
1274
	protected static function set_shipping_data( &$object, $data ) {
1275
		if ( $object->get_virtual() ) {
1276
			$object->set_weight( '' );
1277
			$object->set_height( '' );
1278
			$object->set_length( '' );
1279
			$object->set_width( '' );
1280
		} else {
1281
			if ( isset( $data['weight'] ) ) {
1282
				$object->set_weight( $data['weight'] );
1283
			}
1284
1285
			// Height.
1286
			if ( isset( $data['dimensions']['height'] ) ) {
1287
				$object->set_height( $data['dimensions']['height'] );
1288
			}
1289
1290
			// Width.
1291
			if ( isset( $data['dimensions']['width'] ) ) {
1292
				$object->set_width( $data['dimensions']['width'] );
1293
			}
1294
1295
			// Length.
1296
			if ( isset( $data['dimensions']['length'] ) ) {
1297
				$object->set_length( $data['dimensions']['length'] );
1298
			}
1299
		}
1300
1301
		// Shipping class.
1302
		if ( isset( $data['shipping_class'] ) ) {
1303
			$data_store        = $object->get_data_store();
1304
			$shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $data['shipping_class'] ) );
1305
			$object->set_shipping_class_id( $shipping_class_id );
1306
		}
1307
	}
1308
1309
	/**
1310
	 * Save downloadable files.
1311
	 *
1312
	 * @param \WC_Product $object    Product instance.
1313
	 * @param array       $downloads  Downloads data.
1314
	 */
1315
	protected static function set_downloadable_files( &$object, $downloads ) {
1316
		$files = array();
1317
		foreach ( $downloads as $key => $file ) {
1318
			if ( empty( $file['file'] ) ) {
1319
				continue;
1320
			}
1321
1322
			$download = new \WC_Product_Download();
1323
			$download->set_id( ! empty( $file['id'] ) ? $file['id'] : wp_generate_uuid4() );
1324
			$download->set_name( $file['name'] ? $file['name'] : wc_get_filename_from_url( $file['file'] ) );
1325
			$download->set_file( apply_filters( 'woocommerce_file_download_path', $file['file'], $object, $key ) );
1326
			$files[] = $download;
1327
		}
1328
		$object->set_downloads( $files );
1329
	}
1330
1331
	/**
1332
	 * Save taxonomy terms.
1333
	 *
1334
	 * @param \WC_Product $object  Product instance.
1335
	 * @param array       $terms    Terms data.
1336
	 * @param string      $taxonomy Taxonomy name.
1337
	 */
1338
	protected static function set_taxonomy_terms( &$object, $terms, $taxonomy = 'cat' ) {
1339
		$term_ids = wp_list_pluck( $terms, 'id' );
1340
1341
		if ( 'cat' === $taxonomy ) {
1342
			$object->set_category_ids( $term_ids );
1343
		} elseif ( 'tag' === $taxonomy ) {
1344
			$object->set_tag_ids( $term_ids );
1345
		}
1346
	}
1347
1348
	/**
1349
	 * Save default attributes.
1350
	 *
1351
	 * @param \WC_Product $object Product instance.
1352
	 * @param array       $raw_default_attributes Default attributes.
1353
	 */
1354
	protected static function set_default_attributes( &$object, $raw_default_attributes ) {
1355
		$attributes         = $object->get_attributes();
1356
		$default_attributes = array();
1357
1358
		foreach ( $raw_default_attributes as $attribute ) {
1359
			$attribute_id   = 0;
1360
			$attribute_name = '';
1361
1362
			// Check ID for global attributes or name for product attributes.
1363
			if ( ! empty( $attribute['id'] ) ) {
1364
				$attribute_id   = absint( $attribute['id'] );
1365
				$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
1366
			} elseif ( ! empty( $attribute['name'] ) ) {
1367
				$attribute_name = sanitize_title( $attribute['name'] );
1368
			}
1369
1370
			if ( ! $attribute_id && ! $attribute_name ) {
1371
				continue;
1372
			}
1373
1374
			if ( isset( $attributes[ $attribute_name ] ) ) {
1375
				$_attribute = $attributes[ $attribute_name ];
1376
1377
				if ( $_attribute['is_variation'] ) {
1378
					$value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : '';
1379
1380
					if ( ! empty( $_attribute['is_taxonomy'] ) ) {
1381
						// If dealing with a taxonomy, we need to get the slug from the name posted to the API.
1382
						$term = get_term_by( 'name', $value, $attribute_name );
1383
1384
						if ( $term && ! is_wp_error( $term ) ) {
1385
							$value = $term->slug;
1386
						} else {
1387
							$value = sanitize_title( $value );
1388
						}
1389
					}
1390
1391
					if ( $value ) {
1392
						$default_attributes[ $attribute_name ] = $value;
1393
					}
1394
				}
1395
			}
1396
		}
1397
1398
		$object->set_default_attributes( $default_attributes );
1399
	}
1400
}
1401