Completed
Push — master ( d60d3d...21ad19 )
by Mike
11:28
created

WC_AJAX::add_order_item_meta()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
eloc 8
nc 3
nop 0
1
<?php
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit; // Exit if accessed directly
5
}
6
7
/**
8
 * WooCommerce WC_AJAX.
9
 *
10
 * AJAX Event Handler.
11
 *
12
 * @class    WC_AJAX
13
 * @version  2.4.0
14
 * @package  WooCommerce/Classes
15
 * @category Class
16
 * @author   WooThemes
17
 */
18
class WC_AJAX {
19
20
	/**
21
	 * Hook in ajax handlers.
22
	 */
23
	public static function init() {
24
		add_action( 'init', array( __CLASS__, 'define_ajax' ), 0 );
25
		add_action( 'template_redirect', array( __CLASS__, 'do_wc_ajax' ), 0 );
26
		self::add_ajax_events();
27
	}
28
29
	/**
30
	 * Get WC Ajax Endpoint.
31
	 * @param  string $request Optional
32
	 * @return string
33
	 */
34
	public static function get_endpoint( $request = '' ) {
35
		return esc_url_raw( apply_filters( 'woocommerce_ajax_get_endpoint', add_query_arg( 'wc-ajax', $request, remove_query_arg( array( 'remove_item', 'add-to-cart', 'added-to-cart' ) ) ), $request ) );
36
	}
37
38
	/**
39
	 * Set WC AJAX constant and headers.
40
	 */
41
	public static function define_ajax() {
42
		if ( ! empty( $_GET['wc-ajax'] ) ) {
43
			if ( ! defined( 'DOING_AJAX' ) ) {
44
				define( 'DOING_AJAX', true );
45
			}
46
			if ( ! defined( 'WC_DOING_AJAX' ) ) {
47
				define( 'WC_DOING_AJAX', true );
48
			}
49
			// Turn off display_errors during AJAX events to prevent malformed JSON
50
			if ( ! WP_DEBUG || ( WP_DEBUG && ! WP_DEBUG_DISPLAY ) ) {
51
				@ini_set( 'display_errors', 0 );
52
			}
53
			$GLOBALS['wpdb']->hide_errors();
54
		}
55
	}
56
57
	/**
58
	 * Send headers for WC Ajax Requests
59
	 * @since 2.5.0
60
	 */
61
	private static function wc_ajax_headers() {
62
		send_origin_headers();
63
		@header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
64
		@header( 'X-Robots-Tag: noindex' );
65
		send_nosniff_header();
66
		nocache_headers();
67
		status_header( 200 );
68
	}
69
70
	/**
71
	 * Check for WC Ajax request and fire action.
72
	 */
73
	public static function do_wc_ajax() {
74
		global $wp_query;
75
76
		if ( ! empty( $_GET['wc-ajax'] ) ) {
77
			$wp_query->set( 'wc-ajax', sanitize_text_field( $_GET['wc-ajax'] ) );
78
		}
79
80
		if ( $action = $wp_query->get( 'wc-ajax' ) ) {
81
			self::wc_ajax_headers();
82
			do_action( 'wc_ajax_' . sanitize_text_field( $action ) );
83
			die();
84
		}
85
	}
86
87
	/**
88
	 * Hook in methods - uses WordPress ajax handlers (admin-ajax).
89
	 */
90
	public static function add_ajax_events() {
91
		// woocommerce_EVENT => nopriv
92
		$ajax_events = array(
93
			'get_refreshed_fragments'                          => true,
94
			'apply_coupon'                                     => true,
95
			'remove_coupon'                                    => true,
96
			'update_shipping_method'                           => true,
97
			'get_cart_totals'                                  => true,
98
			'update_order_review'                              => true,
99
			'add_to_cart'                                      => true,
100
			'checkout'                                         => true,
101
			'get_variation'                                    => true,
102
			'feature_product'                                  => false,
103
			'mark_order_status'                                => false,
104
			'add_attribute'                                    => false,
105
			'add_new_attribute'                                => false,
106
			'remove_variation'                                 => false,
107
			'remove_variations'                                => false,
108
			'save_attributes'                                  => false,
109
			'add_variation'                                    => false,
110
			'link_all_variations'                              => false,
111
			'revoke_access_to_download'                        => false,
112
			'grant_access_to_download'                         => false,
113
			'get_customer_details'                             => false,
114
			'add_order_item'                                   => false,
115
			'add_order_fee'                                    => false,
116
			'add_order_shipping'                               => false,
117
			'add_order_tax'                                    => false,
118
			'remove_order_item'                                => false,
119
			'remove_order_tax'                                 => false,
120
			'reduce_order_item_stock'                          => false,
121
			'increase_order_item_stock'                        => false,
122
			'add_order_item_meta'                              => false,
123
			'remove_order_item_meta'                           => false,
124
			'calc_line_taxes'                                  => false,
125
			'save_order_items'                                 => false,
126
			'load_order_items'                                 => false,
127
			'add_order_note'                                   => false,
128
			'delete_order_note'                                => false,
129
			'json_search_products'                             => false,
130
			'json_search_products_and_variations'              => false,
131
			'json_search_grouped_products'                     => false,
132
			'json_search_downloadable_products_and_variations' => false,
133
			'json_search_customers'                            => false,
134
			'term_ordering'                                    => false,
135
			'product_ordering'                                 => false,
136
			'refund_line_items'                                => false,
137
			'delete_refund'                                    => false,
138
			'rated'                                            => false,
139
			'update_api_key'                                   => false,
140
			'get_customer_location'                            => true,
141
			'load_variations'                                  => false,
142
			'save_variations'                                  => false,
143
			'bulk_edit_variations'                             => false,
144
			'tax_rates_save_changes'                           => false,
145
			'shipping_zones_save_changes'                      => false,
146
			'shipping_zone_add_method'                         => false,
147
			'shipping_zone_methods_save_changes'               => false,
148
			'shipping_zone_methods_save_settings'              => false,
149
			'shipping_classes_save_changes'                    => false,
150
		);
151
152
		foreach ( $ajax_events as $ajax_event => $nopriv ) {
153
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
154
155
			if ( $nopriv ) {
156
				add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
157
158
				// WC AJAX can be used for frontend ajax requests
159
				add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
160
			}
161
		}
162
	}
163
164
	/**
165
	 * Get a refreshed cart fragment.
166
	 */
167
	public static function get_refreshed_fragments() {
168
169
		// Get mini cart
170
		ob_start();
171
172
		woocommerce_mini_cart();
173
174
		$mini_cart = ob_get_clean();
175
176
		// Fragments and mini cart are returned
177
		$data = array(
178
			'fragments' => apply_filters( 'woocommerce_add_to_cart_fragments', array(
179
					'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>'
180
				)
181
			),
182
			'cart_hash' => apply_filters( 'woocommerce_add_to_cart_hash', WC()->cart->get_cart_for_session() ? md5( json_encode( WC()->cart->get_cart_for_session() ) ) : '', WC()->cart->get_cart_for_session() )
183
		);
184
185
		wp_send_json( $data );
186
187
	}
188
189
	/**
190
	 * AJAX apply coupon on checkout page.
191
	 */
192
	public static function apply_coupon() {
193
194
		check_ajax_referer( 'apply-coupon', 'security' );
195
196
		if ( ! empty( $_POST['coupon_code'] ) ) {
197
			WC()->cart->add_discount( sanitize_text_field( $_POST['coupon_code'] ) );
198
		} else {
199
			wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
200
		}
201
202
		wc_print_notices();
203
204
		die();
205
	}
206
207
	/**
208
	 * AJAX remove coupon on cart and checkout page.
209
	 */
210
	public static function remove_coupon() {
211
212
		check_ajax_referer( 'remove-coupon', 'security' );
213
214
		$coupon = wc_clean( $_POST['coupon'] );
215
216
		if ( ! isset( $coupon ) || empty( $coupon ) ) {
217
			wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ), 'error' );
218
219
		} else {
220
221
			WC()->cart->remove_coupon( $coupon );
222
223
			wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
224
		}
225
226
		wc_print_notices();
227
228
		die();
229
	}
230
231
	/**
232
	 * AJAX update shipping method on cart page.
233
	 */
234
	public static function update_shipping_method() {
235
236
		check_ajax_referer( 'update-shipping-method', 'security' );
237
238
		if ( ! defined('WOOCOMMERCE_CART') ) {
239
			define( 'WOOCOMMERCE_CART', true );
240
		}
241
242
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
243
244 View Code Duplication
		if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) {
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...
245
			foreach ( $_POST['shipping_method'] as $i => $value ) {
246
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
247
			}
248
		}
249
250
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
251
252
		WC()->cart->calculate_totals();
253
254
		woocommerce_cart_totals();
255
256
		die();
257
	}
258
259
	/**
260
	 * AJAX receive updated cart_totals div.
261
	 */
262
	public static function get_cart_totals() {
263
264
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
265
			define( 'WOOCOMMERCE_CART', true );
266
		}
267
268
		WC()->cart->calculate_totals();
269
270
		woocommerce_cart_totals();
271
272
		die();
273
	}
274
275
	/**
276
	 * AJAX update order review on checkout.
277
	 */
278
	public static function update_order_review() {
279
		ob_start();
280
281
		check_ajax_referer( 'update-order-review', 'security' );
282
283
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
284
			define( 'WOOCOMMERCE_CHECKOUT', true );
285
		}
286
287
		if ( WC()->cart->is_empty() ) {
288
			$data = array(
289
				'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
290
					'form.woocommerce-checkout' => '<div class="woocommerce-error">' . __( 'Sorry, your session has expired.', 'woocommerce' ) . ' <a href="' . esc_url( wc_get_page_permalink( 'shop' ) ) . '" class="wc-backward">' . __( 'Return to shop', 'woocommerce' ) . '</a></div>'
291
				) )
292
			);
293
294
			wp_send_json( $data );
295
296
			die();
297
		}
298
299
		do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] );
300
301
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
302
303 View Code Duplication
		if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) {
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...
304
			foreach ( $_POST['shipping_method'] as $i => $value ) {
305
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
306
			}
307
		}
308
309
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
310
		WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
311
312
		if ( isset( $_POST['country'] ) ) {
313
			WC()->customer->set_billing_country( $_POST['country'] );
314
		}
315
316
		if ( isset( $_POST['state'] ) ) {
317
			WC()->customer->set_billing_state( $_POST['state'] );
318
		}
319
320
		if ( isset( $_POST['postcode'] ) ) {
321
			WC()->customer->set_billing_postcode( $_POST['postcode'] );
322
		}
323
324
		if ( isset( $_POST['city'] ) ) {
325
			WC()->customer->set_billing_city( $_POST['city'] );
326
		}
327
328
		if ( isset( $_POST['address'] ) ) {
329
			WC()->customer->set_billing_address( $_POST['address'] );
330
		}
331
332
		if ( isset( $_POST['address_2'] ) ) {
333
			WC()->customer->set_billing_address_2( $_POST['address_2'] );
334
		}
335
336
		if ( wc_ship_to_billing_address_only() ) {
337
338 View Code Duplication
			if ( ! empty( $_POST['country'] ) ) {
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...
339
				WC()->customer->set_shipping_country( $_POST['country'] );
340
				WC()->customer->set_calculated_shipping( true );
341
			}
342
343
			if ( isset( $_POST['state'] ) ) {
344
				WC()->customer->set_shipping_state( $_POST['state'] );
345
			}
346
347
			if ( isset( $_POST['postcode'] ) ) {
348
				WC()->customer->set_shipping_postcode( $_POST['postcode'] );
349
			}
350
351
			if ( isset( $_POST['city'] ) ) {
352
				WC()->customer->set_shipping_city( $_POST['city'] );
353
			}
354
355
			if ( isset( $_POST['address'] ) ) {
356
				WC()->customer->set_shipping_address( $_POST['address'] );
357
			}
358
359
			if ( isset( $_POST['address_2'] ) ) {
360
				WC()->customer->set_shipping_address_2( $_POST['address_2'] );
361
			}
362
		} else {
363
364 View Code Duplication
			if ( ! empty( $_POST['s_country'] ) ) {
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...
365
				WC()->customer->set_shipping_country( $_POST['s_country'] );
366
				WC()->customer->set_calculated_shipping( true );
367
			}
368
369
			if ( isset( $_POST['s_state'] ) ) {
370
				WC()->customer->set_shipping_state( $_POST['s_state'] );
371
			}
372
373
			if ( isset( $_POST['s_postcode'] ) ) {
374
				WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
375
			}
376
377
			if ( isset( $_POST['s_city'] ) ) {
378
				WC()->customer->set_shipping_city( $_POST['s_city'] );
379
			}
380
381
			if ( isset( $_POST['s_address'] ) ) {
382
				WC()->customer->set_shipping_address( $_POST['s_address'] );
383
			}
384
385
			if ( isset( $_POST['s_address_2'] ) ) {
386
				WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
387
			}
388
		}
389
390
		WC()->customer->save();
391
		WC()->cart->calculate_totals();
392
393
		// Get order review fragment
394
		ob_start();
395
		woocommerce_order_review();
396
		$woocommerce_order_review = ob_get_clean();
397
398
		// Get checkout payment fragment
399
		ob_start();
400
		woocommerce_checkout_payment();
401
		$woocommerce_checkout_payment = ob_get_clean();
402
403
		// Get messages if reload checkout is not true
404
		$messages = '';
405
		if ( ! isset( WC()->session->reload_checkout ) ) {
406
			ob_start();
407
			wc_print_notices();
408
			$messages = ob_get_clean();
409
		}
