WC_Meta_Box_Product_Data::save()   F
last analyzed

Complexity

Conditions 43
Paths 540

Size

Total Lines 108

Duplication

Lines 14
Ratio 12.96 %

Code Coverage

Tests 0
CRAP Score 1892

Importance

Changes 0
Metric Value
cc 43
nc 540
nop 2
dl 14
loc 108
ccs 0
cts 81
cp 0
crap 1892
rs 0.5111
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Product Data
4
 *
5
 * Displays the product data box, tabbed, with several panels covering price, stock etc.
6
 *
7
 * @package  WooCommerce/Admin/Meta Boxes
8
 * @version  3.0.0
9
 */
10
11
if ( ! defined( 'ABSPATH' ) ) {
12
	exit;
13
}
14
15
/**
16
 * WC_Meta_Box_Product_Data Class.
17
 */
18
class WC_Meta_Box_Product_Data {
19
20
	/**
21
	 * Output the metabox.
22
	 *
23
	 * @param WP_Post $post Post object.
24
	 */
25
	public static function output( $post ) {
26
		global $thepostid, $product_object;
27
28
		$thepostid      = $post->ID;
29
		$product_object = $thepostid ? wc_get_product( $thepostid ) : new WC_Product();
30
31
		wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
32
33
		include 'views/html-product-data-panel.php';
34
	}
35
36
	/**
37
	 * Show tab content/settings.
38
	 */
39
	private static function output_tabs() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
40
		global $post, $thepostid, $product_object;
41
42
		include 'views/html-product-data-general.php';
43
		include 'views/html-product-data-inventory.php';
44
		include 'views/html-product-data-shipping.php';
45
		include 'views/html-product-data-linked-products.php';
46
		include 'views/html-product-data-attributes.php';
47
		include 'views/html-product-data-advanced.php';
48
	}
49
50
	/**
51
	 * Return array of product type options.
52
	 *
53
	 * @return array
54
	 */
55
	private static function get_product_type_options() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
56
		return apply_filters(
57
			'product_type_options',
58
			array(
59
				'virtual'      => array(
60
					'id'            => '_virtual',
61
					'wrapper_class' => 'show_if_simple',
62
					'label'         => __( 'Virtual', 'woocommerce' ),
63
					'description'   => __( 'Virtual products are intangible and are not shipped.', 'woocommerce' ),
64
					'default'       => 'no',
65
				),
66
				'downloadable' => array(
67
					'id'            => '_downloadable',
68
					'wrapper_class' => 'show_if_simple',
69
					'label'         => __( 'Downloadable', 'woocommerce' ),
70
					'description'   => __( 'Downloadable products give access to a file upon purchase.', 'woocommerce' ),
71
					'default'       => 'no',
72
				),
73
			)
74
		);
75
	}
76
77
	/**
78
	 * Return array of tabs to show.
79
	 *
80
	 * @return array
81
	 */
82
	private static function get_product_data_tabs() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
83
		$tabs = apply_filters(
84
			'woocommerce_product_data_tabs',
85
			array(
86
				'general'        => array(
87
					'label'    => __( 'General', 'woocommerce' ),
88
					'target'   => 'general_product_data',
89
					'class'    => array( 'hide_if_grouped' ),
90
					'priority' => 10,
91
				),
92
				'inventory'      => array(
93
					'label'    => __( 'Inventory', 'woocommerce' ),
94
					'target'   => 'inventory_product_data',
95
					'class'    => array( 'show_if_simple', 'show_if_variable', 'show_if_grouped', 'show_if_external' ),
96
					'priority' => 20,
97
				),
98
				'shipping'       => array(
99
					'label'    => __( 'Shipping', 'woocommerce' ),
100
					'target'   => 'shipping_product_data',
101
					'class'    => array( 'hide_if_virtual', 'hide_if_grouped', 'hide_if_external' ),
102
					'priority' => 30,
103
				),
104
				'linked_product' => array(
105
					'label'    => __( 'Linked Products', 'woocommerce' ),
106
					'target'   => 'linked_product_data',
107
					'class'    => array(),
108
					'priority' => 40,
109
				),
110
				'attribute'      => array(
111
					'label'    => __( 'Attributes', 'woocommerce' ),
112
					'target'   => 'product_attributes',
113
					'class'    => array(),
114
					'priority' => 50,
115
				),
116
				'variations'     => array(
117
					'label'    => __( 'Variations', 'woocommerce' ),
118
					'target'   => 'variable_product_options',
119
					'class'    => array( 'variations_tab', 'show_if_variable' ),
120
					'priority' => 60,
121
				),
122
				'advanced'       => array(
123
					'label'    => __( 'Advanced', 'woocommerce' ),
124
					'target'   => 'advanced_product_data',
125
					'class'    => array(),
126
					'priority' => 70,
127
				),
128
			)
129
		);
