1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Product Data |
4
|
|
|
* |
5
|
|
|
* Displays the product data box, tabbed, with several panels covering price, stock etc. |
6
|
|
|
* |
7
|
|
|
* @package WooCommerce/Admin/Meta Boxes |
8
|
|
|
* @version 3.0.0 |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
12
|
|
|
exit; |
13
|
|
|
} |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* WC_Meta_Box_Product_Data Class. |
17
|
|
|
*/ |
18
|
|
|
class WC_Meta_Box_Product_Data { |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Output the metabox. |
22
|
|
|
* |
23
|
|
|
* @param WP_Post $post Post object. |
24
|
|
|
*/ |
25
|
|
|
public static function output( $post ) { |
26
|
|
|
global $thepostid, $product_object; |
27
|
|
|
|
28
|
|
|
$thepostid = $post->ID; |
29
|
|
|
$product_object = $thepostid ? wc_get_product( $thepostid ) : new WC_Product(); |
30
|
|
|
|
31
|
|
|
wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' ); |
32
|
|
|
|
33
|
|
|
include 'views/html-product-data-panel.php'; |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Show tab content/settings. |
38
|
|
|
*/ |
39
|
|
|
private static function output_tabs() { |
|
|
|
|
40
|
|
|
global $post, $thepostid, $product_object; |
41
|
|
|
|
42
|
|
|
include 'views/html-product-data-general.php'; |
43
|
|
|
include 'views/html-product-data-inventory.php'; |
44
|
|
|
include 'views/html-product-data-shipping.php'; |
45
|
|
|
include 'views/html-product-data-linked-products.php'; |
46
|
|
|
include 'views/html-product-data-attributes.php'; |
47
|
|
|
include 'views/html-product-data-advanced.php'; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Return array of product type options. |
52
|
|
|
* |
53
|
|
|
* @return array |
54
|
|
|
*/ |
55
|
|
|
private static function get_product_type_options() { |
|
|
|
|
56
|
|
|
return apply_filters( |
57
|
|
|
'product_type_options', |
58
|
|
|
array( |
59
|
|
|
'virtual' => array( |
60
|
|
|
'id' => '_virtual', |
61
|
|
|
'wrapper_class' => 'show_if_simple', |
62
|
|
|
'label' => __( 'Virtual', 'woocommerce' ), |
63
|
|
|
'description' => __( 'Virtual products are intangible and are not shipped.', 'woocommerce' ), |
64
|
|
|
'default' => 'no', |
65
|
|
|
), |
66
|
|
|
'downloadable' => array( |
67
|
|
|
'id' => '_downloadable', |
68
|
|
|
'wrapper_class' => 'show_if_simple', |
69
|
|
|
'label' => __( 'Downloadable', 'woocommerce' ), |
70
|
|
|
'description' => __( 'Downloadable products give access to a file upon purchase.', 'woocommerce' ), |
71
|
|
|
'default' => 'no', |
72
|
|
|
), |
73
|
|
|
) |
74
|
|
|
); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Return array of tabs to show. |
79
|
|
|
* |
80
|
|
|
* @return array |
81
|
|
|
*/ |
82
|
|
|
private static function get_product_data_tabs() { |
|
|
|
|
83
|
|
|
$tabs = apply_filters( |
84
|
|
|
'woocommerce_product_data_tabs', |
85
|
|
|
array( |
86
|
|
|
'general' => array( |
87
|
|
|
'label' => __( 'General', 'woocommerce' ), |
88
|
|
|
'target' => 'general_product_data', |
89
|
|
|
'class' => array( 'hide_if_grouped' ), |
90
|
|
|
'priority' => 10, |
91
|
|
|
), |
92
|
|
|
'inventory' => array( |
93
|
|
|
'label' => __( 'Inventory', 'woocommerce' ), |
94
|
|
|
'target' => 'inventory_product_data', |
95
|
|
|
'class' => array( 'show_if_simple', 'show_if_variable', 'show_if_grouped', 'show_if_external' ), |
96
|
|
|
'priority' => 20, |
97
|
|
|
), |
98
|
|
|
'shipping' => array( |
99
|
|
|
'label' => __( 'Shipping', 'woocommerce' ), |
100
|
|
|
'target' => 'shipping_product_data', |
101
|
|
|
'class' => array( 'hide_if_virtual', 'hide_if_grouped', 'hide_if_external' ), |
102
|
|
|
'priority' => 30, |
103
|
|
|
), |
104
|
|
|
'linked_product' => array( |
105
|
|
|
'label' => __( 'Linked Products', 'woocommerce' ), |
106
|
|
|
'target' => 'linked_product_data', |
107
|
|
|
'class' => array(), |
108
|
|
|
'priority' => 40, |
109
|
|
|
), |
110
|
|
|
'attribute' => array( |
111
|
|
|
'label' => __( 'Attributes', 'woocommerce' ), |
112
|
|
|
'target' => 'product_attributes', |
113
|
|
|
'class' => array(), |
114
|
|
|
'priority' => 50, |
115
|
|
|
), |
116
|
|
|
'variations' => array( |
117
|
|
|
'label' => __( 'Variations', 'woocommerce' ), |
118
|
|
|
'target' => 'variable_product_options', |
119
|
|
|
'class' => array( 'variations_tab', 'show_if_variable' ), |
120
|
|
|
'priority' => 60, |
121
|
|
|
), |
122
|
|
|
'advanced' => array( |
123
|
|
|
'label' => __( 'Advanced', 'woocommerce' ), |
124
|
|
|
'target' => 'advanced_product_data', |
125
|
|
|
'class' => array(), |
126
|
|
|
'priority' => 70, |
127
|
|
|
), |
128
|
|
|
) |
129
|
|
|
); |
130
|
|
|
|
131
|
|
|
// Sort tabs based on priority. |
132
|
|
|
uasort( $tabs, array( __CLASS__, 'product_data_tabs_sort' ) ); |
133
|
|
|
|
134
|
|
|
return $tabs; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Callback to sort product data tabs on priority. |
139
|
|
|
* |
140
|
|
|
* @since 3.1.0 |
141
|
|
|
* @param int $a First item. |
142
|
|
|
* @param int $b Second item. |
143
|
|
|
* |
144
|
|
|
* @return bool |
145
|
|
|
*/ |
146
|
|
|
private static function product_data_tabs_sort( $a, $b ) { |
147
|
|
|
if ( ! isset( $a['priority'], $b['priority'] ) ) { |
148
|
|
|
return -1; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
if ( $a['priority'] === $b['priority'] ) { |
152
|
|
|
return 0; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
return $a['priority'] < $b['priority'] ? -1 : 1; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Filter callback for finding variation attributes. |
160
|
|
|
* |
161
|
|
|
* @param WC_Product_Attribute $attribute Product attribute. |
162
|
|
|
* @return bool |
163
|
|
|
*/ |
164
|
|
|
private static function filter_variation_attributes( $attribute ) { |
165
|
|
|
return true === $attribute->get_variation(); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Show options for the variable product type. |
170
|
|
|
*/ |
171
|
|
|
public static function output_variations() { |
172
|
|
|
global $post, $wpdb, $product_object; |
173
|
|
|
|
174
|
|
|
$variation_attributes = array_filter( $product_object->get_attributes(), array( __CLASS__, 'filter_variation_attributes' ) ); |
175
|
|
|
$default_attributes = $product_object->get_default_attributes(); |
176
|
|
|
$variations_count = absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_count', $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(ID) FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'product_variation' AND post_status IN ('publish', 'private')", $post->ID ) ), $post->ID ) ); |
177
|
|
|
$variations_per_page = absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_per_page', 15 ) ); |
178
|
|
|
$variations_total_pages = ceil( $variations_count / $variations_per_page ); |
179
|
|
|
|
180
|
|
|
include 'views/html-product-data-variations.php'; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Prepare downloads for save. |
185
|
|
|
* |
186
|
|
|
* @param array $file_names File names. |
187
|
|
|
* @param array $file_urls File urls. |
188
|
|
|
* @param array $file_hashes File hashes. |
189
|
|
|
* |
190
|
|
|
* @return array |
191
|
|
|
*/ |
192
|
|
|
private static function prepare_downloads( $file_names, $file_urls, $file_hashes ) { |
193
|
|
|
$downloads = array(); |
194
|
|
|
|
195
|
|
|
if ( ! empty( $file_urls ) ) { |
196
|
|
|
$file_url_size = count( $file_urls ); |
197
|
|
|
|
198
|
|
|
for ( $i = 0; $i < $file_url_size; $i ++ ) { |
199
|
|
|
if ( ! empty( $file_urls[ $i ] ) ) { |
200
|
|
|
$downloads[] = array( |
201
|
|
|
'name' => wc_clean( $file_names[ $i ] ), |
202
|
|
|
'file' => wp_unslash( trim( $file_urls[ $i ] ) ), |
203
|
|
|
'download_id' => wc_clean( $file_hashes[ $i ] ), |
204
|
|
|
); |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
return $downloads; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Prepare children for save. |
213
|
|
|
* |
214
|
|
|
* @return array |
215
|
|
|
*/ |
216
|
|
|
private static function prepare_children() { |
217
|
|
|
return isset( $_POST['grouped_products'] ) ? array_filter( array_map( 'intval', (array) $_POST['grouped_products'] ) ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Prepare attributes for save. |
222
|
|
|
* |
223
|
|
|
* @param array $data Attribute data. |
224
|
|
|
* |
225
|
|
|
* @return array |
226
|
|
|
*/ |
227
|
|
|
public static function prepare_attributes( $data = false ) { |
228
|
|
|
$attributes = array(); |
229
|
|
|
|
230
|
|
|
if ( ! $data ) { |
231
|
|
|
$data = stripslashes_deep( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
if ( isset( $data['attribute_names'], $data['attribute_values'] ) ) { |
235
|
|
|
$attribute_names = $data['attribute_names']; |
236
|
|
|
$attribute_values = $data['attribute_values']; |
237
|
|
|
$attribute_visibility = isset( $data['attribute_visibility'] ) ? $data['attribute_visibility'] : array(); |
238
|
|
|
$attribute_variation = isset( $data['attribute_variation'] ) ? $data['attribute_variation'] : array(); |
239
|
|
|
$attribute_position = $data['attribute_position']; |
240
|
|
|
$attribute_names_max_key = max( array_keys( $attribute_names ) ); |
241
|
|
|
|
242
|
|
|
for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) { |
243
|
|
|
if ( empty( $attribute_names[ $i ] ) || ! isset( $attribute_values[ $i ] ) ) { |
244
|
|
|
continue; |
245
|
|
|
} |
246
|
|
|
$attribute_id = 0; |
247
|
|
|
$attribute_name = wc_clean( $attribute_names[ $i ] ); |
248
|
|
|
|
249
|
|
|
if ( 'pa_' === substr( $attribute_name, 0, 3 ) ) { |
250
|
|
|
$attribute_id = wc_attribute_taxonomy_id_by_name( $attribute_name ); |
|
|
|
|
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 List of attribute keys. |
285
|
|
|
* @param string $key_prefix Attribute key prefix. |
286
|
|
|
* @param int $index Attribute array index. |
287
|
|
|
* @return array |
288
|
|
|
*/ |
289
|
|
|
private static function prepare_set_attributes( $all_attributes, $key_prefix = 'attribute_', $index = null ) { |
290
|
|
|
$attributes = array(); |
291
|
|
|
|
292
|
|
|
if ( $all_attributes ) { |
|
|
|
|
293
|
|
|
foreach ( $all_attributes as $attribute ) { |
294
|
|
|
if ( $attribute->get_variation() ) { |
295
|
|
|
$attribute_key = sanitize_title( $attribute->get_name() ); |
296
|
|
|
|
297
|
|
|
if ( ! is_null( $index ) ) { |
298
|
|
|
$value = isset( $_POST[ $key_prefix . $attribute_key ][ $index ] ) ? wp_unslash( $_POST[ $key_prefix . $attribute_key ][ $index ] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
299
|
|
|
} else { |
300
|
|
|
$value = isset( $_POST[ $key_prefix . $attribute_key ] ) ? wp_unslash( $_POST[ $key_prefix . $attribute_key ] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
if ( $attribute->is_taxonomy() ) { |
304
|
|
|
// Don't use wc_clean as it destroys sanitized characters. |
305
|
|
|
$value = sanitize_title( $value ); |
306
|
|
|
} else { |
307
|
|
|
$value = html_entity_decode( wc_clean( $value ), ENT_QUOTES, get_bloginfo( 'charset' ) ); // WPCS: sanitization ok. |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
$attributes[ $attribute_key ] = $value; |
311
|
|
|
} |
312
|
|
|
} |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
return $attributes; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Save meta box data. |
320
|
|
|
* |
321
|
|
|
* @param int $post_id WP post id. |
322
|
|
|
* @param WP_Post $post Post object. |
323
|
|
|
*/ |
324
|
|
|
public static function save( $post_id, $post ) { |
|
|
|
|
325
|
|
|
// phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification |
326
|
|
|
// Process product type first so we have the correct class to run setters. |
327
|
|
|
$product_type = empty( $_POST['product-type'] ) ? WC_Product_Factory::get_product_type( $post_id ) : sanitize_title( wp_unslash( $_POST['product-type'] ) ); |
328
|
|
|
$classname = WC_Product_Factory::get_product_classname( $post_id, $product_type ? $product_type : 'simple' ); |
329
|
|
|
$product = new $classname( $post_id ); |
330
|
|
|
$attributes = self::prepare_attributes(); |
331
|
|
|
$stock = null; |
332
|
|
|
|
333
|
|
|
// Handle stock changes. |
334
|
|
|
if ( isset( $_POST['_stock'] ) ) { |
335
|
|
|
if ( isset( $_POST['_original_stock'] ) && wc_stock_amount( $product->get_stock_quantity( 'edit' ) ) !== wc_stock_amount( wp_unslash( $_POST['_original_stock'] ) ) ) { |
336
|
|
|
/* translators: 1: product ID 2: quantity in stock */ |
337
|
|
|
WC_Admin_Meta_Boxes::add_error( sprintf( __( 'The stock has not been updated because the value has changed since editing. Product %1$d has %2$d units in stock.', 'woocommerce' ), $product->get_id(), $product->get_stock_quantity( 'edit' ) ) ); |
338
|
|
|
} else { |
339
|
|
|
$stock = wc_stock_amount( wp_unslash( $_POST['_stock'] ) ); |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
// Handle dates. |
344
|
|
|
$date_on_sale_from = ''; |
345
|
|
|
$date_on_sale_to = ''; |
346
|
|
|
|
347
|
|
|
// Force date from to beginning of day. |
348
|
|
View Code Duplication |
if ( isset( $_POST['_sale_price_dates_from'] ) ) { |
349
|
|
|
$date_on_sale_from = wc_clean( wp_unslash( $_POST['_sale_price_dates_from'] ) ); |
350
|
|
|
|
351
|
|
|
if ( ! empty( $date_on_sale_from ) ) { |
352
|
|
|
$date_on_sale_from = date( 'Y-m-d 00:00:00', strtotime( $date_on_sale_from ) ); |
353
|
|
|
} |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
// Force date to to the end of the day. |
357
|
|
View Code Duplication |
if ( isset( $_POST['_sale_price_dates_to'] ) ) { |
358
|
|
|
$date_on_sale_to = wc_clean( wp_unslash( $_POST['_sale_price_dates_to'] ) ); |
359
|
|
|
|
360
|
|
|
if ( ! empty( $date_on_sale_to ) ) { |
361
|
|
|
$date_on_sale_to = date( 'Y-m-d 23:59:59', strtotime( $date_on_sale_to ) ); |
362
|
|
|
} |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
$errors = $product->set_props( |
366
|
|
|
array( |
367
|
|
|
'sku' => isset( $_POST['_sku'] ) ? wc_clean( wp_unslash( $_POST['_sku'] ) ) : null, |
368
|
|
|
'purchase_note' => isset( $_POST['_purchase_note'] ) ? wp_kses_post( wp_unslash( $_POST['_purchase_note'] ) ) : '', |
369
|
|
|
'downloadable' => isset( $_POST['_downloadable'] ), |
370
|
|
|
'virtual' => isset( $_POST['_virtual'] ), |
371
|
|
|
'featured' => isset( $_POST['_featured'] ), |
372
|
|
|
'catalog_visibility' => isset( $_POST['_visibility'] ) ? wc_clean( wp_unslash( $_POST['_visibility'] ) ) : null, |
373
|
|
|
'tax_status' => isset( $_POST['_tax_status'] ) ? wc_clean( wp_unslash( $_POST['_tax_status'] ) ) : null, |
374
|
|
|
'tax_class' => isset( $_POST['_tax_class'] ) ? wc_clean( wp_unslash( $_POST['_tax_class'] ) ) : null, |
375
|
|
|
'weight' => isset( $_POST['_weight'] ) ? wc_clean( wp_unslash( $_POST['_weight'] ) ) : null, |
376
|
|
|
'length' => isset( $_POST['_length'] ) ? wc_clean( wp_unslash( $_POST['_length'] ) ) : null, |
377
|
|
|
'width' => isset( $_POST['_width'] ) ? wc_clean( wp_unslash( $_POST['_width'] ) ) : null, |
378
|
|
|
'height' => isset( $_POST['_height'] ) ? wc_clean( wp_unslash( $_POST['_height'] ) ) : null, |
379
|
|
|
'shipping_class_id' => isset( $_POST['product_shipping_class'] ) ? absint( wp_unslash( $_POST['product_shipping_class'] ) ) : null, |
380
|
|
|
'sold_individually' => ! empty( $_POST['_sold_individually'] ), |
381
|
|
|
'upsell_ids' => isset( $_POST['upsell_ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['upsell_ids'] ) ) : array(), |
382
|
|
|
'cross_sell_ids' => isset( $_POST['crosssell_ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['crosssell_ids'] ) ) : array(), |
383
|
|
|
'regular_price' => isset( $_POST['_regular_price'] ) ? wc_clean( wp_unslash( $_POST['_regular_price'] ) ) : null, |
384
|
|
|
'sale_price' => isset( $_POST['_sale_price'] ) ? wc_clean( wp_unslash( $_POST['_sale_price'] ) ) : null, |
385
|
|
|
'date_on_sale_from' => $date_on_sale_from, |
386
|
|
|
'date_on_sale_to' => $date_on_sale_to, |
387
|
|
|
'manage_stock' => ! empty( $_POST['_manage_stock'] ), |
388
|
|
|
'backorders' => isset( $_POST['_backorders'] ) ? wc_clean( wp_unslash( $_POST['_backorders'] ) ) : null, |
389
|
|
|
'stock_status' => isset( $_POST['_stock_status'] ) ? wc_clean( wp_unslash( $_POST['_stock_status'] ) ) : null, |
390
|
|
|
'stock_quantity' => $stock, |
391
|
|
|
'low_stock_amount' => isset( $_POST['_low_stock_amount'] ) && '' !== $_POST['_low_stock_amount'] ? wc_stock_amount( wp_unslash( $_POST['_low_stock_amount'] ) ) : '', |
392
|
|
|
'download_limit' => isset( $_POST['_download_limit'] ) && '' !== $_POST['_download_limit'] ? absint( wp_unslash( $_POST['_download_limit'] ) ) : '', |
393
|
|
|
'download_expiry' => isset( $_POST['_download_expiry'] ) && '' !== $_POST['_download_expiry'] ? absint( wp_unslash( $_POST['_download_expiry'] ) ) : '', |
394
|
|
|
// Those are sanitized inside prepare_downloads. |
395
|
|
|
'downloads' => self::prepare_downloads( |
396
|
|
|
isset( $_POST['_wc_file_names'] ) ? wp_unslash( $_POST['_wc_file_names'] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
397
|
|
|
isset( $_POST['_wc_file_urls'] ) ? wp_unslash( $_POST['_wc_file_urls'] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
398
|
|
|
isset( $_POST['_wc_file_hashes'] ) ? wp_unslash( $_POST['_wc_file_hashes'] ) : array() // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
399
|
|
|
), |
400
|
|
|
'product_url' => isset( $_POST['_product_url'] ) ? esc_url_raw( wp_unslash( $_POST['_product_url'] ) ) : '', |
401
|
|
|
'button_text' => isset( $_POST['_button_text'] ) ? wc_clean( wp_unslash( $_POST['_button_text'] ) ) : '', |
402
|
|
|
'children' => 'grouped' === $product_type ? self::prepare_children() : null, |
403
|
|
|
'reviews_allowed' => ! empty( $_POST['comment_status'] ) && 'open' === $_POST['comment_status'], |
404
|
|
|
'attributes' => $attributes, |
405
|
|
|
'default_attributes' => self::prepare_set_attributes( $attributes, 'default_attribute_' ), |
406
|
|
|
) |
407
|
|
|
); |
408
|
|
|
|
409
|
|
|
if ( is_wp_error( $errors ) ) { |
410
|
|
|
WC_Admin_Meta_Boxes::add_error( $errors->get_error_message() ); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* Set props before save. |
415
|
|
|
* |
416
|
|
|
* @since 3.0.0 |
417
|
|
|
*/ |
418
|
|
|
do_action( 'woocommerce_admin_process_product_object', $product ); |
419
|
|
|
|
420
|
|
|
$product->save(); |
421
|
|
|
|
422
|
|
|
if ( $product->is_type( 'variable' ) ) { |
423
|
|
|
$original_post_title = isset( $_POST['original_post_title'] ) ? wc_clean( wp_unslash( $_POST['original_post_title'] ) ) : ''; |
424
|
|
|
$post_title = isset( $_POST['post_title'] ) ? wc_clean( wp_unslash( $_POST['post_title'] ) ) : ''; |
425
|
|
|
|
426
|
|
|
$product->get_data_store()->sync_variation_names( $product, $original_post_title, $post_title ); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
do_action( 'woocommerce_process_product_meta_' . $product_type, $post_id ); |
430
|
|
|
// phpcs:enable WordPress.Security.NonceVerification.NoNonceVerification |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* Save variation meta box data. |
435
|
|
|
* |
436
|
|
|
* @param int $post_id WP post id. |
437
|
|
|
* @param WP_Post $post Post object. |
438
|
|
|
*/ |
439
|
|
|
public static function save_variations( $post_id, $post ) { |
|
|
|
|
440
|
|
|
// phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification |
441
|
|
|
if ( isset( $_POST['variable_post_id'] ) ) { |
442
|
|
|
$parent = wc_get_product( $post_id ); |
443
|
|
|
$parent->set_default_attributes( self::prepare_set_attributes( $parent->get_attributes(), 'default_attribute_' ) ); |
444
|
|
|
$parent->save(); |
445
|
|
|
|
446
|
|
|
$max_loop = max( array_keys( wp_unslash( $_POST['variable_post_id'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
447
|
|
|
$data_store = $parent->get_data_store(); |
448
|
|
|
$data_store->sort_all_product_variations( $parent->get_id() ); |
449
|
|
|
|
450
|
|
|
for ( $i = 0; $i <= $max_loop; $i++ ) { |
451
|
|
|
|
452
|
|
|
if ( ! isset( $_POST['variable_post_id'][ $i ] ) ) { |
453
|
|
|
continue; |
454
|
|
|
} |
455
|
|
|
$variation_id = absint( $_POST['variable_post_id'][ $i ] ); |
456
|
|
|
$variation = new WC_Product_Variation( $variation_id ); |
457
|
|
|
$stock = null; |
458
|
|
|
|
459
|
|
|
// Handle stock changes. |
460
|
|
|
if ( isset( $_POST['variable_stock'], $_POST['variable_stock'][ $i ] ) ) { |
461
|
|
|
if ( isset( $_POST['variable_original_stock'], $_POST['variable_original_stock'][ $i ] ) && wc_stock_amount( $variation->get_stock_quantity( 'edit' ) ) !== wc_stock_amount( wp_unslash( $_POST['variable_original_stock'][ $i ] ) ) ) { |
462
|
|
|
/* translators: 1: product ID 2: quantity in stock */ |
463
|
|
|
WC_Admin_Meta_Boxes::add_error( sprintf( __( 'The stock has not been updated because the value has changed since editing. Product %1$d has %2$d units in stock.', 'woocommerce' ), $variation->get_id(), $variation->get_stock_quantity( 'edit' ) ) ); |
464
|
|
|
} else { |
465
|
|
|
$stock = wc_stock_amount( wp_unslash( $_POST['variable_stock'][ $i ] ) ); |
466
|
|
|
} |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
// Handle dates. |
470
|
|
|
$date_on_sale_from = ''; |
471
|
|
|
$date_on_sale_to = ''; |
472
|
|
|
|
473
|
|
|
// Force date from to beginning of day. |
474
|
|
View Code Duplication |
if ( isset( $_POST['variable_sale_price_dates_from'][ $i ] ) ) { |
475
|
|
|
$date_on_sale_from = wc_clean( wp_unslash( $_POST['variable_sale_price_dates_from'][ $i ] ) ); |
476
|
|
|
|
477
|
|
|
if ( ! empty( $date_on_sale_from ) ) { |
478
|
|
|
$date_on_sale_from = date( 'Y-m-d 00:00:00', strtotime( $date_on_sale_from ) ); |
479
|
|
|
} |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
// Force date to to the end of the day. |
483
|
|
View Code Duplication |
if ( isset( $_POST['variable_sale_price_dates_to'][ $i ] ) ) { |
484
|
|
|
$date_on_sale_to = wc_clean( wp_unslash( $_POST['variable_sale_price_dates_to'][ $i ] ) ); |
485
|
|
|
|
486
|
|
|
if ( ! empty( $date_on_sale_to ) ) { |
487
|
|
|
$date_on_sale_to = date( 'Y-m-d 23:59:59', strtotime( $date_on_sale_to ) ); |
488
|
|
|
} |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
$errors = $variation->set_props( |
492
|
|
|
array( |
493
|
|
|
'status' => isset( $_POST['variable_enabled'][ $i ] ) ? 'publish' : 'private', |
494
|
|
|
'menu_order' => isset( $_POST['variation_menu_order'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variation_menu_order'][ $i ] ) ) : null, |
495
|
|
|
'regular_price' => isset( $_POST['variable_regular_price'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_regular_price'][ $i ] ) ) : null, |
496
|
|
|
'sale_price' => isset( $_POST['variable_sale_price'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_sale_price'][ $i ] ) ) : null, |
497
|
|
|
'virtual' => isset( $_POST['variable_is_virtual'][ $i ] ), |
498
|
|
|
'downloadable' => isset( $_POST['variable_is_downloadable'][ $i ] ), |
499
|
|
|
'date_on_sale_from' => $date_on_sale_from, |
500
|
|
|
'date_on_sale_to' => $date_on_sale_to, |
501
|
|
|
'description' => isset( $_POST['variable_description'][ $i ] ) ? wp_kses_post( wp_unslash( $_POST['variable_description'][ $i ] ) ) : null, |
502
|
|
|
'download_limit' => isset( $_POST['variable_download_limit'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_download_limit'][ $i ] ) ) : null, |
503
|
|
|
'download_expiry' => isset( $_POST['variable_download_expiry'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_download_expiry'][ $i ] ) ) : null, |
504
|
|
|
// Those are sanitized inside prepare_downloads. |
505
|
|
|
'downloads' => self::prepare_downloads( |
506
|
|
|
isset( $_POST['_wc_variation_file_names'][ $variation_id ] ) ? wp_unslash( $_POST['_wc_variation_file_names'][ $variation_id ] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
507
|
|
|
isset( $_POST['_wc_variation_file_urls'][ $variation_id ] ) ? wp_unslash( $_POST['_wc_variation_file_urls'][ $variation_id ] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
508
|
|
|
isset( $_POST['_wc_variation_file_hashes'][ $variation_id ] ) ? wp_unslash( $_POST['_wc_variation_file_hashes'][ $variation_id ] ) : array() // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
509
|
|
|
), |
510
|
|
|
'manage_stock' => isset( $_POST['variable_manage_stock'][ $i ] ), |
511
|
|
|
'stock_quantity' => $stock, |
512
|
|
|
'backorders' => isset( $_POST['variable_backorders'], $_POST['variable_backorders'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_backorders'][ $i ] ) ) : null, |
513
|
|
|
'stock_status' => isset( $_POST['variable_stock_status'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_stock_status'][ $i ] ) ) : null, |
514
|
|
|
'image_id' => isset( $_POST['upload_image_id'][ $i ] ) ? wc_clean( wp_unslash( $_POST['upload_image_id'][ $i ] ) ) : null, |
515
|
|
|
'attributes' => self::prepare_set_attributes( $parent->get_attributes(), 'attribute_', $i ), |
516
|
|
|
'sku' => isset( $_POST['variable_sku'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_sku'][ $i ] ) ) : '', |
517
|
|
|
'weight' => isset( $_POST['variable_weight'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_weight'][ $i ] ) ) : '', |
518
|
|
|
'length' => isset( $_POST['variable_length'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_length'][ $i ] ) ) : '', |
519
|
|
|
'width' => isset( $_POST['variable_width'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_width'][ $i ] ) ) : '', |
520
|
|
|
'height' => isset( $_POST['variable_height'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_height'][ $i ] ) ) : '', |
521
|
|
|
'shipping_class_id' => isset( $_POST['variable_shipping_class'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_shipping_class'][ $i ] ) ) : null, |
522
|
|
|
'tax_class' => isset( $_POST['variable_tax_class'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_tax_class'][ $i ] ) ) : null, |
523
|
|
|
) |
524
|
|
|
); |
525
|
|
|
|
526
|
|
|
if ( is_wp_error( $errors ) ) { |
527
|
|
|
WC_Admin_Meta_Boxes::add_error( $errors->get_error_message() ); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
$variation->save(); |
531
|
|
|
|
532
|
|
|
do_action( 'woocommerce_save_product_variation', $variation_id, $i ); |
533
|
|
|
} |
534
|
|
|
} |
535
|
|
|
// phpcs:enable WordPress.Security.NonceVerification.NoNonceVerification |
536
|
|
|
} |
537
|
|
|
} |
538
|
|
|
|