410
411
		$data = array(
412
			'result'    => empty( $messages ) ? 'success' : 'failure',
413
			'messages'  => $messages,
414
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
415
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
416
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
417
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
418
			) )
419
		);
420
421
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
422
423
		wp_send_json( $data );
424
425
		die();
426
	}
427
428
	/**
429
	 * AJAX add to cart.
430
	 */
431
	public static function add_to_cart() {
432
		ob_start();
433
434
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
435
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
436
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
437
		$product_status    = get_post_status( $product_id );
438
439
		if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) && 'publish' === $product_status ) {
440
441
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
442
443
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
444
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
445
			}
446
447
			// Return fragments
448
			self::get_refreshed_fragments();
449
450
		} else {
451
452
			// If there was an error adding to the cart, redirect to the product page to show any errors
453
			$data = array(
454
				'error'       => true,
455
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
456
			);
457
458
			wp_send_json( $data );
459
460
		}
461
462
		die();
463
	}
464
465
	/**
466
	 * Process ajax checkout form.
467
	 */
468
	public static function checkout() {
469
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
470
			define( 'WOOCOMMERCE_CHECKOUT', true );
471
		}
472
473
		WC()->checkout()->process_checkout();
474
475
		die(0);
476
	}
477
478
	/**
479
	 * Get a matching variation based on posted attributes.
480
	 */
481
	public static function get_variation() {
482
		ob_start();
483
484
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
485
			die();
486
		}
487
488
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
489
490
		if ( $variation_id ) {
491
			$variation = $variable_product->get_available_variation( $variation_id );
492
		} else {
493
			$variation = false;
494
		}
495
496
		wp_send_json( $variation );
497
498
		die();
499
	}
500
501
	/**
502
	 * Feature a product from admin.
503
	 */
504
	public static function feature_product() {
505
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
506
			$product_id = absint( $_GET['product_id'] );
507
508
			if ( 'product' === get_post_type( $product_id ) ) {
509
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
510
511
				delete_transient( 'wc_featured_products' );
512
			}
513
		}
514
515
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=product' ) );
516
		die();
517
	}
518
519
	/**
520
	 * Mark an order with a status.
521
	 */
522
	public static function mark_order_status() {
523
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
524
			$status   = sanitize_text_field( $_GET['status'] );
525
			$order_id = absint( $_GET['order_id'] );
526
527
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
528
				$order = wc_get_order( $order_id );
529
				$order->update_status( $status, '', true );
530
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
531
			}
532
		}
533
534
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
535
		die();
536
	}
537
538
	/**
539
	 * Add an attribute row.
540
	 */
541
	public static function add_attribute() {
542
		ob_start();
543
544
		check_ajax_referer( 'add-attribute', 'security' );
545
546
		if ( ! current_user_can( 'edit_products' ) ) {
547
			die(-1);
548
		}
549
550
		global $wc_product_attributes;
551
552
		$thepostid     = 0;
553
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
554
		$i             = absint( $_POST['i'] );
555
		$position      = 0;
556
		$metabox_class = array();
557
		$attribute     = array(
558
			'name'         => $taxonomy,
559
			'value'        => '',
560
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
561
			'is_variation' => apply_filters( 'woocommerce_attribute_default_is_variation', 0 ),
562
			'is_taxonomy'  => $taxonomy ? 1 : 0
563
		);
564
565
		if ( $taxonomy ) {
566
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
567
			$metabox_class[]    = 'taxonomy';
568
			$metabox_class[]    = $taxonomy;
569
			$attribute_label    = wc_attribute_label( $taxonomy );
570
		} else {
571
			$attribute_label = '';
572
		}
573
574
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
575
		die();
576
	}
577
578
	/**
579
	 * Add a new attribute via ajax function.
580
	 */
581
	public static function add_new_attribute() {
582
		ob_start();
583
584
		check_ajax_referer( 'add-attribute', 'security' );
585
586
		if ( ! current_user_can( 'manage_product_terms' ) ) {
587
			die(-1);
588
		}
589
590
		$taxonomy = esc_attr( $_POST['taxonomy'] );
591
		$term     = wc_clean( $_POST['term'] );
592
593
		if ( taxonomy_exists( $taxonomy ) ) {
594
595
			$result = wp_insert_term( $term, $taxonomy );
596
597
			if ( is_wp_error( $result ) ) {
598
				wp_send_json( array(
599
					'error' => $result->get_error_message()
600
				) );
601
			} else {
602
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
603
				wp_send_json( array(
604
					'term_id' => $term->term_id,
605
					'name'    => $term->name,
606
					'slug'    => $term->slug
607
				) );
608
			}
609
		}
610
611
		die();
612
	}
613
614
	/**
615
	 * Delete variations via ajax function.
616
	 */
617
	public static function remove_variations() {
618
		check_ajax_referer( 'delete-variations', 'security' );
619
620
		if ( ! current_user_can( 'edit_products' ) ) {
621
			die(-1);
622
		}
623
624
		$variation_ids = (array) $_POST['variation_ids'];
625
626
		foreach ( $variation_ids as $variation_id ) {
627
			$variation = get_post( $variation_id );
628
629
			if ( $variation && 'product_variation' == $variation->post_type ) {
630
				wp_delete_post( $variation_id );
631
			}
632
		}
633
634
		die();
635
	}
636
637
	/**
638
	 * Save attributes via ajax.
639
	 */
640
	public static function save_attributes() {
641
642
		check_ajax_referer( 'save-attributes', 'security' );
643
644
		if ( ! current_user_can( 'edit_products' ) ) {
645
			die(-1);
646
		}
647
648
		// Get post data
649
		parse_str( $_POST['data'], $data );
650
		$post_id = absint( $_POST['post_id'] );
651
652
		// Save Attributes
653
		$attributes = array();
654
655
		if ( isset( $data['attribute_names'] ) ) {
656
657
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
658
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
659
660
			if ( isset( $data['attribute_visibility'] ) ) {
661
				$attribute_visibility = $data['attribute_visibility'];
662
			}
663
664
			if ( isset( $data['attribute_variation'] ) ) {
665
				$attribute_variation = $data['attribute_variation'];
666
			}
667
668
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
669
			$attribute_position      = $data['attribute_position'];
670
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
671
672
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
673
				if ( empty( $attribute_names[ $i ] ) ) {
674
					continue;
675
				}
676
677
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
678
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
679
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
680
681
				if ( $is_taxonomy ) {
682
683
					if ( isset( $attribute_values[ $i ] ) ) {
684
685
						// Select based attributes - Format values (posted values are slugs)
686
						if ( is_array( $attribute_values[ $i ] ) ) {
687
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
688
689
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
690
						} else {
691
							$values     = array();
692
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
693
694
							foreach ( $raw_values as $value ) {
695
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
696
								if ( ! $term ) {
697
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
698
699
									if ( $term && ! is_wp_error( $term ) ) {
700
										$values[] = $term['term_id'];
701
									}
702
								} else {
703
									$values[] = $term->term_id;
704
								}
705
							}
706
						}
707
708
						// Remove empty items in the array
709
						$values = array_filter( $values, 'strlen' );
710
711
					} else {
712
						$values = array();
713
					}
714
715
					// Update post terms
716
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
717
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
718
					}
719
720 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...
721
						// Add attribute to array, but don't set values
722
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
723
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
724
							'value' 		=> '',
725
							'position' 		=> $attribute_position[ $i ],
726
							'is_visible' 	=> $is_visible,
727
							'is_variation' 	=> $is_variation,
728
							'is_taxonomy' 	=> $is_taxonomy
729
						);
730
					}
731
732 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...
733
734
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
735
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
736
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
737
738
					// Custom attribute - Add attribute to array and set the values
739
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
740
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
741
						'value' 		=> $values,
742
						'position' 		=> $attribute_position[ $i ],
743
						'is_visible' 	=> $is_visible,
744
						'is_variation' 	=> $is_variation,
745
						'is_taxonomy' 	=> $is_taxonomy
746
					);
747
				}
748
749
			 }
750
		}
751
752
		uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
753
754
		update_post_meta( $post_id, '_product_attributes', $attributes );
755
756
		die();
757
	}
758
759
	/**
760
	 * Add variation via ajax function.
761
	 */
762
	public static function add_variation() {
763
764
		check_ajax_referer( 'add-variation', 'security' );
765
766
		if ( ! current_user_can( 'edit_products' ) ) {
767
			die(-1);
768
		}
769
770
		global $post;
771
772
		$post_id = intval( $_POST['post_id'] );
773
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
774
		$loop    = intval( $_POST['loop'] );
775
776
		$variation = array(
777
			'post_title'   => 'Product #' . $post_id . ' Variation',
778
			'post_content' => '',
779
			'post_status'  => 'publish',
780
			'post_author'  => get_current_user_id(),
781
			'post_parent'  => $post_id,
782
			'post_type'    => 'product_variation',
783
			'menu_order'   => -1
784
		);
785
786
		$variation_id = wp_insert_post( $variation );
787
788
		do_action( 'woocommerce_create_product_variation', $variation_id );
789
790
		if ( $variation_id ) {
791
			$variation        = get_post( $variation_id );
792
			$variation_meta   = get_post_meta( $variation_id );
793
			$variation_data   = array();
794
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
795
			$variation_fields = array(
796
				'_sku'                   => '',
797
				'_stock'                 => '',
798
				'_regular_price'         => '',
799
				'_sale_price'            => '',
800
				'_weight'                => '',
801
				'_length'                => '',
802
				'_width'                 => '',
803
				'_height'                => '',
804
				'_download_limit'        => '',
805
				'_download_expiry'       => '',
806
				'_downloadable_files'    => '',
807
				'_downloadable'          => '',
808
				'_virtual'               => '',
809
				'_thumbnail_id'          => '',
810
				'_sale_price_dates_from' => '',
811
				'_sale_price_dates_to'   => '',
812
				'_manage_stock'          => '',
813
				'_stock_status'          => '',
814
				'_backorders'            => null,
815
				'_tax_class'             => null,
816
				'_variation_description' => ''
817
			);
818
819 View Code Duplication
			foreach ( $variation_fields as $field => $value ) {
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...
820
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
821
			}
822
823
			// Add the variation attributes
824
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
825
826
			// Formatting
827
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
828
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
829
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
830
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
831
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
832
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
833
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
834
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
835
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
836
			$variation_data['menu_order']     = $variation->menu_order;
837
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
838
839
			// Get tax classes
840
			$tax_classes           = WC_Tax::get_tax_classes();
841
			$tax_class_options     = array();
842
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
843
844 View Code Duplication
			if ( ! empty( $tax_classes ) ) {
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...
845
				foreach ( $tax_classes as $class ) {
846
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
847
				}
848
			}
849
850
			// Set backorder options
851
			$backorder_options = array(
852
				'no'     => __( 'Do not allow', 'woocommerce' ),
853
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
854
				'yes'    => __( 'Allow', 'woocommerce' )
855
			);
856
857
			// set stock status options
858
			$stock_status_options = array(
859
				'instock'    => __( 'In stock', 'woocommerce' ),
860
				'outofstock' => __( 'Out of stock', 'woocommerce' )
861
			);
862
863
			// Get attributes
864
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
865
866
			$parent_data = array(
867
				'id'                   => $post_id,
868
				'attributes'           => $attributes,
869
				'tax_class_options'    => $tax_class_options,
870
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
871
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
872
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
873
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
874
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
875
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
876
				'backorder_options'    => $backorder_options,
877
				'stock_status_options' => $stock_status_options
878
			);
879
880
			if ( ! $parent_data['weight'] ) {
881
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
882
			}
883
884
			if ( ! $parent_data['length'] ) {
885
				$parent_data['length'] = wc_format_localized_decimal( 0 );
886
			}
887
888
			if ( ! $parent_data['width'] ) {
889
				$parent_data['width'] = wc_format_localized_decimal( 0 );
890
			}
891
892
			if ( ! $parent_data['height'] ) {
893
				$parent_data['height'] = wc_format_localized_decimal( 0 );
894
			}
895
896
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
897
		}
898
899
		die();
900
	}
901
902
	/**
903
	 * Link all variations via ajax function.
904
	 */
905
	public static function link_all_variations() {
906
907
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
908
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
909
		}
910
911
		check_ajax_referer( 'link-variations', 'security' );
912
913
		if ( ! current_user_can( 'edit_products' ) ) {
914
			die(-1);
915
		}
916
917
		wc_set_time_limit( 0 );
918
919
		$post_id = intval( $_POST['post_id'] );
920
921
		if ( ! $post_id ) {
922
			die();
923
		}
924
925
		$variations = array();
926
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
927
928
		// Put variation attributes into an array
929
		foreach ( $_product->get_attributes() as $attribute ) {
930
931
			if ( ! $attribute['is_variation'] ) {
932
				continue;
933
			}
934
935
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
936
937
			if ( $attribute['is_taxonomy'] ) {
938
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
939
			} else {
940
				$options = explode( WC_DELIMITER, $attribute['value'] );
941
			}
942
943
			$options = array_map( 'trim', $options );
944
945
			$variations[ $attribute_field_name ] = $options;
946
		}
947
948
		// Quit out if none were found
949
		if ( sizeof( $variations ) == 0 ) {
950
			die();
951
		}
952
953
		// Get existing variations so we don't create duplicates
954
		$available_variations = array();
955
956
		foreach( $_product->get_children() as $child_id ) {
957
			$child = $_product->get_child( $child_id );
958
959
			if ( ! empty( $child->variation_id ) ) {
960
				$available_variations[] = $child->get_variation_attributes();
961
			}
962
		}
