WC_Meta_Box_Product_Data::save()   F
last analyzed

Complexity

Conditions 115
Paths > 20000

Size

Total Lines 461
Code Lines 252

Duplication

Lines 81
Ratio 17.57 %

Importance

Changes 0
Metric Value
cc 115
eloc 252
nc 4294967295
nop 2
dl 81
loc 461
rs 2
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
 * @author   WooThemes
8
 * @category Admin
9
 * @package  WooCommerce/Admin/Meta Boxes
10
 * @version  2.4.0
11
 */
12
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit; // Exit if accessed directly
15
}
16
17
/**
18
 * WC_Meta_Box_Product_Data Class.
19
 */
20
class WC_Meta_Box_Product_Data {
21
22
	/**
23
	 * Output the metabox.
24
	 *
25
	 * @param WP_Post $post
26
	 */
27
	public static function output( $post ) {
28
		global $post, $thepostid;
29
30
		wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
31
32
		$thepostid = $post->ID;
33
34
		if ( $terms = wp_get_object_terms( $post->ID, 'product_type' ) ) {
35
			$product_type = sanitize_title( current( $terms )->name );
36
		} else {
37
			$product_type = apply_filters( 'default_product_type', 'simple' );
38
		}
39
40
		$product_type_selector = apply_filters( 'product_type_selector', array(
41
			'simple'   => __( 'Simple product', 'woocommerce' ),
42
			'grouped'  => __( 'Grouped product', 'woocommerce' ),
43
			'external' => __( 'External/Affiliate product', 'woocommerce' ),
44
			'variable' => __( 'Variable product', 'woocommerce' )
45
		), $product_type );
46
47
		$type_box = '<label for="product-type"><select id="product-type" name="product-type"><optgroup label="' . esc_attr__( 'Product Type', 'woocommerce' ) . '">';
48
49
		foreach ( $product_type_selector as $value => $label ) {
50
			$type_box .= '<option value="' . esc_attr( $value ) . '" ' . selected( $product_type, $value, false ) .'>' . esc_html( $label ) . '</option>';
51
		}
52
53
		$type_box .= '</optgroup></select></label>';
54
55
		$product_type_options = apply_filters( 'product_type_options', array(
56
			'virtual' => array(
57
				'id'            => '_virtual',
58
				'wrapper_class' => 'show_if_simple',
59
				'label'         => __( 'Virtual', 'woocommerce' ),
60
				'description'   => __( 'Virtual products are intangible and aren\'t shipped.', 'woocommerce' ),
61
				'default'       => 'no'
62
			),
63
			'downloadable' => array(
64
				'id'            => '_downloadable',
65
				'wrapper_class' => 'show_if_simple',
66
				'label'         => __( 'Downloadable', 'woocommerce' ),
67
				'description'   => __( 'Downloadable products give access to a file upon purchase.', 'woocommerce' ),
68
				'default'       => 'no'
69
			)
70
		) );
71
72
		foreach ( $product_type_options as $key => $option ) {
73
			$selected_value = get_post_meta( $post->ID, '_' . $key, true );
74
75
			if ( '' == $selected_value && isset( $option['default'] ) ) {
76
				$selected_value = $option['default'];
77
			}
78
79
			$type_box .= '<label for="' . esc_attr( $option['id'] ) . '" class="'. esc_attr( $option['wrapper_class'] ) . ' tips" data-tip="' . esc_attr( $option['description'] ) . '">' . esc_html( $option['label'] ) . ': <input type="checkbox" name="' . esc_attr( $option['id'] ) . '" id="' . esc_attr( $option['id'] ) . '" ' . checked( $selected_value, 'yes', false ) .' /></label>';
80
		}
81
82
		?>
83
		<div class="panel-wrap product_data">
84
85
			<span class="type_box hidden"> &mdash; <?php echo $type_box; ?></span>
86
87
			<ul class="product_data_tabs wc-tabs">
88
				<?php
89
					$product_data_tabs = apply_filters( 'woocommerce_product_data_tabs', array(
90
						'general' => array(
91
							'label'  => __( 'General', 'woocommerce' ),
92
							'target' => 'general_product_data',
93
							'class'  => array( 'hide_if_grouped' ),
94
						),
95
						'inventory' => array(
96
							'label'  => __( 'Inventory', 'woocommerce' ),
97
							'target' => 'inventory_product_data',
98
							'class'  => array( 'show_if_simple', 'show_if_variable', 'show_if_grouped' ),
99
						),
100
						'shipping' => array(
101
							'label'  => __( 'Shipping', 'woocommerce' ),
102
							'target' => 'shipping_product_data',
103
							'class'  => array( 'hide_if_virtual', 'hide_if_grouped', 'hide_if_external' ),
104
						),
105
						'linked_product' => array(
106
							'label'  => __( 'Linked Products', 'woocommerce' ),
107
							'target' => 'linked_product_data',
108
							'class'  => array(),
109
						),
110
						'attribute' => array(
111
							'label'  => __( 'Attributes', 'woocommerce' ),
112
							'target' => 'product_attributes',
113
							'class'  => array(),
114
						),
115
						'variations' => array(
116
							'label'  => __( 'Variations', 'woocommerce' ),
117
							'target' => 'variable_product_options',
118
							'class'  => array( 'variations_tab', 'show_if_variable' ),
119
						),
120
						'advanced' => array(
121
							'label'  => __( 'Advanced', 'woocommerce' ),
122
							'target' => 'advanced_product_data',
123
							'class'  => array(),
124
						)
125
					) );
126
127 View Code Duplication
					foreach ( $product_data_tabs as $key => $tab ) {
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...
128
						?><li class="<?php echo $key; ?>_options <?php echo $key; ?>_tab <?php echo implode( ' ' , $tab['class'] ); ?>">
129
							<a href="#<?php echo $tab['target']; ?>"><?php echo esc_html( $tab['label'] ); ?></a>
130
						</li><?php
131
					}
132
133
					do_action( 'woocommerce_product_write_panel_tabs' );
134
				?>
135
			</ul>
136
			<div id="general_product_data" class="panel woocommerce_options_panel"><?php
137
138
				echo '<div class="options_group show_if_external">';
139
140
					// External URL
141
					woocommerce_wp_text_input( array( 'id' => '_product_url', 'label' => __( 'Product URL', 'woocommerce' ), 'placeholder' => 'http://', 'description' => __( 'Enter the external URL to the product.', 'woocommerce' ) ) );
142
143
					// Button text
144
					woocommerce_wp_text_input( array( 'id' => '_button_text', 'label' => __( 'Button text', 'woocommerce' ), 'placeholder' => _x('Buy product', 'placeholder', 'woocommerce'), 'description' => __( 'This text will be shown on the button linking to the external product.', 'woocommerce' ) ) );
145
146
				echo '</div>';
147
148
				echo '<div class="options_group pricing show_if_simple show_if_external hidden">';
149
150
					// Price
151
					woocommerce_wp_text_input( array( 'id' => '_regular_price', 'label' => __( 'Regular price', 'woocommerce' ) . ' (' . get_woocommerce_currency_symbol() . ')', 'data_type' => 'price' ) );
152
153
					// Special Price
154
					woocommerce_wp_text_input( array( 'id' => '_sale_price', 'data_type' => 'price', 'label' => __( 'Sale price', 'woocommerce' ) . ' ('.get_woocommerce_currency_symbol().')', 'description' => '<a href="#" class="sale_schedule">' . __( 'Schedule', 'woocommerce' ) . '</a>' ) );
155
156
					// Special Price date range
157
					$sale_price_dates_from = ( $date = get_post_meta( $thepostid, '_sale_price_dates_from', true ) ) ? date_i18n( 'Y-m-d', $date ) : '';
158
					$sale_price_dates_to   = ( $date = get_post_meta( $thepostid, '_sale_price_dates_to', true ) ) ? date_i18n( 'Y-m-d', $date ) : '';
159
160
					echo '<p class="form-field sale_price_dates_fields">
161
								<label for="_sale_price_dates_from">' . __( 'Sale price dates', 'woocommerce' ) . '</label>
162
								<input type="text" class="short" name="_sale_price_dates_from" id="_sale_price_dates_from" value="' . esc_attr( $sale_price_dates_from ) . '" placeholder="' . _x( 'From&hellip;', 'placeholder', 'woocommerce' ) . ' YYYY-MM-DD" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />
163
								<input type="text" class="short" name="_sale_price_dates_to" id="_sale_price_dates_to" value="' . esc_attr( $sale_price_dates_to ) . '" placeholder="' . _x( 'To&hellip;', 'placeholder', 'woocommerce' ) . '  YYYY-MM-DD" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />
164
								<a href="#" class="cancel_sale_schedule">'. __( 'Cancel', 'woocommerce' ) . '</a>' . wc_help_tip( __( 'The sale will end at the beginning of the set date.', 'woocommerce' ) ) . '
165
							</p>';
166
167
					do_action( 'woocommerce_product_options_pricing' );
168
169
				echo '</div>';
170
171
				echo '<div class="options_group show_if_downloadable hidden">';
172
173
					?>
174
					<div class="form-field downloadable_files">
175
						<label><?php _e( 'Downloadable files', 'woocommerce' ); ?></label>
176
						<table class="widefat">
177
							<thead>
178
								<tr>
179
									<th class="sort">&nbsp;</th>
180
									<th><?php _e( 'Name', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'This is the name of the download shown to the customer.', 'woocommerce' ) ); ?></th>
181
									<th colspan="2"><?php _e( 'File URL', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'This is the URL or absolute path to the file which customers will get access to. URLs entered here should already be encoded.', 'woocommerce' ) ); ?></th>
182
									<th>&nbsp;</th>
183
								</tr>
184
							</thead>
185
							<tbody>
186
								<?php
187
								$downloadable_files = get_post_meta( $post->ID, '_downloadable_files', true );
188
189
								if ( $downloadable_files ) {
190
									foreach ( $downloadable_files as $key => $file ) {
191
										include( 'views/html-product-download.php' );
192
									}
193
								}
194
								?>
195
							</tbody>
196
							<tfoot>
197
								<tr>
198
									<th colspan="5">
199
										<a href="#" class="button insert" data-row="<?php
