Completed
Push — master ( fb5f9c...377d79 )
by Mike
64:31 queued 55:52
created

meta-boxes/class-wc-meta-box-product-data.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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  3.0.0
11
 */
12
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
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 $thepostid, $product_object;
29
30
		$thepostid      = $post->ID;
31
		$product_object = $thepostid ? wc_get_product( $thepostid ) : new WC_Product();
32
33
		wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
34
35
		include 'views/html-product-data-panel.php';
36
	}
37
38
	/**
39
	 * Show tab content/settings.
40
	 */
41
	private static function output_tabs() {
42
		global $post, $thepostid, $product_object;
43
44
		include 'views/html-product-data-general.php';
45
		include 'views/html-product-data-inventory.php';
46
		include 'views/html-product-data-shipping.php';
47
		include 'views/html-product-data-linked-products.php';
48
		include 'views/html-product-data-attributes.php';
49
		include 'views/html-product-data-advanced.php';
50
	}
51
52
	/**
53
	 * Return array of product type options.
54
	 *
55
	 * @return array
56
	 */
57
	private static function get_product_type_options() {
58
		return apply_filters(
59
			'product_type_options', array(
60
				'virtual'      => array(
61
					'id'            => '_virtual',
62
					'wrapper_class' => 'show_if_simple',
63
					'label'         => __( 'Virtual', 'woocommerce' ),
64
					'description'   => __( 'Virtual products are intangible and are not shipped.', 'woocommerce' ),
65
					'default'       => 'no',
66
				),
67
				'downloadable' => array(
68
					'id'            => '_downloadable',
69
					'wrapper_class' => 'show_if_simple',
70
					'label'         => __( 'Downloadable', 'woocommerce' ),
71
					'description'   => __( 'Downloadable products give access to a file upon purchase.', 'woocommerce' ),
72
					'default'       => 'no',
73
				),
74
			)
75
		);
76
	}
77
78
	/**
79
	 * Return array of tabs to show.
80
	 *
81
	 * @return array
82
	 */