963
964
		// Created posts will all have the following data
965
		$variation_post_data = array(
966
			'post_title'   => 'Product #' . $post_id . ' Variation',
967
			'post_content' => '',
968
			'post_status'  => 'publish',
969
			'post_author'  => get_current_user_id(),
970
			'post_parent'  => $post_id,
971
			'post_type'    => 'product_variation'
972
		);
973
974
		$variation_ids       = array();
975
		$added               = 0;
976
		$possible_variations = wc_array_cartesian( $variations );
977
978
		foreach ( $possible_variations as $variation ) {
979
980
			// Check if variation already exists
981
			if ( in_array( $variation, $available_variations ) ) {
982
				continue;
983
			}
984
985
			$variation_id = wp_insert_post( $variation_post_data );
986
987
			$variation_ids[] = $variation_id;
988
989
			foreach ( $variation as $key => $value ) {
990
				update_post_meta( $variation_id, $key, $value );
991
			}
992
993
			// Save stock status
994
			update_post_meta( $variation_id, '_stock_status', 'instock' );
995
996
			$added++;
997
998
			do_action( 'product_variation_linked', $variation_id );
999
1000
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
1001
				break;
1002
			}
1003
		}
1004
1005
		delete_transient( 'wc_product_children_' . $post_id );
1006
1007
		echo $added;
1008
1009
		die();
1010
	}
1011
1012
	/**
1013
	 * Delete download permissions via ajax function.
1014
	 */
1015
	public static function revoke_access_to_download() {
1016
		check_ajax_referer( 'revoke-access', 'security' );
1017
1018
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1019
			die( -1 );
1020
		}
1021
1022
		global $wpdb;
1023
1024
		$download_id   = $_POST['download_id'];
1025
		$product_id    = intval( $_POST['product_id'] );
1026
		$order_id      = intval( $_POST['order_id'] );
1027
		$permission_id = absint( $_POST['permission_id'] );
1028
1029
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d;", $permission_id ) );
1030
1031
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id, $permission_id );
1032
1033
		die();
1034
	}
1035
1036
	/**
1037
	 * Grant download permissions via ajax function.
1038
	 */
1039
	public static function grant_access_to_download() {
1040
1041
		check_ajax_referer( 'grant-access', 'security' );
1042
1043
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1044
			die(-1);
1045
		}
1046
1047
		global $wpdb;
1048
1049
		$wpdb->hide_errors();
1050
1051
		$order_id     = intval( $_POST['order_id'] );
1052
		$product_ids  = $_POST['product_ids'];
1053
		$loop         = intval( $_POST['loop'] );
1054
		$file_counter = 0;
1055
		$order        = wc_get_order( $order_id );
1056
1057
		if ( ! is_array( $product_ids ) ) {
1058
			$product_ids = array( $product_ids );
1059
		}
1060
1061
		foreach ( $product_ids as $product_id ) {
1062
			$product = wc_get_product( $product_id );
1063
			$files   = $product->get_files();
1064
1065
			if ( ! $order->get_billing_email() ) {
1066
				die();
1067
			}
1068
1069
			if ( ! empty( $files ) ) {
1070
				foreach ( $files as $download_id => $file ) {
1071
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
0 ignored issues
show
Documentation introduced by
$order is of type false|object, but the function expects a object<WC_Order>.

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...
1072
1073
						// insert complete - get inserted data
1074
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1075
1076
						$loop ++;
1077
						$file_counter ++;
1078
1079 View Code Duplication
						if ( isset( $file['name'] ) ) {
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...
1080
							$file_count = $file['name'];
1081
						} else {
1082
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1083
						}
1084
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1085
					}
1086
				}
1087
			}
1088
		}
1089
1090
		die();
1091
	}
1092
1093
	/**
1094
	 * Get customer details via ajax.
1095
	 */
1096
	public static function get_customer_details() {
1097
		ob_start();
1098
1099
		check_ajax_referer( 'get-customer-details', 'security' );
1100
1101
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1102
			die(-1);
1103
		}
1104
1105
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1106
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1107
1108
		$customer_data = array(
1109
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1110
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1111
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1112
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1113
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1114
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1115
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1116
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1117
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1118
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1119
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1120
		);
1121
1122
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1123
1124
		wp_send_json( $customer_data );
1125
	}
1126
1127
	/**
1128
	 * Add order item via ajax.
1129
	 */
1130
	public static function add_order_item() {
1131
		check_ajax_referer( 'order-item', 'security' );
1132
1133
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1134
			die(-1);
1135
		}
1136
1137
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1138
		$order_id    = absint( $_POST['order_id'] );
1139
1140
		// Find the item
1141
		if ( ! is_numeric( $item_to_add ) ) {
1142
			die();
1143
		}
1144
1145
		$post = get_post( $item_to_add );
1146
1147
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1148
			die();
1149
		}
1150
1151
		$product     = wc_get_product( $post->ID );
1152
		$order       = wc_get_order( $order_id );
1153
		$order_taxes = $order->get_taxes();
1154
		$class       = 'new_row';
1155
		$item_id     = $order->add_product( $product );
1156
		$item        = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id );
1157
1158
		do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1159
1160
		include( 'admin/meta-boxes/views/html-order-item.php' );
1161
		die();
1162
	}
1163
1164
	/**
1165
	 * Add order fee via ajax.
1166
	 */
1167
	public static function add_order_fee() {
1168
1169
		check_ajax_referer( 'order-item', 'security' );
1170
1171
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1172
			die(-1);
1173
		}
1174
1175
		$order_id      = absint( $_POST['order_id'] );
1176
		$order         = wc_get_order( $order_id );
1177
		$order_taxes   = $order->get_taxes();
1178
		$item          = array();
1179
1180
		// Add new fee
1181
		$fee            = new stdClass();
1182
		$fee->name      = '';
1183
		$fee->tax_class = '';
1184
		$fee->taxable   = $fee->tax_class !== '0';
1185
		$fee->amount    = '';
1186
		$fee->tax       = '';
1187
		$fee->tax_data  = array();
1188
		$item_id        = $order->add_fee( $fee );
1189
1190
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1191
1192
		// Quit out
1193
		die();
1194
	}
1195
1196
	/**
1197
	 * Add order shipping cost via ajax.
1198
	 */
1199
	public static function add_order_shipping() {
1200
1201
		check_ajax_referer( 'order-item', 'security' );
1202
1203
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1204
			die(-1);
1205
		}
1206
1207
		$order_id         = absint( $_POST['order_id'] );
1208
		$order            = wc_get_order( $order_id );
1209
		$order_taxes      = $order->get_taxes();
1210
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1211
1212
		// Add new shipping
1213
		$shipping = new WC_Shipping_Rate();
1214
		$item_id  = $order->add_shipping( $shipping );
1215
		$item     = $order->get_item( $item_id );
1216
1217
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1218
1219
		// Quit out
1220
		die();
1221
	}
1222
1223
	/**
1224
	 * Add order tax column via ajax.
1225
	 */
1226
	public static function add_order_tax() {
1227
		global $wpdb;
1228
1229
		check_ajax_referer( 'order-item', 'security' );
1230
1231
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1232
			die(-1);
1233
		}
1234
1235
		$order_id = absint( $_POST['order_id'] );
1236
		$rate_id  = absint( $_POST['rate_id'] );
1237
		$order    = wc_get_order( $order_id );
1238
		$data     = get_post_meta( $order_id );
1239
1240
		// Add new tax
1241
		$order->add_tax( $rate_id, 0, 0 );
1242
1243
		// Return HTML items
1244
		include( 'admin/meta-boxes/views/html-order-items.php' );
1245
1246
		die();
1247
	}
1248
1249
	/**
1250
	 * Remove an order item.
1251
	 */
1252
	public static function remove_order_item() {
1253
		check_ajax_referer( 'order-item', 'security' );
1254
1255
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1256
			die(-1);
1257
		}
1258
1259
		$order_item_ids = $_POST['order_item_ids'];
1260
1261
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1262
			$order_item_ids = array( $order_item_ids );
1263
		}
1264
1265
		if ( sizeof( $order_item_ids ) > 0 ) {
1266
			foreach( $order_item_ids as $id ) {
1267
				wc_delete_order_item( absint( $id ) );
1268
			}
1269
		}
1270
1271
		die();
1272
	}
1273
1274
	/**
1275
	 * Remove an order tax.
1276
	 */
1277
	public static function remove_order_tax() {
1278
1279
		check_ajax_referer( 'order-item', 'security' );
1280
1281
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1282
			die(-1);
1283
		}
1284
1285
		$order_id = absint( $_POST['order_id'] );
1286
		$rate_id  = absint( $_POST['rate_id'] );
1287
1288
		wc_delete_order_item( $rate_id );
1289
1290
		// Return HTML items
1291
		$order = wc_get_order( $order_id );
1292
		$data  = get_post_meta( $order_id );
1293
		include( 'admin/meta-boxes/views/html-order-items.php' );
1294
1295
		die();
1296
	}
1297
1298
	/**
1299
	 * Reduce order item stock.
1300
	 */
1301
	public static function reduce_order_item_stock() {
1302
		check_ajax_referer( 'order-item', 'security' );
1303
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1304
			die(-1);
1305
		}
1306
		$order_id       = absint( $_POST['order_id'] );
1307
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1308
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1309
		$order          = wc_get_order( $order_id );
1310
		$order_items    = $order->get_items();
1311
		$return         = array();
1312
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1313
			foreach ( $order_items as $item_id => $order_item ) {
1314
				// Only reduce checked items
1315
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1316
					continue;
1317
				}
1318
				$_product = $order->get_product_from_item( $order_item );
1319
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1320
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1321
					$new_stock    = $_product->reduce_stock( $stock_change );
1322
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $_product->id;
1323
1324 View Code Duplication
					if ( ! empty( $_product->variation_id ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1325
						$note = sprintf( __( 'Item %s variation #%s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $_product->variation_id, $new_stock + $stock_change, $new_stock );
1326
					} else {
1327
						$note = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1328
					}
1329
1330
					$return[]     = $note;
1331
					$order->add_order_note( $note );
1332
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1333
				}
1334
			}
1335
			do_action( 'woocommerce_reduce_order_stock', $order );
1336
			if ( empty( $return ) ) {
1337
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1338
			}
1339
			echo implode( ', ', $return );
1340
		}
1341
		die();
1342
	}
1343
1344
	/**
1345
	 * Increase order item stock.
1346
	 */
1347
	public static function increase_order_item_stock() {
1348
		check_ajax_referer( 'order-item', 'security' );
1349
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1350
			die(-1);
1351
		}
1352
		$order_id       = absint( $_POST['order_id'] );
1353
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1354
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1355
		$order          = wc_get_order( $order_id );
1356
		$order_items    = $order->get_items();
1357
		$return         = array();
1358
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1359
			foreach ( $order_items as $item_id => $order_item ) {
1360
				// Only reduce checked items
1361
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1362
					continue;
1363
				}
1364
				$_product = $order->get_product_from_item( $order_item );
1365
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1366
					$old_stock    = $_product->get_stock_quantity();
1367
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1368
					$new_quantity = $_product->increase_stock( $stock_change );
1369
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $_product->id;
1370
1371 View Code Duplication
					if ( ! empty( $_product->variation_id ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1372
						$note = sprintf( __( 'Item %s variation #%s stock increased from %s to %s.', 'woocommerce' ), $item_name, $_product->variation_id, $old_stock, $new_quantity );
1373
					} else {
1374
						$note = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1375
					}
1376
1377
					$return[]     = $note;
1378
					$order->add_order_note( $note );
1379
				}
1380
			}
1381
			do_action( 'woocommerce_restore_order_stock', $order );
1382
			if ( empty( $return ) ) {
1383
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1384
			}
1385
			echo implode( ', ', $return );
1386
		}
1387
		die();
1388
	}
1389
1390
	/**
1391
	 * Calc line tax.
1392
	 */
1393
	public static function calc_line_taxes() {
1394
		global $wpdb;
1395
1396
		check_ajax_referer( 'calc-totals', 'security' );
1397
1398
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1399
			die(-1);
1400
		}
1401
1402
		$tax                    = new WC_Tax();
1403
		$tax_based_on           = get_option( 'woocommerce_tax_based_on' );
1404
		$order_id               = absint( $_POST['order_id'] );
1405
		$items                  = array();
1406
		$country                = strtoupper( esc_attr( $_POST['country'] ) );
1407
		$state                  = strtoupper( esc_attr( $_POST['state'] ) );
1408
		$postcode               = strtoupper( esc_attr( $_POST['postcode'] ) );
1409
		$city                   = wc_clean( esc_attr( $_POST['city'] ) );
1410
		$order                  = wc_get_order( $order_id );
1411
		$taxes                  = array();
1412
		$shipping_taxes         = array();
1413
		$order_item_tax_classes = array();
1414
1415
		// Default to base
1416
		if ( 'base' === $tax_based_on || empty( $country ) ) {
1417
			$default  = wc_get_base_location();
1418
			$country  = $default['country'];
1419
			$state    = $default['state'];
1420
			$postcode = '';
1421
			$city     = '';
1422
		}
1423
1424
		// Parse the jQuery serialized items
1425
		parse_str( $_POST['items'], $items );
1426
1427
		// Prevent undefined warnings
1428
		if ( ! isset( $items['line_tax'] ) ) {
1429
			$items['line_tax'] = array();
1430
		}