200
											$file = array(
201
												'file' => '',
202
												'name' => ''
203
											);
204
											ob_start();
205
											include( 'views/html-product-download.php' );
206
											echo esc_attr( ob_get_clean() );
207
										?>"><?php _e( 'Add File', 'woocommerce' ); ?></a>
208
									</th>
209
								</tr>
210
							</tfoot>
211
						</table>
212
					</div>
213
					<?php
214
215
					// Download Limit
216
					woocommerce_wp_text_input( array( 'id' => '_download_limit', 'label' => __( 'Download limit', 'woocommerce' ), 'placeholder' => __( 'Unlimited', 'woocommerce' ), 'description' => __( 'Leave blank for unlimited re-downloads.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array(
217
						'step' 	=> '1',
218
						'min'	=> '0'
219
					) ) );
220
221
					// Expirey
222
					woocommerce_wp_text_input( array( 'id' => '_download_expiry', 'label' => __( 'Download expiry', 'woocommerce' ), 'placeholder' => __( 'Never', 'woocommerce' ), 'description' => __( 'Enter the number of days before a download link expires, or leave blank.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array(
223
						'step' 	=> '1',
224
						'min'	=> '0'
225
					) ) );
226
227
					 // Download Type
228
					woocommerce_wp_select( array( 'id' => '_download_type', 'label' => __( 'Download type', 'woocommerce' ), 'description' => sprintf( __( 'Choose a download type - this controls the <a href="%s">schema</a>.', 'woocommerce' ), 'http://schema.org/' ), 'options' => array(
229
						''            => __( 'Standard Product', 'woocommerce' ),
230
						'application' => __( 'Application/Software', 'woocommerce' ),
231
						'music'       => __( 'Music', 'woocommerce' ),
232
					) ) );
233
234
					do_action( 'woocommerce_product_options_downloads' );
235
236
				echo '</div>';
237
238
				if ( wc_tax_enabled() ) {
239
240
					echo '<div class="options_group show_if_simple show_if_external show_if_variable">';
241
242
						// Tax
243
						woocommerce_wp_select( array(
244
							'id'      => '_tax_status',
245
							'label'   => __( 'Tax status', 'woocommerce' ),
246
							'options' => array(
247
								'taxable' 	=> __( 'Taxable', 'woocommerce' ),
248
								'shipping' 	=> __( 'Shipping only', 'woocommerce' ),
249
								'none' 		=> _x( 'None', 'Tax status', 'woocommerce' )
250
							),
251
							'desc_tip'    => 'true',
252
							'description' => __( 'Define whether or not the entire product is taxable, or just the cost of shipping it.', 'woocommerce' )
253
						) );
254
255
						$tax_classes         = WC_Tax::get_tax_classes();
256
						$classes_options     = array();
257
						$classes_options[''] = __( 'Standard', 'woocommerce' );
258
259
						if ( ! empty( $tax_classes ) ) {
260
							foreach ( $tax_classes as $class ) {
261
								$classes_options[ sanitize_title( $class ) ] = esc_html( $class );
262
							}
263
						}
264
265
						woocommerce_wp_select( array(
266
							'id'          => '_tax_class',
267
							'label'       => __( 'Tax class', 'woocommerce' ),
268
							'options'     => $classes_options,
269
							'desc_tip'    => 'true',
270
							'description' => __( 'Choose a tax class for this product. Tax classes are used to apply different tax rates specific to certain types of product.', 'woocommerce' )
271
						) );
272
273
						do_action( 'woocommerce_product_options_tax' );
274
275
					echo '</div>';
276
277
				}
278
279
				do_action( 'woocommerce_product_options_general_product_data' );
280
				?>
281
			</div>
282
283
			<div id="inventory_product_data" class="panel woocommerce_options_panel hidden">
284
285
				<?php
286
287
				echo '<div class="options_group">';
288
289
				// SKU
290
				if ( wc_product_sku_enabled() ) {
291
					woocommerce_wp_text_input( array( 'id' => '_sku', 'label' => '<abbr title="'. __( 'Stock Keeping Unit', 'woocommerce' ) .'">' . __( 'SKU', 'woocommerce' ) . '</abbr>', 'desc_tip' => 'true', 'description' => __( 'SKU refers to a Stock-keeping unit, a unique identifier for each distinct product and service that can be purchased.', 'woocommerce' ) ) );
292
				} else {
293
					echo '<input type="hidden" name="_sku" value="' . esc_attr( get_post_meta( $thepostid, '_sku', true ) ) . '" />';
294
				}
295
296
				do_action( 'woocommerce_product_options_sku' );
297
298
				if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
299
300
					// manage stock
301
					woocommerce_wp_checkbox( array( 'id' => '_manage_stock', 'wrapper_class' => 'show_if_simple show_if_variable', 'label' => __( 'Manage stock?', 'woocommerce' ), 'description' => __( 'Enable stock management at product level', 'woocommerce' ) ) );
302
303
					do_action( 'woocommerce_product_options_stock' );
304
305
					echo '<div class="stock_fields show_if_simple show_if_variable">';
306
307
					// Stock
308
					woocommerce_wp_text_input( array(
309
						'id'                => '_stock',
310
						'label'             => __( 'Stock quantity', 'woocommerce' ),
311
						'desc_tip'          => true,
312
						'description'       => __( 'Stock quantity. If this is a variable product this value will be used to control stock for all variations, unless you define stock at variation level.', 'woocommerce' ),
313
						'type'              => 'number',
314
						'custom_attributes' => array(
315
							'step' => 'any'
316
						),
317
						'data_type'         => 'stock'
318
					) );
319
320
					// Backorders?
321
					woocommerce_wp_select( array( 'id' => '_backorders', 'label' => __( 'Allow backorders?', 'woocommerce' ), 'options' => array(
322
						'no'     => __( 'Do not allow', 'woocommerce' ),
323
						'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
324
						'yes'    => __( 'Allow', 'woocommerce' )
325
					), 'desc_tip' => true, 'description' => __( 'If managing stock, this controls whether or not backorders are allowed. If enabled, stock quantity can go below 0.', 'woocommerce' ) ) );
326
327
					do_action( 'woocommerce_product_options_stock_fields' );
328
329
					echo '</div>';
330
331
				}
332
333
				// Stock status
334
				woocommerce_wp_select( array( 'id' => '_stock_status', 'wrapper_class' => 'hide_if_variable', 'label' => __( 'Stock status', 'woocommerce' ), 'options' => array(
335
					'instock' => __( 'In stock', 'woocommerce' ),
336
					'outofstock' => __( 'Out of stock', 'woocommerce' )
337
				), 'desc_tip' => true, 'description' => __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ) ) );
338
339
				do_action( 'woocommerce_product_options_stock_status' );
340
341
				echo '</div>';
342
343
				echo '<div class="options_group show_if_simple show_if_variable">';
344
345
				// Individual product
346
				woocommerce_wp_checkbox( array( 'id' => '_sold_individually', 'wrapper_class' => 'show_if_simple show_if_variable', 'label' => __( 'Sold individually', 'woocommerce' ), 'description' => __( 'Enable this to only allow one of this item to be bought in a single order', 'woocommerce' ) ) );
347
348
				do_action( 'woocommerce_product_options_sold_individually' );
349
350
				echo '</div>';
351
352
				do_action( 'woocommerce_product_options_inventory_product_data' );
353
				?>
354
355
			</div>
356
357
			<div id="shipping_product_data" class="panel woocommerce_options_panel hidden">
358
359
				<?php
360
361
				echo '<div class="options_group">';
362
363
					// Weight
364
					if ( wc_product_weight_enabled() ) {
365
						woocommerce_wp_text_input( array( 'id' => '_weight', 'label' => __( 'Weight', 'woocommerce' ) . ' (' . get_option( 'woocommerce_weight_unit' ) . ')', 'placeholder' => wc_format_localized_decimal( 0 ), 'desc_tip' => 'true', 'description' => __( 'Weight in decimal form', 'woocommerce' ), 'type' => 'text', 'data_type' => 'decimal' ) );
366
					}
367
368
					// Size fields
369
					if ( wc_product_dimensions_enabled() ) {
370
						?><p class="form-field dimensions_field">
371
							<label for="product_length"><?php echo __( 'Dimensions', 'woocommerce' ) . ' (' . get_option( 'woocommerce_dimension_unit' ) . ')'; ?></label>
372
							<span class="wrap">
373
								<input id="product_length" placeholder="<?php esc_attr_e( 'Length', 'woocommerce' ); ?>" class="input-text wc_input_decimal" size="6" type="text" name="_length" value="<?php echo esc_attr( wc_format_localized_decimal( get_post_meta( $thepostid, '_length', true ) ) ); ?>" />
374
								<input placeholder="<?php esc_attr_e( 'Width', 'woocommerce' ); ?>" class="input-text wc_input_decimal" size="6" type="text" name="_width" value="<?php echo esc_attr( wc_format_localized_decimal( get_post_meta( $thepostid, '_width', true ) ) ); ?>" />
375
								<input placeholder="<?php esc_attr_e( 'Height', 'woocommerce' ); ?>" class="input-text wc_input_decimal last" size="6" type="text" name="_height" value="<?php echo esc_attr( wc_format_localized_decimal( get_post_meta( $thepostid, '_height', true ) ) ); ?>" />
376
							</span>
377
							<?php echo wc_help_tip( __( 'LxWxH in decimal form', 'woocommerce' ) ); ?>
378
						</p><?php
379
					}
380
381
					do_action( 'woocommerce_product_options_dimensions' );
382
383
				echo '</div>';
384
385
				echo '<div class="options_group">';
386
387
					// Shipping Class
388
					$classes = get_the_terms( $thepostid, 'product_shipping_class' );
389
					if ( $classes && ! is_wp_error( $classes ) ) {
390
						$current_shipping_class = current( $classes )->term_id;
391
					} else {
392
						$current_shipping_class = '';
393
					}
394
395
					$args = array(
396
						'taxonomy'         => 'product_shipping_class',
397
						'hide_empty'       => 0,
398
						'show_option_none' => __( 'No shipping class', 'woocommerce' ),
399
						'name'             => 'product_shipping_class',
400
						'id'               => 'product_shipping_class',
401
						'selected'         => $current_shipping_class,
402
						'class'            => 'select short'
403
					);