130
131
		// Sort tabs based on priority.
132
		uasort( $tabs, array( __CLASS__, 'product_data_tabs_sort' ) );
133
134
		return $tabs;
135
	}
136
137
	/**
138
	 * Callback to sort product data tabs on priority.
139
	 *
140
	 * @since 3.1.0
141
	 * @param int $a First item.
142
	 * @param int $b Second item.
143
	 *
144
	 * @return bool
145
	 */
146
	private static function product_data_tabs_sort( $a, $b ) {
147
		if ( ! isset( $a['priority'], $b['priority'] ) ) {
148
			return -1;
149
		}
150
151
		if ( $a['priority'] === $b['priority'] ) {
152
			return 0;
153
		}
154
155
		return $a['priority'] < $b['priority'] ? -1 : 1;
156
	}
157
158
	/**
159
	 * Filter callback for finding variation attributes.
160
	 *
161
	 * @param  WC_Product_Attribute $attribute Product attribute.
162
	 * @return bool
163
	 */
164
	private static function filter_variation_attributes( $attribute ) {
165
		return true === $attribute->get_variation();
166
	}
167
168
	/**
169
	 * Show options for the variable product type.
170
	 */
171
	public static function output_variations() {
172
		global $post, $wpdb, $product_object;
173
174
		$variation_attributes   = array_filter( $product_object->get_attributes(), array( __CLASS__, 'filter_variation_attributes' ) );
175
		$default_attributes     = $product_object->get_default_attributes();
176
		$variations_count       = absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_count', $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(ID) FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'product_variation' AND post_status IN ('publish', 'private')", $post->ID ) ), $post->ID ) );
177
		$variations_per_page    = absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_per_page', 15 ) );
178
		$variations_total_pages = ceil( $variations_count / $variations_per_page );
179
180
		include 'views/html-product-data-variations.php';
181
	}
182
183
	/**
184
	 * Prepare downloads for save.
185
	 *
186
	 * @param array $file_names File names.
187
	 * @param array $file_urls File urls.
188
	 * @param array $file_hashes File hashes.
189
	 *
190
	 * @return array
191
	 */
192
	private static function prepare_downloads( $file_names, $file_urls, $file_hashes ) {
193
		$downloads = array();
194
195
		if ( ! empty( $file_urls ) ) {
196
			$file_url_size = count( $file_urls );
197
198
			for ( $i = 0; $i < $file_url_size; $i ++ ) {
199
				if ( ! empty( $file_urls[ $i ] ) ) {
200
					$downloads[] = array(
201
						'name'        => wc_clean( $file_names[ $i ] ),
202
						'file'        => wp_unslash( trim( $file_urls[ $i ] ) ),
203
						'download_id' => wc_clean( $file_hashes[ $i ] ),
204
					);
205
				}
206
			}
207
		}
208
		return $downloads;
209
	}
210
211
	/**
212
	 * Prepare children for save.
213
	 *
214
	 * @return array
215
	 */