1431
		if ( ! isset( $items['line_subtotal_tax'] ) ) {
1432
			$items['line_subtotal_tax'] = array();
1433
		}
1434
		$items['order_taxes'] = array();
1435
1436
		// Action
1437
		$items = apply_filters( 'woocommerce_ajax_calc_line_taxes', $items, $order_id, $country, $_POST );
1438
1439
		$is_vat_exempt = get_post_meta( $order_id, '_is_vat_exempt', true );
1440
1441
		// Tax is calculated only if tax is enabled and order is not vat exempted
1442
		if ( wc_tax_enabled() && $is_vat_exempt !== 'yes' ) {
1443
1444
			// Get items and fees taxes
1445
			if ( isset( $items['order_item_id'] ) ) {
1446
				$line_total = $line_subtotal = array();
1447
1448
				foreach ( $items['order_item_id'] as $item_id ) {
1449
					$item_id                            = absint( $item_id );
1450
					$line_total[ $item_id ]             = isset( $items['line_total'][ $item_id ] ) ? wc_format_decimal( $items['line_total'][ $item_id ] ) : 0;
1451
					$line_subtotal[ $item_id ]          = isset( $items['line_subtotal'][ $item_id ] ) ? wc_format_decimal( $items['line_subtotal'][ $item_id ] ) : $line_total[ $item_id ];
1452
					$order_item_tax_classes[ $item_id ] = isset( $items['order_item_tax_class'][ $item_id ] ) ? sanitize_text_field( $items['order_item_tax_class'][ $item_id ] ) : '';
1453
					$product_id                         = $order->get_item_meta( $item_id, '_product_id', true );
1454
1455
					// Get product details
1456
					if ( get_post_type( $product_id ) == 'product' ) {
1457
						$_product        = wc_get_product( $product_id );
1458
						$item_tax_status = $_product->get_tax_status();
1459
					} else {
1460
						$item_tax_status = 'taxable';
1461
					}
1462
1463
					if ( '0' !== $order_item_tax_classes[ $item_id ] && 'taxable' === $item_tax_status ) {
1464
						$tax_rates = WC_Tax::find_rates( array(
1465
							'country'   => $country,
1466
							'state'     => $state,
1467
							'postcode'  => $postcode,
1468
							'city'      => $city,
1469
							'tax_class' => $order_item_tax_classes[ $item_id ]
1470
						) );
1471
1472
						$line_taxes          = WC_Tax::calc_tax( $line_total[ $item_id ], $tax_rates, false );
1473
						$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal[ $item_id ], $tax_rates, false );
1474
1475
						// Set the new line_tax
1476
						foreach ( $line_taxes as $_tax_id => $_tax_value ) {
1477
							$items['line_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1478
						}
1479
1480
						// Set the new line_subtotal_tax
1481
						foreach ( $line_subtotal_taxes as $_tax_id => $_tax_value ) {
1482
							$items['line_subtotal_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1483
						}
1484
1485
						// Sum the item taxes
1486
						foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1487
							$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1488
						}
1489
					}
1490
				}
1491
			}
1492
1493
			// Get shipping taxes
1494
			if ( isset( $items['shipping_method_id'] ) ) {
1495
				$matched_tax_rates      = array();
1496
				$order_item_tax_classes = array_unique( array_values( $order_item_tax_classes ) );
1497
1498
				// If multiple classes are found, use the first one. Don't bother with standard rate, we can get that later.
1499 View Code Duplication
				if ( sizeof( $order_item_tax_classes ) > 1 && ! in_array( '', $order_item_tax_classes ) ) {
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...
1500
					$tax_classes = WC_Tax::get_tax_classes();
1501
1502
					foreach ( $tax_classes as $tax_class ) {
1503
						$tax_class = sanitize_title( $tax_class );
1504
						if ( in_array( $tax_class, $order_item_tax_classes ) ) {
1505
							$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1506
								'country' 	=> $country,
1507
								'state' 	=> $state,
1508
								'postcode' 	=> $postcode,
1509
								'city' 		=> $city,
1510
								'tax_class' => $tax_class,
1511
							) );
1512
							break;
1513
						}
1514
					}
1515
				// If a single tax class is found, use it
1516
				} elseif ( sizeof( $order_item_tax_classes ) === 1 ) {
1517
					$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1518
						'country' 	=> $country,
1519
						'state' 	=> $state,
1520
						'postcode' 	=> $postcode,
1521
						'city' 		=> $city,
1522
						'tax_class' => $order_item_tax_classes[0]
1523
					) );
1524
				}
1525
1526
				// Get standard rate if no taxes were found
1527
				if ( ! sizeof( $matched_tax_rates ) ) {
1528
					$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1529
						'country' 	=> $country,
1530
						'state' 	=> $state,
1531
						'postcode' 	=> $postcode,
1532
						'city' 		=> $city
1533
					) );
1534
				}
1535
1536
				$shipping_cost = $shipping_taxes = array();
1537
1538
				foreach ( $items['shipping_method_id'] as $item_id ) {
1539
					$item_id                   = absint( $item_id );
1540
					$shipping_cost[ $item_id ] = isset( $items['shipping_cost'][ $item_id ] ) ? wc_format_decimal( $items['shipping_cost'][ $item_id ] ) : 0;
1541
					$_shipping_taxes           = WC_Tax::calc_shipping_tax( $shipping_cost[ $item_id ], $matched_tax_rates );
1542
1543
					// Set the new shipping_taxes
1544
					foreach ( $_shipping_taxes as $_tax_id => $_tax_value ) {
1545
						$items['shipping_taxes'][ $item_id ][ $_tax_id ] = $_tax_value;
1546
1547
						$shipping_taxes[ $_tax_id ] = isset( $shipping_taxes[ $_tax_id ] ) ? $shipping_taxes[ $_tax_id ] + $_tax_value : $_tax_value;
1548
					}
1549
				}
1550
			}
1551
		}
1552
1553
		// Remove old tax rows
1554
		$order->remove_order_items( 'tax' );
1555
1556
		// Add tax rows
1557
		foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_id ) {
1558
			$order->add_tax( $tax_rate_id, isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0, isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
1559
		}
1560
1561
		// Create the new order_taxes
1562
		foreach ( $order->get_taxes() as $tax_id => $tax_item ) {
1563
			$items['order_taxes'][ $tax_id ] = absint( $tax_item['rate_id'] );
1564
		}
1565
1566
		$items = apply_filters( 'woocommerce_ajax_after_calc_line_taxes', $items, $order_id, $country, $_POST );
1567
1568
		// Save order items
1569
		wc_save_order_items( $order_id, $items );
1570
1571
		// Return HTML items
1572
		$order = wc_get_order( $order_id );
1573
		$data  = get_post_meta( $order_id );
1574
		include( 'admin/meta-boxes/views/html-order-items.php' );
1575
1576
		die();
1577
	}
1578
1579
	/**
1580
	 * Save order items via ajax.
1581
	 */
1582
	public static function save_order_items() {
1583
		check_ajax_referer( 'order-item', 'security' );
1584
1585
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1586
			die(-1);
1587
		}
1588
1589
		if ( isset( $_POST['order_id'] ) && isset( $_POST['items'] ) ) {
1590
			$order_id = absint( $_POST['order_id'] );
1591
1592
			// Parse the jQuery serialized items
1593
			$items = array();
1594
			parse_str( $_POST['items'], $items );
1595
1596
			// Save order items
1597
			wc_save_order_items( $order_id, $items );
0 ignored issues
show
Bug introduced by
It seems like $items can also be of type null; however, wc_save_order_items() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1598
1599
			// Return HTML items
1600
			$order = wc_get_order( $order_id );
1601
			$data  = get_post_meta( $order_id );
1602
			include( 'admin/meta-boxes/views/html-order-items.php' );
1603
		}
1604
1605
		die();
1606
	}
1607
1608
	/**
1609
	 * Load order items via ajax.
1610
	 */
1611
	public static function load_order_items() {
1612
		check_ajax_referer( 'order-item', 'security' );
1613
1614
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1615
			die(-1);
1616
		}
1617
1618
		// Return HTML items
1619
		$order_id = absint( $_POST['order_id'] );
1620
		$order    = wc_get_order( $order_id );
1621
		$data     = get_post_meta( $order_id );
1622
		include( 'admin/meta-boxes/views/html-order-items.php' );
1623
1624
		die();
1625
	}
1626
1627
	/**
1628
	 * Add order note via ajax.
1629
	 */
1630
	public static function add_order_note() {
1631
1632
		check_ajax_referer( 'add-order-note', 'security' );
1633
1634
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1635
			die(-1);
1636
		}
1637
1638
		$post_id   = absint( $_POST['post_id'] );
1639
		$note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
1640
		$note_type = $_POST['note_type'];
1641
1642
		$is_customer_note = $note_type == 'customer' ? 1 : 0;
1643
1644
		if ( $post_id > 0 ) {
1645
			$order      = wc_get_order( $post_id );
1646
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1647
1648
			echo '<li rel="' . esc_attr( $comment_id ) . '" class="note ';
1649
			if ( $is_customer_note ) {
1650
				echo 'customer-note';
1651
			}
1652
			echo '"><div class="note_content">';
1653
			echo wpautop( wptexturize( $note ) );
1654
			echo '</div><p class="meta"><a href="#" class="delete_note">'.__( 'Delete note', 'woocommerce' ).'</a></p>';
1655
			echo '</li>';
1656
		}
1657
1658
		// Quit out
1659
		die();
1660
	}
1661
1662
	/**
1663
	 * Delete order note via ajax.
1664
	 */
1665
	public static function delete_order_note() {
1666
1667
		check_ajax_referer( 'delete-order-note', 'security' );
1668
1669
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1670
			die(-1);
1671
		}
1672
1673
		$note_id = (int) $_POST['note_id'];
1674
1675
		if ( $note_id > 0 ) {
1676
			wp_delete_comment( $note_id );
1677
		}
1678
1679
		// Quit out
1680
		die();
1681
	}
1682
1683
	/**
1684
	 * Search for products and echo json.
1685
	 *
1686
	 * @param string $term (default: '')
1687
	 * @param string $post_types (default: array('product'))
1688
	 */
1689
	public static function json_search_products( $term = '', $post_types = array( 'product' ) ) {
1690
		global $wpdb;
1691
1692
		ob_start();
1693
1694
		check_ajax_referer( 'search-products', 'security' );
1695
1696
		if ( empty( $term ) ) {
1697
			$term = wc_clean( stripslashes( $_GET['term'] ) );
1698
		} else {
1699
			$term = wc_clean( $term );
1700
		}
1701
1702
		if ( empty( $term ) ) {
1703
			die();
1704
		}
1705
1706
		$like_term = '%' . $wpdb->esc_like( $term ) . '%';
1707
1708
		if ( is_numeric( $term ) ) {
1709
			$query = $wpdb->prepare( "
1710
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1711
				WHERE posts.post_status = 'publish'
1712
				AND (
1713
					posts.post_parent = %s
1714
					OR posts.ID = %s
1715
					OR posts.post_title LIKE %s
1716
					OR (
1717
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1718
					)
1719
				)
1720
			", $term, $term, $term, $like_term );
1721
		} else {
1722
			$query = $wpdb->prepare( "
1723
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1724
				WHERE posts.post_status = 'publish'
1725
				AND (
1726
					posts.post_title LIKE %s
1727
					or posts.post_content LIKE %s
1728
					OR (
1729
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1730
					)
1731
				)
1732
			", $like_term, $like_term, $like_term );
1733
		}
1734
1735
		$query .= " AND posts.post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "')";
1736
1737 View Code Duplication
		if ( ! empty( $_GET['exclude'] ) ) {
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...
1738
			$query .= " AND posts.ID NOT IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['exclude'] ) ) ) . ")";
1739
		}
1740
1741 View Code Duplication
		if ( ! empty( $_GET['include'] ) ) {
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...
1742
			$query .= " AND posts.ID IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['include'] ) ) ) . ")";
1743
		}
1744
1745
		if ( ! empty( $_GET['limit'] ) ) {
1746
			$query .= " LIMIT " . intval( $_GET['limit'] );
1747
		}
1748
1749
		$posts          = array_unique( $wpdb->get_col( $query ) );
1750
		$found_products = array();
1751
1752
		if ( ! empty( $posts ) ) {
1753
			foreach ( $posts as $post ) {
1754
				$product = wc_get_product( $post );
1755
1756
				if ( ! current_user_can( 'read_product', $post ) ) {
1757
					continue;
1758
				}
1759
1760
				if ( ! $product || ( $product->is_type( 'variation' ) && empty( $product->parent ) ) ) {
1761
					continue;
1762
				}
1763
1764
				$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1765
			}
1766
		}
1767
1768
		$found_products = apply_filters( 'woocommerce_json_search_found_products', $found_products );
1769
1770
		wp_send_json( $found_products );
1771
	}
1772
1773
	/**
1774
	 * Search for product variations and return json.
1775
	 *
1776
	 * @see WC_AJAX::json_search_products()
1777
	 */
1778
	public static function json_search_products_and_variations() {
1779
		self::json_search_products( '', array( 'product', 'product_variation' ) );
1780
	}
1781
1782
	/**
1783
	 * Search for grouped products and return json.
1784
	 */