404
					?><p class="form-field dimensions_field"><label for="product_shipping_class"><?php _e( 'Shipping class', 'woocommerce' ); ?></label> <?php wp_dropdown_categories( $args ); ?> <?php echo wc_help_tip( __( 'Shipping classes are used by certain shipping methods to group similar products.', 'woocommerce' ) ); ?></p><?php
405
406
					do_action( 'woocommerce_product_options_shipping' );
407
408
				echo '</div>';
409
				?>
410
411
			</div>
412
413
			<div id="product_attributes" class="panel wc-metaboxes-wrapper hidden">
414
				<div class="toolbar toolbar-top">
415
					<span class="expand-close">
416
						<a href="#" class="expand_all"><?php _e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php _e( 'Close', 'woocommerce' ); ?></a>
417
					</span>
418
					<select name="attribute_taxonomy" class="attribute_taxonomy">
419
						<option value=""><?php _e( 'Custom product attribute', 'woocommerce' ); ?></option>
420
						<?php
421
							global $wc_product_attributes;
422
423
							// Array of defined attribute taxonomies
424
							$attribute_taxonomies = wc_get_attribute_taxonomies();
425
426
							if ( ! empty( $attribute_taxonomies ) ) {
427
								foreach ( $attribute_taxonomies as $tax ) {
428
									$attribute_taxonomy_name = wc_attribute_taxonomy_name( $tax->attribute_name );
429
									$label = $tax->attribute_label ? $tax->attribute_label : $tax->attribute_name;
430
									echo '<option value="' . esc_attr( $attribute_taxonomy_name ) . '">' . esc_html( $label ) . '</option>';
431
								}
432
							}
433
						?>
434
					</select>
435
					<button type="button" class="button add_attribute"><?php _e( 'Add', 'woocommerce' ); ?></button>
436
				</div>
437
				<div class="product_attributes wc-metaboxes">
438
					<?php
439
						// Product attributes - taxonomies and custom, ordered, with visibility and variation attributes set
440
						$attributes           = maybe_unserialize( get_post_meta( $thepostid, '_product_attributes', true ) );
441
442
						// Output All Set Attributes
443
						if ( ! empty( $attributes ) ) {
444
							$attribute_keys  = array_keys( $attributes );
445
							$attribute_total = sizeof( $attribute_keys );
446
447
							for ( $i = 0; $i < $attribute_total; $i ++ ) {
448
								$attribute     = $attributes[ $attribute_keys[ $i ] ];
449
								$position      = empty( $attribute['position'] ) ? 0 : absint( $attribute['position'] );
450
								$taxonomy      = '';
451
								$metabox_class = array();
452
453
								if ( $attribute['is_taxonomy'] ) {
454
									$taxonomy = $attribute['name'];
455
456
									if ( ! taxonomy_exists( $taxonomy ) ) {
457
										continue;
458
									}
459
460
									$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
461
									$metabox_class[]    = 'taxonomy';
462
									$metabox_class[]    = $taxonomy;
463
									$attribute_label    = wc_attribute_label( $taxonomy );
464
								} else {
465
									$attribute_label    = apply_filters( 'woocommerce_attribute_label', $attribute['name'], $attribute['name'], false );
466
								}
467
468
								include( 'views/html-product-attribute.php' );
469
							}
470
						}
471
					?>
472
				</div>
473
				<div class="toolbar">
474
					<span class="expand-close">
475
						<a href="#" class="expand_all"><?php _e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php _e( 'Close', 'woocommerce' ); ?></a>
476
					</span>
477
					<button type="button" class="button save_attributes button-primary"><?php _e( 'Save attributes', 'woocommerce' ); ?></button>
478
				</div>
479
				<?php do_action( 'woocommerce_product_options_attributes' ); ?>
480
			</div>
481
			<div id="linked_product_data" class="panel woocommerce_options_panel hidden">
482
483
				<div class="options_group">
484
485
					<p class="form-field">
486
						<label for="upsell_ids"><?php _e( 'Up-sells', 'woocommerce' ); ?></label>
487
						<input type="hidden" class="wc-product-search" style="width: 50%;" id="upsell_ids" name="upsell_ids" data-placeholder="<?php esc_attr_e( 'Search for a product&hellip;', 'woocommerce' ); ?>" data-action="woocommerce_json_search_products" data-multiple="true" data-exclude="<?php echo intval( $post->ID ); ?>" data-selected="<?php
488
							$product_ids = array_filter( array_map( 'absint', (array) get_post_meta( $post->ID, '_upsell_ids', true ) ) );
489
							$json_ids    = array();
490
491 View Code Duplication
							foreach ( $product_ids as $product_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...
492
								$product = wc_get_product( $product_id );
493
								if ( is_object( $product ) ) {
494
									$json_ids[ $product_id ] = wp_kses_post( html_entity_decode( $product->get_formatted_name(), ENT_QUOTES, get_bloginfo( 'charset' ) ) );
495
								}
496
							}
497
498
							echo esc_attr( json_encode( $json_ids ) );
499
						?>" value="<?php echo implode( ',', array_keys( $json_ids ) ); ?>" /> <?php echo wc_help_tip( __( 'Up-sells are products which you recommend instead of the currently viewed product, for example, products that are more profitable or better quality or more expensive.', 'woocommerce' ) ); ?>
500
					</p>
501
502
					<p class="form-field">
503
						<label for="crosssell_ids"><?php _e( 'Cross-sells', 'woocommerce' ); ?></label>
504
						<input type="hidden" class="wc-product-search" style="width: 50%;" id="crosssell_ids" name="crosssell_ids" data-placeholder="<?php esc_attr_e( 'Search for a product&hellip;', 'woocommerce' ); ?>" data-action="woocommerce_json_search_products" data-multiple="true" data-exclude="<?php echo intval( $post->ID ); ?>" data-selected="<?php
505
							$product_ids = array_filter( array_map( 'absint', (array) get_post_meta( $post->ID, '_crosssell_ids', true ) ) );
506
							$json_ids    = array();
507
508 View Code Duplication
							foreach ( $product_ids as $product_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...
509
								$product = wc_get_product( $product_id );
510
								if ( is_object( $product ) ) {
511
									$json_ids[ $product_id ] = wp_kses_post( html_entity_decode( $product->get_formatted_name(), ENT_QUOTES, get_bloginfo( 'charset' ) ) );
512
								}
513
							}
514
515
							echo esc_attr( json_encode( $json_ids ) );
516
						?>" value="<?php echo implode( ',', array_keys( $json_ids ) ); ?>" /> <?php echo wc_help_tip( __( 'Cross-sells are products which you promote in the cart, based on the current product.', 'woocommerce' ) ); ?>
517
					</p>
518
				</div>
519
520
				<div class="options_group grouping show_if_simple show_if_external">
521
522
					<p class="form-field">
523
						<label for="parent_id"><?php _e( 'Grouping', 'woocommerce' ); ?></label>
524
						<input type="hidden" class="wc-product-search" style="width: 50%;" id="parent_id" name="parent_id" data-placeholder="<?php esc_attr_e( 'Search for a product&hellip;', 'woocommerce' ); ?>" data-action="woocommerce_json_search_grouped_products" data-allow_clear="true" data-multiple="false" data-exclude="<?php echo intval( $post->ID ); ?>" data-selected="<?php
525
							$parent_id = absint( $post->post_parent );
526
527
							if ( $parent_id ) {
528
								$parent    = wc_get_product( $parent_id );
529
								if ( is_object( $parent ) ) {
530
									$parent_title = wp_kses_post( html_entity_decode( $parent->get_formatted_name(), ENT_QUOTES, get_bloginfo( 'charset' ) ) );
531
								}
532
533
								echo esc_attr( $parent_title );
534
							}
535
						?>" value="<?php echo $parent_id ? $parent_id : ''; ?>" /> <?php echo wc_help_tip( __( 'Set this option to make this product part of a grouped product.', 'woocommerce' ) ); ?>
536
					</p>
537
538
					<?php
539
						woocommerce_wp_hidden_input( array( 'id' => 'previous_parent_id', 'value' => absint( $post->post_parent ) ) );
540
541
						do_action( 'woocommerce_product_options_grouping' );
542
					?>
543
				</div>
544
545
				<?php do_action( 'woocommerce_product_options_related' ); ?>
546
			</div>
547
548
			<div id="advanced_product_data" class="panel woocommerce_options_panel hidden">
549
550
				<div class="options_group hide_if_external">
551
					<?php
552
						// Purchase note
553
						woocommerce_wp_textarea_input(  array( 'id' => '_purchase_note', 'label' => __( 'Purchase note', 'woocommerce' ), 'desc_tip' => 'true', 'description' => __( 'Enter an optional note to send the customer after purchase.', 'woocommerce' ) ) );
554
					?>
555
				</div>
556
557
				<div class="options_group">
558
					<?php
559
						// menu_order
560
						woocommerce_wp_text_input(  array( 'id' => 'menu_order', 'label' => __( 'Menu order', 'woocommerce' ), 'desc_tip' => 'true', 'description' => __( 'Custom ordering position.', 'woocommerce' ), 'value' => intval( $post->menu_order ), 'type' => 'number', 'custom_attributes' => array(
561
							'step' 	=> '1'
562
						)  ) );
563
					?>
564
				</div>
565
566
				<div class="options_group reviews">
567
					<?php
568
						woocommerce_wp_checkbox( array( 'id' => 'comment_status', 'label' => __( 'Enable reviews', 'woocommerce' ), 'cbvalue' => 'open', 'value' => esc_attr( $post->comment_status ) ) );
569
570
						do_action( 'woocommerce_product_options_reviews' );
571
					?>
572
				</div>
573
574
				<?php do_action( 'woocommerce_product_options_advanced' ); ?>
575
576
			</div>
577
578
			<?php
579
				self::output_variations();
580
581
				do_action( 'woocommerce_product_data_panels' );
582
				do_action( 'woocommerce_product_write_panels' ); // _deprecated
583
			?>
584
585
			<div class="clear"></div>
586
587
		</div>
588
		<?php
589
	}