83
	private static function get_product_data_tabs() {
84
		$tabs = apply_filters(
85
			'woocommerce_product_data_tabs', 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
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
187
	 * @param array $file_urls
188
	 * @param array $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 = sizeof( $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();
218
	}
219
220
	/**
221
	 * Prepare attributes for save.
222
	 *
223
	 * @param array $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 );
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 );
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 );
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
285
	 * @param  string $key_prefix
286
	 * @param  int    $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 ) {
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 ] ) : '';
299
					} else {
300
						$value = isset( $_POST[ $key_prefix . $attribute_key ] ) ? wp_unslash( $_POST[ $key_prefix . $attribute_key ] ) : '';
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
322
	 * @param $post
323
	 */
324
	public static function save( $post_id, $post ) {
325
		// Process product type first so we have the correct class to run setters.
326
		$product_type = empty( $_POST['product-type'] ) ? WC_Product_Factory::get_product_type( $post_id ) : sanitize_title( stripslashes( $_POST['product-type'] ) );
327
		$classname    = WC_Product_Factory::get_product_classname( $post_id, $product_type ? $product_type : 'simple' );
328
		$product      = new $classname( $post_id );
329
		$attributes   = self::prepare_attributes();
330
		$stock        = null;
331
332
		// Handle stock changes.
333
		if ( isset( $_POST['_stock'] ) ) {
334
			if ( isset( $_POST['_original_stock'] ) && wc_stock_amount( $product->get_stock_quantity( 'edit' ) ) !== wc_stock_amount( $_POST['_original_stock'] ) ) {
335
				/* translators: 1: product ID 2: quantity in stock */
336
				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' ) ) );
337
			} else {
338
				$stock = wc_stock_amount( wp_unslash( $_POST['_stock'] ) );
339
			}
340
		}
341
342
		$errors = $product->set_props(
343
			array(
344
				'sku'                => isset( $_POST['_sku'] ) ? wc_clean( wp_unslash( $_POST['_sku'] ) ) : null,
345
				'purchase_note'      => wp_kses_post( wp_unslash( $_POST['_purchase_note'] ) ),
346
				'downloadable'       => isset( $_POST['_downloadable'] ),
347
				'virtual'            => isset( $_POST['_virtual'] ),
348
				'featured'           => isset( $_POST['_featured'] ),
349
				'catalog_visibility' => wc_clean( wp_unslash( $_POST['_visibility'] ) ),
350
				'tax_status'         => isset( $_POST['_tax_status'] ) ? wc_clean( wp_unslash( $_POST['_tax_status'] ) ) : null,
351
				'tax_class'          => isset( $_POST['_tax_class'] ) ? wc_clean( wp_unslash( $_POST['_tax_class'] ) ) : null,
352
				'weight'             => wc_clean( wp_unslash( $_POST['_weight'] ) ),
353
				'length'             => wc_clean( wp_unslash( $_POST['_length'] ) ),
354
				'width'              => wc_clean( wp_unslash( $_POST['_width'] ) ),
355
				'height'             => wc_clean( wp_unslash( $_POST['_height'] ) ),
356
				'shipping_class_id'  => absint( wp_unslash( $_POST['product_shipping_class'] ) ),
357
				'sold_individually'  => ! empty( $_POST['_sold_individually'] ),
358
				'upsell_ids'         => isset( $_POST['upsell_ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['upsell_ids'] ) ) : array(),
359
				'cross_sell_ids'     => isset( $_POST['crosssell_ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['crosssell_ids'] ) ) : array(),
360
				'regular_price'      => wc_clean( wp_unslash( $_POST['_regular_price'] ) ),
361
				'sale_price'         => wc_clean( wp_unslash( $_POST['_sale_price'] ) ),
362
				'date_on_sale_from'  => wc_clean( wp_unslash( $_POST['_sale_price_dates_from'] ) ),
363
				'date_on_sale_to'    => wc_clean( wp_unslash( $_POST['_sale_price_dates_to'] ) ),
364
				'manage_stock'       => ! empty( $_POST['_manage_stock'] ),
365
				'backorders'         => isset( $_POST['_backorders'] ) ? wc_clean( wp_unslash( $_POST['_backorders'] ) ) : null,
366
				'stock_status'       => wc_clean( wp_unslash( $_POST['_stock_status'] ) ),
367
				'stock_quantity'     => $stock,
368
				'low_stock_amount'   => isset( $_POST['_low_stock_amount'] ) ? wc_stock_amount( wp_unslash( $_POST['_low_stock_amount'] ) ) : null,
0 ignored issues
show
Detected usage of a non-sanitized input variable: $_POST
Loading history...
369
				'download_limit'     => '' === $_POST['_download_limit'] ? '' : absint( wp_unslash( $_POST['_download_limit'] ) ),
370
				'download_expiry'    => '' === $_POST['_download_expiry'] ? '' : absint( wp_unslash( $_POST['_download_expiry'] ) ),
371
				'downloads'          => self::prepare_downloads(
372
					isset( $_POST['_wc_file_names'] ) ? wp_unslash( $_POST['_wc_file_names'] ) : array(),
373
					isset( $_POST['_wc_file_urls'] ) ? wp_unslash( $_POST['_wc_file_urls'] ) : array(),
374
					isset( $_POST['_wc_file_hashes'] ) ? wp_unslash( $_POST['_wc_file_hashes'] ) : array()
375
				),
376
				'product_url'         => esc_url_raw( wp_unslash( $_POST['_product_url'] ) ),
377
				'button_text'         => wc_clean( wp_unslash( $_POST['_button_text'] ) ),
378
				'children'            => 'grouped' === $product_type ? self::prepare_children() : null,
379
				'reviews_allowed'     => ! empty( $_POST['comment_status'] ) && 'open' === $_POST['comment_status'],
380
				'attributes'          => $attributes,
381
				'default_attributes'  => self::prepare_set_attributes( $attributes, 'default_attribute_' ),
382
			)
383
		);
384
385
		if ( is_wp_error( $errors ) ) {
386
			WC_Admin_Meta_Boxes::add_error( $errors->get_error_message() );
387
		}
388
389
		/**
390
		 * @since 3.0.0 to set props before save.
391
		 */
392
		do_action( 'woocommerce_admin_process_product_object', $product );
393
394
		$product->save();
395
396
		if ( $product->is_type( 'variable' ) ) {
397
			$product->get_data_store()->sync_variation_names( $product, wc_clean( $_POST['original_post_title'] ), wc_clean( $_POST['post_title'] ) );
398
		}
399
400
		do_action( 'woocommerce_process_product_meta_' . $product_type, $post_id );
401
	}
402
403
	/**
404
	 * Save meta box data.
405
	 *
406
	 * @param int     $post_id
407
	 * @param WP_Post $post
408
	 */
409
	public static function save_variations( $post_id, $post ) {
410
		if ( isset( $_POST['variable_post_id'] ) ) {
411
			$parent = wc_get_product( $post_id );
412
			$parent->set_default_attributes( self::prepare_set_attributes( $parent->get_attributes(), 'default_attribute_' ) );
413
			$parent->save();
414
415
			$max_loop   = max( array_keys( $_POST['variable_post_id'] ) );
416
			$data_store = $parent->get_data_store();
417
			$data_store->sort_all_product_variations( $parent->get_id() );
418
419
			for ( $i = 0; $i <= $max_loop; $i ++ ) {
420
421
				if ( ! isset( $_POST['variable_post_id'][ $i ] ) ) {
422
					continue;
423
				}
424
				$variation_id = absint( $_POST['variable_post_id'][ $i ] );
425
				$variation    = new WC_Product_Variation( $variation_id );
426
				$stock        = null;
427
428
				// Handle stock changes.
429
				if ( isset( $_POST['variable_stock'], $_POST['variable_stock'][ $i ] ) ) {
430
					if ( isset( $_POST['variable_original_stock'], $_POST['variable_original_stock'][ $i ] ) && wc_stock_amount( $variation->get_stock_quantity( 'edit' ) ) !== wc_stock_amount( $_POST['variable_original_stock'][ $i ] ) ) {
431
						/* translators: 1: product ID 2: quantity in stock */
432
						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' ) ) );
433
					} else {
434
						$stock = wc_stock_amount( $_POST['variable_stock'][ $i ] );
435
					}
436
				}
437
438
				$errors = $variation->set_props(
439
					array(
440
						'status'            => isset( $_POST['variable_enabled'][ $i ] ) ? 'publish' : 'private',
441
						'menu_order'        => wc_clean( $_POST['variation_menu_order'][ $i ] ),
442
						'regular_price'     => wc_clean( $_POST['variable_regular_price'][ $i ] ),
443
						'sale_price'        => wc_clean( $_POST['variable_sale_price'][ $i ] ),
444
						'virtual'           => isset( $_POST['variable_is_virtual'][ $i ] ),
445
						'downloadable'      => isset( $_POST['variable_is_downloadable'][ $i ] ),
446
						'date_on_sale_from' => wc_clean( $_POST['variable_sale_price_dates_from'][ $i ] ),
447
						'date_on_sale_to'   => wc_clean( $_POST['variable_sale_price_dates_to'][ $i ] ),
448
						'description'       => wp_kses_post( $_POST['variable_description'][ $i ] ),
449
						'download_limit'    => wc_clean( $_POST['variable_download_limit'][ $i ] ),
450
						'download_expiry'   => wc_clean( $_POST['variable_download_expiry'][ $i ] ),
451
						'downloads'         => self::prepare_downloads(
452
							isset( $_POST['_wc_variation_file_names'][ $variation_id ] ) ? $_POST['_wc_variation_file_names'][ $variation_id ] : array(),
453
							isset( $_POST['_wc_variation_file_urls'][ $variation_id ] ) ? $_POST['_wc_variation_file_urls'][ $variation_id ] : array(),
454
							isset( $_POST['_wc_variation_file_hashes'][ $variation_id ] ) ? $_POST['_wc_variation_file_hashes'][ $variation_id ] : array()
455
						),
456
						'manage_stock'      => isset( $_POST['variable_manage_stock'][ $i ] ),
457
						'stock_quantity'    => $stock,
458
						'backorders'        => isset( $_POST['variable_backorders'], $_POST['variable_backorders'][ $i ] ) ? wc_clean( $_POST['variable_backorders'][ $i ] ) : null,
459
						'stock_status'      => wc_clean( $_POST['variable_stock_status'][ $i ] ),
460
						'image_id'          => wc_clean( $_POST['upload_image_id'][ $i ] ),
461
						'attributes'        => self::prepare_set_attributes( $parent->get_attributes(), 'attribute_', $i ),
462
						'sku'               => isset( $_POST['variable_sku'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_sku'][ $i ] ) ) : '',
463
						'weight'            => isset( $_POST['variable_weight'][ $i ] ) ? wc_clean( $_POST['variable_weight'][ $i ] ) : '',
464
						'length'            => isset( $_POST['variable_length'][ $i ] ) ? wc_clean( $_POST['variable_length'][ $i ] ) : '',
465
						'width'             => isset( $_POST['variable_width'][ $i ] ) ? wc_clean( $_POST['variable_width'][ $i ] ) : '',
466
						'height'            => isset( $_POST['variable_height'][ $i ] ) ? wc_clean( $_POST['variable_height'][ $i ] ) : '',
467
						'shipping_class_id' => wc_clean( $_POST['variable_shipping_class'][ $i ] ),
468
						'tax_class'         => isset( $_POST['variable_tax_class'][ $i ] ) ? wc_clean( $_POST['variable_tax_class'][ $i ] ) : null,
469
					)
470
				);
471
472
				if ( is_wp_error( $errors ) ) {
473
					WC_Admin_Meta_Boxes::add_error( $errors->get_error_message() );
474
				}
475
476
				$variation->save();
477
478
				do_action( 'woocommerce_save_product_variation', $variation_id, $i );
479
			}
480
		}
481
	}
482
}
483