1785
	public static function json_search_grouped_products() {
1786
		ob_start();
1787
1788
		check_ajax_referer( 'search-products', 'security' );
1789
1790
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1791
		$exclude = array();
1792
1793
		if ( empty( $term ) ) {
1794
			die();
1795
		}
1796
1797 View Code Duplication
		if ( ! empty( $_GET['exclude'] ) ) {
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...
1798
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1799
		}
1800
1801
		$found_products = array();
1802
1803
		if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
1804
1805
			$posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
1806
1807
			if ( sizeof( $posts_in ) > 0 ) {
1808
1809
				$args = array(
1810
					'post_type'        => 'product',
1811
					'post_status'      => 'any',
1812
					'numberposts'      => -1,
1813
					'orderby'          => 'title',
1814
					'order'            => 'asc',
1815
					'post_parent'      => 0,
1816
					'suppress_filters' => 0,
1817
					'include'          => $posts_in,
1818
					's'                => $term,
1819
					'fields'           => 'ids',
1820
					'exclude'          => $exclude
1821
				);
1822
1823
				$posts = get_posts( $args );
1824
1825
				if ( ! empty( $posts ) ) {
1826
					foreach ( $posts as $post ) {
1827
						$product = wc_get_product( $post );
1828
1829
						if ( ! current_user_can( 'read_product', $post ) ) {
1830
							continue;
1831
						}
1832
1833
						$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1834
					}
1835
				}
1836
			}
1837
		}
1838
1839
		$found_products = apply_filters( 'woocommerce_json_search_found_grouped_products', $found_products );
1840
1841
		wp_send_json( $found_products );
1842
	}
1843
1844
	/**
1845
	 * Search for downloadable product variations and return json.
1846
	 *
1847
	 * @see WC_AJAX::json_search_products()
1848
	 */
1849
	public static function json_search_downloadable_products_and_variations() {
1850
		ob_start();
1851
1852
		check_ajax_referer( 'search-products', 'security' );
1853
1854
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1855
		$exclude = array();
1856
1857 View Code Duplication
		if ( ! empty( $_GET['exclude'] ) ) {
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...
1858
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1859
		}
1860
1861
		$args = array(
1862
			'post_type'      => array( 'product', 'product_variation' ),
1863
			'posts_per_page' => -1,
1864
			'post_status'    => 'publish',
1865
			'order'          => 'ASC',
1866
			'orderby'        => 'parent title',
1867
			'meta_query'     => array(
1868
				array(
1869
					'key'   => '_downloadable',
1870
					'value' => 'yes'
1871
				)
1872
			),
1873
			's'              => $term,
1874
			'exclude'        => $exclude
1875
		);
1876
1877
		$posts = get_posts( $args );
1878
		$found_products = array();
1879
1880
		if ( ! empty( $posts ) ) {
1881
			foreach ( $posts as $post ) {
1882
				$product = wc_get_product( $post->ID );
1883
1884
				if ( ! current_user_can( 'read_product', $post->ID ) ) {
1885
					continue;
1886
				}
1887
1888
				$found_products[ $post->ID ] = $product->get_formatted_name();
1889
			}
1890
		}
1891
1892
		wp_send_json( $found_products );
1893
	}
1894
1895
	/**
1896
	 * Search for customers and return json.
1897
	 */
1898
	public static function json_search_customers() {
1899
		ob_start();
1900
1901
		check_ajax_referer( 'search-customers', 'security' );
1902
1903
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1904
			die(-1);
1905
		}
1906
1907
		$term    = wc_clean( stripslashes( $_GET['term'] ) );
1908
		$exclude = array();
1909
1910
		if ( empty( $term ) ) {
1911
			die();
1912
		}
1913
1914
		if ( ! empty( $_GET['exclude'] ) ) {
1915
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1916
		}
1917
1918
		$found_customers = array();
1919
1920
		add_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1921
1922
		$customers_query = new WP_User_Query( apply_filters( 'woocommerce_json_search_customers_query', array(
1923
			'fields'         => 'all',
1924
			'orderby'        => 'display_name',
1925
			'search'         => '*' . $term . '*',
1926
			'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename' )
1927
		) ) );
1928
1929
		remove_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1930
1931
		$customers = $customers_query->get_results();
1932
1933
		if ( ! empty( $customers ) ) {
1934
			foreach ( $customers as $customer ) {
1935
				if ( ! in_array( $customer->ID, $exclude ) ) {
1936
					$found_customers[ $customer->ID ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
1937
				}
1938
			}
1939
		}
1940
1941
		$found_customers = apply_filters( 'woocommerce_json_search_found_customers', $found_customers );
1942
1943
		wp_send_json( $found_customers );
1944
	}
1945
1946
	/**
1947
	 * When searching using the WP_User_Query, search names (user meta) too.
1948
	 * @param  object $query
1949
	 * @return object
1950
	 */
1951
	public static function json_search_customer_name( $query ) {
1952
		global $wpdb;
1953
1954
		$term = wc_clean( stripslashes( $_GET['term'] ) );
1955
		if ( method_exists( $wpdb, 'esc_like' ) ) {
1956
			$term = $wpdb->esc_like( $term );
1957
		} else {
1958
			$term = like_escape( $term );
1959
		}
1960
1961
		$query->query_from  .= " INNER JOIN {$wpdb->usermeta} AS user_name ON {$wpdb->users}.ID = user_name.user_id AND ( user_name.meta_key = 'first_name' OR user_name.meta_key = 'last_name' ) ";
1962
		$query->query_where .= $wpdb->prepare( " OR user_name.meta_value LIKE %s ", '%' . $term . '%' );
1963
	}
1964
1965
	/**
1966
	 * Ajax request handling for categories ordering.
1967
	 */
1968
	public static function term_ordering() {
1969
1970
		// check permissions again and make sure we have what we need
1971
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
1972
			die(-1);
1973
		}
1974
1975
		$id       = (int) $_POST['id'];
1976
		$next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
1977
		$taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null;
1978
		$term     = get_term_by( 'id', $id, $taxonomy );
1979
1980
		if ( ! $id || ! $term || ! $taxonomy ) {
1981
			die(0);
1982
		}
1983
1984
		wc_reorder_terms( $term, $next_id, $taxonomy );
1985
1986
		$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
1987
1988
		if ( $term && sizeof( $children ) ) {
1989
			echo 'children';
1990
			die();
1991
		}
1992
	}
1993
1994
	/**
1995
	 * Ajax request handling for product ordering.
1996
	 *
1997
	 * Based on Simple Page Ordering by 10up (https://wordpress.org/extend/plugins/simple-page-ordering/).
1998
	 */
1999
	public static function product_ordering() {
2000
		global $wpdb;
2001
2002
		ob_start();
2003
2004
		// check permissions again and make sure we have what we need
2005
		if ( ! current_user_can('edit_products') || empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
2006
			die(-1);
2007
		}
2008
2009
		// real post?
2010
		if ( ! $post = get_post( $_POST['id'] ) ) {
2011
			die(-1);
2012
		}
2013
2014
		$previd  = isset( $_POST['previd'] ) ? $_POST['previd'] : false;
2015
		$nextid  = isset( $_POST['nextid'] ) ? $_POST['nextid'] : false;
2016
		$new_pos = array(); // store new positions for ajax
2017
2018
		$siblings = $wpdb->get_results( $wpdb->prepare( "
2019
			SELECT ID, menu_order FROM {$wpdb->posts} AS posts
2020
			WHERE 	posts.post_type 	= 'product'
2021
			AND 	posts.post_status 	IN ( 'publish', 'pending', 'draft', 'future', 'private' )
2022
			AND 	posts.ID			NOT IN (%d)
2023
			ORDER BY posts.menu_order ASC, posts.ID DESC
2024
		", $post->ID ) );
2025
2026
		$menu_order = 0;
2027
2028
		foreach ( $siblings as $sibling ) {
2029
2030
			// if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
2031 View Code Duplication
			if ( $nextid == $sibling->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...
2032
				$wpdb->update(
2033
					$wpdb->posts,
2034
					array(
2035
						'menu_order' => $menu_order
2036
					),
2037
					array( 'ID' => $post->ID ),
2038
					array( '%d' ),
2039
					array( '%d' )
2040
				);
2041
				$new_pos[ $post->ID ] = $menu_order;
2042
				$menu_order++;
2043
			}
2044
2045
			// if repositioned post has been set, and new items are already in the right order, we can stop
2046
			if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $menu_order ) {
2047
				break;
2048
			}
2049
2050
			// set the menu order of the current sibling and increment the menu order
2051
			$wpdb->update(
2052
				$wpdb->posts,
2053
				array(
2054
					'menu_order' => $menu_order
2055
				),
2056
				array( 'ID' => $sibling->ID ),
2057
				array( '%d' ),
2058
				array( '%d' )
2059
			);
2060
			$new_pos[ $sibling->ID ] = $menu_order;
2061
			$menu_order++;
2062
2063 View Code Duplication
			if ( ! $nextid && $previd == $sibling->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...
2064
				$wpdb->update(
2065
					$wpdb->posts,
2066
					array(
2067
						'menu_order' => $menu_order
2068
					),
2069
					array( 'ID' => $post->ID ),
2070
					array( '%d' ),
2071
					array( '%d' )
2072
				);
2073
				$new_pos[$post->ID] = $menu_order;
2074
				$menu_order++;
2075
			}
2076
2077
		}
2078
2079
		do_action( 'woocommerce_after_product_ordering' );
2080
2081
		wp_send_json( $new_pos );
2082
	}
2083
2084
	/**
2085
	 * Handle a refund via the edit order screen.
2086
	 */
2087
	public static function refund_line_items() {
2088
		ob_start();
2089
2090
		check_ajax_referer( 'order-item', 'security' );
2091
2092
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2093
			die(-1);
2094
		}
2095
2096
		$order_id               = absint( $_POST['order_id'] );
2097
		$refund_amount          = wc_format_decimal( sanitize_text_field( $_POST['refund_amount'] ), wc_get_price_decimals() );
2098
		$refund_reason          = sanitize_text_field( $_POST['refund_reason'] );
2099
		$line_item_qtys         = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_qtys'] ) ), true );
2100
		$line_item_totals       = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_totals'] ) ), true );
2101
		$line_item_tax_totals   = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_tax_totals'] ) ), true );
2102
		$api_refund             = $_POST['api_refund'] === 'true' ? true : false;
2103
		$restock_refunded_items = $_POST['restock_refunded_items'] === 'true' ? true : false;
2104
		$refund                 = false;
2105
		$response_data          = array();
2106
2107
		try {
2108
			// Validate that the refund can occur
2109
			$order       = wc_get_order( $order_id );
2110
			$order_items = $order->get_items();
2111
			$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded(), wc_get_price_decimals() );
2112
2113
			if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) {
2114
				throw new exception( __( 'Invalid refund amount', 'woocommerce' ) );
2115
			}
2116
2117
			// Prepare line items which we are refunding
2118
			$line_items = array();
2119
			$item_ids   = array_unique( array_merge( array_keys( $line_item_qtys, $line_item_totals ) ) );
2120
2121
			foreach ( $item_ids as $item_id ) {
2122
				$line_items[ $item_id ] = array( 'qty' => 0, 'refund_total' => 0, 'refund_tax' => array() );
2123
			}
2124
			foreach ( $line_item_qtys as $item_id => $qty ) {
2125
				$line_items[ $item_id ]['qty'] = max( $qty, 0 );
2126
			}
2127
			foreach ( $line_item_totals as $item_id => $total ) {
2128
				$line_items[ $item_id ]['refund_total'] = wc_format_decimal( $total );
2129
			}
2130
			foreach ( $line_item_tax_totals as $item_id => $tax_totals ) {
2131
				$line_items[ $item_id ]['refund_tax'] = array_map( 'wc_format_decimal', $tax_totals );
2132
			}
2133
2134
			// Create the refund object
2135
			$refund = wc_create_refund( array(
2136
				'amount'     => $refund_amount,
2137
				'reason'     => $refund_reason,
2138
				'order_id'   => $order_id,
2139
				'line_items' => $line_items,
2140
			) );
2141
2142
			if ( is_wp_error( $refund ) ) {
2143
				throw new Exception( $refund->get_error_message() );
2144
			}
2145
2146
			// Refund via API
2147
			if ( $api_refund ) {
2148
				if ( WC()->payment_gateways() ) {
2149
					$payment_gateways = WC()->payment_gateways->payment_gateways();
2150
				}
2151
				if ( isset( $payment_gateways[ $order->get_payment_method() ] ) && $payment_gateways[ $order->get_payment_method() ]->supports( 'refunds' ) ) {
2152
					$result = $payment_gateways[ $order->get_payment_method() ]->process_refund( $order_id, $refund_amount, $refund_reason );
2153
2154
					do_action( 'woocommerce_refund_processed', $refund, $result );
2155
2156
					if ( is_wp_error( $result ) ) {
2157
						throw new Exception( $result->get_error_message() );
2158
					} elseif ( ! $result ) {
2159
						throw new Exception( __( 'Refund failed', 'woocommerce' ) );
2160
					}
2161
				}
2162
			}
2163
2164
			// restock items
2165
			foreach ( $line_item_qtys as $item_id => $qty ) {
2166
				if ( $restock_refunded_items && $qty && isset( $order_items[ $item_id ] ) ) {
2167
					$order_item = $order_items[ $item_id ];
2168
					$_product   = $order->get_product_from_item( $order_item );
2169
2170
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2171
						$old_stock    = wc_stock_amount( $_product->stock );
2172
						$new_quantity = $_product->increase_stock( $qty );
2173
2174
						$order->add_order_note( sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
2175
2176
						do_action( 'woocommerce_restock_refunded_item', $_product->id, $old_stock, $new_quantity, $order, $_product );
2177
					}
2178
				}
2179
			}