590
591
	/**
592
	 * Show options for the variable product type.
593
	 */
594
	public static function output_variations() {
595
		global $post, $wpdb;
596
597
		// Get attributes
598
		$attributes = maybe_unserialize( get_post_meta( $post->ID, '_product_attributes', true ) );
599
600
		// See if any are set
601
		$variation_attribute_found = false;
602
603
		if ( $attributes ) {
604
			foreach ( $attributes as $attribute ) {
605
				if ( ! empty( $attribute['is_variation'] ) ) {
606
					$variation_attribute_found = true;
607
					break;
608
				}
609
			}
610
		}
611
612
		$variations_count       = absint( $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 ) ) );
613
		$variations_per_page    = absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_per_page', 15 ) );
614
		$variations_total_pages = ceil( $variations_count / $variations_per_page );
615
		?>
616
		<div id="variable_product_options" class="panel wc-metaboxes-wrapper hidden"><div id="variable_product_options_inner">
617
618
			<?php if ( ! $variation_attribute_found ) : ?>
619
620
				<div id="message" class="inline notice woocommerce-message">
621
					<p><?php _e( 'Before you can add a variation you need to add some variation attributes on the <strong>Attributes</strong> tab.', 'woocommerce' ); ?></p>
622
					<p>
623
						<a class="button-primary" href="<?php echo esc_url( apply_filters( 'woocommerce_docs_url', 'https://docs.woothemes.com/document/variable-product/', 'product-variations' ) ); ?>" target="_blank"><?php _e( 'Learn more', 'woocommerce' ); ?></a>
624
					</p>
625
				</div>
626
627
			<?php else : ?>
628
629
				<div class="toolbar toolbar-variations-defaults">
630
					<div class="variations-defaults">
631
						<strong><?php _e( 'Default Form Values', 'woocommerce' ); ?>: <?php echo wc_help_tip( __( 'These are the attributes that will be pre-selected on the frontend.', 'woocommerce' ) ); ?></strong>
632
						<?php
633
							$default_attributes = maybe_unserialize( get_post_meta( $post->ID, '_default_attributes', true ) );
634
635
							foreach ( $attributes as $attribute ) {
636
637
								// Only deal with attributes that are variations
638
								if ( ! $attribute['is_variation'] ) {
639
									continue;
640
								}
641
642
								// Get current value for variation (if set)
643
								$variation_selected_value = isset( $default_attributes[ sanitize_title( $attribute['name'] ) ] ) ? $default_attributes[ sanitize_title( $attribute['name'] ) ] : '';
644
645
								// Name will be something like attribute_pa_color
646
								echo '<select name="default_attribute_' . sanitize_title( $attribute['name'] ) . '" data-current="' . esc_attr( $variation_selected_value ) . '"><option value="">' . __( 'No default', 'woocommerce' ) . ' ' . esc_html( wc_attribute_label( $attribute['name'] ) ) . '&hellip;</option>';
647
648
								// Get terms for attribute taxonomy or value if its a custom attribute
649 View Code Duplication
								if ( $attribute['is_taxonomy'] ) {
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...
650
									$post_terms = wp_get_post_terms( $post->ID, $attribute['name'] );
651
652
									foreach ( $post_terms as $term ) {
653
										echo '<option ' . selected( $variation_selected_value, $term->slug, false ) . ' value="' . esc_attr( $term->slug ) . '">' . esc_html( apply_filters( 'woocommerce_variation_option_name', $term->name ) ) . '</option>';
654
									}
655
656
								} else {
657
									$options = wc_get_text_attributes( $attribute['value'] );
658
659
									foreach ( $options as $option ) {
660
										$selected = sanitize_title( $variation_selected_value ) === $variation_selected_value ? selected( $variation_selected_value, sanitize_title( $option ), false ) : selected( $variation_selected_value, $option, false );
661
										echo '<option ' . $selected . ' value="' . esc_attr( $option ) . '">' . esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) )  . '</option>';
662
									}
663
664
								}
665
666
								echo '</select>';
667
							}
668
						?>
669
					</div>
670
					<div class="clear"></div>
671
				</div>
672
673
				<div class="toolbar toolbar-top">
674
					<select id="field_to_edit" class="variation_actions">
675
						<option data-global="true" value="add_variation"><?php _e( 'Add variation', 'woocommerce' ); ?></option>
676
						<option data-global="true" value="link_all_variations"><?php _e( 'Create variations from all attributes', 'woocommerce' ); ?></option>
677
						<option value="delete_all"><?php _e( 'Delete all variations', 'woocommerce' ); ?></option>
678
						<optgroup label="<?php esc_attr_e( 'Status', 'woocommerce' ); ?>">
679
							<option value="toggle_enabled"><?php _e( 'Toggle &quot;Enabled&quot;', 'woocommerce' ); ?></option>
680
							<option value="toggle_downloadable"><?php _e( 'Toggle &quot;Downloadable&quot;', 'woocommerce' ); ?></option>
681
							<option value="toggle_virtual"><?php _e( 'Toggle &quot;Virtual&quot;', 'woocommerce' ); ?></option>
682
						</optgroup>
683
						<optgroup label="<?php esc_attr_e( 'Pricing', 'woocommerce' ); ?>">
684
							<option value="variable_regular_price"><?php _e( 'Set regular prices', 'woocommerce' ); ?></option>