216
	private static function prepare_children() {
217
		return isset( $_POST['grouped_products'] ) ? array_filter( array_map( 'intval', (array) $_POST['grouped_products'] ) ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
218
	}
219
220
	/**
221
	 * Prepare attributes for save.
222
	 *
223
	 * @param array $data Attribute data.
224
	 *
225
	 * @return array
226
	 */
227
	public static function prepare_attributes( $data = false ) {
228
		$attributes = array();
229
230
		if ( ! $data ) {
231
			$data = stripslashes_deep( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
232
		}
233
234
		if ( isset( $data['attribute_names'], $data['attribute_values'] ) ) {
235
			$attribute_names         = $data['attribute_names'];
236
			$attribute_values        = $data['attribute_values'];
237
			$attribute_visibility    = isset( $data['attribute_visibility'] ) ? $data['attribute_visibility'] : array();
238
			$attribute_variation     = isset( $data['attribute_variation'] ) ? $data['attribute_variation'] : array();
239
			$attribute_position      = $data['attribute_position'];
240
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
241
242
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
243
				if ( empty( $attribute_names[ $i ] ) || ! isset( $attribute_values[ $i ] ) ) {
244
					continue;
245
				}
246
				$attribute_id   = 0;
247
				$attribute_name = wc_clean( $attribute_names[ $i ] );
248
249
				if ( 'pa_' === substr( $attribute_name, 0, 3 ) ) {
250
					$attribute_id = wc_attribute_taxonomy_id_by_name( $attribute_name );
0 ignored issues
show
Bug introduced by
It seems like $attribute_name defined by wc_clean($attribute_names[$i]) on line 247 can also be of type array; however, wc_attribute_taxonomy_id_by_name() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
251
				}
252
253
				$options = isset( $attribute_values[ $i ] ) ? $attribute_values[ $i ] : '';
254
255
				if ( is_array( $options ) ) {
256
					// Term ids sent as array.
257
					$options = wp_parse_id_list( $options );
258
				} else {
259
					// Terms or text sent in textarea.
260
					$options = 0 < $attribute_id ? wc_sanitize_textarea( wc_sanitize_term_text_based( $options ) ) : wc_sanitize_textarea( $options );
261
					$options = wc_get_text_attributes( $options );
262
				}
263
264
				if ( empty( $options ) ) {
265
					continue;
266
				}
267
268
				$attribute = new WC_Product_Attribute();
269
				$attribute->set_id( $attribute_id );
270
				$attribute->set_name( $attribute_name );
0 ignored issues
show
Documentation introduced by
$attribute_name is of type string|array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
271
				$attribute->set_options( $options );
272
				$attribute->set_position( $attribute_position[ $i ] );
273
				$attribute->set_visible( isset( $attribute_visibility[ $i ] ) );
274
				$attribute->set_variation( isset( $attribute_variation[ $i ] ) );
275
				$attributes[] = apply_filters( 'woocommerce_admin_meta_boxes_prepare_attribute', $attribute, $data, $i );
276
			}
277
		}
278
		return $attributes;
279
	}
280
281
	/**
282
	 * Prepare attributes for a specific variation or defaults.
283
	 *
284
	 * @param  array  $all_attributes List of attribute keys.
285
	 * @param  string $key_prefix Attribute key prefix.
286
	 * @param  int    $index Attribute array index.
287
	 * @return array
288
	 */
289
	private static function prepare_set_attributes( $all_attributes, $key_prefix = 'attribute_', $index = null ) {
290
		$attributes = array();
291
292
		if ( $all_attributes ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $all_attributes 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...
293
			foreach ( $all_attributes as $attribute ) {
294
				if ( $attribute->get_variation() ) {
295
					$attribute_key = sanitize_title( $attribute->get_name() );
296
297
					if ( ! is_null( $index ) ) {
298
						$value = isset( $_POST[ $key_prefix . $attribute_key ][ $index ] ) ? wp_unslash( $_POST[ $key_prefix . $attribute_key ][ $index ] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
299
					} else {
300
						$value = isset( $_POST[ $key_prefix . $attribute_key ] ) ? wp_unslash( $_POST[ $key_prefix . $attribute_key ] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
301
					}
302
303
					if ( $attribute->is_taxonomy() ) {
304
						// Don't use wc_clean as it destroys sanitized characters.
305
						$value = sanitize_title( $value );
306
					} else {
307
						$value = html_entity_decode( wc_clean( $value ), ENT_QUOTES, get_bloginfo( 'charset' ) ); // WPCS: sanitization ok.
308
					}
309
310
					$attributes[ $attribute_key ] = $value;
311
				}
312
			}
313
		}
314
315
		return $attributes;
316
	}
317
318
	/**
319
	 * Save meta box data.
320
	 *
321
	 * @param int     $post_id WP post id.
322
	 * @param WP_Post $post Post object.
323
	 */
324
	public static function save( $post_id, $post ) {
0 ignored issues
show
Unused Code introduced by
The parameter $post is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
325
		// phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification
326
		// Process product type first so we have the correct class to run setters.
327
		$product_type = empty( $_POST['product-type'] ) ? WC_Product_Factory::get_product_type( $post_id ) : sanitize_title( wp_unslash( $_POST['product-type'] ) );
328
		$classname    = WC_Product_Factory::get_product_classname( $post_id, $product_type ? $product_type : 'simple' );
329
		$product      = new $classname( $post_id );
330
		$attributes   = self::prepare_attributes();
331
		$stock        = null;
332
333
		// Handle stock changes.
334
		if ( isset( $_POST['_stock'] ) ) {
335
			if ( isset( $_POST['_original_stock'] ) && wc_stock_amount( $product->get_stock_quantity( 'edit' ) ) !== wc_stock_amount( wp_unslash( $_POST['_original_stock'] ) ) ) {
336
				/* translators: 1: product ID 2: quantity in stock */
337
				WC_Admin_Meta_Boxes::add_error( sprintf( __( 'The stock has not been updated because the value has changed since editing. Product %1$d has %2$d units in stock.', 'woocommerce' ), $product->get_id(), $product->get_stock_quantity( 'edit' ) ) );
338
			} else {
339
				$stock = wc_stock_amount( wp_unslash( $_POST['_stock'] ) );
340
			}
341
		}
342
343
		// Handle dates.
344
		$date_on_sale_from = '';
345
		$date_on_sale_to   = '';
346
347
		// Force date from to beginning of day.
348 View Code Duplication
		if ( isset( $_POST['_sale_price_dates_from'] ) ) {
349
			$date_on_sale_from = wc_clean( wp_unslash( $_POST['_sale_price_dates_from'] ) );
350
351
			if ( ! empty( $date_on_sale_from ) ) {
352
				$date_on_sale_from = date( 'Y-m-d 00:00:00', strtotime( $date_on_sale_from ) );
353
			}
354
		}
355
356
		// Force date to to the end of the day.
357 View Code Duplication
		if ( isset( $_POST['_sale_price_dates_to'] ) ) {
358
			$date_on_sale_to = wc_clean( wp_unslash( $_POST['_sale_price_dates_to'] ) );
359
360
			if ( ! empty( $date_on_sale_to ) ) {
361
				$date_on_sale_to = date( 'Y-m-d 23:59:59', strtotime( $date_on_sale_to ) );
362
			}
363
		}
364
365
		$errors = $product->set_props(
366
			array(
367
				'sku'                => isset( $_POST['_sku'] ) ? wc_clean( wp_unslash( $_POST['_sku'] ) ) : null,
368
				'purchase_note'      => isset( $_POST['_purchase_note'] ) ? wp_kses_post( wp_unslash( $_POST['_purchase_note'] ) ) : '',
369
				'downloadable'       => isset( $_POST['_downloadable'] ),
370
				'virtual'            => isset( $_POST['_virtual'] ),
371
				'featured'           => isset( $_POST['_featured'] ),
372
				'catalog_visibility' => isset( $_POST['_visibility'] ) ? wc_clean( wp_unslash( $_POST['_visibility'] ) ) : null,
373
				'tax_status'         => isset( $_POST['_tax_status'] ) ? wc_clean( wp_unslash( $_POST['_tax_status'] ) ) : null,
374
				'tax_class'          => isset( $_POST['_tax_class'] ) ? wc_clean( wp_unslash( $_POST['_tax_class'] ) ) : null,
375
				'weight'             => isset( $_POST['_weight'] ) ? wc_clean( wp_unslash( $_POST['_weight'] ) ) : null,
376
				'length'             => isset( $_POST['_length'] ) ? wc_clean( wp_unslash( $_POST['_length'] ) ) : null,
377
				'width'              => isset( $_POST['_width'] ) ? wc_clean( wp_unslash( $_POST['_width'] ) ) : null,
378
				'height'             => isset( $_POST['_height'] ) ? wc_clean( wp_unslash( $_POST['_height'] ) ) : null,
379
				'shipping_class_id'  => isset( $_POST['product_shipping_class'] ) ? absint( wp_unslash( $_POST['product_shipping_class'] ) ) : null,
380
				'sold_individually'  => ! empty( $_POST['_sold_individually'] ),
381
				'upsell_ids'         => isset( $_POST['upsell_ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['upsell_ids'] ) ) : array(),
382
				'cross_sell_ids'     => isset( $_POST['crosssell_ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['crosssell_ids'] ) ) : array(),
383
				'regular_price'      => isset( $_POST['_regular_price'] ) ? wc_clean( wp_unslash( $_POST['_regular_price'] ) ) : null,
384
				'sale_price'         => isset( $_POST['_sale_price'] ) ? wc_clean( wp_unslash( $_POST['_sale_price'] ) ) : null,
385
				'date_on_sale_from'  => $date_on_sale_from,
386
				'date_on_sale_to'    => $date_on_sale_to,
387
				'manage_stock'       => ! empty( $_POST['_manage_stock'] ),
388
				'backorders'         => isset( $_POST['_backorders'] ) ? wc_clean( wp_unslash( $_POST['_backorders'] ) ) : null,
389
				'stock_status'       => isset( $_POST['_stock_status'] ) ? wc_clean( wp_unslash( $_POST['_stock_status'] ) ) : null,
390
				'stock_quantity'     => $stock,
391
				'low_stock_amount'   => isset( $_POST['_low_stock_amount'] ) && '' !== $_POST['_low_stock_amount'] ? wc_stock_amount( wp_unslash( $_POST['_low_stock_amount'] ) ) : '',
392
				'download_limit'     => isset( $_POST['_download_limit'] ) && '' !== $_POST['_download_limit'] ? absint( wp_unslash( $_POST['_download_limit'] ) ) : '',
393
				'download_expiry'    => isset( $_POST['_download_expiry'] ) && '' !== $_POST['_download_expiry'] ? absint( wp_unslash( $_POST['_download_expiry'] ) ) : '',
394
				// Those are sanitized inside prepare_downloads.
395
				'downloads'          => self::prepare_downloads(
396
					isset( $_POST['_wc_file_names'] ) ? wp_unslash( $_POST['_wc_file_names'] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
397
					isset( $_POST['_wc_file_urls'] ) ? wp_unslash( $_POST['_wc_file_urls'] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
398
					isset( $_POST['_wc_file_hashes'] ) ? wp_unslash( $_POST['_wc_file_hashes'] ) : array() // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
399
				),
400
				'product_url'        => isset( $_POST['_product_url'] ) ? esc_url_raw( wp_unslash( $_POST['_product_url'] ) ) : '',
401
				'button_text'        => isset( $_POST['_button_text'] ) ? wc_clean( wp_unslash( $_POST['_button_text'] ) ) : '',
402
				'children'           => 'grouped' === $product_type ? self::prepare_children() : null,
403
				'reviews_allowed'    => ! empty( $_POST['comment_status'] ) && 'open' === $_POST['comment_status'],
404
				'attributes'         => $attributes,
405
				'default_attributes' => self::prepare_set_attributes( $attributes, 'default_attribute_' ),
406
			)
407
		);
408
409
		if ( is_wp_error( $errors ) ) {
410
			WC_Admin_Meta_Boxes::add_error( $errors->get_error_message() );
411
		}
412
413
		/**
414
		 * Set props before save.
415
		 *
416
		 * @since 3.0.0
417
		 */
418
		do_action( 'woocommerce_admin_process_product_object', $product );
419
420
		$product->save();
421
422
		if ( $product->is_type( 'variable' ) ) {
423
			$original_post_title = isset( $_POST['original_post_title'] ) ? wc_clean( wp_unslash( $_POST['original_post_title'] ) ) : '';
424
			$post_title          = isset( $_POST['post_title'] ) ? wc_clean( wp_unslash( $_POST['post_title'] ) ) : '';
425
426
			$product->get_data_store()->sync_variation_names( $product, $original_post_title, $post_title );
427
		}
428
429
		do_action( 'woocommerce_process_product_meta_' . $product_type, $post_id );
430
		// phpcs:enable WordPress.Security.NonceVerification.NoNonceVerification
431
	}
432
433
	/**
434
	 * Save variation meta box data.
435
	 *
436
	 * @param int     $post_id WP post id.
437
	 * @param WP_Post $post Post object.
438
	 */
439
	public static function save_variations( $post_id, $post ) {
0 ignored issues
show
Unused Code introduced by
The parameter $post is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
440
		// phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification
441
		if ( isset( $_POST['variable_post_id'] ) ) {
442
			$parent = wc_get_product( $post_id );
443
			$parent->set_default_attributes( self::prepare_set_attributes( $parent->get_attributes(), 'default_attribute_' ) );
444
			$parent->save();
445
446
			$max_loop   = max( array_keys( wp_unslash( $_POST['variable_post_id'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
447
			$data_store = $parent->get_data_store();
448
			$data_store->sort_all_product_variations( $parent->get_id() );
449
450
			for ( $i = 0; $i <= $max_loop; $i++ ) {
451
452
				if ( ! isset( $_POST['variable_post_id'][ $i ] ) ) {
453
					continue;
454
				}
455
				$variation_id = absint( $_POST['variable_post_id'][ $i ] );
456
				$variation    = new WC_Product_Variation( $variation_id );
457
				$stock        = null;
458
459
				// Handle stock changes.
460
				if ( isset( $_POST['variable_stock'], $_POST['variable_stock'][ $i ] ) ) {
461
					if ( isset( $_POST['variable_original_stock'], $_POST['variable_original_stock'][ $i ] ) && wc_stock_amount( $variation->get_stock_quantity( 'edit' ) ) !== wc_stock_amount( wp_unslash( $_POST['variable_original_stock'][ $i ] ) ) ) {
462
						/* translators: 1: product ID 2: quantity in stock */
463
						WC_Admin_Meta_Boxes::add_error( sprintf( __( 'The stock has not been updated because the value has changed since editing. Product %1$d has %2$d units in stock.', 'woocommerce' ), $variation->get_id(), $variation->get_stock_quantity( 'edit' ) ) );
464
					} else {
465
						$stock = wc_stock_amount( wp_unslash( $_POST['variable_stock'][ $i ] ) );
466
					}
467
				}
468
469
				// Handle dates.
470
				$date_on_sale_from = '';
471
				$date_on_sale_to   = '';
472
473
				// Force date from to beginning of day.
474 View Code Duplication
				if ( isset( $_POST['variable_sale_price_dates_from'][ $i ] ) ) {
475
					$date_on_sale_from = wc_clean( wp_unslash( $_POST['variable_sale_price_dates_from'][ $i ] ) );
476
477
					if ( ! empty( $date_on_sale_from ) ) {
478
						$date_on_sale_from = date( 'Y-m-d 00:00:00', strtotime( $date_on_sale_from ) );
479
					}
480
				}
481
482
				// Force date to to the end of the day.
483 View Code Duplication
				if ( isset( $_POST['variable_sale_price_dates_to'][ $i ] ) ) {
484
					$date_on_sale_to = wc_clean( wp_unslash( $_POST['variable_sale_price_dates_to'][ $i ] ) );
485
486
					if ( ! empty( $date_on_sale_to ) ) {
487
						$date_on_sale_to = date( 'Y-m-d 23:59:59', strtotime( $date_on_sale_to ) );
488
					}
489
				}
490
491
				$errors = $variation->set_props(
492
					array(
493
						'status'            => isset( $_POST['variable_enabled'][ $i ] ) ? 'publish' : 'private',
494
						'menu_order'        => isset( $_POST['variation_menu_order'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variation_menu_order'][ $i ] ) ) : null,
495
						'regular_price'     => isset( $_POST['variable_regular_price'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_regular_price'][ $i ] ) ) : null,
496
						'sale_price'        => isset( $_POST['variable_sale_price'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_sale_price'][ $i ] ) ) : null,
497
						'virtual'           => isset( $_POST['variable_is_virtual'][ $i ] ),
498
						'downloadable'      => isset( $_POST['variable_is_downloadable'][ $i ] ),
499
						'date_on_sale_from' => $date_on_sale_from,
500
						'date_on_sale_to'   => $date_on_sale_to,
501
						'description'       => isset( $_POST['variable_description'][ $i ] ) ? wp_kses_post( wp_unslash( $_POST['variable_description'][ $i ] ) ) : null,
502
						'download_limit'    => isset( $_POST['variable_download_limit'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_download_limit'][ $i ] ) ) : null,
503
						'download_expiry'   => isset( $_POST['variable_download_expiry'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_download_expiry'][ $i ] ) ) : null,
504
						// Those are sanitized inside prepare_downloads.
505
						'downloads'         => self::prepare_downloads(
506
							isset( $_POST['_wc_variation_file_names'][ $variation_id ] ) ? wp_unslash( $_POST['_wc_variation_file_names'][ $variation_id ] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
507
							isset( $_POST['_wc_variation_file_urls'][ $variation_id ] ) ? wp_unslash( $_POST['_wc_variation_file_urls'][ $variation_id ] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
508
							isset( $_POST['_wc_variation_file_hashes'][ $variation_id ] ) ? wp_unslash( $_POST['_wc_variation_file_hashes'][ $variation_id ] ) : array() // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
509
						),
510
						'manage_stock'      => isset( $_POST['variable_manage_stock'][ $i ] ),
511
						'stock_quantity'    => $stock,
512
						'backorders'        => isset( $_POST['variable_backorders'], $_POST['variable_backorders'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_backorders'][ $i ] ) ) : null,
513
						'stock_status'      => isset( $_POST['variable_stock_status'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_stock_status'][ $i ] ) ) : null,
514
						'image_id'          => isset( $_POST['upload_image_id'][ $i ] ) ? wc_clean( wp_unslash( $_POST['upload_image_id'][ $i ] ) ) : null,
515
						'attributes'        => self::prepare_set_attributes( $parent->get_attributes(), 'attribute_', $i ),
516
						'sku'               => isset( $_POST['variable_sku'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_sku'][ $i ] ) ) : '',
517
						'weight'            => isset( $_POST['variable_weight'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_weight'][ $i ] ) ) : '',
518
						'length'            => isset( $_POST['variable_length'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_length'][ $i ] ) ) : '',
519
						'width'             => isset( $_POST['variable_width'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_width'][ $i ] ) ) : '',
520
						'height'            => isset( $_POST['variable_height'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_height'][ $i ] ) ) : '',
521
						'shipping_class_id' => isset( $_POST['variable_shipping_class'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_shipping_class'][ $i ] ) ) : null,
522
						'tax_class'         => isset( $_POST['variable_tax_class'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_tax_class'][ $i ] ) ) : null,
523
					)
524
				);
525
526
				if ( is_wp_error( $errors ) ) {
527
					WC_Admin_Meta_Boxes::add_error( $errors->get_error_message() );
528
				}
529
530
				$variation->save();
531
532
				do_action( 'woocommerce_save_product_variation', $variation_id, $i );
533
			}
534
		}
535
		// phpcs:enable WordPress.Security.NonceVerification.NoNonceVerification
536
	}
537
}
538