2180
2181
			// Trigger notifications and status changes
2182
			if ( $order->get_remaining_refund_amount() > 0 || ( $order->has_free_item() && $order->get_remaining_refund_items() > 0 ) ) {
2183
				/**
2184
				 * woocommerce_order_partially_refunded.
2185
				 *
2186
				 * @since 2.4.0
2187
				 * Note: 3rd arg was added in err. Kept for bw compat. 2.4.3.
2188
				 */
2189
				do_action( 'woocommerce_order_partially_refunded', $order_id, $refund->id, $refund->id );
2190
			} else {
2191
				do_action( 'woocommerce_order_fully_refunded', $order_id, $refund->id );
2192
2193
				$order->update_status( apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order_id, $refund->id ) );
2194
				$response_data['status'] = 'fully_refunded';
2195
			}
2196
2197
			do_action( 'woocommerce_order_refunded', $order_id, $refund->id );
2198
2199
			// Clear transients
2200
			wc_delete_shop_order_transients( $order_id );
2201
			wp_send_json_success( $response_data );
2202
2203
		} catch ( Exception $e ) {
2204
			if ( $refund && is_a( $refund, 'WC_Order_Refund' ) ) {
2205
				wp_delete_post( $refund->id, true );
2206
			}
2207
2208
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
2209
		}
2210
	}
2211
2212
	/**
2213
	 * Delete a refund.
2214
	 */
2215
	public static function delete_refund() {
2216
		check_ajax_referer( 'order-item', 'security' );
2217
2218
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2219
			die(-1);
2220
		}
2221
2222
		$refund_ids = array_map( 'absint', is_array( $_POST['refund_id'] ) ? $_POST['refund_id'] : array( $_POST['refund_id'] ) );
2223
		foreach ( $refund_ids as $refund_id ) {
2224
			if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
2225
				$order_id = wp_get_post_parent_id( $refund_id );
2226
				wc_delete_shop_order_transients( $order_id );
2227
				wp_delete_post( $refund_id );
2228
				do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
2229
			}
2230
		}
2231
		die();
2232
	}
2233
2234
	/**
2235
	 * Triggered when clicking the rating footer.
2236
	 */
2237
	public static function rated() {
2238
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2239
			die(-1);
2240
		}
2241
2242
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
2243
		die();
2244
	}
2245
2246
	/**
2247
	 * Create/Update API key.
2248
	 */
2249
	public static function update_api_key() {
2250
		ob_start();
2251
2252
		global $wpdb;
2253
2254
		check_ajax_referer( 'update-api-key', 'security' );
2255
2256
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2257
			die(-1);
2258
		}
2259
2260
		try {
2261
			if ( empty( $_POST['description'] ) ) {
2262
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
2263
			}
2264
			if ( empty( $_POST['user'] ) ) {
2265
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
2266
			}
2267
			if ( empty( $_POST['permissions'] ) ) {
2268
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
2269
			}
2270
2271
			$key_id      = absint( $_POST['key_id'] );
2272
			$description = sanitize_text_field( wp_unslash( $_POST['description'] ) );
2273
			$permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read';
2274
			$user_id     = absint( $_POST['user'] );
2275
2276
			if ( 0 < $key_id ) {
2277
				$data = array(
2278
					'user_id'     => $user_id,
2279
					'description' => $description,
2280
					'permissions' => $permissions
2281
				);
2282
2283
				$wpdb->update(
2284
					$wpdb->prefix . 'woocommerce_api_keys',
2285
					$data,
2286
					array( 'key_id' => $key_id ),
2287
					array(
2288
						'%d',
2289
						'%s',
2290
						'%s'
2291
					),
2292
					array( '%d' )
2293
				);
2294
2295
				$data['consumer_key']    = '';
2296
				$data['consumer_secret'] = '';
2297
				$data['message']         = __( 'API Key updated successfully.', 'woocommerce' );
2298
			} else {
2299
				$consumer_key    = 'ck_' . wc_rand_hash();
2300
				$consumer_secret = 'cs_' . wc_rand_hash();
2301
2302
				$data = array(
2303
					'user_id'         => $user_id,
2304
					'description'     => $description,
2305
					'permissions'     => $permissions,
2306
					'consumer_key'    => wc_api_hash( $consumer_key ),
2307
					'consumer_secret' => $consumer_secret,
2308
					'truncated_key'   => substr( $consumer_key, -7 )
2309
				);
2310
2311
				$wpdb->insert(
2312
					$wpdb->prefix . 'woocommerce_api_keys',
2313
					$data,
2314
					array(
2315
						'%d',
2316
						'%s',
2317
						'%s',
2318
						'%s',
2319
						'%s',
2320
						'%s'
2321
					)
2322
				);
2323
2324
				$key_id                  = $wpdb->insert_id;
2325
				$data['consumer_key']    = $consumer_key;
2326
				$data['consumer_secret'] = $consumer_secret;
2327
				$data['message']         = __( 'API Key generated successfully. Make sure to copy your new API keys now. You won\'t be able to see it again!', 'woocommerce' );
2328
				$data['revoke_url']      = '<a style="color: #a00; text-decoration: none;" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'revoke-key' => $key_id ), admin_url( 'admin.php?page=wc-settings&tab=api&section=keys' ) ), 'revoke' ) ). '">' . __( 'Revoke Key', 'woocommerce' ) . '</a>';
2329
			}
2330
2331
			wp_send_json_success( $data );
2332
		} catch ( Exception $e ) {
2333
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
2334
		}
2335
	}
2336
2337
	/**
2338
	 * Locate user via AJAX.
2339
	 */
2340
	public static function get_customer_location() {
2341
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
2342
		wp_send_json_success( array( 'hash' => $location_hash ) );
2343
	}
2344
2345
	/**
2346
	 * Load variations via AJAX.
2347
	 */
2348
	public static function load_variations() {
2349
		ob_start();
2350
2351
		check_ajax_referer( 'load-variations', 'security' );
2352
2353
		// Check permissions again and make sure we have what we need
2354 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) || empty( $_POST['attributes'] ) ) {
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...
2355
			die( -1 );
2356
		}
2357
2358
		global $post;
2359
2360
		$product_id = absint( $_POST['product_id'] );
2361
		$post       = get_post( $product_id ); // Set $post global so its available like within the admin screens
2362
		$per_page   = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
2363
		$page       = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
2364
2365
		// Get attributes
2366
		$attributes        = array();
2367
		$posted_attributes = wp_unslash( $_POST['attributes'] );
2368
2369
		foreach ( $posted_attributes as $key => $value ) {
2370
			$attributes[ $key ] = array_map( 'wc_clean', $value );
2371
		}
2372
2373
		// Get tax classes
2374
		$tax_classes           = WC_Tax::get_tax_classes();
2375
		$tax_class_options     = array();
2376
		$tax_class_options[''] = __( 'Standard', 'woocommerce' );
2377
2378 View Code Duplication
		if ( ! empty( $tax_classes ) ) {
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...
2379
			foreach ( $tax_classes as $class ) {
2380
				$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
2381
			}
2382
		}
2383
2384
		// Set backorder options
2385
		$backorder_options = array(
2386
			'no'     => __( 'Do not allow', 'woocommerce' ),
2387
			'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
2388
			'yes'    => __( 'Allow', 'woocommerce' )
2389
		);
2390
2391
		// set stock status options
2392
		$stock_status_options = array(
2393
			'instock'    => __( 'In stock', 'woocommerce' ),
2394
			'outofstock' => __( 'Out of stock', 'woocommerce' )
2395
		);
2396
2397
		$parent_data = array(
2398
			'id'                   => $product_id,
2399
			'attributes'           => $attributes,
2400
			'tax_class_options'    => $tax_class_options,
2401
			'sku'                  => get_post_meta( $product_id, '_sku', true ),
2402
			'weight'               => wc_format_localized_decimal( get_post_meta( $product_id, '_weight', true ) ),
2403
			'length'               => wc_format_localized_decimal( get_post_meta( $product_id, '_length', true ) ),
2404
			'width'                => wc_format_localized_decimal( get_post_meta( $product_id, '_width', true ) ),
2405
			'height'               => wc_format_localized_decimal( get_post_meta( $product_id, '_height', true ) ),
2406
			'tax_class'            => get_post_meta( $product_id, '_tax_class', true ),
2407
			'backorder_options'    => $backorder_options,
2408
			'stock_status_options' => $stock_status_options
2409
		);
2410
2411
		if ( ! $parent_data['weight'] ) {
2412
			$parent_data['weight'] = wc_format_localized_decimal( 0 );
2413
		}
2414
2415
		if ( ! $parent_data['length'] ) {
2416
			$parent_data['length'] = wc_format_localized_decimal( 0 );
2417
		}
2418
2419
		if ( ! $parent_data['width'] ) {
2420
			$parent_data['width'] = wc_format_localized_decimal( 0 );
2421
		}
2422
2423
		if ( ! $parent_data['height'] ) {
2424
			$parent_data['height'] = wc_format_localized_decimal( 0 );
2425
		}
2426
2427
		// Get variations
2428
		$args = apply_filters( 'woocommerce_ajax_admin_get_variations_args', array(
2429
			'post_type'      => 'product_variation',
2430
			'post_status'    => array( 'private', 'publish' ),
2431
			'posts_per_page' => $per_page,
2432
			'paged'          => $page,
2433
			'orderby'        => array( 'menu_order' => 'ASC', 'ID' => 'DESC' ),
2434
			'post_parent'    => $product_id
2435
		), $product_id );
2436
2437
		$variations = get_posts( $args );
2438
		$loop = 0;
2439
2440
		if ( $variations ) {
2441
2442
			foreach ( $variations as $variation ) {
2443
				$variation_id     = absint( $variation->ID );
2444
				$variation_meta   = get_post_meta( $variation_id );
2445
				$variation_data   = array();
2446
				$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
2447
				$variation_fields = array(
2448
					'_sku'                   => '',
2449
					'_stock'                 => '',
2450
					'_regular_price'         => '',
2451
					'_sale_price'            => '',
2452
					'_weight'                => '',
2453
					'_length'                => '',
2454
					'_width'                 => '',
2455
					'_height'                => '',
2456
					'_download_limit'        => '',
2457
					'_download_expiry'       => '',
2458
					'_downloadable_files'    => '',
2459
					'_downloadable'          => '',
2460
					'_virtual'               => '',
2461
					'_thumbnail_id'          => '',
2462
					'_sale_price_dates_from' => '',
2463
					'_sale_price_dates_to'   => '',
2464
					'_manage_stock'          => '',
2465
					'_stock_status'          => '',
2466
					'_backorders'            => null,
2467
					'_tax_class'             => null,
2468
					'_variation_description' => ''
2469
				);
2470
2471 View Code Duplication
				foreach ( $variation_fields as $field => $value ) {
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...
2472
					$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
2473
				}
2474
2475
				// Add the variation attributes
2476
				$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
2477
2478
				// Formatting
2479
				$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
2480
				$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
2481
				$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
2482
				$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
2483
				$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
2484
				$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
2485
				$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
2486
				$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
2487
				$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
2488
				$variation_data['menu_order']     = $variation->menu_order;
2489
				$variation_data['_stock']         = '' === $variation_data['_stock'] ? '' : wc_stock_amount( $variation_data['_stock'] );
2490
2491
				include( 'admin/meta-boxes/views/html-variation-admin.php' );
2492
2493
				$loop++;
2494
			}
2495
		}
2496
2497
		die();
2498
	}
2499
2500
	/**
2501
	 * Save variations via AJAX.
2502
	 */
2503
	public static function save_variations() {
2504
		ob_start();
2505
2506
		check_ajax_referer( 'save-variations', 'security' );
2507
2508
		// Check permissions again and make sure we have what we need
2509 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST ) || empty( $_POST['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...
2510
			die( -1 );
2511
		}
2512
2513
		// Remove previous meta box errors
2514
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
2515
2516
		$product_id   = absint( $_POST['product_id'] );
2517
		$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
2518
2519
		$product_type_terms = wp_get_object_terms( $product_id, 'product_type' );
2520
2521
		// If the product type hasn't been set or it has changed, update it before saving variations
2522
		if ( empty( $product_type_terms ) || $product_type !== sanitize_title( current( $product_type_terms )->name ) ) {
2523
			wp_set_object_terms( $product_id, $product_type, 'product_type' );
2524
		}
2525
2526
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
2527
2528
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
2529
2530
		// Clear cache/transients
2531
		wc_delete_product_transients( $product_id );
2532
2533
		if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
2534
			echo '<div class="error notice is-dismissible">';
2535
2536
			foreach ( $errors as $error ) {
2537
				echo '<p>' . wp_kses_post( $error ) . '</p>';
2538
			}
2539
2540
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
2541
			echo '</div>';
2542
2543
			delete_option( 'woocommerce_meta_box_errors' );
2544
		}
2545
2546
		die();
2547
	}
2548
2549
	/**
2550
	 * Bulk action - Toggle Enabled.
2551
	 * @access private
2552
	 * @used-by bulk_edit_variations
2553
	 * @param  array $variations
2554
	 * @param  array $data
2555
	 */
2556
	private static function variation_bulk_action_toggle_enabled( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Unused Code introduced by
The parameter $data 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...
2557
		global $wpdb;
2558
2559
		foreach ( $variations as $variation_id ) {
2560
			$post_status = get_post_status( $variation_id );
2561
			$new_status  = 'private' === $post_status ? 'publish' : 'private';
2562
			$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $variation_id ) );
2563
		}
2564
	}