685
							<option value="variable_regular_price_increase"><?php _e( 'Increase regular prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
686
							<option value="variable_regular_price_decrease"><?php _e( 'Decrease regular prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
687
							<option value="variable_sale_price"><?php _e( 'Set sale prices', 'woocommerce' ); ?></option>
688
							<option value="variable_sale_price_increase"><?php _e( 'Increase sale prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
689
							<option value="variable_sale_price_decrease"><?php _e( 'Decrease sale prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
690
							<option value="variable_sale_schedule"><?php _e( 'Set scheduled sale dates', 'woocommerce' ); ?></option>
691
						</optgroup>
692
						<optgroup label="<?php esc_attr_e( 'Inventory', 'woocommerce' ); ?>">
693
							<option value="toggle_manage_stock"><?php _e( 'Toggle &quot;Manage stock&quot;', 'woocommerce' ); ?></option>
694
							<option value="variable_stock"><?php _e( 'Stock', 'woocommerce' ); ?></option>
695
						</optgroup>
696
						<optgroup label="<?php esc_attr_e( 'Shipping', 'woocommerce' ); ?>">
697
							<option value="variable_length"><?php _e( 'Length', 'woocommerce' ); ?></option>
698
							<option value="variable_width"><?php _e( 'Width', 'woocommerce' ); ?></option>
699
							<option value="variable_height"><?php _e( 'Height', 'woocommerce' ); ?></option>
700
							<option value="variable_weight"><?php _e( 'Weight', 'woocommerce' ); ?></option>
701
						</optgroup>
702
						<optgroup label="<?php esc_attr_e( 'Downloadable products', 'woocommerce' ); ?>">
703
							<option value="variable_download_limit"><?php _e( 'Download limit', 'woocommerce' ); ?></option>
704
							<option value="variable_download_expiry"><?php _e( 'Download expiry', 'woocommerce' ); ?></option>
705
						</optgroup>
706
						<?php do_action( 'woocommerce_variable_product_bulk_edit_actions' ); ?>
707
					</select>
708
					<a class="button bulk_edit do_variation_action"><?php _e( 'Go', 'woocommerce' ); ?></a>
709
710
					<div class="variations-pagenav">
711
						<span class="displaying-num"><?php printf( _n( '%s item', '%s items', $variations_count, 'woocommerce' ), $variations_count ); ?></span>
712
						<span class="expand-close">
713
							(<a href="#" class="expand_all"><?php _e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php _e( 'Close', 'woocommerce' ); ?></a>)
714
						</span>
715
						<span class="pagination-links">
716
							<a class="first-page disabled" title="<?php esc_attr_e( 'Go to the first page', 'woocommerce' ); ?>" href="#">&laquo;</a>
717
							<a class="prev-page disabled" title="<?php esc_attr_e( 'Go to the previous page', 'woocommerce' ); ?>" href="#">&lsaquo;</a>
718
							<span class="paging-select">
719
								<label for="current-page-selector-1" class="screen-reader-text"><?php _e( 'Select Page', 'woocommerce' ); ?></label>
720
								<select class="page-selector" id="current-page-selector-1" title="<?php esc_attr_e( 'Current page', 'woocommerce' ); ?>">
721 View Code Duplication
									<?php for ( $i = 1; $i <= $variations_total_pages; $i++ ) : ?>
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...
722
										<option value="<?php echo $i; ?>"><?php echo $i; ?></option>
723
									<?php endfor; ?>
724
								</select>
725
								 <?php _ex( 'of', 'number of pages', 'woocommerce' ); ?> <span class="total-pages"><?php echo $variations_total_pages; ?></span>
726
							</span>
727
							<a class="next-page" title="<?php esc_attr_e( 'Go to the next page', 'woocommerce' ); ?>" href="#">&rsaquo;</a>
728
							<a class="last-page" title="<?php esc_attr_e( 'Go to the last page', 'woocommerce' ); ?>" href="#">&raquo;</a>
729
						</span>
730
					</div>
731
					<div class="clear"></div>
732
				</div>
733
734
				<div class="woocommerce_variations wc-metaboxes" data-attributes="<?php
735
					// esc_attr does not double encode - htmlspecialchars does
736
					echo htmlspecialchars( json_encode( $attributes ) );
737
				?>" data-total="<?php echo $variations_count; ?>" data-total_pages="<?php echo $variations_total_pages; ?>" data-page="1" data-edited="false">
738
				</div>
739
740
				<div class="toolbar">
741
					<button type="button" class="button-primary save-variation-changes" disabled="disabled"><?php _e( 'Save changes', 'woocommerce' ); ?></button>
742
					<button type="button" class="button cancel-variation-changes" disabled="disabled"><?php _e( 'Cancel', 'woocommerce' ); ?></button>
743
744
					<div class="variations-pagenav">
745
						<span class="displaying-num"><?php printf( _n( '%s item', '%s items', $variations_count, 'woocommerce' ), $variations_count ); ?></span>
746
						<span class="expand-close">
747
							(<a href="#" class="expand_all"><?php _e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php _e( 'Close', 'woocommerce' ); ?></a>)
748
						</span>
749
						<span class="pagination-links">
750
							<a class="first-page disabled" title="<?php esc_attr_e( 'Go to the first page', 'woocommerce' ); ?>" href="#">&laquo;</a>
751
							<a class="prev-page disabled" title="<?php esc_attr_e( 'Go to the previous page', 'woocommerce' ); ?>" href="#">&lsaquo;</a>
752
							<span class="paging-select">
753
								<label for="current-page-selector-1" class="screen-reader-text"><?php _e( 'Select Page', 'woocommerce' ); ?></label>
754
								<select class="page-selector" id="current-page-selector-1" title="<?php esc_attr_e( 'Current page', 'woocommerce' ); ?>">
755 View Code Duplication
									<?php for ( $i = 1; $i <= $variations_total_pages; $i++ ) : ?>
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...
756
										<option value="<?php echo $i; ?>"><?php echo $i; ?></option>
757
									<?php endfor; ?>
758
								</select>
759
								 <?php _ex( 'of', 'number of pages', 'woocommerce' ); ?> <span class="total-pages"><?php echo $variations_total_pages; ?></span>
760
							</span>
761
							<a class="next-page" title="<?php esc_attr_e( 'Go to the next page', 'woocommerce' ); ?>" href="#">&rsaquo;</a>
762
							<a class="last-page" title="<?php esc_attr_e( 'Go to the last page', 'woocommerce' ); ?>" href="#">&raquo;</a>
763
						</span>
764
					</div>
765
766
					<div class="clear"></div>
767
				</div>
768
769
			<?php endif; ?>
770
		</div></div>
771
		<?php
772
	}
773
774
	/**
775
	 * Save meta box data.
776
	 */
777
	public static function save( $post_id, $post ) {
778
		global $wpdb;
779
780
		// Add any default post meta
781
		add_post_meta( $post_id, 'total_sales', '0', true );
782
783
		// Get types
784
		$product_type    = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
785
		$is_downloadable = isset( $_POST['_downloadable'] ) ? 'yes' : 'no';
786
		$is_virtual      = isset( $_POST['_virtual'] ) ? 'yes' : 'no';
787
788
		// Product type + Downloadable/Virtual
789
		wp_set_object_terms( $post_id, $product_type, 'product_type' );
790
		update_post_meta( $post_id, '_downloadable', $is_downloadable );
791
		update_post_meta( $post_id, '_virtual', $is_virtual );
792
793
		// Update post meta
794
		if ( isset( $_POST['_tax_status'] ) ) {
795
			update_post_meta( $post_id, '_tax_status', wc_clean( $_POST['_tax_status'] ) );
796
		}
797
798
		if ( isset( $_POST['_tax_class'] ) ) {
799
			update_post_meta( $post_id, '_tax_class', wc_clean( $_POST['_tax_class'] ) );
800
		}
801
802
		if ( isset( $_POST['_purchase_note'] ) ) {
803
			update_post_meta( $post_id, '_purchase_note', wp_kses_post( stripslashes( $_POST['_purchase_note'] ) ) );
804
		}
805
806
		// Featured
807
		if ( update_post_meta( $post_id, '_featured', isset( $_POST['_featured'] ) ? 'yes' : 'no' ) ) {
808
			delete_transient( 'wc_featured_products' );
809
		}
810
811
		// Dimensions
812
		if ( 'no' == $is_virtual ) {
813
814 View Code Duplication
			if ( isset( $_POST['_weight'] ) ) {
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...
815
				update_post_meta( $post_id, '_weight', ( '' === $_POST['_weight'] ) ? '' : wc_format_decimal( $_POST['_weight'] ) );
816
			}
817
818 View Code Duplication
			if ( isset( $_POST['_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...
819
				update_post_meta( $post_id, '_length', ( '' === $_POST['_length'] ) ? '' : wc_format_decimal( $_POST['_length'] ) );
820
			}
821
822 View Code Duplication
			if ( isset( $_POST['_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...
823
				update_post_meta( $post_id, '_width', ( '' === $_POST['_width'] ) ? '' : wc_format_decimal( $_POST['_width'] ) );
824
			}
825
826 View Code Duplication
			if ( isset( $_POST['_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...
827
				update_post_meta( $post_id, '_height', ( '' === $_POST['_height'] ) ? '' : wc_format_decimal( $_POST['_height'] ) );
828
			}
829
830
		} else {
831
			update_post_meta( $post_id, '_weight', '' );
832
			update_post_meta( $post_id, '_length', '' );
833
			update_post_meta( $post_id, '_width', '' );
834
			update_post_meta( $post_id, '_height', '' );
835
		}
836
837
		// Save shipping class
838
		$product_shipping_class = $_POST['product_shipping_class'] > 0 && $product_type != 'external' ? absint( $_POST['product_shipping_class'] ) : '';
839
		wp_set_object_terms( $post_id, $product_shipping_class, 'product_shipping_class');
840
841
		// Unique SKU
842
		$sku     = get_post_meta( $post_id, '_sku', true );
843
		$new_sku = wc_clean( $_POST['_sku'] );
844
845
		if ( '' == $new_sku ) {
846
			update_post_meta( $post_id, '_sku', '' );
847
		} elseif ( $new_sku !== $sku ) {
848
			if ( ! empty( $new_sku ) ) {
849
				$unique_sku = wc_product_has_unique_sku( $post_id, $new_sku );
850
851
				if ( ! $unique_sku ) {
852
					WC_Admin_Meta_Boxes::add_error( __( 'Product SKU must be unique.', 'woocommerce' ) );
853
				} else {
854
					update_post_meta( $post_id, '_sku', $new_sku );
855
				}
856
			} else {
857
				update_post_meta( $post_id, '_sku', '' );
858
			}
859
		}
860
861
		// Save Attributes
862
		$attributes = array();
863
864
		if ( isset( $_POST['attribute_names'] ) && isset( $_POST['attribute_values'] ) ) {
865
866
			$attribute_names  = $_POST['attribute_names'];
867
			$attribute_values = $_POST['attribute_values'];
868
869
			if ( isset( $_POST['attribute_visibility'] ) ) {
870
				$attribute_visibility = $_POST['attribute_visibility'];
871
			}
872
873
			if ( isset( $_POST['attribute_variation'] ) ) {
874
				$attribute_variation = $_POST['attribute_variation'];
875
			}
876
877
			$attribute_is_taxonomy   = $_POST['attribute_is_taxonomy'];
878
			$attribute_position      = $_POST['attribute_position'];
879
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
880
881
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
882
				if ( empty( $attribute_names[ $i ] ) ) {
883
					continue;
884
				}
885
886
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
887
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
888
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
889
890
				if ( $is_taxonomy ) {
891
892
					$values_are_slugs = false;
893
894
					if ( isset( $attribute_values[ $i ] ) ) {
895
896
						// Select based attributes - Format values (posted values are slugs)
897
						if ( is_array( $attribute_values[ $i ] ) ) {
898
							$values           = array_map( 'sanitize_title', $attribute_values[ $i ] );
899
							$values_are_slugs = true;
900
901
						// Text based attributes - Posted values are term names - don't change to slugs
902
						} else {
903
							$values = array_map( 'stripslashes', array_map( 'strip_tags', explode( WC_DELIMITER, $attribute_values[ $i ] ) ) );
904
						}
905
906
						// Remove empty items in the array
907
						$values = array_filter( $values, 'strlen' );
908
909
					} else {
910
						$values = array();
911
					}
912
913
					// Update post terms
914
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
915
916
						foreach( $values as $key => $value ) {
917
							$term = get_term_by( $values_are_slugs ? 'slug' : 'name', trim( $value ), $attribute_names[ $i ] );
918
919
							if ( $term ) {
920
								$values[ $key ] = intval( $term->term_id );
921
							} else {
922
								$term = wp_insert_term( trim( $value ), $attribute_names[ $i ] );
923
								if ( isset( $term->term_id ) ) {
924
									$values[ $key ] = intval($term->term_id);
925
								}
926
							}
927
						}
928
929
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
930
					}
931
932 View Code Duplication
					if ( ! empty( $values ) ) {
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...
933
						// Add attribute to array, but don't set values
934
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
935
							'name'         => wc_clean( $attribute_names[ $i ] ),
936
							'value'        => '',
937
							'position'     => $attribute_position[ $i ],
938
							'is_visible'   => $is_visible,
939
							'is_variation' => $is_variation,
940
							'is_taxonomy'  => $is_taxonomy
941
						);
942
					}
943
944 View Code Duplication
				} elseif ( isset( $attribute_values[ $i ] ) ) {
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...
945
946
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
947
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
948
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
949
950
					// Custom attribute - Add attribute to array and set the values
951
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
952
						'name'         => wc_clean( $attribute_names[ $i ] ),
953
						'value'        => $values,
954
						'position'     => $attribute_position[ $i ],
955
						'is_visible'   => $is_visible,
956
						'is_variation' => $is_variation,
957
						'is_taxonomy'  => $is_taxonomy
958
					);
959
				}
960
			}
961
		}
962
963
		uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
964
965
		/**
966
		 * Unset removed attributes by looping over previous values and
967
		 * unsetting the terms.
968
		 */
969
		$old_attributes = array_filter( (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) ) );
970
971
		if ( $old_attributes ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $old_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...
972
			foreach ( $old_attributes as $key => $value ) {
973
				if ( empty( $attributes[ $key ] ) && ! empty( $value['is_taxonomy'] ) && taxonomy_exists( $key ) ) {
974
					wp_set_object_terms( $post_id, array(), $key );
975
				}
976
			}
977
		}
978
979
		/**
980
		 * After removed attributes are unset, we can set the new attribute data.
981
		 */
982
		update_post_meta( $post_id, '_product_attributes', $attributes );
983
984
		// Sales and prices
985
		if ( in_array( $product_type, array( 'variable', 'grouped' ) ) ) {
986
987
			// Variable and grouped products have no prices
988
			update_post_meta( $post_id, '_regular_price', '' );
989
			update_post_meta( $post_id, '_sale_price', '' );
990
			update_post_meta( $post_id, '_sale_price_dates_from', '' );
991
			update_post_meta( $post_id, '_sale_price_dates_to', '' );
992
993
		} else {
994
			$date_from     = isset( $_POST['_sale_price_dates_from'] ) ? wc_clean( $_POST['_sale_price_dates_from'] ) : '';
995
			$date_to       = isset( $_POST['_sale_price_dates_to'] ) ? wc_clean( $_POST['_sale_price_dates_to'] )     : '';
996
			$regular_price = isset( $_POST['_regular_price'] ) ? wc_clean( $_POST['_regular_price'] )                 : '';
997
			$sale_price    = isset( $_POST['_sale_price'] ) ? wc_clean( $_POST['_sale_price'] )                       : '';
998
999
			update_post_meta( $post_id, '_regular_price', '' === $regular_price ? '' : wc_format_decimal( $regular_price ) );
1000
			update_post_meta( $post_id, '_sale_price', '' === $sale_price ? '' : wc_format_decimal( $sale_price ) );
1001
1002
			// Dates
1003
			update_post_meta( $post_id, '_sale_price_dates_from', $date_from ? strtotime( $date_from ) : '' );
1004
			update_post_meta( $post_id, '_sale_price_dates_to', $date_to ? strtotime( $date_to ) : '' );
1005
1006
			if ( $date_to && ! $date_from ) {
1007
				$date_from = date( 'Y-m-d' );
1008
				update_post_meta( $post_id, '_sale_price_dates_from', strtotime( $date_from ) );
1009
			}
1010
1011
			// Update price if on sale
1012
			if ( '' !== $sale_price && '' === $date_to && '' === $date_from ) {
1013
				update_post_meta( $post_id, '_price', wc_format_decimal( $sale_price ) );
1014 View Code Duplication
			} elseif ( '' !== $sale_price && $date_from && strtotime( $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...
1015
				update_post_meta( $post_id, '_price', wc_format_decimal( $sale_price ) );
1016
			} else {
1017
				update_post_meta( $post_id, '_price', '' === $regular_price ? '' : wc_format_decimal( $regular_price ) );
1018
			}
1019
1020
			if ( $date_to && strtotime( $date_to ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
1021
				update_post_meta( $post_id, '_price', '' === $regular_price ? '' : wc_format_decimal( $regular_price ) );
1022
				update_post_meta( $post_id, '_sale_price', '' );
1023
				update_post_meta( $post_id, '_sale_price_dates_from', '' );
1024
				update_post_meta( $post_id, '_sale_price_dates_to', '' );
1025
			}
1026
		}
1027
1028
		// Update parent if grouped so price sorting works and stays in sync with the cheapest child
1029
		if ( $post->post_parent > 0 || 'grouped' === $product_type || $_POST['previous_parent_id'] > 0 ) {
1030
1031
			$clear_parent_ids = array();
1032
1033
			if ( $post->post_parent > 0 ) {
1034
				$clear_parent_ids[] = $post->post_parent;
1035
			}
1036
1037
			if ( 'grouped' === $product_type ) {
1038
				$clear_parent_ids[] = $post_id;
1039
			}
1040
1041
			if ( $_POST['previous_parent_id'] > 0 ) {
1042
				$clear_parent_ids[] = absint( $_POST['previous_parent_id'] );
1043
			}
1044
1045
			if ( ! empty( $clear_parent_ids ) ) {
1046
				foreach ( $clear_parent_ids as $clear_id ) {
1047
					$children_by_price = get_posts( array(
1048
						'post_parent'    => $clear_id,
1049
						'orderby'        => 'meta_value_num',
1050
						'order'          => 'asc',
1051
						'meta_key'       => '_price',
1052
						'posts_per_page' => 1,
1053
						'post_type'      => 'product',
1054
						'fields'         => 'ids'
1055
					) );
1056
1057
					if ( $children_by_price ) {
1058
						foreach ( $children_by_price as $child ) {
1059
							$child_price = get_post_meta( $child, '_price', true );
1060
							update_post_meta( $clear_id, '_price', $child_price );
1061
						}
1062
					}
1063
1064
					wc_delete_product_transients( $clear_id );
1065
				}
1066
			}
1067
		}
1068
1069
		// Sold Individually
1070
		if ( ! empty( $_POST['_sold_individually'] ) ) {
1071
			update_post_meta( $post_id, '_sold_individually', 'yes' );
1072
		} else {
1073
			update_post_meta( $post_id, '_sold_individually', '' );
1074
		}
1075
1076
		// Stock Data
1077
		if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
1078
1079
			$manage_stock = 'no';
1080
			$backorders   = 'no';
1081
			$stock_status = wc_clean( $_POST['_stock_status'] );
1082
1083
			if ( 'external' === $product_type ) {
1084
1085
				$stock_status = 'instock';
1086
1087
			} elseif ( 'variable' === $product_type ) {
1088
1089
				// Stock status is always determined by children so sync later
1090
				$stock_status = '';
1091
1092
				if ( ! empty( $_POST['_manage_stock'] ) ) {
1093
					$manage_stock = 'yes';
1094
					$backorders   = wc_clean( $_POST['_backorders'] );
1095
				}
1096
1097
			} elseif ( 'grouped' !== $product_type && ! empty( $_POST['_manage_stock'] ) ) {
1098
				$manage_stock = 'yes';
1099
				$backorders   = wc_clean( $_POST['_backorders'] );
1100
			}
1101
1102
			update_post_meta( $post_id, '_manage_stock', $manage_stock );
1103
			update_post_meta( $post_id, '_backorders', $backorders );
1104
1105
			if ( $stock_status ) {
1106
				wc_update_product_stock_status( $post_id, $stock_status );
0 ignored issues
show
Documentation introduced by
$stock_status 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...
1107
			}
1108
1109 View Code Duplication
			if ( ! empty( $_POST['_manage_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...
1110
				wc_update_product_stock( $post_id, wc_stock_amount( $_POST['_stock'] ) );
1111
			} else {
1112
				update_post_meta( $post_id, '_stock', '' );
1113
			}
1114
1115
		} elseif ( 'variable' !== $product_type ) {
1116
			wc_update_product_stock_status( $post_id, wc_clean( $_POST['_stock_status'] ) );
0 ignored issues
show
Documentation introduced by
wc_clean($_POST['_stock_status']) 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...
1117
		}
1118
1119
		// Cross sells and upsells
1120
		$upsells    = isset( $_POST['upsell_ids'] ) ? array_filter( array_map( 'intval', explode( ',', $_POST['upsell_ids'] ) ) ) : array();
1121
		$crosssells = isset( $_POST['crosssell_ids'] ) ? array_filter( array_map( 'intval', explode( ',', $_POST['crosssell_ids'] ) ) ) : array();
1122
1123
		update_post_meta( $post_id, '_upsell_ids', $upsells );
1124
		update_post_meta( $post_id, '_crosssell_ids', $crosssells );
1125
1126
		// Downloadable options
1127
		if ( 'yes' == $is_downloadable ) {
1128
1129
			$_download_limit = absint( $_POST['_download_limit'] );
1130
			if ( ! $_download_limit ) {
1131
				$_download_limit = ''; // 0 or blank = unlimited
1132
			}
1133
1134
			$_download_expiry = absint( $_POST['_download_expiry'] );
1135
			if ( ! $_download_expiry ) {
1136
				$_download_expiry = ''; // 0 or blank = unlimited
1137
			}
1138
1139
			// file paths will be stored in an array keyed off md5(file path)
1140
			$files = array();
1141
1142
			if ( isset( $_POST['_wc_file_urls'] ) ) {
1143
				$file_names         = isset( $_POST['_wc_file_names'] ) ? $_POST['_wc_file_names'] : array();
1144
				$file_urls          = isset( $_POST['_wc_file_urls'] )  ? wp_unslash( array_map( 'trim', $_POST['_wc_file_urls'] ) ) : array();
1145
				$file_url_size      = sizeof( $file_urls );
1146
				$allowed_file_types = apply_filters( 'woocommerce_downloadable_file_allowed_mime_types', get_allowed_mime_types() );
1147
1148
				for ( $i = 0; $i < $file_url_size; $i ++ ) {
1149
					if ( ! empty( $file_urls[ $i ] ) ) {
1150
						// Find type and file URL
1151 View Code Duplication
						if ( 0 === strpos( $file_urls[ $i ], 'http' ) ) {
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...
1152
							$file_is  = 'absolute';
1153
							$file_url = esc_url_raw( $file_urls[ $i ] );
1154
						} elseif ( '[' === substr( $file_urls[ $i ], 0, 1 ) && ']' === substr( $file_urls[ $i ], -1 ) ) {
1155
							$file_is  = 'shortcode';
1156
							$file_url = wc_clean( $file_urls[ $i ] );
1157
						} else {
1158
							$file_is = 'relative';
1159
							$file_url = wc_clean( $file_urls[ $i ] );
1160
						}
1161
1162
						$file_name = wc_clean( $file_names[ $i ] );
1163
						$file_hash = md5( $file_url );
1164
1165
						// Validate the file extension
1166 View Code Duplication
						if ( in_array( $file_is, array( 'absolute', 'relative' ) ) ) {
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...
1167
							$file_type  = wp_check_filetype( strtok( $file_url, '?' ), $allowed_file_types );
1168
							$parsed_url = parse_url( $file_url, PHP_URL_PATH );
1169
							$extension  = pathinfo( $parsed_url, PATHINFO_EXTENSION );
1170
1171
							if ( ! empty( $extension ) && ! in_array( $file_type['type'], $allowed_file_types ) ) {
1172
								WC_Admin_Meta_Boxes::add_error( sprintf( __( 'The downloadable file %s cannot be used as it does not have an allowed file type. Allowed types include: %s', 'woocommerce' ), '<code>' . basename( $file_url ) . '</code>', '<code>' . implode( ', ', array_keys( $allowed_file_types ) ) . '</code>' ) );
1173
								continue;
1174
							}
1175
						}
1176
1177
						// Validate the file exists
1178
						if ( 'relative' === $file_is ) {
1179
							$_file_url = $file_url;
1180
							if ( '..' === substr( $file_url, 0, 2 ) || '/' !== substr( $file_url, 0, 1 ) ) {
1181
								$_file_url = realpath( ABSPATH . $file_url );
1182
							}
1183
1184 View Code Duplication
							if ( ! apply_filters( 'woocommerce_downloadable_file_exists', file_exists( $_file_url ), $file_url ) ) {
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...
1185
								WC_Admin_Meta_Boxes::add_error( sprintf( __( 'The downloadable file %s cannot be used as it does not exist on the server.', 'woocommerce' ), '<code>' . $file_url . '</code>' ) );
1186
								continue;
1187
							}
1188
						}
1189
1190
						$files[ $file_hash ] = array(
1191
							'name' => $file_name,
1192
							'file' => $file_url
1193
						);
1194
					}
1195
				}
1196
			}
1197
1198
			// grant permission to any newly added files on any existing orders for this product prior to saving
1199
			do_action( 'woocommerce_process_product_file_download_paths', $post_id, 0, $files );
1200
1201
			update_post_meta( $post_id, '_downloadable_files', $files );
1202
			update_post_meta( $post_id, '_download_limit', $_download_limit );
1203
			update_post_meta( $post_id, '_download_expiry', $_download_expiry );
1204
1205
			if ( isset( $_POST['_download_type'] ) ) {
1206
				update_post_meta( $post_id, '_download_type', wc_clean( $_POST['_download_type'] ) );
1207
			}
1208
		}
1209
1210
		// Product url
1211 View Code Duplication
		if ( 'external' == $product_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...
1212
1213
			if ( isset( $_POST['_product_url'] ) ) {
1214
				update_post_meta( $post_id, '_product_url', esc_url_raw( $_POST['_product_url'] ) );
1215
			}
1216
1217
			if ( isset( $_POST['_button_text'] ) ) {
1218
				update_post_meta( $post_id, '_button_text', wc_clean( $_POST['_button_text'] ) );
1219
			}
1220
		}
1221
1222
		// Save variations
1223
		if ( 'variable' == $product_type ) {
1224
			// Update parent if variable so price sorting works and stays in sync with the cheapest child
1225
			WC_Product_Variable::sync( $post_id );
1226
			WC_Product_Variable::sync_stock_status( $post_id );
1227
		}
1228
1229
		// Update version after saving
1230
		update_post_meta( $post_id, '_product_version', WC_VERSION );
1231
1232
		// Do action for product type
1233
		do_action( 'woocommerce_process_product_meta_' . $product_type, $post_id );
1234
1235
		// Clear cache/transients
1236
		wc_delete_product_transients( $post_id );
1237
	}
1238
1239
	/**
1240
	 * Save meta box data.
1241
	 *
1242
	 * @param int $post_id
1243
	 * @param WP_Post $post
1244
	 */
1245
	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...
1246
		global $wpdb;
1247
1248
		$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
1249
1250
		if ( isset( $_POST['variable_sku'] ) ) {
1251
			$variable_post_id               = $_POST['variable_post_id'];
1252
			$variable_sku                   = $_POST['variable_sku'];
1253
			$variable_regular_price         = $_POST['variable_regular_price'];
1254
			$variable_sale_price            = $_POST['variable_sale_price'];
1255
			$upload_image_id                = $_POST['upload_image_id'];
1256
			$variable_download_limit        = $_POST['variable_download_limit'];
1257
			$variable_download_expiry       = $_POST['variable_download_expiry'];
1258
			$variable_shipping_class        = $_POST['variable_shipping_class'];
1259
			$variable_tax_class             = isset( $_POST['variable_tax_class'] ) ? $_POST['variable_tax_class'] : array();
1260
			$variable_menu_order            = $_POST['variation_menu_order'];
1261
			$variable_sale_price_dates_from = $_POST['variable_sale_price_dates_from'];
1262
			$variable_sale_price_dates_to   = $_POST['variable_sale_price_dates_to'];
1263
1264
			$variable_weight                = isset( $_POST['variable_weight'] ) ? $_POST['variable_weight'] : array();
1265
			$variable_length                = isset( $_POST['variable_length'] ) ? $_POST['variable_length'] : array();
1266
			$variable_width                 = isset( $_POST['variable_width'] ) ? $_POST['variable_width'] : array();
1267
			$variable_height                = isset( $_POST['variable_height'] ) ? $_POST['variable_height'] : array();
1268
			$variable_enabled               = isset( $_POST['variable_enabled'] ) ? $_POST['variable_enabled'] : array();
1269
			$variable_is_virtual            = isset( $_POST['variable_is_virtual'] ) ? $_POST['variable_is_virtual'] : array();
1270
			$variable_is_downloadable       = isset( $_POST['variable_is_downloadable'] ) ? $_POST['variable_is_downloadable'] : array();
1271
1272
			$variable_manage_stock          = isset( $_POST['variable_manage_stock'] ) ? $_POST['variable_manage_stock'] : array();
1273
			$variable_stock                 = isset( $_POST['variable_stock'] ) ? $_POST['variable_stock'] : array();
1274
			$variable_backorders            = isset( $_POST['variable_backorders'] ) ? $_POST['variable_backorders'] : array();
1275
			$variable_stock_status          = isset( $_POST['variable_stock_status'] ) ? $_POST['variable_stock_status'] : array();
1276
1277
			$variable_description           = isset( $_POST['variable_description'] ) ? $_POST['variable_description'] : array();
1278
1279
			$max_loop = max( array_keys( $_POST['variable_post_id'] ) );
1280
1281
			for ( $i = 0; $i <= $max_loop; $i ++ ) {
1282
1283
				if ( ! isset( $variable_post_id[ $i ] ) ) {
1284
					continue;
1285
				}
1286
1287
				$variation_id = absint( $variable_post_id[ $i ] );
1288
1289
				// Checkboxes
1290
				$is_virtual      = isset( $variable_is_virtual[ $i ] ) ? 'yes' : 'no';
1291
				$is_downloadable = isset( $variable_is_downloadable[ $i ] ) ? 'yes' : 'no';
1292
				$post_status     = isset( $variable_enabled[ $i ] ) ? 'publish' : 'private';
1293
				$manage_stock    = isset( $variable_manage_stock[ $i ] ) ? 'yes' : 'no';
1294
1295
				// Generate a useful post title
1296
				$variation_post_title = sprintf( __( 'Variation #%s of %s', 'woocommerce' ), absint( $variation_id ), esc_html( get_the_title( $post_id ) ) );
1297
1298
				// Update or Add post
1299
				if ( ! $variation_id ) {
1300
1301
					$variation = array(
1302
						'post_title'   => $variation_post_title,
1303
						'post_content' => '',
1304
						'post_status'  => $post_status,
1305
						'post_author'  => get_current_user_id(),
1306
						'post_parent'  => $post_id,
1307
						'post_type'    => 'product_variation',
1308
						'menu_order'   => $variable_menu_order[ $i ]
1309
					);
1310
1311
					$variation_id = wp_insert_post( $variation );
1312
1313
					do_action( 'woocommerce_create_product_variation', $variation_id );
1314
1315
				} else {
1316
1317
					$modified_date = date_i18n( 'Y-m-d H:i:s', current_time( 'timestamp' ) );
1318
1319
					$wpdb->update( $wpdb->posts, array(
1320
							'post_status'       => $post_status,
1321
							'post_title'        => $variation_post_title,
1322
							'menu_order'        => $variable_menu_order[ $i ],
1323
							'post_modified'     => $modified_date,
1324
							'post_modified_gmt' => get_gmt_from_date( $modified_date )
1325
					), array( 'ID' => $variation_id ) );
1326
1327
					clean_post_cache( $variation_id );
1328
1329
					do_action( 'woocommerce_update_product_variation', $variation_id );
1330
1331
				}
1332
1333
				// Only continue if we have a variation ID
1334
				if ( ! $variation_id ) {
1335
					continue;
1336
				}
1337
1338
				// Unique SKU
1339
				$sku     = get_post_meta( $variation_id, '_sku', true );
1340
				$new_sku = wc_clean( $variable_sku[ $i ] );
1341
1342
				if ( '' == $new_sku ) {
1343
					update_post_meta( $variation_id, '_sku', '' );
1344
				} elseif ( $new_sku !== $sku ) {
1345
					if ( ! empty( $new_sku ) ) {
1346
						$unique_sku = wc_product_has_unique_sku( $variation_id, $new_sku );
1347
1348
						if ( ! $unique_sku ) {
1349
							WC_Admin_Meta_Boxes::add_error( sprintf( __( '#%s &ndash; Variation SKU must be unique.', 'woocommerce' ), $variation_id ) );
1350
						} else {
1351
							update_post_meta( $variation_id, '_sku', $new_sku );
1352
						}
1353
					} else {
1354
						update_post_meta( $variation_id, '_sku', '' );
1355
					}
1356
				}
1357
1358
				// Update post meta
1359
				update_post_meta( $variation_id, '_thumbnail_id', absint( $upload_image_id[ $i ] ) );
1360
				update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
1361
				update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
1362
1363 View Code Duplication
				if ( isset( $variable_weight[ $i ] ) ) {
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
					update_post_meta( $variation_id, '_weight', ( '' === $variable_weight[ $i ] ) ? '' : wc_format_decimal( $variable_weight[ $i ] ) );
1365
				}
1366
1367 View Code Duplication
				if ( isset( $variable_length[ $i ] ) ) {
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...
1368
					update_post_meta( $variation_id, '_length', ( '' === $variable_length[ $i ] ) ? '' : wc_format_decimal( $variable_length[ $i ] ) );
1369
				}
1370
1371 View Code Duplication
				if ( isset( $variable_width[ $i ] ) ) {
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...
1372
					update_post_meta( $variation_id, '_width', ( '' === $variable_width[ $i ] ) ? '' : wc_format_decimal( $variable_width[ $i ] ) );
1373
				}
1374
1375 View Code Duplication
				if ( isset( $variable_height[ $i ] ) ) {
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...
1376
					update_post_meta( $variation_id, '_height', ( '' === $variable_height[ $i ] ) ? '' : wc_format_decimal( $variable_height[ $i ] ) );
1377
				}
1378
1379
				// Stock handling
1380
				update_post_meta( $variation_id, '_manage_stock', $manage_stock );
1381
1382
				if ( 'yes' === $manage_stock ) {
1383
					update_post_meta( $variation_id, '_backorders', wc_clean( $variable_backorders[ $i ] ) );
1384
					wc_update_product_stock( $variation_id, wc_stock_amount( $variable_stock[ $i ] ) );
1385
				} else {
1386
					delete_post_meta( $variation_id, '_backorders' );
1387
					delete_post_meta( $variation_id, '_stock' );
1388
				}
1389
1390
				// Only update stock status to user setting if changed by the user, but do so before looking at stock levels at variation level
1391
				if ( ! empty( $variable_stock_status[ $i ] ) ) {
1392
					wc_update_product_stock_status( $variation_id, $variable_stock_status[ $i ] );
1393
				}
1394
1395
				// Price handling
1396
				$regular_price = wc_format_decimal( $variable_regular_price[ $i ] );
1397
				$sale_price    = $variable_sale_price[ $i ] === '' ? '' : wc_format_decimal( $variable_sale_price[ $i ] );
1398
				$date_from     = wc_clean( $variable_sale_price_dates_from[ $i ] );
1399
				$date_to       = wc_clean( $variable_sale_price_dates_to[ $i ] );
1400
1401
				update_post_meta( $variation_id, '_regular_price', $regular_price );
1402
				update_post_meta( $variation_id, '_sale_price', $sale_price );
1403
1404
				// Save Dates
1405
				update_post_meta( $variation_id, '_sale_price_dates_from', $date_from ? strtotime( $date_from ) : '' );
1406
				update_post_meta( $variation_id, '_sale_price_dates_to', $date_to ? strtotime( $date_to ) : '' );
1407
1408
				if ( $date_to && ! $date_from ) {
1409
					update_post_meta( $variation_id, '_sale_price_dates_from', strtotime( 'NOW', current_time( 'timestamp' ) ) );
1410
				}
1411
1412
				// Update price if on sale
1413 View Code Duplication
				if ( '' !== $sale_price && '' === $date_to && '' === $date_from ) {
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...
1414
					update_post_meta( $variation_id, '_price', $sale_price );
1415
				} else {
1416
					update_post_meta( $variation_id, '_price', $regular_price );
1417
				}
1418
1419 View Code Duplication
				if ( '' !== $sale_price && $date_from && strtotime( $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...
1420
					update_post_meta( $variation_id, '_price', $sale_price );
1421
				}
1422
1423 View Code Duplication
				if ( $date_to && strtotime( $date_to ) < 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...
1424
					update_post_meta( $variation_id, '_price', $regular_price );
1425
					update_post_meta( $variation_id, '_sale_price_dates_from', '' );
1426
					update_post_meta( $variation_id, '_sale_price_dates_to', '' );
1427
				}
1428
1429
				if ( isset( $variable_tax_class[ $i ] ) && $variable_tax_class[ $i ] !== 'parent' ) {
1430
					update_post_meta( $variation_id, '_tax_class', wc_clean( $variable_tax_class[ $i ] ) );
1431
				} else {
1432
					delete_post_meta( $variation_id, '_tax_class' );
1433
				}
1434
1435
				if ( 'yes' == $is_downloadable ) {
1436
					update_post_meta( $variation_id, '_download_limit', wc_clean( $variable_download_limit[ $i ] ) );
1437
					update_post_meta( $variation_id, '_download_expiry', wc_clean( $variable_download_expiry[ $i ] ) );
1438
1439
					$files              = array();
1440
					$file_names         = isset( $_POST['_wc_variation_file_names'][ $variation_id ] ) ? array_map( 'wc_clean', $_POST['_wc_variation_file_names'][ $variation_id ] ) : array();
1441
					$file_urls          = isset( $_POST['_wc_variation_file_urls'][ $variation_id ] ) ? array_map( 'wc_clean', $_POST['_wc_variation_file_urls'][ $variation_id ] ) : array();
1442
					$file_url_size      = sizeof( $file_urls );
1443
					$allowed_file_types = get_allowed_mime_types();
1444
1445
					for ( $ii = 0; $ii < $file_url_size; $ii ++ ) {
1446
						if ( ! empty( $file_urls[ $ii ] ) ) {
1447
							// Find type and file URL
1448 View Code Duplication
							if ( 0 === strpos( $file_urls[ $ii ], 'http' ) ) {
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...
1449
								$file_is  = 'absolute';
1450
								$file_url = esc_url_raw( $file_urls[ $ii ] );
1451
							} elseif ( '[' === substr( $file_urls[ $ii ], 0, 1 ) && ']' === substr( $file_urls[ $ii ], -1 ) ) {
1452
								$file_is  = 'shortcode';
1453
								$file_url = wc_clean( $file_urls[ $ii ] );
1454
							} else {
1455
								$file_is = 'relative';
1456
								$file_url = wc_clean( $file_urls[ $ii ] );
1457
							}
1458
1459
							$file_name = wc_clean( $file_names[ $ii ] );
1460
							$file_hash = md5( $file_url );
1461
1462
							// Validate the file extension
1463 View Code Duplication
							if ( in_array( $file_is, array( 'absolute', 'relative' ) ) ) {
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...
1464
								$file_type  = wp_check_filetype( strtok( $file_url, '?' ), $allowed_file_types );
1465
								$parsed_url = parse_url( $file_url, PHP_URL_PATH );
1466
								$extension  = pathinfo( $parsed_url, PATHINFO_EXTENSION );
1467
1468
								if ( ! empty( $extension ) && ! in_array( $file_type['type'], $allowed_file_types ) ) {
1469
									WC_Admin_Meta_Boxes::add_error( sprintf( __( '#%s &ndash; The downloadable file %s cannot be used as it does not have an allowed file type. Allowed types include: %s', 'woocommerce' ), $variation_id, '<code>' . basename( $file_url ) . '</code>', '<code>' . implode( ', ', array_keys( $allowed_file_types ) ) . '</code>' ) );
1470
									continue;
1471
								}
1472
							}
1473
1474
							// Validate the file exists
1475 View Code Duplication
							if ( 'relative' === $file_is && ! apply_filters( 'woocommerce_downloadable_file_exists', file_exists( $file_url ), $file_url ) ) {
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...
1476
								WC_Admin_Meta_Boxes::add_error( sprintf( __( '#%s &ndash; The downloadable file %s cannot be used as it does not exist on the server.', 'woocommerce' ), $variation_id, '<code>' . $file_url . '</code>' ) );
1477
								continue;
1478
							}
1479
1480
							$files[ $file_hash ] = array(
1481
								'name' => $file_name,
1482
								'file' => $file_url
1483
							);
1484
						}
1485
					}
1486
1487
					// grant permission to any newly added files on any existing orders for this product prior to saving
1488
					do_action( 'woocommerce_process_product_file_download_paths', $post_id, $variation_id, $files );
1489
1490
					update_post_meta( $variation_id, '_downloadable_files', $files );
1491
				} else {
1492
					update_post_meta( $variation_id, '_download_limit', '' );
1493
					update_post_meta( $variation_id, '_download_expiry', '' );
1494
					update_post_meta( $variation_id, '_downloadable_files', '' );
1495
				}
1496
1497
				update_post_meta( $variation_id, '_variation_description', wp_kses_post( $variable_description[ $i ] ) );
1498
1499
				// Save shipping class
1500
				$variable_shipping_class[ $i ] = ! empty( $variable_shipping_class[ $i ] ) ? (int) $variable_shipping_class[ $i ] : '';
1501
				wp_set_object_terms( $variation_id, $variable_shipping_class[ $i ], 'product_shipping_class');
1502
1503
				// Update Attributes
1504
				$updated_attribute_keys = array();
1505
				foreach ( $attributes as $attribute ) {
1506
					if ( $attribute['is_variation'] ) {
1507
						$attribute_key            = 'attribute_' . sanitize_title( $attribute['name'] );
1508
						$updated_attribute_keys[] = $attribute_key;
1509
1510
						if ( $attribute['is_taxonomy'] ) {
1511
							// Don't use wc_clean as it destroys sanitized characters
1512
							$value = isset( $_POST[ $attribute_key ][ $i ] ) ? sanitize_title( stripslashes( $_POST[ $attribute_key ][ $i ] ) ) : '';
1513
						} else {
1514
							$value = isset( $_POST[ $attribute_key ][ $i ] ) ? wc_clean( stripslashes( $_POST[ $attribute_key ][ $i ] ) ) : '';
1515
						}
1516
1517
						update_post_meta( $variation_id, $attribute_key, $value );
1518
					}
1519
				}
1520
1521
				// Remove old taxonomies attributes so data is kept up to date - first get attribute key names
1522
				$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 ) );
1523
1524
				foreach ( $delete_attribute_keys as $key ) {
1525
					delete_post_meta( $variation_id, $key );
1526
				}
1527
1528
				do_action( 'woocommerce_save_product_variation', $variation_id, $i );
1529
			}
1530
		}
1531
1532
		// Update parent if variable so price sorting works and stays in sync with the cheapest child
1533
		WC_Product_Variable::sync( $post_id );
1534
1535
		// Update default attribute options setting
1536
		$default_attributes = array();
1537
1538
		foreach ( $attributes as $attribute ) {
1539
1540
			if ( $attribute['is_variation'] ) {
1541
				$value = '';
1542
1543
				if ( isset( $_POST[ 'default_attribute_' . sanitize_title( $attribute['name'] ) ] ) ) {
1544
					if ( $attribute['is_taxonomy'] ) {
1545
						// Don't use wc_clean as it destroys sanitized characters
1546
						$value = sanitize_title( trim( stripslashes( $_POST[ 'default_attribute_' . sanitize_title( $attribute['name'] ) ] ) ) );
1547
					} else {
1548
						$value = wc_clean( trim( stripslashes( $_POST[ 'default_attribute_' . sanitize_title( $attribute['name'] ) ] ) ) );
1549
					}
1550
				}
1551
1552
				if ( $value ) {
1553
					$default_attributes[ sanitize_title( $attribute['name'] ) ] = $value;
1554
				}
1555
			}
1556
		}
1557
1558
		update_post_meta( $post_id, '_default_attributes', $default_attributes );
1559
	}
1560
}
1561