WC_CLI_Product::get_attributes()   B
last analyzed

Complexity

Conditions 5
Paths 3

Size

Total Lines 41
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 22
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 41
rs 8.439
1
<?php
2
3
/**
4
 * Manage Products.
5
 *
6
 * @since    2.5.0
7
 * @package  WooCommerce/CLI
8
 * @category CLI
9
 * @author   WooThemes
10
 */
11
class WC_CLI_Product extends WC_CLI_Command {
12
13
	/**
14
	 * Create a product.
15
	 *
16
	 * ## OPTIONS
17
	 *
18
	 * [--<field>=<value>]
19
	 * : Associative args for the new product.
20
	 *
21
	 * [--porcelain]
22
	 * : Outputs just the new product id.
23
	 *
24
	 * ## AVAILABLE FIELDS
25
	 *
26
	 * Required fields:
27
	 *
28
	 * * title
29
	 *
30
	 * These fields are optionally available for create command:
31
	 *
32
	 * * type
33
	 * * status
34
	 * * downloadable
35
	 * * virtual
36
	 * * sku
37
	 * * regular_price
38
	 * * sale_price
39
	 * * sale_price_dates_from
40
	 * * sale_price_dates_to
41
	 * * tax_status
42
	 * * tax_class
43
	 * * managing_stock
44
	 * * stock_quantity
45
	 * * in_stock
46
	 * * backorders
47
	 * * sold_individually
48
	 * * featured
49
	 * * shipping_class
50
	 * * description
51
	 * * enable_html_description
52
	 * * short_description
53
	 * * enable_html_short_description
54
	 * * reviews_allowed
55
	 * * upsell_ids
56
	 * * cross_sell_ids
57
	 * * parent_id
58
	 * * categories
59
	 * * tags
60
	 *
61
	 * Dimensions fields:
62
	 *
63
	 * * dimensions.length
64
	 * * dimensions.width
65
	 * * dimensions.height
66
	 * * dimensions.unit
67
	 *
68
	 * Images is an array in which element can be set by specifying its index:
69
	 *
70
	 * * images
71
	 * * images.size
72
	 * * images.0.id
73
	 * * images.0.created_at
74
	 * * images.0.updated_at
75
	 * * images.0.src
76
	 * * images.0.title
77
	 * * images.0.alt
78
	 * * images.0.position
79
	 *
80
	 * Attributes is an array in which element can be set by specifying its index:
81
	 *
82
	 * * attributes
83
	 * * attributes.size
84
	 * * attributes.0.name
85
	 * * attributes.0.slug
86
	 * * attributes.0.position
87
	 * * attributes.0.visible
88
	 * * attributes.0.variation
89
	 * * attributes.0.options
90
	 *
91
	 * Downloads is an array in which element can be accessed by specifying its index:
92
	 *
93
	 * * downloads
94
	 * * downloads.size
95
	 * * downloads.0.id
96
	 * * downloads.0.name
97
	 * * downloads.0.file
98
	 *
99
	 * Variations is an array in which element can be accessed by specifying its index:
100
	 *
101
	 * * variations
102
	 * * variations.size
103
	 * * variations.0.id
104
	 * * variations.0.created_at
105
	 * * variations.0.updated_at
106
	 * * variations.0.downloadable
107
	 * * variations.0.virtual
108
	 * * variations.0.permalink
109
	 * * variations.0.sku
110
	 * * variations.0.price
111
	 * * variations.0.regular_price
112
	 * * variations.0.sale_price
113
	 * * variations.0.sale_price_dates_from
114
	 * * variations.0.sale_price_dates_to
115
	 * * variations.0.taxable
116
	 * * variations.0.tax_status
117
	 * * variations.0.tax_class
118
	 * * variations.0.managing_stock
119
	 * * variations.0.stock_quantity
120
	 * * variations.0.in_stock
121
	 * * variations.0.backordered
122
	 * * variations.0.purchaseable
123
	 * * variations.0.visible
124
	 * * variations.0.on_sale
125
	 * * variations.0.weight
126
	 * * variations.0.dimensions -- See dimensions fields
127
	 * * variations.0.shipping_class
128
	 * * variations.0.shipping_class_id
129
	 * * variations.0.images -- See images fields
130
	 * * variations.0.attributes -- See attributes fields
131
	 * * variations.0.downloads -- See downloads fields
132
	 * * variations.0.download_limit
133
	 * * variations.0.download_expiry
134
	 *
135
	 * ## EXAMPLES
136
	 *
137
	 *     wp wc product create --title="Product Name"
138
	 *
139
	 *     wp wc product create --title="Product Name" --
140
	 *
141
	 * @since 2.5.0
142
	 */
143
	public function create( $args, $assoc_args ) {
144
		$id = 0;
145
146
		try {
147
			$porcelain = isset( $assoc_args['porcelain'] );
148
			unset( $assoc_args['porcelain'] );
149
150
			$data = apply_filters( 'woocommerce_cli_create_product_data', $this->unflatten_array( $assoc_args ) );
151
152
			// Check if product title is specified
153
			if ( ! isset( $data['title'] ) ) {
154
				throw new WC_CLI_Exception( 'woocommerce_missing_product_title', sprintf( __( 'Missing parameter %s', 'woocommerce' ), 'title' ) );
155
			}
156
157
			// Check product type
158
			if ( ! isset( $data['type'] ) ) {
159
				$data['type'] = 'simple';
160
			}
161
162
			// Set visible visibility when not sent
163
			if ( ! isset( $data['catalog_visibility'] ) ) {
164
				$data['catalog_visibility'] = 'visible';
165
			}
166
167
			// Validate the product type
168
			if ( ! in_array( wc_clean( $data['type'] ), array_keys( wc_get_product_types() ) ) ) {
169
				throw new WC_CLI_Exception( 'woocommerce_cli_invalid_product_type', sprintf( __( 'Invalid product type - the product type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_product_types() ) ) ) );
170
			}
171
172
			// Enable description html tags.
173
			$post_content = isset( $data['description'] ) ? wc_clean( $data['description'] ) : '';
174
			if ( $post_content && isset( $data['enable_html_description'] ) && $this->is_true( $data['enable_html_description'] ) ) {
175
				$post_content = $data['description'];
176
			}
177
178
			// Enable short description html tags.
179
			$post_excerpt = isset( $data['short_description'] ) ? wc_clean( $data['short_description'] ) : '';
180
			if ( $post_excerpt && isset( $data['enable_html_short_description'] ) && $this->is_true( $data['enable_html_short_description'] ) ) {
181
				$post_excerpt = $data['short_description'];
182
			}
183
184
			$new_product = array(
185
				'post_title'   => wc_clean( $data['title'] ),
186
				'post_status'  => ( isset( $data['status'] ) ? wc_clean( $data['status'] ) : 'publish' ),
187
				'post_type'    => 'product',
188
				'post_excerpt' => ( isset( $data['short_description'] ) ? $post_excerpt : '' ),
189
				'post_content' => ( isset( $data['description'] ) ? $post_content : '' ),
190
				'post_author'  => get_current_user_id(),
191
			);
192
193
			// Attempts to create the new product
194
			$id = wp_insert_post( $new_product, true );
195
196
			// Checks for an error in the product creation
197
			if ( is_wp_error( $id ) ) {
198
				throw new WC_CLI_Exception( 'woocommerce_cli_cannot_create_product', $id->get_error_message() );
199
			}
200
201
			// Check for featured/gallery images, upload it and set it
202
			if ( isset( $data['images'] ) ) {
203
				$this->save_product_images( $id, $data['images'] );
204
			}
205
206
			// Save product meta fields
207
			$this->save_product_meta( $id, $data );
208
209
			// Save variations
210 View Code Duplication
			if ( isset( $data['type'] ) && 'variable' == $data['type'] && isset( $data['variations'] ) && is_array( $data['variations'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
211
				$this->save_variations( $id, $data );
212
			}
213
214
			do_action( 'woocommerce_cli_create_product', $id, $data );
215
216
			// Clear cache/transients
217
			wc_delete_product_transients( $id );
218
219
			if ( $porcelain ) {
220
				WP_CLI::line( $id );
221
			} else {
222
				WP_CLI::success( "Created product $id." );
223
			}
224
		} catch ( WC_CLI_Exception $e ) {
225
			// Remove the product when fails
226
			$this->clear_product( $id );
227
228
			WP_CLI::error( $e->getMessage() );
229
		}
230
	}
231
232
	/**
233
	 * Delete products.
234
	 *
235
	 * ## OPTIONS
236
	 *
237
	 * <id>...
238
	 * : The product ID to delete.
239
	 *
240
	 * ## EXAMPLES
241
	 *
242
	 *     wp wc product delete 123
243
	 *
244
	 *     wp wc product delete $(wp wc product list --format=ids)
245
	 *
246
	 * @since 2.5.0
247
	 */
248
	public function delete( $args, $assoc_args ) {
249
		$exit_code = 0;
250
		foreach ( $args as $id ) {
251
			do_action( 'woocommerce_cli_delete_product', $id );
252
			$r = wp_delete_post( $id, true );
253
254
			if ( $r ) {
255
				WP_CLI::success( "Deleted product $id." );
256
			} else {
257
				$exit_code += 1;
258
				WP_CLI::warning( "Failed deleting product $id." );
259
			}
260
		}
261
		exit( $exit_code ? 1 : 0 );
262
	}
263
264
	/**
265
	 * Get a product.
266
	 *
267
	 * ## OPTIONS
268
	 *
269
	 * <id>
270
	 * : Product ID.
271
	 *
272
	 * [--field=<field>]
273
	 * : Instead of returning the whole product fields, returns the value of a single fields.
274
	 *
275
	 * [--fields=<fields>]
276
	 * : Get a specific subset of the product's fields.
277
	 *
278
	 * [--format=<format>]
279
	 * : Accepted values: table, json, csv. Default: table.
280
	 *
281
	 * ## AVAILABLE FIELDS
282
	 *
283
	 * For more fields, see: wp wc product list --help
284
	 *
285
	 * ## EXAMPLES
286
	 *
287
	 *     wp wc product get 123 --fields=id,title,sku
288
	 *
289
	 * @since 2.5.0
290
	 */
291 View Code Duplication
	public function get( $args, $assoc_args ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
292
		try {
293
			$product = wc_get_product( $args[0] );
294
			if ( ! $product ) {
295
				throw new WC_CLI_Exception( 'woocommerce_cli_invalid_product', sprintf( __( 'Invalid product "%s"', 'woocommerce' ), $args[0] ) );
296
			}
297
			$product_data = $this->get_product_data( $product );
298
299
			$formatter = $this->get_formatter( $assoc_args );
300
			$formatter->display_item( $product_data );
301
		} catch ( WC_CLI_Exception $e ) {
302
			WP_CLI::error( $e->getMessage() );
303
		}
304
	}
305
306
	/**
307
	 * List products.
308
	 *
309
	 * ## OPTIONS
310
	 *
311
	 * [--<field>=<value>]
312
	 * : Filter products based on product property.
313
	 *
314
	 * [--field=<field>]
315
	 * : Prints the value of a single field for each product.
316
	 *
317
	 * [--fields=<fields>]
318
	 * : Limit the output to specific product fields.
319
	 *
320
	 * [--format=<format>]
321
	 * : Acceptec values: table, csv, json, count, ids. Default: table.
322
	 *
323
	 * ## AVAILABLE FIELDS
324
	 *
325
	 * These fields will be displayed by default for each product:
326
	 *
327
	 * * id
328
	 * * title
329
	 * * sku
330
	 * * in_stock
331
	 * * price
332
	 * * sale_price
333
	 * * categories
334
	 * * tags
335
	 * * type
336
	 * * created_at
337
	 *
338
	 * These fields are optionally available:
339
	 *
340
	 * * updated_at
341
	 * * status
342
	 * * downloadable
343
	 * * virtual
344
	 * * permalink
345
	 * * regular_price
346
	 * * sale_price_dates_from
347
	 * * sale_price_dates_to
348
	 * * price_html
349
	 * * taxable
350
	 * * tax_status
351
	 * * tax_class
352
	 * * managing_stock
353
	 * * stock_quantity
354
	 * * backorders_allowed
355
	 * * backordered
356
	 * * backorders
357
	 * * sold_individually
358
	 * * purchaseable
359
	 * * featured
360
	 * * visible
361
	 * * catalog_visibility
362
	 * * on_sale
363
	 * * weight
364
	 * * shipping_required
365
	 * * shipping_taxable
366
	 * * shipping_class
367
	 * * shipping_class_id
368
	 * * description
369
	 * * enable_html_description
370
	 * * short_description
371
	 * * enable_html_short_description
372
	 * * reviews_allowed
373
	 * * average_rating
374
	 * * rating_count
375
	 * * related_ids
376
	 * * upsell_ids
377
	 * * cross_sell_ids
378
	 * * parent_id
379
	 * * featured_src
380
	 * * download_limit
381
	 * * download_expiry
382
	 * * download_type
383
	 * * purchase_note
384
	 * * total_sales
385
	 * * parent
386
	 * * product_url
387
	 * * button_text
388
	 *
389
	 * There are some properties that are nested array. In such case, if array.size
390
	 * is zero then listing the fields with `array.0.some_field` will results
391
	 * in error that field `array.0.some_field` does not exists.
392
	 *
393
	 * Dimensions fields:
394
	 *
395
	 * * dimensions.length
396
	 * * dimensions.width
397
	 * * dimensions.height
398
	 * * dimensions.unit
399
	 *
400
	 * Images is an array in which element can be accessed by specifying its index:
401
	 *
402
	 * * images
403
	 * * images.size
404
	 * * images.0.id
405
	 * * images.0.created_at
406
	 * * images.0.updated_at
407
	 * * images.0.src
408
	 * * images.0.title
409
	 * * images.0.alt
410
	 * * images.0.position
411
	 *
412
	 * Attributes is an array in which element can be accessed by specifying its index:
413
	 *
414
	 * * attributes
415
	 * * attributes.size
416
	 * * attributes.0.name
417
	 * * attributes.0.slug
418
	 * * attributes.0.position
419
	 * * attributes.0.visible
420
	 * * attributes.0.variation
421
	 * * attributes.0.options
422
	 *
423
	 * Downloads is an array in which element can be accessed by specifying its index:
424
	 *
425
	 * * downloads
426
	 * * downloads.size
427
	 * * downloads.0.id
428
	 * * downloads.0.name
429
	 * * downloads.0.file
430
	 *
431
	 * Variations is an array in which element can be accessed by specifying its index:
432
	 *
433
	 * * variations
434
	 * * variations.size
435
	 * * variations.0.id
436
	 * * variations.0.created_at
437
	 * * variations.0.updated_at
438
	 * * variations.0.downloadable
439
	 * * variations.0.virtual
440
	 * * variations.0.permalink
441
	 * * variations.0.sku
442
	 * * variations.0.price
443
	 * * variations.0.regular_price
444
	 * * variations.0.sale_price
445
	 * * variations.0.sale_price_dates_from
446
	 * * variations.0.sale_price_dates_to
447
	 * * variations.0.taxable
448
	 * * variations.0.tax_status
449
	 * * variations.0.tax_class
450
	 * * variations.0.managing_stock
451
	 * * variations.0.stock_quantity
452
	 * * variations.0.in_stock
453
	 * * variations.0.backordered
454
	 * * variations.0.purchaseable
455
	 * * variations.0.visible
456
	 * * variations.0.on_sale
457
	 * * variations.0.weight
458
	 * * variations.0.dimensions -- See dimensions fields
459
	 * * variations.0.shipping_class
460
	 * * variations.0.shipping_class_id
461
	 * * variations.0.images -- See images fields
462
	 * * variations.0.attributes -- See attributes fields
463
	 * * variations.0.downloads -- See downloads fields
464
	 * * variations.0.download_limit
465
	 * * variations.0.download_expiry
466
	 *
467
	 * Fields for filtering query result also available:
468
	 *
469
	 * * q              Filter products with search query.
470
	 * * created_at_min Filter products whose created after this date.
471
	 * * created_at_max Filter products whose created before this date.
472
	 * * updated_at_min Filter products whose updated after this date.
473
	 * * updated_at_max Filter products whose updated before this date.
474
	 * * limit          The maximum returned number of results.
475
	 * * offset         Offset the returned results.
476
	 * * order          Accepted values: ASC and DESC. Default: DESC.
477
	 * * orderby        Sort retrieved products by parameter. One or more options can be passed.
478
	 *
479
	 * ## EXAMPLES
480
	 *
481
	 *     wp wc product list
482
	 *
483
	 *     wp wc product list --field=id
484
	 *
485
	 *     wp wc product list --fields=id,title,type --format=json
486
	 *
487
	 * @subcommand list
488
	 * @since      2.5.0
489
	 */
490 View Code Duplication
	public function list_( $args, $assoc_args ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
491
		$query_args = $this->merge_wp_query_args( $this->get_list_query_args( $assoc_args ), $assoc_args );
492
		$formatter  = $this->get_formatter( $assoc_args );
493
494
		if ( 'ids' === $formatter->format ) {
495
			$query_args['fields'] = 'ids';
496
			$query = new WP_Query( $query_args );
497
			echo implode( ' ', $query->posts );
498
		} else {
499
			$query = new WP_Query( $query_args );
500
			$items = $this->format_posts_to_items( $query->posts );
501
			$formatter->display_items( $items );
502
		}
503
	}
504
505
	/**
506
	 * List of product reviews.
507
	 *
508
	 * ## OPTIONS
509
	 *
510
	 * <id>
511
	 * : Product ID.
512
	 *
513
	 * [--field=<field>]
514
	 * : Instead of returning the whole review fields, returns the value of a single fields.
515
	 *
516
	 * [--fields=<fields>]
517
	 * : Get a specific subset of the review's fields.
518
	 *
519
	 * [--format=<format>]
520
	 * : Accepted values: table, json, csv. Default: table.
521
	 *
522
	 * ## AVAILABLE FIELDS
523
	 *
524
	 * * id
525
	 * * rating
526
	 * * reviewer_name
527
	 * * reviewer_email
528
	 * * verified
529
	 * * created_at
530
	 *
531
	 * ## EXAMPLES
532
	 *
533
	 *     wp wc product reviews 123
534
	 *
535
	 *     wp wc product reviews 123 --fields=id,rating,reviewer_email
536
	 *
537
	 * @since 2.5.0
538
	 */
539
	public function reviews( $args, $assoc_args ) {
540
		try {
541
			$id      = $args[0];
542
			$product = wc_get_product( $id );
543
			if ( ! $product ) {
544
				throw new WC_CLI_Exception( 'woocommerce_cli_invalid_product', sprintf( __( 'Invalid product "%s"', 'woocommerce' ), $id ) );
545
			}
546
547
			$comments = get_approved_comments( $id );
548
			$reviews  = array();
549
550
			foreach ( $comments as $comment ) {
551
				$reviews[] = array(
552
					'id'             => intval( $comment->comment_ID ),
553
					'created_at'     => $this->format_datetime( $comment->comment_date_gmt ),
554
					'review'         => $comment->comment_content,
555
					'rating'         => get_comment_meta( $comment->comment_ID, 'rating', true ),
556
					'reviewer_name'  => $comment->comment_author,
557
					'reviewer_email' => $comment->comment_author_email,
558
					'verified'       => (bool) get_comment_meta( $comment->comment_ID, 'verified', true ),
559
				);
560
			}
561
562
			if ( empty( $assoc_args['fields'] ) ) {
563
				$assoc_args['fields'] = $this->get_review_fields();
564
			}
565
566
			$formatter = $this->get_formatter( $assoc_args );
567
			$formatter->display_items( $reviews );
568
		} catch ( WC_CLI_Exception $e ) {
569
			WP_CLI::error( $e->getMessage() );
570
		}
571
	}
572
573
	/**
574
	 * Get product types.
575
	 *
576
	 * ## EXAMPLES
577
	 *
578
	 *     wp wc product types
579
	 *
580
	 * @since 2.5.0
581
	 */
582
	public function types( $__, $___ ) {
583
		$product_types = wc_get_product_types();
584
		foreach ( $product_types as $type => $label ) {
585
			WP_CLI::line( sprintf( '%s: %s', $label, $type ) );
586
		}
587
	}
588
589
	/**
590
	 * Update one or more products.
591
	 *
592
	 * ## OPTIONS
593
	 *
594
	 * <id>
595
	 * : Product ID
596
	 *
597
	 * [--<field>=<value>]
598
	 * : One or more fields to update.
599
	 *
600
	 * ## AVAILABLE_FIELDS
601
	 *
602
	 * For more fields, see: wp wc product create --help
603
	 *
604
	 * ## EXAMPLES
605
	 *
606
	 *     wp wc product update 123 --title="New Product Title" --description="New description"
607
	 *
608
	 * @since 2.5.0
609
	 */
610
	public function update( $args, $assoc_args ) {
611
		try {
612
			$id   = $args[0];
613
			$data = apply_filters( 'woocommerce_cli_update_product_data', $this->unflatten_array( $assoc_args ) );
614
615
			// Product title.
616
			if ( isset( $data['title'] ) ) {
617
				wp_update_post( array( 'ID' => $id, 'post_title' => wc_clean( $data['title'] ) ) );
618
			}
619
620
			// Product name (slug).
621
			if ( isset( $data['name'] ) ) {
622
				wp_update_post( array( 'ID' => $id, 'post_name' => sanitize_title( $data['name'] ) ) );
623
			}
624
625
			// Product status.
626
			if ( isset( $data['status'] ) ) {
627
				wp_update_post( array( 'ID' => $id, 'post_status' => wc_clean( $data['status'] ) ) );
628
			}
629
630
			// Product short description.
631 View Code Duplication
			if ( isset( $data['short_description'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
632
				// Enable short description html tags.
633
				$post_excerpt = ( isset( $data['enable_html_short_description'] ) && $this->is_true( $data['enable_html_short_description'] ) ) ? $data['short_description'] : wc_clean( $data['short_description'] );
634
635
				wp_update_post( array( 'ID' => $id, 'post_excerpt' => $post_excerpt ) );
636
			}
637
638
			// Product description.
639 View Code Duplication
			if ( isset( $data['description'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
640
				// Enable description html tags.
641
				$post_content = ( isset( $data['enable_html_description'] ) && $this->is_true( $data['enable_html_description'] ) ) ? $data['description'] : wc_clean( $data['description'] );
642
643
				wp_update_post( array( 'ID' => $id, 'post_content' => $post_content ) );
644
			}
645
646
			// Validate the product type
647
			if ( isset( $data['type'] ) && ! in_array( wc_clean( $data['type'] ), array_keys( wc_get_product_types() ) ) ) {
648
				throw new WC_CLI_Exception( 'woocommerce_cli_invalid_product_type', sprintf( __( 'Invalid product type - the product type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_product_types() ) ) ) );
649
			}
650
651
			// Check for featured/gallery images, upload it and set it
652
			if ( isset( $data['images'] ) ) {
653
				$this->save_product_images( $id, $data['images'] );
654
			}
655
656
			// Save product meta fields
657
			$this->save_product_meta( $id, $data );
658
659
			// Save variations
660 View Code Duplication
			if ( isset( $data['type'] ) && 'variable' == $data['type'] && isset( $data['variations'] ) && is_array( $data['variations'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
661
				$this->save_variations( $id, $data );
662
			}
663
664
			do_action( 'woocommerce_cli_update_product', $id, $data );
665
666
			// Clear cache/transients
667
			wc_delete_product_transients( $id );
668
669
			WP_CLI::success( "Updated product $id." );
670
		} catch ( WC_CLI_Exception $e ) {
671
			WP_CLI::error( $e->getMessage() );
672
		}
673
	}
674
675
	/**
676
	 * Get query args for list subcommand.
677
	 *
678
	 * @since  2.5.0
679
	 * @param  array $args Args from command line
680
	 * @return array
681
	 */
682
	protected function get_list_query_args( $args ) {
683
		$query_args = array(
684
			'post_type'      => 'product',
685
			'post_status'    => 'publish',
686
			'posts_per_page' => -1,
687
			'meta_query'     => array(),
688
		);
689
690 View Code Duplication
		if ( ! empty( $args['type'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
691
			$types                   = explode( ',', $args['type'] );
692
			$query_args['tax_query'] = array(
693
				array(
694
					'taxonomy' => 'product_type',
695
					'field'    => 'slug',
696
					'terms'    => $types,
697
				),
698
			);
699
		}
700
701
		// Filter products by category
702
		if ( ! empty( $args['category'] ) ) {
703
			$query_args['product_cat'] = $args['category'];
704
		}
705
706
		// Filter by specific sku
707 View Code Duplication
		if ( ! empty( $args['sku'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
708
			if ( ! is_array( $query_args['meta_query'] ) ) {
709
				$query_args['meta_query'] = array();
710
			}
711
712
			$query_args['meta_query'][] = array(
713
				'key'     => '_sku',
714
				'value'   => $args['sku'],
715
				'compare' => '='
716
			);
717
		}
718
719
		return $query_args;
720
	}
721
722
	/**
723
	 * Get default format fields that will be used in `list` and `get` subcommands.
724
	 *
725
	 * @since  2.5.0
726
	 * @return string
727
	 */
728
	protected function get_default_format_fields() {
729
		return 'id,title,sku,in_stock,price,sale_price,categories,tags,type,created_at';
730
	}
731
732
	/**
733
	 * Get review fields for formatting.
734
	 *
735
	 * @since  2.5.0
736
	 * @return string
737
	 */
738
	protected function get_review_fields() {
739
		return 'id,rating,reviewer_name,reviewer_email,verified,created_at';
740
	}
741
742
	/**
743
	 * Format posts from WP_Query result to items in which each item contain
744
	 * common properties of item, for instance `post_title` will be `title`.
745
	 *
746
	 * @since  2.5.0
747
	 * @param  array $posts Array of post
748
	 * @return array Items
749
	 */
750
	protected function format_posts_to_items( $posts ) {
751
		$items = array();
752
		foreach ( $posts as $post ) {
753
			$product = wc_get_product( $post->ID );
754
			if ( ! $product ) {
755
				continue;
756
			}
757
			$items[] = $this->get_product_data( $product );
758
		}
759
760
		return $items;
761
	}
762
763
	/**
764
	 * Get standard product data that applies to every product type.
765
	 *
766
	 * @since  2.5.0
767
	 * @param  WC_Product $product
768
	 * @return array
769
	 */
770
	private function get_product_data( $product ) {
771
772
		// Add data that applies to every product type.
773
		$product_data = array(
774
			'title'              => $product->get_title(),
775
			'id'                 => (int) $product->is_type( 'variation' ) ? $product->get_variation_id() : $product->id,
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_variation_id() does only exist in the following sub-classes of WC_Product: WC_Product_Variation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
776
			'created_at'         => $this->format_datetime( $product->get_post_data()->post_date_gmt ),
777
			'updated_at'         => $this->format_datetime( $product->get_post_data()->post_modified_gmt ),
778
			'type'               => $product->product_type,
779
			'status'             => $product->get_post_data()->post_status,
780
			'downloadable'       => $product->is_downloadable(),
781
			'virtual'            => $product->is_virtual(),
782
			'permalink'          => $product->get_permalink(),
783
			'sku'                => $product->get_sku(),
784
			'price'              => $product->get_price(),
785
			'regular_price'      => $product->get_regular_price(),
786
			'sale_price'         => $product->get_sale_price() ? $product->get_sale_price() : null,
787
			'price_html'         => $product->get_price_html(),
788
			'taxable'            => $product->is_taxable(),
789
			'tax_status'         => $product->get_tax_status(),
790
			'tax_class'          => $product->get_tax_class(),
791
			'managing_stock'     => $product->managing_stock(),
792
			'stock_quantity'     => $product->get_stock_quantity(),
793
			'in_stock'           => $product->is_in_stock() ? 'yes' : 'no',
794
			'backorders_allowed' => $product->backorders_allowed(),
795
			'backordered'        => $product->is_on_backorder(),
796
			'sold_individually'  => $product->is_sold_individually(),
797
			'purchaseable'       => $product->is_purchasable(),
798
			'featured'           => $product->is_featured(),
799
			'visible'            => $product->is_visible(),
800
			'catalog_visibility' => $product->visibility,
801
			'on_sale'            => $product->is_on_sale(),
802
			'product_url'        => $product->is_type( 'external' ) ? $product->get_product_url() : '',
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_product_url() does only exist in the following sub-classes of WC_Product: WC_Product_External. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
803
			'button_text'        => $product->is_type( 'external' ) ? $product->get_button_text() : '',
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_button_text() does only exist in the following sub-classes of WC_Product: WC_Product_External. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
804
			'weight'             => $product->get_weight() ? $product->get_weight() : null,
805
			'dimensions'         => array(
806
				'length' => $product->length,
807
				'width'  => $product->width,
808
				'height' => $product->height,
809
				'unit'   => get_option( 'woocommerce_dimension_unit' ),
810
			),
811
			'shipping_required'  => $product->needs_shipping(),
812
			'shipping_taxable'   => $product->is_shipping_taxable(),
813
			'shipping_class'     => $product->get_shipping_class(),
814
			'shipping_class_id'  => ( 0 !== $product->get_shipping_class_id() ) ? $product->get_shipping_class_id() : null,
815
			'description'        => wpautop( do_shortcode( $product->get_post_data()->post_content ) ),
816
			'short_description'  => apply_filters( 'woocommerce_short_description', $product->get_post_data()->post_excerpt ),
817
			'reviews_allowed'    => ( 'open' === $product->get_post_data()->comment_status ),
818
			'average_rating'     => wc_format_decimal( $product->get_average_rating(), 2 ),
819
			'rating_count'       => (int) $product->get_rating_count(),
820
			'related_ids'        => implode( ', ', $product->get_related() ),
821
			'upsell_ids'         => implode( ', ', $product->get_upsells() ),
822
			'cross_sell_ids'     => implode( ', ', $product->get_cross_sells() ),
823
			'parent_id'          => $product->post->post_parent,
824
			'categories'         => implode( ', ', wp_get_post_terms( $product->id, 'product_cat', array( 'fields' => 'names' ) ) ),
825
			'tags'               => implode( ', ', wp_get_post_terms( $product->id, 'product_tag', array( 'fields' => 'names' ) ) ),
826
			'images'             => $this->get_images( $product ),
827
			'featured_src'       => wp_get_attachment_url( get_post_thumbnail_id( $product->is_type( 'variation' ) ? $product->variation_id : $product->id ) ),
828
			'attributes'         => $this->get_attributes( $product ),
829
			'downloads'          => $this->get_downloads( $product ),
830
			'download_limit'     => (int) $product->download_limit,
831
			'download_expiry'    => (int) $product->download_expiry,
832
			'download_type'      => $product->download_type,
833
			'purchase_note'      => wpautop( do_shortcode( wp_kses_post( $product->purchase_note ) ) ),
834
			'total_sales'        => metadata_exists( 'post', $product->id, 'total_sales' ) ? (int) get_post_meta( $product->id, 'total_sales', true ) : 0,
835
			'variations'         => array(),
836
			'parent'             => array(),
837
		);
838
839
		// add variations to variable products
840
		if ( $product->is_type( 'variable' ) && $product->has_child() ) {
841
			$product_data['variations'] = $this->get_variation_data( $product );
842
		}
843
844
		// add the parent product data to an individual variation
845
		if ( $product->is_type( 'variation' ) ) {
846
			$product_data['parent'] = $this->get_product_data( $product->parent );
847
		}
848
849
		return $this->flatten_array( $product_data );
850
	}
851
852
	/**
853
	 * Get the images for a product or product variation
854
	 *
855
	 * @since  2.5.0
856
	 * @param  WC_Product|WC_Product_Variation $product
857
	 * @return array
858
	 */
859
	private function get_images( $product ) {
860
861
		$images = $attachment_ids = array();
862
863
		if ( $product->is_type( 'variation' ) ) {
864
865
			if ( has_post_thumbnail( $product->get_variation_id() ) ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_variation_id() does only exist in the following sub-classes of WC_Product: WC_Product_Variation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
866
867
				// Add variation image if set
868
				$attachment_ids[] = get_post_thumbnail_id( $product->get_variation_id() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_variation_id() does only exist in the following sub-classes of WC_Product: WC_Product_Variation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
869
870
			} elseif ( has_post_thumbnail( $product->id ) ) {
871
872
				// Otherwise use the parent product featured image if set
873
				$attachment_ids[] = get_post_thumbnail_id( $product->id );
874
			}
875
876
		} else {
877
878
			// Add featured image
879
			if ( has_post_thumbnail( $product->id ) ) {
880
				$attachment_ids[] = get_post_thumbnail_id( $product->id );
881
			}
882
883
			// Add gallery images
884
			$attachment_ids = array_merge( $attachment_ids, $product->get_gallery_attachment_ids() );
885
		}
886
887
		// Build image data
888
		foreach ( $attachment_ids as $position => $attachment_id ) {
889
890
			$attachment_post = get_post( $attachment_id );
891
892
			if ( is_null( $attachment_post ) ) {
893
				continue;
894
			}
895
896
			$attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
897
898
			if ( ! is_array( $attachment ) ) {
899
				continue;
900
			}
901
902
			$images[] = array(
903
				'id'         => (int) $attachment_id,
904
				'created_at' => $this->format_datetime( $attachment_post->post_date_gmt ),
905
				'updated_at' => $this->format_datetime( $attachment_post->post_modified_gmt ),
906
				'src'        => current( $attachment ),
907
				'title'      => get_the_title( $attachment_id ),
908
				'alt'        => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
909
				'position'   => (int) $position,
910
			);
911
		}
912
913
		// Set a placeholder image if the product has no images set
914
		if ( empty( $images ) ) {
915
916
			$images[] = array(
917
				'id'         => 0,
918
				'created_at' => $this->format_datetime( time() ), // Default to now
919
				'updated_at' => $this->format_datetime( time() ),
920
				'src'        => wc_placeholder_img_src(),
921
				'title'      => __( 'Placeholder', 'woocommerce' ),
922
				'alt'        => __( 'Placeholder', 'woocommerce' ),
923
				'position'   => 0,
924
			);
925
		}
926
927
		return $images;
928
	}
929
930
	/**
931
	 * Get the attributes for a product or product variation
932
	 *
933
	 * @since  2.5.0
934
	 * @param  WC_Product|WC_Product_Variation $product
935
	 * @return array
936
	 */
937
	private function get_attributes( $product ) {
938
939
		$attributes = array();
940
941
		if ( $product->is_type( 'variation' ) ) {
942
943
			// variation attributes
944
			foreach ( $product->get_variation_attributes() as $attribute_name => $attribute ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_variation_attributes() does only exist in the following sub-classes of WC_Product: WC_Product_Variable, WC_Product_Variation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
945
946
				// taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_`
947
				$attributes[] = array(
948
					'name'   => wc_attribute_label( str_replace( 'attribute_', '', $attribute_name ) ),
949
					'slug'   => str_replace( 'attribute_', '', str_replace( 'pa_', '', $attribute_name ) ),
950
					'option' => $attribute,
951
				);
952
			}
953
954
		} else {
955
956
			foreach ( $product->get_attributes() as $attribute ) {
957
958
				// taxonomy-based attributes are comma-separated, others are pipe (|) separated
959
				if ( $attribute['is_taxonomy'] ) {
960
					$options = explode( ',', $product->get_attribute( $attribute['name'] ) );
961
				} else {
962
					$options = explode( '|', $product->get_attribute( $attribute['name'] ) );
963
				}
964
965
				$attributes[] = array(
966
					'name'      => wc_attribute_label( $attribute['name'] ),
967
					'slug'      => str_replace( 'pa_', '', $attribute['name'] ),
968
					'position'  => (int) $attribute['position'],
969
					'visible'   => (bool) $attribute['is_visible'],
970
					'variation' => (bool) $attribute['is_variation'],
971
					'options'   => array_map( 'trim', $options ),
972
				);
973
			}
974
		}
975
976
		return $attributes;
977
	}
978
979
	/**
980
	 * Get the downloads for a product or product variation
981
	 *
982
	 * @since  2.5.0
983
	 * @param  WC_Product|WC_Product_Variation $product
984
	 * @return array
985
	 */
986 View Code Duplication
	private function get_downloads( $product ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
987
988
		$downloads = array();
989
990
		if ( $product->is_downloadable() ) {
991
992
			foreach ( $product->get_files() as $file_id => $file ) {
993
994
				$downloads[] = array(
995
					'id'   => $file_id, // do not cast as int as this is a hash
996
					'name' => $file['name'],
997
					'file' => $file['file'],
998
				);
999
			}
1000
		}
1001
1002
		return $downloads;
1003
	}
1004
1005
	/**
1006
	 * Get an individual variation's data
1007
	 *
1008
	 * @since  2.5.0
1009
	 * @param  WC_Product $product
1010
	 * @return array
1011
	 */
1012
	private function get_variation_data( $product ) {
1013
		$variations = array();
1014
1015
		foreach ( $product->get_children() as $child_id ) {
1016
1017
			$variation = $product->get_child( $child_id );
1018
1019
			if ( ! $variation->exists() ) {
1020
				continue;
1021
			}
1022
1023
			$variations[] = array(
1024
					'id'                => $variation->get_variation_id(),
1025
					'created_at'        => $this->format_datetime( $variation->get_post_data()->post_date_gmt ),
1026
					'updated_at'        => $this->format_datetime( $variation->get_post_data()->post_modified_gmt ),
1027
					'downloadable'      => $variation->is_downloadable(),
1028
					'virtual'           => $variation->is_virtual(),
1029
					'permalink'         => $variation->get_permalink(),
1030
					'sku'               => $variation->get_sku(),
1031
					'price'             => $variation->get_price(),
1032
					'regular_price'     => $variation->get_regular_price(),
1033
					'sale_price'        => $variation->get_sale_price() ? $variation->get_sale_price() : null,
1034
					'taxable'           => $variation->is_taxable(),
1035
					'tax_status'        => $variation->get_tax_status(),
1036
					'tax_class'         => $variation->get_tax_class(),
1037
					'managing_stock'    => $variation->managing_stock(),
1038
					'stock_quantity'    => $variation->get_stock_quantity(),
1039
					'in_stock'          => $variation->is_in_stock(),
1040
					'backordered'       => $variation->is_on_backorder(),
1041
					'purchaseable'      => $variation->is_purchasable(),
1042
					'visible'           => $variation->variation_is_visible(),
1043
					'on_sale'           => $variation->is_on_sale(),
1044
					'weight'            => $variation->get_weight() ? $variation->get_weight() : null,
1045
					'dimensions'        => array(
1046
						'length' => $variation->length,
1047
						'width'  => $variation->width,
1048
						'height' => $variation->height,
1049
						'unit'   => get_option( 'woocommerce_dimension_unit' ),
1050
					),
1051
					'shipping_class'    => $variation->get_shipping_class(),
1052
					'shipping_class_id' => ( 0 !== $variation->get_shipping_class_id() ) ? $variation->get_shipping_class_id() : null,
1053
					'image'             => $this->get_images( $variation ),
1054
					'attributes'        => $this->get_attributes( $variation ),
1055
					'downloads'         => $this->get_downloads( $variation ),
1056
					'download_limit'    => (int) $product->download_limit,
1057
					'download_expiry'   => (int) $product->download_expiry,
1058
			);
1059
		}
1060
1061
		return $variations;
1062
	}
1063
1064
	/**
1065
	 * Save product meta
1066
	 *
1067
	 * @since  2.5.0
1068
	 * @param  int $product_id
1069
	 * @param  array $data
1070
	 * @return bool
1071
	 * @throws WC_CLI_Exception
1072
	 */
1073
	private function save_product_meta( $product_id, $data ) {
1074
		global $wpdb;
1075
1076
		// Product Type
1077
		$product_type = null;
1078 View Code Duplication
		if ( isset( $data['type'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1079
			$product_type = wc_clean( $data['type'] );
1080
			wp_set_object_terms( $product_id, $product_type, 'product_type' );
1081
		} else {
1082
			$_product_type = get_the_terms( $product_id, 'product_type' );
1083
			if ( is_array( $_product_type ) ) {
1084
				$_product_type = current( $_product_type );
1085
				$product_type  = $_product_type->slug;
1086
			}
1087
		}
1088
1089
		// Virtual
1090 View Code Duplication
		if ( isset( $data['virtual'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1091
			update_post_meta( $product_id, '_virtual', ( $this->is_true( $data['virtual'] ) ) ? 'yes' : 'no' );
1092
		}
1093
1094
		// Tax status
1095
		if ( isset( $data['tax_status'] ) ) {
1096
			update_post_meta( $product_id, '_tax_status', wc_clean( $data['tax_status'] ) );
1097
		}
1098
1099
		// Tax Class
1100
		if ( isset( $data['tax_class'] ) ) {
1101
			update_post_meta( $product_id, '_tax_class', wc_clean( $data['tax_class'] ) );
1102
		}
1103
1104
		// Catalog Visibility
1105
		if ( isset( $data['catalog_visibility'] ) ) {
1106
			update_post_meta( $product_id, '_visibility', wc_clean( $data['catalog_visibility'] ) );
1107
		}
1108
1109
		// Purchase Note
1110
		if ( isset( $data['purchase_note'] ) ) {
1111
			update_post_meta( $product_id, '_purchase_note', wc_clean( $data['purchase_note'] ) );
1112
		}
1113
1114
		// Featured Product
1115 View Code Duplication
		if ( isset( $data['featured'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1116
			update_post_meta( $product_id, '_featured', ( $this->is_true( $data['featured'] ) ) ? 'yes' : 'no' );
1117
		}
1118
1119
		// Shipping data
1120
		$this->save_product_shipping_data( $product_id, $data );
1121
1122
		// SKU
1123 View Code Duplication
		if ( isset( $data['sku'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1124
			$sku     = get_post_meta( $product_id, '_sku', true );
1125
			$new_sku = wc_clean( $data['sku'] );
1126
1127
			if ( '' == $new_sku ) {
1128
				update_post_meta( $product_id, '_sku', '' );
1129
			} elseif ( $new_sku !== $sku ) {
1130
				if ( ! empty( $new_sku ) ) {
1131
					$unique_sku = wc_product_has_unique_sku( $product_id, $new_sku );
1132
					if ( ! $unique_sku ) {
1133
						throw new WC_CLI_Exception( 'woocommerce_cli_product_sku_already_exists', __( 'The SKU already exists on another product', 'woocommerce' ) );
1134
					} else {
1135
						update_post_meta( $product_id, '_sku', $new_sku );
1136
					}
1137
				} else {
1138
					update_post_meta( $product_id, '_sku', '' );
1139
				}
1140
			}
1141
		}
1142
1143
		// Attributes
1144
		if ( isset( $data['attributes'] ) ) {
1145
			$attributes = array();
1146
1147
			foreach ( $data['attributes'] as $attribute ) {
1148
				$is_taxonomy    = 0;
1149
				$taxonomy       = 0;
1150
1151
				if ( ! isset( $attribute['name'] ) ) {
1152
					continue;
1153
				}
1154
1155
				$attribute_slug = sanitize_title( $attribute['name'] );
1156
1157
				if ( isset( $attribute['slug'] ) ) {
1158
					$taxonomy       = $this->get_attribute_taxonomy_by_slug( $attribute['slug'] );
1159
					$attribute_slug = sanitize_title( $attribute['slug'] );
1160
				}
1161
1162
				if ( $taxonomy ) {
1163
					$is_taxonomy = 1;
1164
				}
1165
1166
				if ( $is_taxonomy ) {
1167
1168
					if ( isset( $attribute['options'] ) ) {
1169
						$options = $attribute['options'];
1170
1171
						if ( ! is_array( $attribute['options'] ) ) {
1172
							// Text based attributes - Posted values are term names
1173
							$options = explode( WC_DELIMITER, $options );
1174
						}
1175
1176
						$values = array_map( 'wc_sanitize_term_text_based', $options );
1177
						$values = array_filter( $values, 'strlen' );
1178
					} else {
1179
						$values = array();
1180
					}
1181
1182
					// Update post terms
1183
					if ( taxonomy_exists( $taxonomy ) ) {
1184
						wp_set_object_terms( $product_id, $values, $taxonomy );
1185
					}
1186
1187 View Code Duplication
					if ( $values ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1188
						// Add attribute to array, but don't set values
1189
						$attributes[ $taxonomy ] = array(
1190
							'name'         => $taxonomy,
1191
							'value'        => '',
1192
							'position'     => isset( $attribute['position'] ) ? absint( $attribute['position'] ) : 0,
1193
							'is_visible'   => ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0,
1194
							'is_variation' => ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0,
1195
							'is_taxonomy'  => $is_taxonomy
1196
						);
1197
					}
1198
1199 View Code Duplication
				} elseif ( isset( $attribute['options'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1200
					// Array based
1201
					if ( is_array( $attribute['options'] ) ) {
1202
						$values = implode( ' ' . WC_DELIMITER . ' ', array_map( 'wc_clean', $attribute['options'] ) );
1203
1204
					// Text based, separate by pipe
1205
					} else {
1206
						$values = implode( ' ' . WC_DELIMITER . ' ', array_map( 'wc_clean', explode( WC_DELIMITER, $attribute['options'] ) ) );
1207
					}
1208
1209
					// Custom attribute - Add attribute to array and set the values
1210
					$attributes[ $attribute_slug ] = array(
1211
						'name'         => wc_clean( $attribute['name'] ),
1212
						'value'        => $values,
1213
						'position'     => isset( $attribute['position'] ) ? absint( $attribute['position'] ) : 0,
1214
						'is_visible'   => ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0,
1215
						'is_variation' => ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0,
1216
						'is_taxonomy'  => $is_taxonomy
1217
					);
1218
				}
1219
			}
1220
1221
			uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
1222
1223
			update_post_meta( $product_id, '_product_attributes', $attributes );
1224
		}
1225
1226
		// Sales and prices
1227
		if ( in_array( $product_type, array( 'variable', 'grouped' ) ) ) {
1228
1229
			// Variable and grouped products have no prices
1230
			update_post_meta( $product_id, '_regular_price', '' );
1231
			update_post_meta( $product_id, '_sale_price', '' );
1232
			update_post_meta( $product_id, '_sale_price_dates_from', '' );
1233
			update_post_meta( $product_id, '_sale_price_dates_to', '' );
1234
			update_post_meta( $product_id, '_price', '' );
1235
1236
		} else {
1237
1238
			// Regular Price
1239
			if ( isset( $data['regular_price'] ) ) {
1240
				$regular_price = ( '' === $data['regular_price'] ) ? '' : wc_format_decimal( $data['regular_price'] );
1241
				update_post_meta( $product_id, '_regular_price', $regular_price );
1242
			} else {
1243
				$regular_price = get_post_meta( $product_id, '_regular_price', true );
1244
			}
1245
1246
			// Sale Price
1247
			if ( isset( $data['sale_price'] ) ) {
1248
				$sale_price = ( '' === $data['sale_price'] ) ? '' : wc_format_decimal( $data['sale_price'] );
1249
				update_post_meta( $product_id, '_sale_price', $sale_price );
1250
			} else {
1251
				$sale_price = get_post_meta( $product_id, '_sale_price', true );
1252
			}
1253
1254
			$date_from = isset( $data['sale_price_dates_from'] ) ? strtotime( $data['sale_price_dates_from'] ) : get_post_meta( $product_id, '_sale_price_dates_from', true );
1255
			$date_to   = isset( $data['sale_price_dates_to'] ) ? strtotime( $data['sale_price_dates_to'] ) : get_post_meta( $product_id, '_sale_price_dates_to', true );
1256
1257
			// Dates
1258
			if ( $date_from ) {
1259
				update_post_meta( $product_id, '_sale_price_dates_from', $date_from );
1260
			} else {
1261
				update_post_meta( $product_id, '_sale_price_dates_from', '' );
1262
			}
1263
1264
			if ( $date_to ) {
1265
				update_post_meta( $product_id, '_sale_price_dates_to', $date_to );
1266
			} else {
1267
				update_post_meta( $product_id, '_sale_price_dates_to', '' );
1268
			}
1269
1270
			if ( $date_to && ! $date_from ) {
1271
				$date_from = strtotime( 'NOW', current_time( 'timestamp' ) );
1272
				update_post_meta( $product_id, '_sale_price_dates_from', $date_from );
1273
			}
1274
1275
			// Update price if on sale
1276
			if ( '' !== $sale_price && '' == $date_to && '' == $date_from ) {
1277
				update_post_meta( $product_id, '_price', wc_format_decimal( $sale_price ) );
1278
			} else {
1279
				update_post_meta( $product_id, '_price', $regular_price );
1280
			}
1281
1282 View Code Duplication
			if ( '' !== $sale_price && $date_from && $date_from <= strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1283
				update_post_meta( $product_id, '_price', wc_format_decimal( $sale_price ) );
1284
			}
1285
1286
			if ( $date_to && $date_to < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
1287
				update_post_meta( $product_id, '_price', $regular_price );
1288
				update_post_meta( $product_id, '_sale_price_dates_from', '' );
1289
				update_post_meta( $product_id, '_sale_price_dates_to', '' );
1290
			}
1291
		}
1292
1293
		// Product parent ID for groups
1294 View Code Duplication
		if ( isset( $data['parent_id'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1295
			wp_update_post( array( 'ID' => $product_id, 'post_parent' => absint( $data['parent_id'] ) ) );
1296
		}
1297
1298
		// Update parent if grouped so price sorting works and stays in sync with the cheapest child
1299
		$_product = wc_get_product( $product_id );
1300
		if ( $_product && $_product->post->post_parent > 0 || $product_type == 'grouped' ) {
1301
1302
			$clear_parent_ids = array();
1303
1304
			if ( $_product->post->post_parent > 0 ) {
1305
				$clear_parent_ids[] = $_product->post->post_parent;
1306
			}
1307
1308
			if ( $product_type == 'grouped' ) {
1309
				$clear_parent_ids[] = $product_id;
1310
			}
1311
1312
			if ( $clear_parent_ids ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $clear_parent_ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1313
				foreach ( $clear_parent_ids as $clear_id ) {
1314
1315
					$children_by_price = get_posts( array(
1316
						'post_parent'    => $clear_id,
1317
						'orderby'        => 'meta_value_num',
1318
						'order'          => 'asc',
1319
						'meta_key'       => '_price',
1320
						'posts_per_page' => 1,
1321
						'post_type'      => 'product',
1322
						'fields'         => 'ids'
1323
					) );
1324
1325
					if ( $children_by_price ) {
1326
						foreach ( $children_by_price as $child ) {
1327
							$child_price = get_post_meta( $child, '_price', true );
1328
							update_post_meta( $clear_id, '_price', $child_price );
1329
						}
1330
					}
1331
				}
1332
			}
1333
		}
1334
1335
		// Sold Individually
1336
		if ( isset( $data['sold_individually'] ) ) {
1337
			update_post_meta( $product_id, '_sold_individually', ( $this->is_true( $data['sold_individually'] ) ) ? 'yes' : '' );
1338
		}
1339
1340
		// Stock status
1341 View Code Duplication
		if ( isset( $data['in_stock'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1342
			$stock_status = ( $this->is_true( $data['in_stock'] ) ) ? 'instock' : 'outofstock';
1343
		} else {
1344
			$stock_status = get_post_meta( $product_id, '_stock_status', true );
1345
1346
			if ( '' === $stock_status ) {
1347
				$stock_status = 'instock';
1348
			}
1349
		}
1350
1351
		// Stock Data
1352
		if ( 'yes' == get_option( 'woocommerce_manage_stock' ) ) {
1353
			// Manage stock
1354 View Code Duplication
			if ( isset( $data['managing_stock'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1355
				$managing_stock = ( $this->is_true( $data['managing_stock'] ) ) ? 'yes' : 'no';
1356
				update_post_meta( $product_id, '_manage_stock', $managing_stock );
1357
			} else {
1358
				$managing_stock = get_post_meta( $product_id, '_manage_stock', true );
1359
			}
1360
1361
			// Backorders
1362
			if ( isset( $data['backorders'] ) ) {
1363 View Code Duplication
				if ( 'notify' == $data['backorders'] ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1364
					$backorders = 'notify';
1365
				} else {
1366
					$backorders = ( $this->is_true( $data['backorders'] ) ) ? 'yes' : 'no';
1367
				}
1368
1369
				update_post_meta( $product_id, '_backorders', $backorders );
1370
			} else {
1371
				$backorders = get_post_meta( $product_id, '_backorders', true );
1372
			}
1373
1374
			if ( 'grouped' == $product_type ) {
1375
1376
				update_post_meta( $product_id, '_manage_stock', 'no' );
1377
				update_post_meta( $product_id, '_backorders', 'no' );
1378
				update_post_meta( $product_id, '_stock', '' );
1379
1380
				wc_update_product_stock_status( $product_id, $stock_status );
1381
1382
			} elseif ( 'external' == $product_type ) {
1383
1384
				update_post_meta( $product_id, '_manage_stock', 'no' );
1385
				update_post_meta( $product_id, '_backorders', 'no' );
1386
				update_post_meta( $product_id, '_stock', '' );
1387
1388
				wc_update_product_stock_status( $product_id, 'instock' );
1389
1390
			} elseif ( 'yes' == $managing_stock ) {
1391
				update_post_meta( $product_id, '_backorders', $backorders );
1392
1393
				wc_update_product_stock_status( $product_id, $stock_status );
1394
1395
				// Stock quantity
1396
				if ( isset( $data['stock_quantity'] ) ) {
1397
					wc_update_product_stock( $product_id, intval( $data['stock_quantity'] ) );
1398
				}
1399
			} else {
1400
1401
				// Don't manage stock
1402
				update_post_meta( $product_id, '_manage_stock', 'no' );
1403
				update_post_meta( $product_id, '_backorders', $backorders );
1404
				update_post_meta( $product_id, '_stock', '' );
1405
1406
				wc_update_product_stock_status( $product_id, $stock_status );
1407
			}
1408
1409
		} else {
1410
			wc_update_product_stock_status( $product_id, $stock_status );
1411
		}
1412
1413
		// Upsells
1414 View Code Duplication
		if ( isset( $data['upsell_ids'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1415
			$upsells = array();
1416
			$ids     = $data['upsell_ids'];
1417
1418
			if ( ! empty( $ids ) ) {
1419
				foreach ( $ids as $id ) {
1420
					if ( $id && $id > 0 ) {
1421
						$upsells[] = $id;
1422
					}
1423
				}
1424
1425
				update_post_meta( $product_id, '_upsell_ids', $upsells );
1426
			} else {
1427
				delete_post_meta( $product_id, '_upsell_ids' );
1428
			}
1429
		}
1430
1431
		// Cross sells
1432 View Code Duplication
		if ( isset( $data['cross_sell_ids'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1433
			$crosssells = array();
1434
			$ids        = $data['cross_sell_ids'];
1435
1436
			if ( ! empty( $ids ) ) {
1437
				foreach ( $ids as $id ) {
1438
					if ( $id && $id > 0 ) {
1439
						$crosssells[] = $id;
1440
					}
1441
				}
1442
1443
				update_post_meta( $product_id, '_crosssell_ids', $crosssells );
1444
			} else {
1445
				delete_post_meta( $product_id, '_crosssell_ids' );
1446
			}
1447
		}
1448
1449
		// Product categories
1450 View Code Duplication
		if ( isset( $data['categories'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1451
			$term_ids = array_unique( array_map( 'intval', (array) $data['categories'] ) );
1452
			wp_set_object_terms( $product_id, $term_ids, 'product_cat' );
1453
		}
1454
1455
		// Product tags
1456 View Code Duplication
		if ( isset( $data['tags'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1457
			$term_ids = array_unique( array_map( 'intval', (array) $data['tags'] ) );
1458
			wp_set_object_terms( $product_id, $term_ids, 'product_tag' );
1459
		}
1460
1461
		// Downloadable
1462 View Code Duplication
		if ( isset( $data['downloadable'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1463
			$is_downloadable = ( $this->is_true( $data['downloadable'] ) ) ? 'yes' : 'no';
1464
			update_post_meta( $product_id, '_downloadable', $is_downloadable );
1465
		} else {
1466
			$is_downloadable = get_post_meta( $product_id, '_downloadable', true );
1467
		}
1468
1469
		// Downloadable options
1470
		if ( 'yes' == $is_downloadable ) {
1471
1472
			// Downloadable files
1473
			if ( isset( $data['downloads'] ) && is_array( $data['downloads'] ) ) {
1474
				$this->save_downloadable_files( $product_id, $data['downloads'] );
1475
			}
1476
1477
			// Download limit
1478 View Code Duplication
			if ( isset( $data['download_limit'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1479
				update_post_meta( $product_id, '_download_limit', ( '' === $data['download_limit'] ) ? '' : absint( $data['download_limit'] ) );
1480
			}
1481
1482
			// Download expiry
1483 View Code Duplication
			if ( isset( $data['download_expiry'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1484
				update_post_meta( $product_id, '_download_expiry', ( '' === $data['download_expiry'] ) ? '' : absint( $data['download_expiry'] ) );
1485
			}
1486
1487
			// Download type
1488
			if ( isset( $data['download_type'] ) ) {
1489
				update_post_meta( $product_id, '_download_type', wc_clean( $data['download_type'] ) );
1490
			}
1491
		}
1492
1493
		// Product url
1494 View Code Duplication
		if ( $product_type == 'external' ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1495
			if ( isset( $data['product_url'] ) ) {
1496
				update_post_meta( $product_id, '_product_url', wc_clean( $data['product_url'] ) );
1497
			}
1498
1499
			if ( isset( $data['button_text'] ) ) {
1500
				update_post_meta( $product_id, '_button_text', wc_clean( $data['button_text'] ) );
1501
			}
1502
		}
1503
1504
		// Reviews allowed
1505
		if ( isset( $data['reviews_allowed'] ) ) {
1506
			$reviews_allowed = ( $this->is_true( $data['reviews_allowed'] ) ) ? 'open' : 'closed';
1507
1508
			$wpdb->update( $wpdb->posts, array( 'comment_status' => $reviews_allowed ), array( 'ID' => $product_id ) );
1509
		}
1510
1511
		// Do action for product type
1512
		do_action( 'woocommerce_cli_process_product_meta_' . $product_type, $product_id, $data );
1513
1514
		return true;
1515
	}
1516
1517
	/**
1518
	 * Save variations.
1519
	 *
1520
	 * @since  2.5.0
1521
	 * @param  int $id
1522
	 * @param  array $data
1523
	 * @return bool
1524
	 * @throws WC_CLI_Exception
1525
	 */
1526
	private function save_variations( $id, $data ) {
1527
		global $wpdb;
1528
1529
		$variations = $data['variations'];
1530
		$attributes = (array) maybe_unserialize( get_post_meta( $id, '_product_attributes', true ) );
1531
1532
		foreach ( $variations as $menu_order => $variation ) {
1533
			$variation_id = isset( $variation['id'] ) ? absint( $variation['id'] ) : 0;
1534
1535
			// Generate a useful post title
1536
			$variation_post_title = sprintf( __( 'Variation #%s of %s', 'woocommerce' ), $variation_id, esc_html( get_the_title( $id ) ) );
1537
1538
			// Update or Add post
1539 View Code Duplication
			if ( ! $variation_id ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1540
				$post_status = ( isset( $variation['visible'] ) && false === $variation['visible'] ) ? 'private' : 'publish';
1541
1542
				$new_variation = array(
1543
					'post_title'   => $variation_post_title,
1544
					'post_content' => '',
1545
					'post_status'  => $post_status,
1546
					'post_author'  => get_current_user_id(),
1547
					'post_parent'  => $id,
1548
					'post_type'    => 'product_variation',
1549
					'menu_order'   => $menu_order
1550
				);
1551
1552
				$variation_id = wp_insert_post( $new_variation );
1553
1554
				do_action( 'woocommerce_create_product_variation', $variation_id );
1555
			} else {
1556
				$update_variation = array( 'post_title' => $variation_post_title, 'menu_order' => $menu_order );
1557
				if ( isset( $variation['visible'] ) ) {
1558
					$post_status = ( false === $variation['visible'] ) ? 'private' : 'publish';
1559
					$update_variation['post_status'] = $post_status;
1560
				}
1561
1562
				$wpdb->update( $wpdb->posts, $update_variation, array( 'ID' => $variation_id ) );
1563
1564
				do_action( 'woocommerce_update_product_variation', $variation_id );
1565
			}
1566
1567
			// Stop with we don't have a variation ID
1568
			if ( is_wp_error( $variation_id ) ) {
1569
				throw new WC_CLI_Exception( 'woocommerce_cli_cannot_save_product_variation', $variation_id->get_error_message() );
1570
			}
1571
1572
			// SKU
1573 View Code Duplication
			if ( isset( $variation['sku'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1574
				$sku     = get_post_meta( $variation_id, '_sku', true );
1575
				$new_sku = wc_clean( $variation['sku'] );
1576
1577
				if ( '' == $new_sku ) {
1578
					update_post_meta( $variation_id, '_sku', '' );
1579
				} elseif ( $new_sku !== $sku ) {
1580
					if ( ! empty( $new_sku ) ) {
1581
						$unique_sku = wc_product_has_unique_sku( $variation_id, $new_sku );
1582
						if ( ! $unique_sku ) {
1583
							throw new WC_CLI_Exception( 'woocommerce_cli_product_sku_already_exists', __( 'The SKU already exists on another product', 'woocommerce' ) );
1584
						} else {
1585
							update_post_meta( $variation_id, '_sku', $new_sku );
1586
						}
1587
					} else {
1588
						update_post_meta( $variation_id, '_sku', '' );
1589
					}
1590
				}
1591
			}
1592
1593
			// Thumbnail
1594
			if ( isset( $variation['image'] ) && is_array( $variation['image'] ) ) {
1595
				$image = current( $variation['image'] );
1596
				if ( $image && is_array( $image ) ) {
1597
					if ( isset( $image['position'] ) && isset( $image['src'] ) && $image['position'] == 0 ) {
1598
						$upload = $this->upload_product_image( wc_clean( $image['src'] ) );
1599
						if ( is_wp_error( $upload ) ) {
1600
							throw new WC_CLI_Exception( 'woocommerce_cli_cannot_upload_product_image', $upload->get_error_message() );
1601
						}
1602
						$attachment_id = $this->set_product_image_as_attachment( $upload, $id );
1603
						update_post_meta( $variation_id, '_thumbnail_id', $attachment_id );
1604
					}
1605
				} else {
1606
					delete_post_meta( $variation_id, '_thumbnail_id' );
1607
				}
1608
			}
1609
1610
			// Virtual variation
1611 View Code Duplication
			if ( isset( $variation['virtual'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1612
				$is_virtual = ( $this->is_true( $variation['virtual'] ) ) ? 'yes' : 'no';
1613
				update_post_meta( $variation_id, '_virtual', $is_virtual );
1614
			}
1615
1616
			// Downloadable variation
1617 View Code Duplication
			if ( isset( $variation['downloadable'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1618
				$is_downloadable = ( $this->is_true( $variation['downloadable'] ) ) ? 'yes' : 'no';
1619
				update_post_meta( $variation_id, '_downloadable', $is_downloadable );
1620
			} else {
1621
				$is_downloadable = get_post_meta( $variation_id, '_downloadable', true );
1622
			}
1623
1624
			// Shipping data
1625
			$this->save_product_shipping_data( $variation_id, $variation );
1626
1627
			// Stock handling
1628 View Code Duplication
			if ( isset( $variation['managing_stock'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1629
				$managing_stock = ( $this->is_true( $variation['managing_stock'] ) ) ? 'yes' : 'no';
1630
				update_post_meta( $variation_id, '_manage_stock', $managing_stock );
1631
			} else {
1632
				$managing_stock = get_post_meta( $variation_id, '_manage_stock', true );
1633
			}
1634
1635
			// Only update stock status to user setting if changed by the user, but do so before looking at stock levels at variation level
1636
			if ( isset( $variation['in_stock'] ) ) {
1637
				$stock_status = ( $this->is_true( $variation['in_stock'] ) ) ? 'instock' : 'outofstock';
1638
				wc_update_product_stock_status( $variation_id, $stock_status );
1639
			}
1640
1641
			if ( 'yes' === $managing_stock ) {
1642
				if ( isset( $variation['backorders'] ) ) {
1643 View Code Duplication
					if ( 'notify' === $variation['backorders'] ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1644
						$backorders = 'notify';
1645
					} else {
1646
						$backorders = ( $this->is_true( $variation['backorders'] ) ) ? 'yes' : 'no';
1647
					}
1648
				} else {
1649
					$backorders = 'no';
1650
				}
1651
1652
				update_post_meta( $variation_id, '_backorders', $backorders );
1653
1654
				if ( isset( $variation['stock_quantity'] ) ) {
1655
					wc_update_product_stock( $variation_id, wc_stock_amount( $variation['stock_quantity'] ) );
1656
				}
1657
			} else {
1658
				delete_post_meta( $variation_id, '_backorders' );
1659
				delete_post_meta( $variation_id, '_stock' );
1660
			}
1661
1662
			// Regular Price
1663
			if ( isset( $variation['regular_price'] ) ) {
1664
				$regular_price = ( '' === $variation['regular_price'] ) ? '' : wc_format_decimal( $variation['regular_price'] );
1665
				update_post_meta( $variation_id, '_regular_price', $regular_price );
1666
			} else {
1667
				$regular_price = get_post_meta( $variation_id, '_regular_price', true );
1668
			}
1669
1670
			// Sale Price
1671
			if ( isset( $variation['sale_price'] ) ) {
1672
				$sale_price = ( '' === $variation['sale_price'] ) ? '' : wc_format_decimal( $variation['sale_price'] );
1673
				update_post_meta( $variation_id, '_sale_price', $sale_price );
1674
			} else {
1675
				$sale_price = get_post_meta( $variation_id, '_sale_price', true );
1676
			}
1677
1678
			$date_from = isset( $variation['sale_price_dates_from'] ) ? strtotime( $variation['sale_price_dates_from'] ) : get_post_meta( $variation_id, '_sale_price_dates_from', true );
1679
			$date_to   = isset( $variation['sale_price_dates_to'] ) ? strtotime( $variation['sale_price_dates_to'] ) : get_post_meta( $variation_id, '_sale_price_dates_to', true );
1680
1681
			// Save Dates
1682
			if ( $date_from ) {
1683
				update_post_meta( $variation_id, '_sale_price_dates_from', $date_from );
1684
			} else {
1685
				update_post_meta( $variation_id, '_sale_price_dates_from', '' );
1686
			}
1687
1688
			if ( $date_to ) {
1689
				update_post_meta( $variation_id, '_sale_price_dates_to', $date_to );
1690
			} else {
1691
				update_post_meta( $variation_id, '_sale_price_dates_to', '' );
1692
			}
1693
1694
			if ( $date_to && ! $date_from ) {
1695
				update_post_meta( $variation_id, '_sale_price_dates_from', strtotime( 'NOW', current_time( 'timestamp' ) ) );
1696
			}
1697
1698
			// Update price if on sale
1699
			if ( '' != $sale_price && '' == $date_to && '' == $date_from ) {
1700
				update_post_meta( $variation_id, '_price', $sale_price );
1701
			} else {
1702
				update_post_meta( $variation_id, '_price', $regular_price );
1703
			}
1704
1705
			if ( '' != $sale_price && $date_from && $date_from < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
1706
				update_post_meta( $variation_id, '_price', $sale_price );
1707
			}
1708
1709
			if ( $date_to && $date_to < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
1710
				update_post_meta( $variation_id, '_price', $regular_price );
1711
				update_post_meta( $variation_id, '_sale_price_dates_from', '' );
1712
				update_post_meta( $variation_id, '_sale_price_dates_to', '' );
1713
			}
1714
1715
			// Tax class
1716 View Code Duplication
			if ( isset( $variation['tax_class'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1717
				if ( $variation['tax_class'] !== 'parent' ) {
1718
					update_post_meta( $variation_id, '_tax_class', wc_clean( $variation['tax_class'] ) );
1719
				} else {
1720
					delete_post_meta( $variation_id, '_tax_class' );
1721
				}
1722
			}
1723
1724
			// Downloads
1725
			if ( 'yes' == $is_downloadable ) {
1726
				// Downloadable files
1727 View Code Duplication
				if ( isset( $variation['downloads'] ) && is_array( $variation['downloads'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1728
					$this->save_downloadable_files( $id, $variation['downloads'], $variation_id );
1729
				}
1730
1731
				// Download limit
1732
				if ( isset( $variation['download_limit'] ) ) {
1733
					$download_limit = absint( $variation['download_limit'] );
1734
					update_post_meta( $variation_id, '_download_limit', ( ! $download_limit ) ? '' : $download_limit );
1735
				}
1736
1737
				// Download expiry
1738
				if ( isset( $variation['download_expiry'] ) ) {
1739
					$download_expiry = absint( $variation['download_expiry'] );
1740
					update_post_meta( $variation_id, '_download_expiry', ( ! $download_expiry ) ? '' : $download_expiry );
1741
				}
1742
			} else {
1743
				update_post_meta( $variation_id, '_download_limit', '' );
1744
				update_post_meta( $variation_id, '_download_expiry', '' );
1745
				update_post_meta( $variation_id, '_downloadable_files', '' );
1746
			}
1747
1748
			// Update taxonomies
1749
			if ( isset( $variation['attributes'] ) ) {
1750
				$updated_attribute_keys = array();
1751
1752
				foreach ( $variation['attributes'] as $slug => $value ) {
1753
					$taxonomy   = sanitize_title( $slug );
1754
					$_attribute = array();
1755
1756
					if ( $this->get_attribute_taxonomy_by_slug( $slug ) !== null ) {
1757
						$taxonomy = $this->get_attribute_taxonomy_by_slug( $slug );
1758
					}
1759
1760
					if ( isset( $attributes[ $taxonomy ] ) ) {
1761
						$_attribute = $attributes[ $taxonomy ];
1762
					}
1763
1764
					if ( isset( $_attribute['is_variation'] ) && $_attribute['is_variation'] ) {
1765
						$attribute_key   = 'attribute_' . sanitize_title( $_attribute['name'] );
1766
						$attribute_value = ! empty( $value ) ? sanitize_title( stripslashes( $value ) ) : '';
1767
						$updated_attribute_keys[] = $attribute_key;
1768
1769
						update_post_meta( $variation_id, $attribute_key, $attribute_value );
1770
					}
1771
				}
1772
1773
				// Remove old taxonomies attributes so data is kept up to date - first get attribute key names
1774
				$delete_attribute_keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->postmeta} WHERE meta_key LIKE 'attribute_%%' AND meta_key NOT IN ( '" . implode( "','", $updated_attribute_keys ) . "' ) AND post_id = %d;", $variation_id ) );
1775
1776
				foreach ( $delete_attribute_keys as $key ) {
1777
					delete_post_meta( $variation_id, $key );
1778
				}
1779
			}
1780
1781
			do_action( 'woocommerce_cli_save_product_variation', $variation_id, $menu_order, $variation );
1782
		}
1783
1784
		// Update parent if variable so price sorting works and stays in sync with the cheapest child
1785
		WC_Product_Variable::sync( $id );
1786
1787
		// Update default attributes options setting
1788
		if ( isset( $data['default_attribute'] ) ) {
1789
			$data['default_attributes'] = $data['default_attribute'];
1790
		}
1791
1792
		if ( isset( $data['default_attributes'] ) && is_array( $data['default_attributes'] ) ) {
1793
			$default_attributes = array();
1794
1795
			foreach ( $data['default_attributes'] as $default_attr_key => $default_attr ) {
1796
				if ( ! isset( $default_attr['name'] ) ) {
1797
					continue;
1798
				}
1799
1800
				$taxonomy = sanitize_title( $default_attr['name'] );
1801
1802
				if ( isset( $default_attr['slug'] ) ) {
1803
					$taxonomy = $this->get_attribute_taxonomy_by_slug( $default_attr['slug'] );
1804
				}
1805
1806
				if ( isset( $attributes[ $taxonomy ] ) ) {
1807
					$_attribute = $attributes[ $taxonomy ];
1808
1809
					if ( $_attribute['is_variation'] ) {
1810
						// Don't use wc_clean as it destroys sanitized characters
1811
						if ( isset( $default_attr['option'] ) ) {
1812
							$value = sanitize_title( trim( stripslashes( $default_attr['option'] ) ) );
1813
						} else {
1814
							$value = '';
1815
						}
1816
1817
						if ( $value ) {
1818
							$default_attributes[ $taxonomy ] = $value;
1819
						}
1820
					}
1821
				}
1822
			}
1823
1824
			update_post_meta( $id, '_default_attributes', $default_attributes );
1825
		}
1826
1827
		return true;
1828
	}
1829
1830
	/**
1831
	 * Save product images.
1832
	 *
1833
	 * @since  2.5.0
1834
	 * @param  array $images
1835
	 * @param  int $id
1836
	 * @throws WC_CLI_Exception
1837
	 */
1838
	private function save_product_images( $id, $images ) {
1839
		if ( is_array( $images ) ) {
1840
			$gallery = array();
1841
1842
			foreach ( $images as $image ) {
1843
				if ( isset( $image['position'] ) && $image['position'] == 0 ) {
1844
					$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
1845
1846
					if ( 0 === $attachment_id && isset( $image['src'] ) ) {
1847
						$upload = $this->upload_product_image( esc_url_raw( $image['src'] ) );
1848
1849
						if ( is_wp_error( $upload ) ) {
1850
							throw new WC_CLI_Exception( 'woocommerce_cli_cannot_upload_product_image', $upload->get_error_message() );
1851
						}
1852
1853
						$attachment_id = $this->set_product_image_as_attachment( $upload, $id );
1854
					}
1855
1856
					set_post_thumbnail( $id, $attachment_id );
1857
				} else {
1858
					$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
1859
1860
					if ( 0 === $attachment_id && isset( $image['src'] ) ) {
1861
						$upload = $this->upload_product_image( esc_url_raw( $image['src'] ) );
1862
1863
						if ( is_wp_error( $upload ) ) {
1864
							throw new WC_CLI_Exception( 'woocommerce_cli_cannot_upload_product_image', $upload->get_error_message() );
1865
						}
1866
1867
						$gallery[] = $this->set_product_image_as_attachment( $upload, $id );
1868
					} else {
1869
						$gallery[] = $attachment_id;
1870
					}
1871
				}
1872
			}
1873
1874
			if ( ! empty( $gallery ) ) {
1875
				update_post_meta( $id, '_product_image_gallery', implode( ',', $gallery ) );
1876
			}
1877
		} else {
1878
			delete_post_thumbnail( $id );
1879
			update_post_meta( $id, '_product_image_gallery', '' );
1880
		}
1881
	}
1882
1883
	/**
1884
	 * Save product shipping data.
1885
	 *
1886
	 * @since 2.5.0
1887
	 * @param int $id
1888
	 * @param array $data
1889
	 */
1890
	private function save_product_shipping_data( $id, $data ) {
1891
		if ( isset( $data['weight'] ) ) {
1892
			update_post_meta( $id, '_weight', ( '' === $data['weight'] ) ? '' : wc_format_decimal( $data['weight'] ) );
1893
		}
1894
1895
		// Product dimensions
1896
		if ( isset( $data['dimensions'] ) ) {
1897
			// Height
1898 View Code Duplication
			if ( isset( $data['dimensions']['height'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1899
				update_post_meta( $id, '_height', ( '' === $data['dimensions']['height'] ) ? '' : wc_format_decimal( $data['dimensions']['height'] ) );
1900
			}
1901
1902
			// Width
1903 View Code Duplication
			if ( isset( $data['dimensions']['width'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1904
				update_post_meta( $id, '_width', ( '' === $data['dimensions']['width'] ) ? '' : wc_format_decimal($data['dimensions']['width'] ) );
1905
			}
1906
1907
			// Length
1908 View Code Duplication
			if ( isset( $data['dimensions']['length'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1909
				update_post_meta( $id, '_length', ( '' === $data['dimensions']['length'] ) ? '' : wc_format_decimal( $data['dimensions']['length'] ) );
1910
			}
1911
		}
1912
1913
		// Virtual
1914
		if ( isset( $data['virtual'] ) ) {
1915
			$virtual = ( $this->is_true( $data['virtual'] ) ) ? 'yes' : 'no';
1916
1917
			if ( 'yes' == $virtual ) {
1918
				update_post_meta( $id, '_weight', '' );
1919
				update_post_meta( $id, '_length', '' );
1920
				update_post_meta( $id, '_width', '' );
1921
				update_post_meta( $id, '_height', '' );
1922
			}
1923
		}
1924
1925
		// Shipping class
1926
		if ( isset( $data['shipping_class'] ) ) {
1927
			wp_set_object_terms( $id, wc_clean( $data['shipping_class'] ), 'product_shipping_class' );
1928
		}
1929
	}
1930
1931
	/**
1932
	 * Save downloadable files.
1933
	 *
1934
	 * @since 2.5.0
1935
	 * @param int $product_id
1936
	 * @param array $downloads
1937
	 * @param int $variation_id
1938
	 */
1939 View Code Duplication
	private function save_downloadable_files( $product_id, $downloads, $variation_id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1940
		$files = array();
1941
1942
		// File paths will be stored in an array keyed off md5(file path)
1943
		foreach ( $downloads as $key => $file ) {
1944
			if ( isset( $file['url'] ) ) {
1945
				$file['file'] = $file['url'];
1946
			}
1947
1948
			if ( ! isset( $file['file'] ) ) {
1949
				continue;
1950
			}
1951
1952
			$file_name = isset( $file['name'] ) ? wc_clean( $file['name'] ) : '';
1953
1954
			if ( 0 === strpos( $file['file'], 'http' ) ) {
1955
				$file_url = esc_url_raw( $file['file'] );
1956
			} else {
1957
				$file_url = wc_clean( $file['file'] );
1958
			}
1959
1960
			$files[ md5( $file_url ) ] = array(
1961
				'name' => $file_name,
1962
				'file' => $file_url
1963
			);
1964
		}
1965
1966
		// Grant permission to any newly added files on any existing orders for this product prior to saving
1967
		do_action( 'woocommerce_process_product_file_download_paths', $product_id, $variation_id, $files );
1968
1969
		$id = ( 0 === $variation_id ) ? $product_id : $variation_id;
1970
		update_post_meta( $id, '_downloadable_files', $files );
1971
	}
1972
1973
	/**
1974
	 * Get attribute taxonomy by slug.
1975
	 *
1976
	 * @since  2.5.0
1977
	 * @param  string $slug
1978
	 * @return string|null
1979
	 */
1980
	private function get_attribute_taxonomy_by_slug( $slug ) {
1981
		$taxonomy = null;
1982
		$attribute_taxonomies = wc_get_attribute_taxonomies();
1983
1984
		foreach ( $attribute_taxonomies as $key => $tax ) {
1985
			if ( $slug == $tax->attribute_name ) {
1986
				$taxonomy = 'pa_' . $tax->attribute_name;
1987
1988
				break;
1989
			}
1990
		}
1991
1992
		return $taxonomy;
1993
	}
1994
1995
	/**
1996
	 * Upload image from URL
1997
	 *
1998
	 * @since  2.5.0
1999
	 * @param  string $image_url
2000
	 * @return int|WP_Error attachment id
2001
	 * @throws WC_CLI_Exception
2002
	 */
2003
	private function upload_product_image( $image_url ) {
2004
		$file_name 		= basename( current( explode( '?', $image_url ) ) );
2005
		$wp_filetype 	= wp_check_filetype( $file_name, null );
2006
		$parsed_url 	= @parse_url( $image_url );
2007
2008
		// Check parsed URL
2009
		if ( ! $parsed_url || ! is_array( $parsed_url ) ) {
2010
			throw new WC_CLI_Exception( 'woocommerce_cli_invalid_product_image', sprintf( __( 'Invalid URL %s', 'woocommerce' ), $image_url ) );
2011
		}
2012
2013
		// Ensure url is valid
2014
		$image_url = str_replace( ' ', '%20', $image_url );
2015
2016
		// Get the file
2017
		$response = wp_safe_remote_get( $image_url, array(
2018
			'timeout' => 10
2019
		) );
2020
2021 View Code Duplication
		if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2022
			throw new WC_CLI_Exception( 'woocommerce_cli_invalid_remote_product_image', sprintf( __( 'Error getting remote image %s', 'woocommerce' ), $image_url ) );
2023
		}
2024
2025
		// Ensure we have a file name and type
2026 View Code Duplication
		if ( ! $wp_filetype['type'] ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2027
			$headers = wp_remote_retrieve_headers( $response );
2028
			if ( isset( $headers['content-disposition'] ) && strstr( $headers['content-disposition'], 'filename=' ) ) {
2029
				$disposition = end( explode( 'filename=', $headers['content-disposition'] ) );
2030
				$disposition = sanitize_file_name( $disposition );
2031
				$file_name   = $disposition;
2032
			} elseif ( isset( $headers['content-type'] ) && strstr( $headers['content-type'], 'image/' ) ) {
2033
				$file_name = 'image.' . str_replace( 'image/', '', $headers['content-type'] );
2034
			}
2035
			unset( $headers );
2036
		}
2037
2038
		// Upload the file.
2039
		$upload = wp_upload_bits( $file_name, '', wp_remote_retrieve_body( $response ) );
2040
2041
		if ( $upload['error'] ) {
2042
			throw new WC_CLI_Exception( 'woocommerce_cli_product_image_upload_error', $upload['error'] );
2043
		}
2044
2045
		// Get filesize
2046
		$filesize = filesize( $upload['file'] );
2047
2048
		if ( 0 == $filesize ) {
2049
			@unlink( $upload['file'] );
2050
			unset( $upload );
2051
			throw new WC_CLI_Exception( 'woocommerce_cli_product_image_upload_file_error', __( 'Zero size file downloaded', 'woocommerce' ) );
2052
		}
2053
2054
		unset( $response );
2055
2056
		return $upload;
2057
	}
2058
2059
	/**
2060
	 * Get product image as attachment
2061
	 *
2062
	 * @since  2.5.0
2063
	 * @param  int $upload
2064
	 * @param  int $id
2065
	 * @return int
2066
	 */
2067
	private function set_product_image_as_attachment( $upload, $id ) {
2068
		$info    = wp_check_filetype( $upload['file'] );
2069
		$title   = '';
2070
		$content = '';
2071
2072 View Code Duplication
		if ( $image_meta = @wp_read_image_metadata( $upload['file'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2073
			if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
2074
				$title = $image_meta['title'];
2075
			}
2076
			if ( trim( $image_meta['caption'] ) ) {
2077
				$content = $image_meta['caption'];
2078
			}
2079
		}
2080
2081
		$attachment = array(
2082
			'post_mime_type' => $info['type'],
2083
			'guid'           => $upload['url'],
2084
			'post_parent'    => $id,
2085
			'post_title'     => $title,
2086
			'post_content'   => $content
2087
		);
2088
2089
		$attachment_id = wp_insert_attachment( $attachment, $upload['file'], $id );
2090
		if ( ! is_wp_error( $attachment_id ) ) {
2091
			wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $upload['file'] ) );
2092
		}
2093
2094
		return $attachment_id;
2095
	}
2096
2097
	/**
2098
	 * Clear product
2099
	 *
2100
	 * @since 2.5.0
2101
	 * @param int $product_id Product ID
2102
	 */
2103
	private function clear_product( $product_id ) {
2104
		if ( ! is_numeric( $product_id ) || 0 >= $product_id ) {
2105
			return;
2106
		}
2107
2108
		// Delete product attachments
2109
		$attachments = get_children( array(
2110
			'post_parent' => $product_id,
2111
			'post_status' => 'any',
2112
			'post_type'   => 'attachment',
2113
		) );
2114
2115
		foreach ( (array) $attachments as $attachment ) {
2116
			wp_delete_attachment( $attachment->ID, true );
2117
		}
2118
2119
		// Delete product
2120
		wp_delete_post( $product_id, true );
2121
	}
2122
}
2123