2565
2566
	/**
2567
	 * Bulk action - Toggle Downloadable Checkbox.
2568
	 * @access private
2569
	 * @used-by bulk_edit_variations
2570
	 * @param  array $variations
2571
	 * @param  array $data
2572
	 */
2573 View Code Duplication
	private static function variation_bulk_action_toggle_downloadable( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Unused Code introduced by
The parameter $data 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...
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2574
		foreach ( $variations as $variation_id ) {
2575
			$_downloadable   = get_post_meta( $variation_id, '_downloadable', true );
2576
			$is_downloadable = 'no' === $_downloadable ? 'yes' : 'no';
2577
			update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
2578
		}
2579
	}
2580
2581
	/**
2582
	 * Bulk action - Toggle Virtual Checkbox.
2583
	 * @access private
2584
	 * @used-by bulk_edit_variations
2585
	 * @param  array $variations
2586
	 * @param  array $data
2587
	 */
2588 View Code Duplication
	private static function variation_bulk_action_toggle_virtual( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Unused Code introduced by
The parameter $data 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...
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2589
		foreach ( $variations as $variation_id ) {
2590
			$_virtual   = get_post_meta( $variation_id, '_virtual', true );
2591
			$is_virtual = 'no' === $_virtual ? 'yes' : 'no';
2592
			update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
2593
		}
2594
	}
2595
2596
	/**
2597
	 * Bulk action - Toggle Manage Stock Checkbox.
2598
	 * @access private
2599
	 * @used-by bulk_edit_variations
2600
	 * @param  array $variations
2601
	 * @param  array $data
2602
	 */
2603
	private static function variation_bulk_action_toggle_manage_stock( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Unused Code introduced by
The parameter $data 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...
2604
		foreach ( $variations as $variation_id ) {
2605
			$_manage_stock   = get_post_meta( $variation_id, '_manage_stock', true );
2606
			$is_manage_stock = 'no' === $_manage_stock || '' === $_manage_stock ? 'yes' : 'no';
2607
			update_post_meta( $variation_id, '_manage_stock', $is_manage_stock );
2608
		}
2609
	}
2610
2611
	/**
2612
	 * Bulk action - Set Regular Prices.
2613
	 * @access private
2614
	 * @used-by bulk_edit_variations
2615
	 * @param  array $variations
2616
	 * @param  array $data
2617
	 */
2618 View Code Duplication
	private static function variation_bulk_action_variable_regular_price( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2619
		if ( ! isset( $data['value'] ) ) {
2620
			return;
2621
		}
2622
2623
		foreach ( $variations as $variation_id ) {
2624
			// Price fields
2625
			$regular_price = wc_clean( $data['value'] );
2626
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2627
2628
			// Date fields
2629
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2630
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2631
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2632
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2633
2634
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
0 ignored issues
show
Documentation introduced by
$regular_price is of type string|array, but the function expects a double.

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...
2635
		}
2636
	}
2637
2638
	/**
2639
	 * Bulk action - Set Sale Prices.
2640
	 * @access private
2641
	 * @used-by bulk_edit_variations
2642
	 * @param  array $variations
2643
	 * @param  array $data
2644
	 */
2645 View Code Duplication
	private static function variation_bulk_action_variable_sale_price( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2646
		if ( ! isset( $data['value'] ) ) {
2647
			return;
2648
		}
2649
2650
		foreach ( $variations as $variation_id ) {
2651
			// Price fields
2652
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2653
			$sale_price    = wc_clean( $data['value'] );
2654
2655
			// Date fields
2656
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2657
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2658
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2659
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2660
2661
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
0 ignored issues
show
Bug introduced by
It seems like $sale_price defined by wc_clean($data['value']) on line 2653 can also be of type array; however, _wc_save_product_price() does only seem to accept string|double, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
2662
		}
2663
	}
2664
2665
	/**
2666
	 * Bulk action - Set Stock.
2667
	 * @access private
2668
	 * @used-by bulk_edit_variations
2669
	 * @param  array $variations
2670
	 * @param  array $data
2671
	 */
2672
	private static function variation_bulk_action_variable_stock( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2673
		if ( ! isset( $data['value'] ) ) {
2674
			return;
2675
		}
2676
2677
		$value = wc_clean( $data['value'] );
2678
2679
		foreach ( $variations as $variation_id ) {
2680
			if ( 'yes' === get_post_meta( $variation_id, '_manage_stock', true ) ) {
2681
				wc_update_product_stock( $variation_id, wc_stock_amount( $value ) );
0 ignored issues
show
Documentation introduced by
$value is of type string|array, but the function expects a integer|double.

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...
2682
			} else {
2683
				delete_post_meta( $variation_id, '_stock' );
2684
			}
2685
		}
2686
	}
2687
2688
	/**
2689
	 * Bulk action - Set Weight.
2690
	 * @access private
2691
	 * @used-by bulk_edit_variations
2692
	 * @param  array $variations
2693
	 * @param  array $data
2694
	 */
2695
	private static function variation_bulk_action_variable_weight( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2696
		self::variation_bulk_set_meta( $variations, '_weight', wc_clean( $data['value'] ) );
2697
	}
2698
2699
	/**
2700
	 * Bulk action - Set Length.
2701
	 * @access private
2702
	 * @used-by bulk_edit_variations
2703
	 * @param  array $variations
2704
	 * @param  array $data
2705
	 */
2706
	private static function variation_bulk_action_variable_length( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2707
		self::variation_bulk_set_meta( $variations, '_length', wc_clean( $data['value'] ) );
2708
	}
2709
2710
	/**
2711
	 * Bulk action - Set Width.
2712
	 * @access private
2713
	 * @used-by bulk_edit_variations
2714
	 * @param  array $variations
2715
	 * @param  array $data
2716
	 */
2717
	private static function variation_bulk_action_variable_width( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2718
		self::variation_bulk_set_meta( $variations, '_width', wc_clean( $data['value'] ) );
2719
	}
2720
2721
	/**
2722
	 * Bulk action - Set Height.
2723
	 * @access private
2724
	 * @used-by bulk_edit_variations
2725
	 * @param  array $variations
2726
	 * @param  array $data
2727
	 */
2728
	private static function variation_bulk_action_variable_height( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2729
		self::variation_bulk_set_meta( $variations, '_height', wc_clean( $data['value'] ) );
2730
	}
2731
2732
	/**
2733
	 * Bulk action - Set Download Limit.
2734
	 * @access private
2735
	 * @used-by bulk_edit_variations
2736
	 * @param  array $variations
2737
	 * @param  array $data
2738
	 */
2739
	private static function variation_bulk_action_variable_download_limit( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2740
		self::variation_bulk_set_meta( $variations, '_download_limit', wc_clean( $data['value'] ) );
2741
	}
2742
2743
	/**
2744
	 * Bulk action - Set Download Expiry.
2745
	 * @access private
2746
	 * @used-by bulk_edit_variations
2747
	 * @param  array $variations
2748
	 * @param  array $data
2749
	 */
2750
	private static function variation_bulk_action_variable_download_expiry( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2751
		self::variation_bulk_set_meta( $variations, '_download_expiry', wc_clean( $data['value'] ) );
2752
	}
2753
2754
	/**
2755
	 * Bulk action - Delete all.
2756
	 * @access private
2757
	 * @used-by bulk_edit_variations
2758
	 * @param  array $variations
2759
	 * @param  array $data
2760
	 */
2761
	private static function variation_bulk_action_delete_all( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2762
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2763
			foreach ( $variations as $variation_id ) {
2764
				wp_delete_post( $variation_id );
2765
			}
2766
		}
2767
	}
2768
2769
	/**
2770
	 * Bulk action - Sale Schedule.
2771
	 * @access private
2772
	 * @used-by bulk_edit_variations
2773
	 * @param  array $variations
2774
	 * @param  array $data
2775
	 */
2776
	private static function variation_bulk_action_variable_sale_schedule( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2777
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2778
			return;
2779
		}
2780
2781
		foreach ( $variations as $variation_id ) {
2782
			// Price fields
2783
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2784
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2785
2786
			// Date fields
2787
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2788
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2789
2790 View Code Duplication
			if ( 'false' === $data['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...
2791
				$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2792
			} else {
2793
				$date_from = $data['date_from'];
2794
			}
2795
2796 View Code Duplication
			if ( 'false' === $data['date_to'] ) {
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...
2797
				$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2798
			} else {
2799
				$date_to = $data['date_to'];
2800
			}
2801
2802
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
2803
		}
2804
	}
2805
2806
	/**
2807
	 * Bulk action - Increase Regular Prices.
2808
	 * @access private
2809
	 * @used-by bulk_edit_variations
2810
	 * @param  array $variations
2811
	 * @param  array $data
2812
	 */
2813
	private static function variation_bulk_action_variable_regular_price_increase( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2814
		self::variation_bulk_adjust_price( $variations, '_regular_price', '+', wc_clean( $data['value'] ) );
2815
	}
2816
2817
	/**
2818
	 * Bulk action - Decrease Regular Prices.
2819
	 * @access private
2820
	 * @used-by bulk_edit_variations
2821
	 * @param  array $variations
2822
	 * @param  array $data
2823
	 */
2824
	private static function variation_bulk_action_variable_regular_price_decrease( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2825
		self::variation_bulk_adjust_price( $variations, '_regular_price', '-', wc_clean( $data['value'] ) );
2826
	}
2827
2828
	/**
2829
	 * Bulk action - Increase Sale Prices.
2830
	 * @access private
2831
	 * @used-by bulk_edit_variations
2832
	 * @param  array $variations
2833
	 * @param  array $data
2834
	 */
2835
	private static function variation_bulk_action_variable_sale_price_increase( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2836
		self::variation_bulk_adjust_price( $variations, '_sale_price', '+', wc_clean( $data['value'] ) );
2837
	}
2838
2839
	/**
2840
	 * Bulk action - Decrease Sale Prices.
2841
	 * @access private
2842
	 * @used-by bulk_edit_variations
2843
	 * @param  array $variations
2844
	 * @param  array $data
2845
	 */
2846
	private static function variation_bulk_action_variable_sale_price_decrease( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2847
		self::variation_bulk_adjust_price( $variations, '_sale_price', '-', wc_clean( $data['value'] ) );
2848
	}
2849
2850
	/**
2851
	 * Bulk action - Set Price.
2852
	 * @access private
2853
	 * @used-by bulk_edit_variations
2854
	 * @param  array $variations
2855
	 * @param string $operator + or -
2856
	 * @param string $field price being adjusted
2857
	 * @param string $value Price or Percent
2858
	 */
2859
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2860
		foreach ( $variations as $variation_id ) {
2861
			// Get existing data
2862
			$_regular_price = get_post_meta( $variation_id, '_regular_price', true );
2863
			$_sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2864
			$date_from      = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2865
			$date_to        = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2866
			$date_from      = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2867
			$date_to        = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2868
2869
			if ( '%' === substr( $value, -1 ) ) {
2870
				$percent = wc_format_decimal( substr( $value, 0, -1 ) );
2871
				$$field  += ( ( $$field / 100 ) * $percent ) * "{$operator}1";
2872
			} else {
2873
				$$field  += $value * "{$operator}1";
2874
			}
2875
			_wc_save_product_price( $variation_id, $_regular_price, $_sale_price, $date_from, $date_to );
2876
		}
2877
	}
2878
2879
	/**
2880
	 * Bulk action - Set Meta.
2881
	 * @access private
2882
	 * @param array $variations
2883
	 * @param string $field
2884
	 * @param string $value
2885
	 */
2886
	private static function variation_bulk_set_meta( $variations, $field, $value ) {
2887
		foreach ( $variations as $variation_id ) {
2888
			update_post_meta( $variation_id, $field, $value );
2889
		}
2890
	}
2891
2892
	/**
2893
	 * Bulk edit variations via AJAX.
2894
	 * @uses WC_AJAX::variation_bulk_set_meta()
2895
	 * @uses WC_AJAX::variation_bulk_adjust_price()
2896
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price_decrease()
2897
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price_increase()
2898
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price_decrease()
2899
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price_increase()
2900
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_schedule()
2901
	 * @uses WC_AJAX::variation_bulk_action_delete_all()
2902
	 * @uses WC_AJAX::variation_bulk_action_variable_download_expiry()
2903
	 * @uses WC_AJAX::variation_bulk_action_variable_download_limit()
2904
	 * @uses WC_AJAX::variation_bulk_action_variable_height()
2905
	 * @uses WC_AJAX::variation_bulk_action_variable_width()
2906
	 * @uses WC_AJAX::variation_bulk_action_variable_length()
2907
	 * @uses WC_AJAX::variation_bulk_action_variable_weight()
2908
	 * @uses WC_AJAX::variation_bulk_action_variable_stock()
2909
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price()
2910
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price()
2911
	 * @uses WC_AJAX::variation_bulk_action_toggle_manage_stock()
2912
	 * @uses WC_AJAX::variation_bulk_action_toggle_virtual()
2913
	 * @uses WC_AJAX::variation_bulk_action_toggle_downloadable()
2914
	 * @uses WC_AJAX::variation_bulk_action_toggle_enabled
2915
	 */
2916
	public static function bulk_edit_variations() {
2917
		ob_start();
2918
2919
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2920
2921
		// Check permissions again and make sure we have what we need
2922 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) || empty( $_POST['bulk_action'] ) ) {
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...
2923
			die( -1 );
2924
		}
2925
2926
		$product_id  = absint( $_POST['product_id'] );
2927
		$bulk_action = wc_clean( $_POST['bulk_action'] );
2928
		$data        = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array();
2929
		$variations  = array();
2930
2931
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2932
			$variations = get_posts( array(
2933
				'post_parent'    => $product_id,
2934
				'posts_per_page' => -1,
2935
				'post_type'      => 'product_variation',
2936
				'fields'         => 'ids',
2937
				'post_status'    => array( 'publish', 'private' )
2938
			) );
2939
		}
2940
2941
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2942
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2943
		} else {
2944
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2945
		}
2946
2947
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2948
2949
		// Sync and update transients
2950
		WC_Product_Variable::sync( $product_id );
2951
		wc_delete_product_transients( $product_id );
2952
		die();
2953
	}
2954
2955
	/**
2956
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2957
	 */
2958
	public static function tax_rates_save_changes() {
2959
		if ( ! isset( $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2960
			wp_send_json_error( 'missing_fields' );
2961
			exit;
2962
		}
2963
2964
		$current_class = ! empty( $_POST['current_class'] ) ? $_POST['current_class'] : ''; // This is sanitized seven lines later.
2965
2966
		if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) {
2967
			wp_send_json_error( 'bad_nonce' );
2968
			exit;
2969
		}
2970
2971
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
2972
2973
		// Check User Caps
2974
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2975
			wp_send_json_error( 'missing_capabilities' );
2976
			exit;
2977
		}
2978
2979
		$changes = $_POST['changes'];
2980
		foreach ( $changes as $tax_rate_id => $data ) {
2981
			if ( isset( $data['deleted'] ) ) {
2982
				if ( isset( $data['newRow'] ) ) {
2983
					// So the user added and deleted a new row.
2984
					// That's fine, it's not in the database anyways. NEXT!
2985
					continue;
2986
				}
2987
				WC_Tax::_delete_tax_rate( $tax_rate_id );
2988
			}
2989
2990
			$tax_rate = array_intersect_key( $data, array(
2991
				'tax_rate_country'  => 1,
2992
				'tax_rate_state'    => 1,
2993
				'tax_rate'          => 1,
2994
				'tax_rate_name'     => 1,
2995
				'tax_rate_priority' => 1,
2996
				'tax_rate_compound' => 1,
2997
				'tax_rate_shipping' => 1,
2998
				'tax_rate_order'    => 1,
2999
			) );
3000
3001
			if ( isset( $data['newRow'] ) ) {
3002
				// Hurrah, shiny and new!
3003
				$tax_rate['tax_rate_class'] = $current_class;
3004
				$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
3005
			} else {
3006
				// Updating an existing rate ...
3007
				if ( ! empty( $tax_rate ) ) {
3008
					WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
3009
				}
3010
			}
3011
3012
			if ( isset( $data['postcode'] ) ) {
3013
				WC_Tax::_update_tax_rate_postcodes( $tax_rate_id, array_map( 'wc_clean', $data['postcode'] ) );
0 ignored issues
show
Documentation introduced by
array_map('wc_clean', $data['postcode']) is of type array, but the function expects a string.

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...
3014
			}
3015
			if ( isset( $data['city'] ) ) {
3016
				WC_Tax::_update_tax_rate_cities( $tax_rate_id, array_map( 'wc_clean', $data['city'] ) );
0 ignored issues
show
Documentation introduced by
array_map('wc_clean', $data['city']) is of type array, but the function expects a string.

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...
3017
			}
3018
		}
3019
3020
		wp_send_json_success( array(
3021
			'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
3022
		) );
3023
	}
3024
3025
	/**
3026
	 * Handle submissions from assets/js/wc-shipping-zones.js Backbone model.
3027
	 */
3028
	public static function shipping_zones_save_changes() {
3029
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['changes'] ) ) {
3030
			wp_send_json_error( 'missing_fields' );
3031
			exit;
3032
		}
3033
3034
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3035
			wp_send_json_error( 'bad_nonce' );
3036
			exit;
3037
		}
3038
3039
		// Check User Caps
3040
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3041
			wp_send_json_error( 'missing_capabilities' );
3042
			exit;
3043
		}
3044
3045
		$changes = $_POST['changes'];
3046
		foreach ( $changes as $zone_id => $data ) {
3047
			if ( isset( $data['deleted'] ) ) {
3048
				if ( isset( $data['newRow'] ) ) {
3049
					// So the user added and deleted a new row.
3050
					// That's fine, it's not in the database anyways. NEXT!
3051
					continue;
3052
				}
3053
				WC_Shipping_Zones::delete_zone( $zone_id );
3054
				continue;
3055
			}
3056
3057
			$zone_data = array_intersect_key( $data, array(
3058
				'zone_id'        => 1,
3059
				'zone_name'      => 1,
3060
				'zone_order'     => 1,
3061
				'zone_locations' => 1,
3062
				'zone_postcodes' => 1
3063
			) );
3064
3065
			if ( isset( $zone_data['zone_id'] ) ) {
3066
				$zone = new WC_Shipping_Zone( $zone_data['zone_id'] );
3067
3068
				if ( isset( $zone_data['zone_name'] ) ) {
3069
					$zone->set_zone_name( $zone_data['zone_name'] );
3070
				}
3071
3072
				if ( isset( $zone_data['zone_order'] ) ) {
3073
					$zone->set_zone_order( $zone_data['zone_order'] );
3074
				}
3075
3076
				if ( isset( $zone_data['zone_locations'] ) ) {
3077
					$zone->clear_locations( array( 'state', 'country', 'continent' ) );
3078
					$locations = array_filter( array_map( 'wc_clean', (array) $zone_data['zone_locations'] ) );
3079
					foreach ( $locations as $location ) {
3080
						// Each posted location will be in the format type:code
3081
						$location_parts = explode( ':', $location );
3082
						switch ( $location_parts[0] ) {
3083
							case 'state' :
3084
								$zone->add_location( $location_parts[1] . ':' . $location_parts[2], 'state' );
3085
							break;
3086
							case 'country' :
3087
								$zone->add_location( $location_parts[1], 'country' );
3088
							break;
3089
							case 'continent' :
3090
								$zone->add_location( $location_parts[1], 'continent' );
3091
							break;
3092
						}
3093
					}
3094
				}
3095
3096
				if ( isset( $zone_data['zone_postcodes'] ) ) {
3097
					$zone->clear_locations( 'postcode' );
3098
					$postcodes = array_filter( array_map( 'strtoupper', array_map( 'wc_clean', explode( "\n", $zone_data['zone_postcodes'] ) ) ) );
3099
					foreach ( $postcodes as $postcode ) {
3100
						$zone->add_location( $postcode, 'postcode' );
3101
					}
3102
				}
3103
3104
				$zone->save();
3105
			}
3106
		}
3107
3108
		wp_send_json_success( array(
3109
			'zones' => WC_Shipping_Zones::get_zones()
3110
		) );
3111
	}
3112
3113
	/**
3114
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
3115
	 */
3116
	public static function shipping_zone_add_method() {
3117
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['method_id'] ) ) {
3118
			wp_send_json_error( 'missing_fields' );
3119
			exit;
3120
		}
3121
3122
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3123
			wp_send_json_error( 'bad_nonce' );
3124
			exit;
3125
		}
3126
3127
		// Check User Caps
3128
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3129
			wp_send_json_error( 'missing_capabilities' );
3130
			exit;
3131
		}
3132
3133
		$zone_id     = absint( $_POST['zone_id'] );
3134
		$zone        = WC_Shipping_Zones::get_zone( $zone_id );
3135
		$instance_id = $zone->add_shipping_method( wc_clean( $_POST['method_id'] ) );
3136
3137
		wp_send_json_success( array(
3138
			'instance_id' => $instance_id,
3139
			'zone_id'     => $zone_id,
3140
			'methods'     => $zone->get_shipping_methods()
3141
		) );
3142
	}
3143
3144
	/**
3145
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
3146
	 */
3147
	public static function shipping_zone_methods_save_changes() {
3148
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['changes'] ) ) {
3149
			wp_send_json_error( 'missing_fields' );
3150
			exit;
3151
		}
3152
3153
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3154
			wp_send_json_error( 'bad_nonce' );
3155
			exit;
3156
		}
3157
3158
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3159
			wp_send_json_error( 'missing_capabilities' );
3160
			exit;
3161
		}
3162
3163
		global $wpdb;
3164
3165
		$zone_id = absint( $_POST['zone_id'] );
3166
		$zone    = new WC_Shipping_Zone( $zone_id );
3167
		$changes = $_POST['changes'];
3168
3169
		foreach ( $changes as $instance_id => $data ) {
3170
			$method_id = $wpdb->get_var( $wpdb->prepare( "SELECT method_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE instance_id = %d", $instance_id ) );
3171
3172
			if ( isset( $data['deleted'] ) ) {
3173
				$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
3174
				$option_key      = $shipping_method->get_instance_option_key();
3175
				if ( $wpdb->delete( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'instance_id' => $instance_id ) ) ) {
3176
					delete_option( $option_key );
3177
					do_action( 'woocommerce_shipping_zone_method_deleted', $instance_id, $method_id, $zone_id );
3178
				}
3179
				continue;
3180
			}
3181
3182
			$method_data = array_intersect_key( $data, array(
3183
				'method_order' => 1,
3184
				'enabled'      => 1
3185
			) );
3186
3187
			if ( isset( $method_data['method_order'] ) ) {
3188
				$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'method_order' => absint( $method_data['method_order'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
3189
			}
3190
3191
			if ( isset( $method_data['enabled'] ) ) {
3192
				$is_enabled = absint( 'yes' === $method_data['enabled'] );
3193
				if ( $wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'is_enabled' => $is_enabled ), array( 'instance_id' => absint( $instance_id ) ) ) ) {
3194
					do_action( 'woocommerce_shipping_zone_method_status_toggled', $instance_id, $method_id, $zone_id, $is_enabled );
3195
				}
3196
			}
3197
		}
3198
3199
		wp_send_json_success( array(
3200
			'methods' => $zone->get_shipping_methods()
3201
		) );
3202
	}
3203
3204
	/**
3205
	 * Save method settings
3206
	 */
3207
	public static function shipping_zone_methods_save_settings() {
3208
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['instance_id'], $_POST['data'] ) ) {
3209
			wp_send_json_error( 'missing_fields' );
3210
			exit;
3211
		}
3212
3213
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3214
			wp_send_json_error( 'bad_nonce' );
3215
			exit;
3216
		}
3217
3218
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3219
			wp_send_json_error( 'missing_capabilities' );
3220
			exit;
3221
		}
3222
3223
		$instance_id     = absint( $_POST['instance_id'] );
3224
		$zone            = WC_Shipping_Zones::get_zone_by( 'instance_id', $instance_id );
3225
		$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
3226
		$shipping_method->set_post_data( $_POST['data'] );
3227
		$shipping_method->process_admin_options();
3228
3229
		wp_send_json_success( array(
3230
			'methods' => $zone->get_shipping_methods(),
3231
			'errors'  => $shipping_method->get_errors(),
3232
		) );
3233
	}
3234
3235
	/**
3236
	 * Handle submissions from assets/js/wc-shipping-classes.js Backbone model.
3237
	 */
3238
	public static function shipping_classes_save_changes() {
3239
		if ( ! isset( $_POST['wc_shipping_classes_nonce'], $_POST['changes'] ) ) {
3240
			wp_send_json_error( 'missing_fields' );
3241
			exit;
3242
		}
3243
3244
		if ( ! wp_verify_nonce( $_POST['wc_shipping_classes_nonce'], 'wc_shipping_classes_nonce' ) ) {
3245
			wp_send_json_error( 'bad_nonce' );
3246
			exit;
3247
		}
3248
3249
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3250
			wp_send_json_error( 'missing_capabilities' );
3251
			exit;
3252
		}
3253
3254
		$changes = $_POST['changes'];
3255
3256
		foreach ( $changes as $term_id => $data ) {
3257
			$term_id = absint( $term_id );
3258
3259
			if ( isset( $data['deleted'] ) ) {
3260
				if ( isset( $data['newRow'] ) ) {
3261
					// So the user added and deleted a new row.
3262
					// That's fine, it's not in the database anyways. NEXT!
3263
					continue;
3264
				}
3265
				wp_delete_term( $term_id, 'product_shipping_class' );
3266
				continue;
3267
			}
3268
3269
			$update_args = array();
3270
3271
			if ( isset( $data['name'] ) ) {
3272
				$update_args['name'] = wc_clean( $data['name'] );
3273
			}
3274
3275
			if ( isset( $data['slug'] ) ) {
3276
				$update_args['slug'] = wc_clean( $data['slug'] );
3277
			}
3278
3279
			if ( isset( $data['description'] ) ) {
3280
				$update_args['description'] = wc_clean( $data['description'] );
3281
			}
3282
3283
			if ( isset( $data['newRow'] ) ) {
3284
				$update_args = array_filter( $update_args );
3285
				if ( empty( $update_args['name'] ) ) {
3286
					continue;
3287
				}
3288
				$term_id = wp_insert_term( $update_args['name'], 'product_shipping_class', $update_args );
3289
			} else {
3290
				wp_update_term( $term_id, 'product_shipping_class', $update_args );
3291
			}
3292
3293
			do_action( 'woocommerce_shipping_classes_save_class', $term_id, $data );
3294
		}
3295
3296
		$wc_shipping = WC_Shipping::instance();
3297
3298
		wp_send_json_success( array(
3299
			'shipping_classes' => $wc_shipping->get_shipping_classes()
3300
		) );
3301
	}
3302
}
3303
3304
WC_AJAX::init();
3305