Completed
Pull Request — master (#11645)
by Mike
07:59
created

WC_AJAX::add_to_cart()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
dl 0
loc 33
rs 8.439
c 0
b 0
f 0
eloc 17
nc 6
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_country( $_POST['country'] );
314
		}
315
316
		if ( isset( $_POST['state'] ) ) {
317
			WC()->customer->set_state( $_POST['state'] );
318
		}
319
320
		if ( isset( $_POST['postcode'] ) ) {
321
			WC()->customer->set_postcode( $_POST['postcode'] );
322
		}
323
324
		if ( isset( $_POST['city'] ) ) {
325
			WC()->customer->set_city( $_POST['city'] );
326
		}
327
328
		if ( isset( $_POST['address'] ) ) {
329
			WC()->customer->set_address( $_POST['address'] );
330
		}
331
332
		if ( isset( $_POST['address_2'] ) ) {
333
			WC()->customer->set_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->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->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()->cart->calculate_totals();
391
392
		// Get order review fragment
393
		ob_start();
394
		woocommerce_order_review();
395
		$woocommerce_order_review = ob_get_clean();
396
397
		// Get checkout payment fragment
398
		ob_start();
399
		woocommerce_checkout_payment();
400
		$woocommerce_checkout_payment = ob_get_clean();
401
402
		// Get messages if reload checkout is not true
403
		$messages = '';
404
		if ( ! isset( WC()->session->reload_checkout ) ) {
405
			ob_start();
406
			wc_print_notices();
407
			$messages = ob_get_clean();
408
		}
409
410
		$data = array(
411
			'result'    => empty( $messages ) ? 'success' : 'failure',
412
			'messages'  => $messages,
413
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
414
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
415
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
416
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
417
			) )
418
		);
419
420
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
421
422
		wp_send_json( $data );
423
424
		die();
425
	}
426
427
	/**
428
	 * AJAX add to cart.
429
	 */
430
	public static function add_to_cart() {
431
		ob_start();
432
433
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
434
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
435
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
436
		$product_status    = get_post_status( $product_id );
437
438
		if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) && 'publish' === $product_status ) {
439
440
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
441
442
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
443
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
444
			}
445
446
			// Return fragments
447
			self::get_refreshed_fragments();
448
449
		} else {
450
451
			// If there was an error adding to the cart, redirect to the product page to show any errors
452
			$data = array(
453
				'error'       => true,
454
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
455
			);
456
457
			wp_send_json( $data );
458
459
		}
460
461
		die();
462
	}
463
464
	/**
465
	 * Process ajax checkout form.
466
	 */
467
	public static function checkout() {
468
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
469
			define( 'WOOCOMMERCE_CHECKOUT', true );
470
		}
471
472
		WC()->checkout()->process_checkout();
473
474
		die(0);
475
	}
476
477
	/**
478
	 * Get a matching variation based on posted attributes.
479
	 */
480
	public static function get_variation() {
481
		ob_start();
482
483
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
484
			die();
485
		}
486
487
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
488
489
		if ( $variation_id ) {
490
			$variation = $variable_product->get_available_variation( $variation_id );
491
		} else {
492
			$variation = false;
493
		}
494
495
		wp_send_json( $variation );
496
497
		die();
498
	}
499
500
	/**
501
	 * Feature a product from admin.
502
	 */
503
	public static function feature_product() {
504
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
505
			$product_id = absint( $_GET['product_id'] );
506
507
			if ( 'product' === get_post_type( $product_id ) ) {
508
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
509
510
				delete_transient( 'wc_featured_products' );
511
			}
512
		}
513
514
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=product' ) );
515
		die();
516
	}
517
518
	/**
519
	 * Mark an order with a status.
520
	 */
521
	public static function mark_order_status() {
522
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
523
			$status   = sanitize_text_field( $_GET['status'] );
524
			$order_id = absint( $_GET['order_id'] );
525
526
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
527
				$order = wc_get_order( $order_id );
528
				$order->update_status( $status, '', true );
529
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
530
			}
531
		}
532
533
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
534
		die();
535
	}
536
537
	/**
538
	 * Add an attribute row.
539
	 */
540
	public static function add_attribute() {
541
		ob_start();
542
543
		check_ajax_referer( 'add-attribute', 'security' );
544
545
		if ( ! current_user_can( 'edit_products' ) ) {
546
			die(-1);
547
		}
548
549
		global $wc_product_attributes;
550
551
		$thepostid     = 0;
552
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
553
		$i             = absint( $_POST['i'] );
554
		$position      = 0;
555
		$metabox_class = array();
556
		$attribute     = array(
557
			'name'         => $taxonomy,
558
			'value'        => '',
559
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
560
			'is_variation' => apply_filters( 'woocommerce_attribute_default_is_variation', 0 ),
561
			'is_taxonomy'  => $taxonomy ? 1 : 0
562
		);
563
564
		if ( $taxonomy ) {
565
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
566
			$metabox_class[]    = 'taxonomy';
567
			$metabox_class[]    = $taxonomy;
568
			$attribute_label    = wc_attribute_label( $taxonomy );
569
		} else {
570
			$attribute_label = '';
571
		}
572
573
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
574
		die();
575
	}
576
577
	/**
578
	 * Add a new attribute via ajax function.
579
	 */
580
	public static function add_new_attribute() {
581
		ob_start();
582
583
		check_ajax_referer( 'add-attribute', 'security' );
584
585
		if ( ! current_user_can( 'manage_product_terms' ) ) {
586
			die(-1);
587
		}
588
589
		$taxonomy = esc_attr( $_POST['taxonomy'] );
590
		$term     = wc_clean( $_POST['term'] );
591
592
		if ( taxonomy_exists( $taxonomy ) ) {
593
594
			$result = wp_insert_term( $term, $taxonomy );
595
596
			if ( is_wp_error( $result ) ) {
597
				wp_send_json( array(
598
					'error' => $result->get_error_message()
599
				) );
600
			} else {
601
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
602
				wp_send_json( array(
603
					'term_id' => $term->term_id,
604
					'name'    => $term->name,
605
					'slug'    => $term->slug
606
				) );
607
			}
608
		}
609
610
		die();
611
	}
612
613
	/**
614
	 * Delete variations via ajax function.
615
	 */
616
	public static function remove_variations() {
617
		check_ajax_referer( 'delete-variations', 'security' );
618
619
		if ( ! current_user_can( 'edit_products' ) ) {
620
			die(-1);
621
		}
622
623
		$variation_ids = (array) $_POST['variation_ids'];
624
625
		foreach ( $variation_ids as $variation_id ) {
626
			$variation = get_post( $variation_id );
627
628
			if ( $variation && 'product_variation' == $variation->post_type ) {
629
				wp_delete_post( $variation_id );
630
			}
631
		}
632
633
		die();
634
	}
635
636
	/**
637
	 * Save attributes via ajax.
638
	 */
639
	public static function save_attributes() {
640
641
		check_ajax_referer( 'save-attributes', 'security' );
642
643
		if ( ! current_user_can( 'edit_products' ) ) {
644
			die(-1);
645
		}
646
647
		// Get post data
648
		parse_str( $_POST['data'], $data );
649
		$post_id = absint( $_POST['post_id'] );
650
651
		// Save Attributes
652
		$attributes = array();
653
654
		if ( isset( $data['attribute_names'] ) ) {
655
656
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
657
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
658
659
			if ( isset( $data['attribute_visibility'] ) ) {
660
				$attribute_visibility = $data['attribute_visibility'];
661
			}
662
663
			if ( isset( $data['attribute_variation'] ) ) {
664
				$attribute_variation = $data['attribute_variation'];
665
			}
666
667
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
668
			$attribute_position      = $data['attribute_position'];
669
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
670
671
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
672
				if ( empty( $attribute_names[ $i ] ) ) {
673
					continue;
674
				}
675
676
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
677
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
678
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
679
680
				if ( $is_taxonomy ) {
681
682
					if ( isset( $attribute_values[ $i ] ) ) {
683
684
						// Select based attributes - Format values (posted values are slugs)
685
						if ( is_array( $attribute_values[ $i ] ) ) {
686
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
687
688
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
689
						} else {
690
							$values     = array();
691
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
692
693
							foreach ( $raw_values as $value ) {
694
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
695
								if ( ! $term ) {
696
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
697
698
									if ( $term && ! is_wp_error( $term ) ) {
699
										$values[] = $term['term_id'];
700
									}
701
								} else {
702
									$values[] = $term->term_id;
703
								}
704
							}
705
						}
706
707
						// Remove empty items in the array
708
						$values = array_filter( $values, 'strlen' );
709
710
					} else {
711
						$values = array();
712
					}
713
714
					// Update post terms
715
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
716
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
717
					}
718
719 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...
720
						// Add attribute to array, but don't set values
721
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
722
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
723
							'value' 		=> '',
724
							'position' 		=> $attribute_position[ $i ],
725
							'is_visible' 	=> $is_visible,
726
							'is_variation' 	=> $is_variation,
727
							'is_taxonomy' 	=> $is_taxonomy
728
						);
729
					}
730
731 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...
732
733
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
734
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
735
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
736
737
					// Custom attribute - Add attribute to array and set the values
738
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
739
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
740
						'value' 		=> $values,
741
						'position' 		=> $attribute_position[ $i ],
742
						'is_visible' 	=> $is_visible,
743
						'is_variation' 	=> $is_variation,
744
						'is_taxonomy' 	=> $is_taxonomy
745
					);
746
				}
747
748
			 }
749
		}
750
751
		uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
752
753
		update_post_meta( $post_id, '_product_attributes', $attributes );
754
755
		die();
756
	}
757
758
	/**
759
	 * Add variation via ajax function.
760
	 */
761
	public static function add_variation() {
762
763
		check_ajax_referer( 'add-variation', 'security' );
764
765
		if ( ! current_user_can( 'edit_products' ) ) {
766
			die(-1);
767
		}
768
769
		global $post;
770
771
		$post_id = intval( $_POST['post_id'] );
772
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
773
		$loop    = intval( $_POST['loop'] );
774
775
		$variation = array(
776
			'post_title'   => 'Product #' . $post_id . ' Variation',
777
			'post_content' => '',
778
			'post_status'  => 'publish',
779
			'post_author'  => get_current_user_id(),
780
			'post_parent'  => $post_id,
781
			'post_type'    => 'product_variation',
782
			'menu_order'   => -1
783
		);
784
785
		$variation_id = wp_insert_post( $variation );
786
787
		do_action( 'woocommerce_create_product_variation', $variation_id );
788
789
		if ( $variation_id ) {
790
			$variation        = get_post( $variation_id );
791
			$variation_meta   = get_post_meta( $variation_id );
792
			$variation_data   = array();
793
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
794
			$variation_fields = array(
795
				'_sku'                   => '',
796
				'_stock'                 => '',
797
				'_regular_price'         => '',
798
				'_sale_price'            => '',
799
				'_weight'                => '',
800
				'_length'                => '',
801
				'_width'                 => '',
802
				'_height'                => '',
803
				'_download_limit'        => '',
804
				'_download_expiry'       => '',
805
				'_downloadable_files'    => '',
806
				'_downloadable'          => '',
807
				'_virtual'               => '',
808
				'_thumbnail_id'          => '',
809
				'_sale_price_dates_from' => '',
810
				'_sale_price_dates_to'   => '',
811
				'_manage_stock'          => '',
812
				'_stock_status'          => '',
813
				'_backorders'            => null,
814
				'_tax_class'             => null,
815
				'_variation_description' => ''
816
			);
817
818 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...
819
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
820
			}
821
822
			// Add the variation attributes
823
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
824
825
			// Formatting
826
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
827
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
828
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
829
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
830
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
831
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
832
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
833
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
834
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
835
			$variation_data['menu_order']     = $variation->menu_order;
836
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
837
838
			// Get tax classes
839
			$tax_classes           = WC_Tax::get_tax_classes();
840
			$tax_class_options     = array();
841
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
842
843 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...
844
				foreach ( $tax_classes as $class ) {
845
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
846
				}
847
			}
848
849
			// Set backorder options
850
			$backorder_options = array(
851
				'no'     => __( 'Do not allow', 'woocommerce' ),
852
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
853
				'yes'    => __( 'Allow', 'woocommerce' )
854
			);
855
856
			// set stock status options
857
			$stock_status_options = array(
858
				'instock'    => __( 'In stock', 'woocommerce' ),
859
				'outofstock' => __( 'Out of stock', 'woocommerce' )
860
			);
861
862
			// Get attributes
863
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
864
865
			$parent_data = array(
866
				'id'                   => $post_id,
867
				'attributes'           => $attributes,
868
				'tax_class_options'    => $tax_class_options,
869
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
870
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
871
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
872
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
873
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
874
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
875
				'backorder_options'    => $backorder_options,
876
				'stock_status_options' => $stock_status_options
877
			);
878
879
			if ( ! $parent_data['weight'] ) {
880
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
881
			}
882
883
			if ( ! $parent_data['length'] ) {
884
				$parent_data['length'] = wc_format_localized_decimal( 0 );
885
			}
886
887
			if ( ! $parent_data['width'] ) {
888
				$parent_data['width'] = wc_format_localized_decimal( 0 );
889
			}
890
891
			if ( ! $parent_data['height'] ) {
892
				$parent_data['height'] = wc_format_localized_decimal( 0 );
893
			}
894
895
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
896
		}
897
898
		die();
899
	}
900
901
	/**
902
	 * Link all variations via ajax function.
903
	 */
904
	public static function link_all_variations() {
905
906
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
907
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
908
		}
909
910
		check_ajax_referer( 'link-variations', 'security' );
911
912
		if ( ! current_user_can( 'edit_products' ) ) {
913
			die(-1);
914
		}
915
916
		wc_set_time_limit( 0 );
917
918
		$post_id = intval( $_POST['post_id'] );
919
920
		if ( ! $post_id ) {
921
			die();
922
		}
923
924
		$variations = array();
925
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
926
927
		// Put variation attributes into an array
928
		foreach ( $_product->get_attributes() as $attribute ) {
929
930
			if ( ! $attribute['is_variation'] ) {
931
				continue;
932
			}
933
934
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
935
936
			if ( $attribute['is_taxonomy'] ) {
937
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
938
			} else {
939
				$options = explode( WC_DELIMITER, $attribute['value'] );
940
			}
941
942
			$options = array_map( 'trim', $options );
943
944
			$variations[ $attribute_field_name ] = $options;
945
		}
946
947
		// Quit out if none were found
948
		if ( sizeof( $variations ) == 0 ) {
949
			die();
950
		}
951
952
		// Get existing variations so we don't create duplicates
953
		$available_variations = array();
954
955
		foreach( $_product->get_children() as $child_id ) {
956
			$child = $_product->get_child( $child_id );
957
958
			if ( ! empty( $child->variation_id ) ) {
959
				$available_variations[] = $child->get_variation_attributes();
960
			}
961
		}
962
963
		// Created posts will all have the following data
964
		$variation_post_data = array(
965
			'post_title'   => 'Product #' . $post_id . ' Variation',
966
			'post_content' => '',
967
			'post_status'  => 'publish',
968
			'post_author'  => get_current_user_id(),
969
			'post_parent'  => $post_id,
970
			'post_type'    => 'product_variation'
971
		);
972
973
		$variation_ids       = array();
974
		$added               = 0;
975
		$possible_variations = wc_array_cartesian( $variations );
976
977
		foreach ( $possible_variations as $variation ) {
978
979
			// Check if variation already exists
980
			if ( in_array( $variation, $available_variations ) ) {
981
				continue;
982
			}
983
984
			$variation_id = wp_insert_post( $variation_post_data );
985
986
			$variation_ids[] = $variation_id;
987
988
			foreach ( $variation as $key => $value ) {
989
				update_post_meta( $variation_id, $key, $value );
990
			}
991
992
			// Save stock status
993
			update_post_meta( $variation_id, '_stock_status', 'instock' );
994
995
			$added++;
996
997
			do_action( 'product_variation_linked', $variation_id );
998
999
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
1000
				break;
1001
			}
1002
		}
1003
1004
		delete_transient( 'wc_product_children_' . $post_id );
1005
1006
		echo $added;
1007
1008
		die();
1009
	}
1010
1011
	/**
1012
	 * Delete download permissions via ajax function.
1013
	 */
1014
	public static function revoke_access_to_download() {
1015
		check_ajax_referer( 'revoke-access', 'security' );
1016
1017
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1018
			die( -1 );
1019
		}
1020
1021
		global $wpdb;
1022
1023
		$download_id   = $_POST['download_id'];
1024
		$product_id    = intval( $_POST['product_id'] );
1025
		$order_id      = intval( $_POST['order_id'] );
1026
		$permission_id = absint( $_POST['permission_id'] );
1027
1028
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d;", $permission_id ) );
1029
1030
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id, $permission_id );
1031
1032
		die();
1033
	}
1034
1035
	/**
1036
	 * Grant download permissions via ajax function.
1037
	 */
1038
	public static function grant_access_to_download() {
1039
1040
		check_ajax_referer( 'grant-access', 'security' );
1041
1042
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1043
			die(-1);
1044
		}
1045
1046
		global $wpdb;
1047
1048
		$wpdb->hide_errors();
1049
1050
		$order_id     = intval( $_POST['order_id'] );
1051
		$product_ids  = $_POST['product_ids'];
1052
		$loop         = intval( $_POST['loop'] );
1053
		$file_counter = 0;
1054
		$order        = wc_get_order( $order_id );
1055
1056
		if ( ! is_array( $product_ids ) ) {
1057
			$product_ids = array( $product_ids );
1058
		}
1059
1060
		foreach ( $product_ids as $product_id ) {
1061
			$product = wc_get_product( $product_id );
1062
			$files   = $product->get_files();
1063
1064
			if ( ! $order->get_billing_email() ) {
1065
				die();
1066
			}
1067
1068
			if ( ! empty( $files ) ) {
1069
				foreach ( $files as $download_id => $file ) {
1070
					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...
1071
1072
						// insert complete - get inserted data
1073
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1074
1075
						$loop ++;
1076
						$file_counter ++;
1077
1078 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...
1079
							$file_count = $file['name'];
1080
						} else {
1081
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1082
						}
1083
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1084
					}
1085
				}
1086
			}
1087
		}
1088
1089
		die();
1090
	}
1091
1092
	/**
1093
	 * Get customer details via ajax.
1094
	 */
1095
	public static function get_customer_details() {
1096
		ob_start();
1097
1098
		check_ajax_referer( 'get-customer-details', 'security' );
1099
1100
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1101
			die(-1);
1102
		}
1103
1104
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1105
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1106
1107
		$customer_data = array(
1108
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1109
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1110
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1111
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1112
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1113
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1114
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1115
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1116
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1117
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1118
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1119
		);
1120
1121
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1122
1123
		wp_send_json( $customer_data );
1124
	}
1125
1126
	/**
1127
	 * Add order item via ajax.
1128
	 */
1129
	public static function add_order_item() {
1130
		check_ajax_referer( 'order-item', 'security' );
1131
1132
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1133
			die(-1);
1134
		}
1135
1136
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1137
		$order_id    = absint( $_POST['order_id'] );
1138
1139
		// Find the item
1140
		if ( ! is_numeric( $item_to_add ) ) {
1141
			die();
1142
		}
1143
1144
		$post = get_post( $item_to_add );
1145
1146
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1147
			die();
1148
		}
1149
1150
		$product     = wc_get_product( $post->ID );
1151
		$order       = wc_get_order( $order_id );
1152
		$order_taxes = $order->get_taxes();
1153
		$class       = 'new_row';
1154
		$item_id     = $order->add_product( $product );
1155
		$item        = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id );
1156
1157
		do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1158
1159
		include( 'admin/meta-boxes/views/html-order-item.php' );
1160
		die();
1161
	}
1162
1163
	/**
1164
	 * Add order fee via ajax.
1165
	 */
1166
	public static function add_order_fee() {
1167
1168
		check_ajax_referer( 'order-item', 'security' );
1169
1170
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1171
			die(-1);
1172
		}
1173
1174
		$order_id      = absint( $_POST['order_id'] );
1175
		$order         = wc_get_order( $order_id );
1176
		$order_taxes   = $order->get_taxes();
1177
		$item          = array();
1178
1179
		// Add new fee
1180
		$fee            = new stdClass();
1181
		$fee->name      = '';
1182
		$fee->tax_class = '';
1183
		$fee->taxable   = $fee->tax_class !== '0';
1184
		$fee->amount    = '';
1185
		$fee->tax       = '';
1186
		$fee->tax_data  = array();
1187
		$item_id        = $order->add_fee( $fee );
1188
1189
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1190
1191
		// Quit out
1192
		die();
1193
	}
1194
1195
	/**
1196
	 * Add order shipping cost via ajax.
1197
	 */
1198
	public static function add_order_shipping() {
1199
1200
		check_ajax_referer( 'order-item', 'security' );
1201
1202
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1203
			die(-1);
1204
		}
1205
1206
		$order_id         = absint( $_POST['order_id'] );
1207
		$order            = wc_get_order( $order_id );
1208
		$order_taxes      = $order->get_taxes();
1209
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1210
1211
		// Add new shipping
1212
		$shipping = new WC_Shipping_Rate();
1213
		$item_id  = $order->add_shipping( $shipping );
1214
		$item     = $order->get_item( $item_id );
1215
1216
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1217
1218
		// Quit out
1219
		die();
1220
	}
1221
1222
	/**
1223
	 * Add order tax column via ajax.
1224
	 */
1225
	public static function add_order_tax() {
1226
		global $wpdb;
1227
1228
		check_ajax_referer( 'order-item', 'security' );
1229
1230
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1231
			die(-1);
1232
		}
1233
1234
		$order_id = absint( $_POST['order_id'] );
1235
		$rate_id  = absint( $_POST['rate_id'] );
1236
		$order    = wc_get_order( $order_id );
1237
		$data     = get_post_meta( $order_id );
1238
1239
		// Add new tax
1240
		$order->add_tax( $rate_id, 0, 0 );
1241
1242
		// Return HTML items
1243
		include( 'admin/meta-boxes/views/html-order-items.php' );
1244
1245
		die();
1246
	}
1247
1248
	/**
1249
	 * Remove an order item.
1250
	 */
1251
	public static function remove_order_item() {
1252
		check_ajax_referer( 'order-item', 'security' );
1253
1254
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1255
			die(-1);
1256
		}
1257
1258
		$order_item_ids = $_POST['order_item_ids'];
1259
1260
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1261
			$order_item_ids = array( $order_item_ids );
1262
		}
1263
1264
		if ( sizeof( $order_item_ids ) > 0 ) {
1265
			foreach( $order_item_ids as $id ) {
1266
				wc_delete_order_item( absint( $id ) );
1267
			}
1268
		}
1269
1270
		die();
1271
	}
1272
1273
	/**
1274
	 * Remove an order tax.
1275
	 */
1276
	public static function remove_order_tax() {
1277
1278
		check_ajax_referer( 'order-item', 'security' );
1279
1280
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1281
			die(-1);
1282
		}
1283
1284
		$order_id = absint( $_POST['order_id'] );
1285
		$rate_id  = absint( $_POST['rate_id'] );
1286
1287
		wc_delete_order_item( $rate_id );
1288
1289
		// Return HTML items
1290
		$order = wc_get_order( $order_id );
1291
		$data  = get_post_meta( $order_id );
1292
		include( 'admin/meta-boxes/views/html-order-items.php' );
1293
1294
		die();
1295
	}
1296
1297
	/**
1298
	 * Reduce order item stock.
1299
	 */
1300
	public static function reduce_order_item_stock() {
1301
		check_ajax_referer( 'order-item', 'security' );
1302
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1303
			die(-1);
1304
		}
1305
		$order_id       = absint( $_POST['order_id'] );
1306
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1307
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1308
		$order          = wc_get_order( $order_id );
1309
		$order_items    = $order->get_items();
1310
		$return         = array();
1311
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1312
			foreach ( $order_items as $item_id => $order_item ) {
1313
				// Only reduce checked items
1314
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1315
					continue;
1316
				}
1317
				$_product = $order->get_product_from_item( $order_item );
1318
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1319
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1320
					$new_stock    = $_product->reduce_stock( $stock_change );
1321
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1322
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1323
					$return[]     = $note;
1324
					$order->add_order_note( $note );
1325
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1326
				}
1327
			}
1328
			do_action( 'woocommerce_reduce_order_stock', $order );
1329
			if ( empty( $return ) ) {
1330
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1331
			}
1332
			echo implode( ', ', $return );
1333
		}
1334
		die();
1335
	}
1336
1337
	/**
1338
	 * Increase order item stock.
1339
	 */
1340
	public static function increase_order_item_stock() {
1341
		check_ajax_referer( 'order-item', 'security' );
1342
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1343
			die(-1);
1344
		}
1345
		$order_id       = absint( $_POST['order_id'] );
1346
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1347
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1348
		$order          = wc_get_order( $order_id );
1349
		$order_items    = $order->get_items();
1350
		$return         = array();
1351
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1352
			foreach ( $order_items as $item_id => $order_item ) {
1353
				// Only reduce checked items
1354
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1355
					continue;
1356
				}
1357
				$_product = $order->get_product_from_item( $order_item );
1358
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1359
					$old_stock    = $_product->get_stock_quantity();
1360
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1361
					$new_quantity = $_product->increase_stock( $stock_change );
1362
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1363
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1364
					$return[]     = $note;
1365
					$order->add_order_note( $note );
1366
				}
1367
			}
1368
			do_action( 'woocommerce_restore_order_stock', $order );
1369
			if ( empty( $return ) ) {
1370
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1371
			}
1372
			echo implode( ', ', $return );
1373
		}
1374
		die();
1375
	}
1376
1377
	/**
1378
	 * Add some meta to a line item.
1379
	 */
1380
	public static function add_order_item_meta() {
1381
		check_ajax_referer( 'order-item', 'security' );
1382
1383
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1384
			die(-1);
1385
		}
1386
1387
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1388
1389
		if ( $meta_id ) {
1390
			echo '<tr data-meta_id="' . esc_attr( $meta_id ) . '"><td><input type="text" name="meta_key[' . $meta_id . ']" /><textarea name="meta_value[' . $meta_id . ']"></textarea></td><td width="1%"><button class="remove_order_item_meta button">&times;</button></td></tr>';
1391
		}
1392
1393
		die();
1394
	}
1395
1396
	/**
1397
	 * Remove meta from a line item.
1398
	 */
1399
	public static function remove_order_item_meta() {
1400
		check_ajax_referer( 'order-item', 'security' );
1401
1402
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1403
			die(-1);
1404
		}
1405
1406
		global $wpdb;
1407
1408
		$wpdb->delete( "{$wpdb->prefix}woocommerce_order_itemmeta", array(
1409
			'meta_id' => absint( $_POST['meta_id'] ),
1410
		) );
1411
1412
		die();
1413
	}
1414
1415
	/**
1416
	 * Calc line tax.
1417
	 */
1418
	public static function calc_line_taxes() {
1419
		global $wpdb;
1420
1421
		check_ajax_referer( 'calc-totals', 'security' );
1422
1423
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1424
			die(-1);
1425
		}
1426
1427
		$tax                    = new WC_Tax();
1428
		$tax_based_on           = get_option( 'woocommerce_tax_based_on' );
1429
		$order_id               = absint( $_POST['order_id'] );
1430
		$items                  = array();
1431
		$country                = strtoupper( esc_attr( $_POST['country'] ) );
1432
		$state                  = strtoupper( esc_attr( $_POST['state'] ) );
1433
		$postcode               = strtoupper( esc_attr( $_POST['postcode'] ) );
1434
		$city                   = wc_clean( esc_attr( $_POST['city'] ) );
1435
		$order                  = wc_get_order( $order_id );
1436
		$taxes                  = array();
1437
		$shipping_taxes         = array();
1438
		$order_item_tax_classes = array();
1439
1440
		// Default to base
1441
		if ( 'base' === $tax_based_on || empty( $country ) ) {
1442
			$default  = wc_get_base_location();
1443
			$country  = $default['country'];
1444
			$state    = $default['state'];
1445
			$postcode = '';
1446
			$city     = '';
1447
		}
1448
1449
		// Parse the jQuery serialized items
1450
		parse_str( $_POST['items'], $items );
1451
1452
		// Prevent undefined warnings
1453
		if ( ! isset( $items['line_tax'] ) ) {
1454
			$items['line_tax'] = array();
1455
		}
1456
		if ( ! isset( $items['line_subtotal_tax'] ) ) {
1457
			$items['line_subtotal_tax'] = array();
1458
		}
1459
		$items['order_taxes'] = array();
1460
1461
		// Action
1462
		$items = apply_filters( 'woocommerce_ajax_calc_line_taxes', $items, $order_id, $country, $_POST );
1463
1464
		$is_vat_exempt = get_post_meta( $order_id, '_is_vat_exempt', true );
1465
1466
		// Tax is calculated only if tax is enabled and order is not vat exempted
1467
		if ( wc_tax_enabled() && $is_vat_exempt !== 'yes' ) {
1468
1469
			// Get items and fees taxes
1470
			if ( isset( $items['order_item_id'] ) ) {
1471
				$line_total = $line_subtotal = array();
1472
1473
				foreach ( $items['order_item_id'] as $item_id ) {
1474
					$item_id                            = absint( $item_id );
1475
					$line_total[ $item_id ]             = isset( $items['line_total'][ $item_id ] ) ? wc_format_decimal( $items['line_total'][ $item_id ] ) : 0;
1476
					$line_subtotal[ $item_id ]          = isset( $items['line_subtotal'][ $item_id ] ) ? wc_format_decimal( $items['line_subtotal'][ $item_id ] ) : $line_total[ $item_id ];
1477
					$order_item_tax_classes[ $item_id ] = isset( $items['order_item_tax_class'][ $item_id ] ) ? sanitize_text_field( $items['order_item_tax_class'][ $item_id ] ) : '';
1478
					$product_id                         = $order->get_item_meta( $item_id, '_product_id', true );
1479
1480
					// Get product details
1481
					if ( get_post_type( $product_id ) == 'product' ) {
1482
						$_product        = wc_get_product( $product_id );
1483
						$item_tax_status = $_product->get_tax_status();
1484
					} else {
1485
						$item_tax_status = 'taxable';
1486
					}
1487
1488
					if ( '0' !== $order_item_tax_classes[ $item_id ] && 'taxable' === $item_tax_status ) {
1489
						$tax_rates = WC_Tax::find_rates( array(
1490
							'country'   => $country,
1491
							'state'     => $state,
1492
							'postcode'  => $postcode,
1493
							'city'      => $city,
1494
							'tax_class' => $order_item_tax_classes[ $item_id ]
1495
						) );
1496
1497
						$line_taxes          = WC_Tax::calc_tax( $line_total[ $item_id ], $tax_rates, false );
1498
						$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal[ $item_id ], $tax_rates, false );
1499
1500
						// Set the new line_tax
1501
						foreach ( $line_taxes as $_tax_id => $_tax_value ) {
1502
							$items['line_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1503
						}
1504
1505
						// Set the new line_subtotal_tax
1506
						foreach ( $line_subtotal_taxes as $_tax_id => $_tax_value ) {
1507
							$items['line_subtotal_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1508
						}
1509
1510
						// Sum the item taxes
1511
						foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1512
							$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1513
						}
1514
					}
1515
				}
1516
			}
1517
1518
			// Get shipping taxes
1519
			if ( isset( $items['shipping_method_id'] ) ) {
1520
				$matched_tax_rates      = array();
1521
				$order_item_tax_classes = array_unique( array_values( $order_item_tax_classes ) );
1522
1523
				// If multiple classes are found, use the first one. Don't bother with standard rate, we can get that later.
1524 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...
1525
					$tax_classes = WC_Tax::get_tax_classes();
1526
1527
					foreach ( $tax_classes as $tax_class ) {
1528
						$tax_class = sanitize_title( $tax_class );
1529
						if ( in_array( $tax_class, $order_item_tax_classes ) ) {
1530
							$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1531
								'country' 	=> $country,
1532
								'state' 	=> $state,
1533
								'postcode' 	=> $postcode,
1534
								'city' 		=> $city,
1535
								'tax_class' => $tax_class,
1536
							) );
1537
							break;
1538
						}
1539
					}
1540
				// If a single tax class is found, use it
1541
				} elseif ( sizeof( $order_item_tax_classes ) === 1 ) {
1542
					$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1543
						'country' 	=> $country,
1544
						'state' 	=> $state,
1545
						'postcode' 	=> $postcode,
1546
						'city' 		=> $city,
1547
						'tax_class' => $order_item_tax_classes[0]
1548
					) );
1549
				}
1550
1551
				// Get standard rate if no taxes were found
1552
				if ( ! sizeof( $matched_tax_rates ) ) {
1553
					$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1554
						'country' 	=> $country,
1555
						'state' 	=> $state,
1556
						'postcode' 	=> $postcode,
1557
						'city' 		=> $city
1558
					) );
1559
				}
1560
1561
				$shipping_cost = $shipping_taxes = array();
1562
1563
				foreach ( $items['shipping_method_id'] as $item_id ) {
1564
					$item_id                   = absint( $item_id );
1565
					$shipping_cost[ $item_id ] = isset( $items['shipping_cost'][ $item_id ] ) ? wc_format_decimal( $items['shipping_cost'][ $item_id ] ) : 0;
1566
					$_shipping_taxes           = WC_Tax::calc_shipping_tax( $shipping_cost[ $item_id ], $matched_tax_rates );
1567
1568
					// Set the new shipping_taxes
1569
					foreach ( $_shipping_taxes as $_tax_id => $_tax_value ) {
1570
						$items['shipping_taxes'][ $item_id ][ $_tax_id ] = $_tax_value;
1571
1572
						$shipping_taxes[ $_tax_id ] = isset( $shipping_taxes[ $_tax_id ] ) ? $shipping_taxes[ $_tax_id ] + $_tax_value : $_tax_value;
1573
					}
1574
				}
1575
			}
1576
		}
1577
1578
		// Remove old tax rows
1579
		$order->remove_order_items( 'tax' );
1580
1581
		// Add tax rows
1582
		foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_id ) {
1583
			$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 );
1584
		}
1585
1586
		// Create the new order_taxes
1587
		foreach ( $order->get_taxes() as $tax_id => $tax_item ) {
1588
			$items['order_taxes'][ $tax_id ] = absint( $tax_item['rate_id'] );
1589
		}
1590
1591
		$items = apply_filters( 'woocommerce_ajax_after_calc_line_taxes', $items, $order_id, $country, $_POST );
1592
1593
		// Save order items
1594
		wc_save_order_items( $order_id, $items );
1595
1596
		// Return HTML items
1597
		$order = wc_get_order( $order_id );
1598
		$data  = get_post_meta( $order_id );
1599
		include( 'admin/meta-boxes/views/html-order-items.php' );
1600
1601
		die();
1602
	}
1603
1604
	/**
1605
	 * Save order items via ajax.
1606
	 */
1607
	public static function save_order_items() {
1608
		check_ajax_referer( 'order-item', 'security' );
1609
1610
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1611
			die(-1);
1612
		}
1613
1614
		if ( isset( $_POST['order_id'] ) && isset( $_POST['items'] ) ) {
1615
			$order_id = absint( $_POST['order_id'] );
1616
1617
			// Parse the jQuery serialized items
1618
			$items = array();
1619
			parse_str( $_POST['items'], $items );
1620
1621
			// Save order items
1622
			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...
1623
1624
			// Return HTML items
1625
			$order = wc_get_order( $order_id );
1626
			$data  = get_post_meta( $order_id );
1627
			include( 'admin/meta-boxes/views/html-order-items.php' );
1628
		}
1629
1630
		die();
1631
	}
1632
1633
	/**
1634
	 * Load order items via ajax.
1635
	 */
1636
	public static function load_order_items() {
1637
		check_ajax_referer( 'order-item', 'security' );
1638
1639
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1640
			die(-1);
1641
		}
1642
1643
		// Return HTML items
1644
		$order_id = absint( $_POST['order_id'] );
1645
		$order    = wc_get_order( $order_id );
1646
		$data     = get_post_meta( $order_id );
1647
		include( 'admin/meta-boxes/views/html-order-items.php' );
1648
1649
		die();
1650
	}
1651
1652
	/**
1653
	 * Add order note via ajax.
1654
	 */
1655
	public static function add_order_note() {
1656
1657
		check_ajax_referer( 'add-order-note', 'security' );
1658
1659
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1660
			die(-1);
1661
		}
1662
1663
		$post_id   = absint( $_POST['post_id'] );
1664
		$note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
1665
		$note_type = $_POST['note_type'];
1666
1667
		$is_customer_note = $note_type == 'customer' ? 1 : 0;
1668
1669
		if ( $post_id > 0 ) {
1670
			$order      = wc_get_order( $post_id );
1671
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1672
1673
			echo '<li rel="' . esc_attr( $comment_id ) . '" class="note ';
1674
			if ( $is_customer_note ) {
1675
				echo 'customer-note';
1676
			}
1677
			echo '"><div class="note_content">';
1678
			echo wpautop( wptexturize( $note ) );
1679
			echo '</div><p class="meta"><a href="#" class="delete_note">'.__( 'Delete note', 'woocommerce' ).'</a></p>';
1680
			echo '</li>';
1681
		}
1682
1683
		// Quit out
1684
		die();
1685
	}
1686
1687
	/**
1688
	 * Delete order note via ajax.
1689
	 */
1690
	public static function delete_order_note() {
1691
1692
		check_ajax_referer( 'delete-order-note', 'security' );
1693
1694
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1695
			die(-1);
1696
		}
1697
1698
		$note_id = (int) $_POST['note_id'];
1699
1700
		if ( $note_id > 0 ) {
1701
			wp_delete_comment( $note_id );
1702
		}
1703
1704
		// Quit out
1705
		die();
1706
	}
1707
1708
	/**
1709
	 * Search for products and echo json.
1710
	 *
1711
	 * @param string $term (default: '')
1712
	 * @param string $post_types (default: array('product'))
1713
	 */
1714
	public static function json_search_products( $term = '', $post_types = array( 'product' ) ) {
1715
		global $wpdb;
1716
1717
		ob_start();
1718
1719
		check_ajax_referer( 'search-products', 'security' );
1720
1721
		if ( empty( $term ) ) {
1722
			$term = wc_clean( stripslashes( $_GET['term'] ) );
1723
		} else {
1724
			$term = wc_clean( $term );
1725
		}
1726
1727
		if ( empty( $term ) ) {
1728
			die();
1729
		}
1730
1731
		$like_term = '%' . $wpdb->esc_like( $term ) . '%';
1732
1733
		if ( is_numeric( $term ) ) {
1734
			$query = $wpdb->prepare( "
1735
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1736
				WHERE posts.post_status = 'publish'
1737
				AND (
1738
					posts.post_parent = %s
1739
					OR posts.ID = %s
1740
					OR posts.post_title LIKE %s
1741
					OR (
1742
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1743
					)
1744
				)
1745
			", $term, $term, $term, $like_term );
1746
		} else {
1747
			$query = $wpdb->prepare( "
1748
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1749
				WHERE posts.post_status = 'publish'
1750
				AND (
1751
					posts.post_title LIKE %s
1752
					or posts.post_content LIKE %s
1753
					OR (
1754
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1755
					)
1756
				)
1757
			", $like_term, $like_term, $like_term );
1758
		}
1759
1760
		$query .= " AND posts.post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "')";
1761
1762 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...
1763
			$query .= " AND posts.ID NOT IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['exclude'] ) ) ) . ")";
1764
		}
1765
1766 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...
1767
			$query .= " AND posts.ID IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['include'] ) ) ) . ")";
1768
		}
1769
1770
		if ( ! empty( $_GET['limit'] ) ) {
1771
			$query .= " LIMIT " . intval( $_GET['limit'] );
1772
		}
1773
1774
		$posts          = array_unique( $wpdb->get_col( $query ) );
1775
		$found_products = array();
1776
1777
		if ( ! empty( $posts ) ) {
1778
			foreach ( $posts as $post ) {
1779
				$product = wc_get_product( $post );
1780
1781
				if ( ! current_user_can( 'read_product', $post ) ) {
1782
					continue;
1783
				}
1784
1785
				if ( ! $product || ( $product->is_type( 'variation' ) && empty( $product->parent ) ) ) {
1786
					continue;
1787
				}
1788
1789
				$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1790
			}
1791
		}
1792
1793
		$found_products = apply_filters( 'woocommerce_json_search_found_products', $found_products );
1794
1795
		wp_send_json( $found_products );
1796
	}
1797
1798
	/**
1799
	 * Search for product variations and return json.
1800
	 *
1801
	 * @see WC_AJAX::json_search_products()
1802
	 */
1803
	public static function json_search_products_and_variations() {
1804
		self::json_search_products( '', array( 'product', 'product_variation' ) );
1805
	}
1806
1807
	/**
1808
	 * Search for grouped products and return json.
1809
	 */
1810
	public static function json_search_grouped_products() {
1811
		ob_start();
1812
1813
		check_ajax_referer( 'search-products', 'security' );
1814
1815
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1816
		$exclude = array();
1817
1818
		if ( empty( $term ) ) {
1819
			die();
1820
		}
1821
1822 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...
1823
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1824
		}
1825
1826
		$found_products = array();
1827
1828
		if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
1829
1830
			$posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
1831
1832
			if ( sizeof( $posts_in ) > 0 ) {
1833
1834
				$args = array(
1835
					'post_type'        => 'product',
1836
					'post_status'      => 'any',
1837
					'numberposts'      => -1,
1838
					'orderby'          => 'title',
1839
					'order'            => 'asc',
1840
					'post_parent'      => 0,
1841
					'suppress_filters' => 0,
1842
					'include'          => $posts_in,
1843
					's'                => $term,
1844
					'fields'           => 'ids',
1845
					'exclude'          => $exclude
1846
				);
1847
1848
				$posts = get_posts( $args );
1849
1850
				if ( ! empty( $posts ) ) {
1851
					foreach ( $posts as $post ) {
1852
						$product = wc_get_product( $post );
1853
1854
						if ( ! current_user_can( 'read_product', $post ) ) {
1855
							continue;
1856
						}
1857
1858
						$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1859
					}
1860
				}
1861
			}
1862
		}
1863
1864
		$found_products = apply_filters( 'woocommerce_json_search_found_grouped_products', $found_products );
1865
1866
		wp_send_json( $found_products );
1867
	}
1868
1869
	/**
1870
	 * Search for downloadable product variations and return json.
1871
	 *
1872
	 * @see WC_AJAX::json_search_products()
1873
	 */
1874
	public static function json_search_downloadable_products_and_variations() {
1875
		ob_start();
1876
1877
		check_ajax_referer( 'search-products', 'security' );
1878
1879
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1880
		$exclude = array();
1881
1882 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...
1883
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1884
		}
1885
1886
		$args = array(
1887
			'post_type'      => array( 'product', 'product_variation' ),
1888
			'posts_per_page' => -1,
1889
			'post_status'    => 'publish',
1890
			'order'          => 'ASC',
1891
			'orderby'        => 'parent title',
1892
			'meta_query'     => array(
1893
				array(
1894
					'key'   => '_downloadable',
1895
					'value' => 'yes'
1896
				)
1897
			),
1898
			's'              => $term,
1899
			'exclude'        => $exclude
1900
		);
1901
1902
		$posts = get_posts( $args );
1903
		$found_products = array();
1904
1905
		if ( ! empty( $posts ) ) {
1906
			foreach ( $posts as $post ) {
1907
				$product = wc_get_product( $post->ID );
1908
1909
				if ( ! current_user_can( 'read_product', $post->ID ) ) {
1910
					continue;
1911
				}
1912
1913
				$found_products[ $post->ID ] = $product->get_formatted_name();
1914
			}
1915
		}
1916
1917
		wp_send_json( $found_products );
1918
	}
1919
1920
	/**
1921
	 * Search for customers and return json.
1922
	 */
1923
	public static function json_search_customers() {
1924
		ob_start();
1925
1926
		check_ajax_referer( 'search-customers', 'security' );
1927
1928
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1929
			die(-1);
1930
		}
1931
1932
		$term    = wc_clean( stripslashes( $_GET['term'] ) );
1933
		$exclude = array();
1934
1935
		if ( empty( $term ) ) {
1936
			die();
1937
		}
1938
1939
		if ( ! empty( $_GET['exclude'] ) ) {
1940
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1941
		}
1942
1943
		$found_customers = array();
1944
1945
		add_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1946
1947
		$customers_query = new WP_User_Query( apply_filters( 'woocommerce_json_search_customers_query', array(
1948
			'fields'         => 'all',
1949
			'orderby'        => 'display_name',
1950
			'search'         => '*' . $term . '*',
1951
			'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename' )
1952
		) ) );
1953
1954
		remove_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1955
1956
		$customers = $customers_query->get_results();
1957
1958
		if ( ! empty( $customers ) ) {
1959
			foreach ( $customers as $customer ) {
1960
				if ( ! in_array( $customer->ID, $exclude ) ) {
1961
					$found_customers[ $customer->ID ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
1962
				}
1963
			}
1964
		}
1965
1966
		$found_customers = apply_filters( 'woocommerce_json_search_found_customers', $found_customers );
1967
1968
		wp_send_json( $found_customers );
1969
	}
1970
1971
	/**
1972
	 * When searching using the WP_User_Query, search names (user meta) too.
1973
	 * @param  object $query
1974
	 * @return object
1975
	 */
1976
	public static function json_search_customer_name( $query ) {
1977
		global $wpdb;
1978
1979
		$term = wc_clean( stripslashes( $_GET['term'] ) );
1980
		if ( method_exists( $wpdb, 'esc_like' ) ) {
1981
			$term = $wpdb->esc_like( $term );
1982
		} else {
1983
			$term = like_escape( $term );
1984
		}
1985
1986
		$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' ) ";
1987
		$query->query_where .= $wpdb->prepare( " OR user_name.meta_value LIKE %s ", '%' . $term . '%' );
1988
	}
1989
1990
	/**
1991
	 * Ajax request handling for categories ordering.
1992
	 */
1993
	public static function term_ordering() {
1994
1995
		// check permissions again and make sure we have what we need
1996
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
1997
			die(-1);
1998
		}
1999
2000
		$id       = (int) $_POST['id'];
2001
		$next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
2002
		$taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null;
2003
		$term     = get_term_by( 'id', $id, $taxonomy );
2004
2005
		if ( ! $id || ! $term || ! $taxonomy ) {
2006
			die(0);
2007
		}
2008
2009
		wc_reorder_terms( $term, $next_id, $taxonomy );
2010
2011
		$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
2012
2013
		if ( $term && sizeof( $children ) ) {
2014
			echo 'children';
2015
			die();
2016
		}
2017
	}
2018
2019
	/**
2020
	 * Ajax request handling for product ordering.
2021
	 *
2022
	 * Based on Simple Page Ordering by 10up (https://wordpress.org/extend/plugins/simple-page-ordering/).
2023
	 */
2024
	public static function product_ordering() {
2025
		global $wpdb;
2026
2027
		ob_start();
2028
2029
		// check permissions again and make sure we have what we need
2030
		if ( ! current_user_can('edit_products') || empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
2031
			die(-1);
2032
		}
2033
2034
		// real post?
2035
		if ( ! $post = get_post( $_POST['id'] ) ) {
2036
			die(-1);
2037
		}
2038
2039
		$previd  = isset( $_POST['previd'] ) ? $_POST['previd'] : false;
2040
		$nextid  = isset( $_POST['nextid'] ) ? $_POST['nextid'] : false;
2041
		$new_pos = array(); // store new positions for ajax
2042
2043
		$siblings = $wpdb->get_results( $wpdb->prepare( "
2044
			SELECT ID, menu_order FROM {$wpdb->posts} AS posts
2045
			WHERE 	posts.post_type 	= 'product'
2046
			AND 	posts.post_status 	IN ( 'publish', 'pending', 'draft', 'future', 'private' )
2047
			AND 	posts.ID			NOT IN (%d)
2048
			ORDER BY posts.menu_order ASC, posts.ID DESC
2049
		", $post->ID ) );
2050
2051
		$menu_order = 0;
2052
2053
		foreach ( $siblings as $sibling ) {
2054
2055
			// if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
2056 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...
2057
				$wpdb->update(
2058
					$wpdb->posts,
2059
					array(
2060
						'menu_order' => $menu_order
2061
					),
2062
					array( 'ID' => $post->ID ),
2063
					array( '%d' ),
2064
					array( '%d' )
2065
				);
2066
				$new_pos[ $post->ID ] = $menu_order;
2067
				$menu_order++;
2068
			}
2069
2070
			// if repositioned post has been set, and new items are already in the right order, we can stop
2071
			if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $menu_order ) {
2072
				break;
2073
			}
2074
2075
			// set the menu order of the current sibling and increment the menu order
2076
			$wpdb->update(
2077
				$wpdb->posts,
2078
				array(
2079
					'menu_order' => $menu_order
2080
				),
2081
				array( 'ID' => $sibling->ID ),
2082
				array( '%d' ),
2083
				array( '%d' )
2084
			);
2085
			$new_pos[ $sibling->ID ] = $menu_order;
2086
			$menu_order++;
2087
2088 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...
2089
				$wpdb->update(
2090
					$wpdb->posts,
2091
					array(
2092
						'menu_order' => $menu_order
2093
					),
2094
					array( 'ID' => $post->ID ),
2095
					array( '%d' ),
2096
					array( '%d' )
2097
				);
2098
				$new_pos[$post->ID] = $menu_order;
2099
				$menu_order++;
2100
			}
2101
2102
		}
2103
2104
		do_action( 'woocommerce_after_product_ordering' );
2105
2106
		wp_send_json( $new_pos );
2107
	}
2108
2109
	/**
2110
	 * Handle a refund via the edit order screen.
2111
	 */
2112
	public static function refund_line_items() {
2113
		ob_start();
2114
2115
		check_ajax_referer( 'order-item', 'security' );
2116
2117
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2118
			die(-1);
2119
		}
2120
2121
		$order_id               = absint( $_POST['order_id'] );
2122
		$refund_amount          = wc_format_decimal( sanitize_text_field( $_POST['refund_amount'] ), wc_get_price_decimals() );
2123
		$refund_reason          = sanitize_text_field( $_POST['refund_reason'] );
2124
		$line_item_qtys         = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_qtys'] ) ), true );
2125
		$line_item_totals       = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_totals'] ) ), true );
2126
		$line_item_tax_totals   = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_tax_totals'] ) ), true );
2127
		$api_refund             = $_POST['api_refund'] === 'true' ? true : false;
2128
		$restock_refunded_items = $_POST['restock_refunded_items'] === 'true' ? true : false;
2129
		$refund                 = false;
2130
		$response_data          = array();
2131
2132
		try {
2133
			// Validate that the refund can occur
2134
			$order       = wc_get_order( $order_id );
2135
			$order_items = $order->get_items();
2136
			$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded(), wc_get_price_decimals() );
2137
2138
			if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) {
2139
				throw new exception( __( 'Invalid refund amount', 'woocommerce' ) );
2140
			}
2141
2142
			// Prepare line items which we are refunding
2143
			$line_items = array();
2144
			$item_ids   = array_unique( array_merge( array_keys( $line_item_qtys, $line_item_totals ) ) );
2145
2146
			foreach ( $item_ids as $item_id ) {
2147
				$line_items[ $item_id ] = array( 'qty' => 0, 'refund_total' => 0, 'refund_tax' => array() );
2148
			}
2149
			foreach ( $line_item_qtys as $item_id => $qty ) {
2150
				$line_items[ $item_id ]['qty'] = max( $qty, 0 );
2151
			}
2152
			foreach ( $line_item_totals as $item_id => $total ) {
2153
				$line_items[ $item_id ]['refund_total'] = wc_format_decimal( $total );
2154
			}
2155
			foreach ( $line_item_tax_totals as $item_id => $tax_totals ) {
2156
				$line_items[ $item_id ]['refund_tax'] = array_map( 'wc_format_decimal', $tax_totals );
2157
			}
2158
2159
			// Create the refund object
2160
			$refund = wc_create_refund( array(
2161
				'amount'     => $refund_amount,
2162
				'reason'     => $refund_reason,
2163
				'order_id'   => $order_id,
2164
				'line_items' => $line_items,
2165
			) );
2166
2167
			if ( is_wp_error( $refund ) ) {
2168
				throw new Exception( $refund->get_error_message() );
2169
			}
2170
2171
			// Refund via API
2172
			if ( $api_refund ) {
2173
				if ( WC()->payment_gateways() ) {
2174
					$payment_gateways = WC()->payment_gateways->payment_gateways();
2175
				}
2176
				if ( isset( $payment_gateways[ $order->get_payment_method() ] ) && $payment_gateways[ $order->get_payment_method() ]->supports( 'refunds' ) ) {
2177
					$result = $payment_gateways[ $order->get_payment_method() ]->process_refund( $order_id, $refund_amount, $refund_reason );
2178
2179
					do_action( 'woocommerce_refund_processed', $refund, $result );
2180
2181
					if ( is_wp_error( $result ) ) {
2182
						throw new Exception( $result->get_error_message() );
2183
					} elseif ( ! $result ) {
2184
						throw new Exception( __( 'Refund failed', 'woocommerce' ) );
2185
					}
2186
				}
2187
			}
2188
2189
			// restock items
2190
			foreach ( $line_item_qtys as $item_id => $qty ) {
2191
				if ( $restock_refunded_items && $qty && isset( $order_items[ $item_id ] ) ) {
2192
					$order_item = $order_items[ $item_id ];
2193
					$_product   = $order->get_product_from_item( $order_item );
2194
2195
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2196
						$old_stock    = wc_stock_amount( $_product->stock );
2197
						$new_quantity = $_product->increase_stock( $qty );
2198
2199
						$order->add_order_note( sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
2200
2201
						do_action( 'woocommerce_restock_refunded_item', $_product->id, $old_stock, $new_quantity, $order, $_product );
2202
					}
2203
				}
2204
			}
2205
2206
			// Trigger notifications and status changes
2207
			if ( $order->get_remaining_refund_amount() > 0 || ( $order->has_free_item() && $order->get_remaining_refund_items() > 0 ) ) {
2208
				/**
2209
				 * woocommerce_order_partially_refunded.
2210
				 *
2211
				 * @since 2.4.0
2212
				 * Note: 3rd arg was added in err. Kept for bw compat. 2.4.3.
2213
				 */
2214
				do_action( 'woocommerce_order_partially_refunded', $order_id, $refund->id, $refund->id );
2215
			} else {
2216
				do_action( 'woocommerce_order_fully_refunded', $order_id, $refund->id );
2217
2218
				$order->update_status( apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order_id, $refund->id ) );
2219
				$response_data['status'] = 'fully_refunded';
2220
			}
2221
2222
			do_action( 'woocommerce_order_refunded', $order_id, $refund->id );
2223
2224
			// Clear transients
2225
			wc_delete_shop_order_transients( $order_id );
2226
			wp_send_json_success( $response_data );
2227
2228
		} catch ( Exception $e ) {
2229
			if ( $refund && is_a( $refund, 'WC_Order_Refund' ) ) {
2230
				wp_delete_post( $refund->id, true );
2231
			}
2232
2233
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
2234
		}
2235
	}
2236
2237
	/**
2238
	 * Delete a refund.
2239
	 */
2240
	public static function delete_refund() {
2241
		check_ajax_referer( 'order-item', 'security' );
2242
2243
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2244
			die(-1);
2245
		}
2246
2247
		$refund_ids = array_map( 'absint', is_array( $_POST['refund_id'] ) ? $_POST['refund_id'] : array( $_POST['refund_id'] ) );
2248
		foreach ( $refund_ids as $refund_id ) {
2249
			if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
2250
				$order_id = wp_get_post_parent_id( $refund_id );
2251
				wc_delete_shop_order_transients( $order_id );
2252
				wp_delete_post( $refund_id );
2253
				do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
2254
			}
2255
		}
2256
		die();
2257
	}
2258
2259
	/**
2260
	 * Triggered when clicking the rating footer.
2261
	 */
2262
	public static function rated() {
2263
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2264
			die(-1);
2265
		}
2266
2267
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
2268
		die();
2269
	}
2270
2271
	/**
2272
	 * Create/Update API key.
2273
	 */
2274
	public static function update_api_key() {
2275
		ob_start();
2276
2277
		global $wpdb;
2278
2279
		check_ajax_referer( 'update-api-key', 'security' );
2280
2281
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2282
			die(-1);
2283
		}
2284
2285
		try {
2286
			if ( empty( $_POST['description'] ) ) {
2287
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
2288
			}
2289
			if ( empty( $_POST['user'] ) ) {
2290
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
2291
			}
2292
			if ( empty( $_POST['permissions'] ) ) {
2293
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
2294
			}
2295
2296
			$key_id      = absint( $_POST['key_id'] );
2297
			$description = sanitize_text_field( wp_unslash( $_POST['description'] ) );
2298
			$permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read';
2299
			$user_id     = absint( $_POST['user'] );
2300
2301
			if ( 0 < $key_id ) {
2302
				$data = array(
2303
					'user_id'     => $user_id,
2304
					'description' => $description,
2305
					'permissions' => $permissions
2306
				);
2307
2308
				$wpdb->update(
2309
					$wpdb->prefix . 'woocommerce_api_keys',
2310
					$data,
2311
					array( 'key_id' => $key_id ),
2312
					array(
2313
						'%d',
2314
						'%s',
2315
						'%s'
2316
					),
2317
					array( '%d' )
2318
				);
2319
2320
				$data['consumer_key']    = '';
2321
				$data['consumer_secret'] = '';
2322
				$data['message']         = __( 'API Key updated successfully.', 'woocommerce' );
2323
			} else {
2324
				$consumer_key    = 'ck_' . wc_rand_hash();
2325
				$consumer_secret = 'cs_' . wc_rand_hash();
2326
2327
				$data = array(
2328
					'user_id'         => $user_id,
2329
					'description'     => $description,
2330
					'permissions'     => $permissions,
2331
					'consumer_key'    => wc_api_hash( $consumer_key ),
2332
					'consumer_secret' => $consumer_secret,
2333
					'truncated_key'   => substr( $consumer_key, -7 )
2334
				);
2335
2336
				$wpdb->insert(
2337
					$wpdb->prefix . 'woocommerce_api_keys',
2338
					$data,
2339
					array(
2340
						'%d',
2341
						'%s',
2342
						'%s',
2343
						'%s',
2344
						'%s',
2345
						'%s'
2346
					)
2347
				);
2348
2349
				$key_id                  = $wpdb->insert_id;
2350
				$data['consumer_key']    = $consumer_key;
2351
				$data['consumer_secret'] = $consumer_secret;
2352
				$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' );
2353
				$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>';
2354
			}
2355
2356
			wp_send_json_success( $data );
2357
		} catch ( Exception $e ) {
2358
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
2359
		}
2360
	}
2361
2362
	/**
2363
	 * Locate user via AJAX.
2364
	 */
2365
	public static function get_customer_location() {
2366
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
2367
		wp_send_json_success( array( 'hash' => $location_hash ) );
2368
	}
2369
2370
	/**
2371
	 * Load variations via AJAX.
2372
	 */
2373
	public static function load_variations() {
2374
		ob_start();
2375
2376
		check_ajax_referer( 'load-variations', 'security' );
2377
2378
		// Check permissions again and make sure we have what we need
2379 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...
2380
			die( -1 );
2381
		}
2382
2383
		global $post;
2384
2385
		$product_id = absint( $_POST['product_id'] );
2386
		$post       = get_post( $product_id ); // Set $post global so its available like within the admin screens
2387
		$per_page   = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
2388
		$page       = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
2389
2390
		// Get attributes
2391
		$attributes        = array();
2392
		$posted_attributes = wp_unslash( $_POST['attributes'] );
2393
2394
		foreach ( $posted_attributes as $key => $value ) {
2395
			$attributes[ $key ] = array_map( 'wc_clean', $value );
2396
		}
2397
2398
		// Get tax classes
2399
		$tax_classes           = WC_Tax::get_tax_classes();
2400
		$tax_class_options     = array();
2401
		$tax_class_options[''] = __( 'Standard', 'woocommerce' );
2402
2403 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...
2404
			foreach ( $tax_classes as $class ) {
2405
				$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
2406
			}
2407
		}
2408
2409
		// Set backorder options
2410
		$backorder_options = array(
2411
			'no'     => __( 'Do not allow', 'woocommerce' ),
2412
			'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
2413
			'yes'    => __( 'Allow', 'woocommerce' )
2414
		);
2415
2416
		// set stock status options
2417
		$stock_status_options = array(
2418
			'instock'    => __( 'In stock', 'woocommerce' ),
2419
			'outofstock' => __( 'Out of stock', 'woocommerce' )
2420
		);
2421
2422
		$parent_data = array(
2423
			'id'                   => $product_id,
2424
			'attributes'           => $attributes,
2425
			'tax_class_options'    => $tax_class_options,
2426
			'sku'                  => get_post_meta( $product_id, '_sku', true ),
2427
			'weight'               => wc_format_localized_decimal( get_post_meta( $product_id, '_weight', true ) ),
2428
			'length'               => wc_format_localized_decimal( get_post_meta( $product_id, '_length', true ) ),
2429
			'width'                => wc_format_localized_decimal( get_post_meta( $product_id, '_width', true ) ),
2430
			'height'               => wc_format_localized_decimal( get_post_meta( $product_id, '_height', true ) ),
2431
			'tax_class'            => get_post_meta( $product_id, '_tax_class', true ),
2432
			'backorder_options'    => $backorder_options,
2433
			'stock_status_options' => $stock_status_options
2434
		);
2435
2436
		if ( ! $parent_data['weight'] ) {
2437
			$parent_data['weight'] = wc_format_localized_decimal( 0 );
2438
		}
2439
2440
		if ( ! $parent_data['length'] ) {
2441
			$parent_data['length'] = wc_format_localized_decimal( 0 );
2442
		}
2443
2444
		if ( ! $parent_data['width'] ) {
2445
			$parent_data['width'] = wc_format_localized_decimal( 0 );
2446
		}
2447
2448
		if ( ! $parent_data['height'] ) {
2449
			$parent_data['height'] = wc_format_localized_decimal( 0 );
2450
		}
2451
2452
		// Get variations
2453
		$args = apply_filters( 'woocommerce_ajax_admin_get_variations_args', array(
2454
			'post_type'      => 'product_variation',
2455
			'post_status'    => array( 'private', 'publish' ),
2456
			'posts_per_page' => $per_page,
2457
			'paged'          => $page,
2458
			'orderby'        => array( 'menu_order' => 'ASC', 'ID' => 'DESC' ),
2459
			'post_parent'    => $product_id
2460
		), $product_id );
2461
2462
		$variations = get_posts( $args );
2463
		$loop = 0;
2464
2465
		if ( $variations ) {
2466
2467
			foreach ( $variations as $variation ) {
2468
				$variation_id     = absint( $variation->ID );
2469
				$variation_meta   = get_post_meta( $variation_id );
2470
				$variation_data   = array();
2471
				$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
2472
				$variation_fields = array(
2473
					'_sku'                   => '',
2474
					'_stock'                 => '',
2475
					'_regular_price'         => '',
2476
					'_sale_price'            => '',
2477
					'_weight'                => '',
2478
					'_length'                => '',
2479
					'_width'                 => '',
2480
					'_height'                => '',
2481
					'_download_limit'        => '',
2482
					'_download_expiry'       => '',
2483
					'_downloadable_files'    => '',
2484
					'_downloadable'          => '',
2485
					'_virtual'               => '',
2486
					'_thumbnail_id'          => '',
2487
					'_sale_price_dates_from' => '',
2488
					'_sale_price_dates_to'   => '',
2489
					'_manage_stock'          => '',
2490
					'_stock_status'          => '',
2491
					'_backorders'            => null,
2492
					'_tax_class'             => null,
2493
					'_variation_description' => ''
2494
				);
2495
2496 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...
2497
					$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
2498
				}
2499
2500
				// Add the variation attributes
2501
				$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
2502
2503
				// Formatting
2504
				$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
2505
				$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
2506
				$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
2507
				$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
2508
				$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
2509
				$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
2510
				$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
2511
				$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
2512
				$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
2513
				$variation_data['menu_order']     = $variation->menu_order;
2514
				$variation_data['_stock']         = '' === $variation_data['_stock'] ? '' : wc_stock_amount( $variation_data['_stock'] );
2515
2516
				include( 'admin/meta-boxes/views/html-variation-admin.php' );
2517
2518
				$loop++;
2519
			}
2520
		}
2521
2522
		die();
2523
	}
2524
2525
	/**
2526
	 * Save variations via AJAX.
2527
	 */
2528
	public static function save_variations() {
2529
		ob_start();
2530
2531
		check_ajax_referer( 'save-variations', 'security' );
2532
2533
		// Check permissions again and make sure we have what we need
2534 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...
2535
			die( -1 );
2536
		}
2537
2538
		// Remove previous meta box errors
2539
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
2540
2541
		$product_id   = absint( $_POST['product_id'] );
2542
		$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
2543
2544
		$product_type_terms = wp_get_object_terms( $product_id, 'product_type' );
2545
2546
		// If the product type hasn't been set or it has changed, update it before saving variations
2547
		if ( empty( $product_type_terms ) || $product_type !== sanitize_title( current( $product_type_terms )->name ) ) {
2548
			wp_set_object_terms( $product_id, $product_type, 'product_type' );
2549
		}
2550
2551
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
2552
2553
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
2554
2555
		// Clear cache/transients
2556
		wc_delete_product_transients( $product_id );
2557
2558
		if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
2559
			echo '<div class="error notice is-dismissible">';
2560
2561
			foreach ( $errors as $error ) {
2562
				echo '<p>' . wp_kses_post( $error ) . '</p>';
2563
			}
2564
2565
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
2566
			echo '</div>';
2567
2568
			delete_option( 'woocommerce_meta_box_errors' );
2569
		}
2570
2571
		die();
2572
	}
2573
2574
	/**
2575
	 * Bulk action - Toggle Enabled.
2576
	 * @access private
2577
	 * @used-by bulk_edit_variations
2578
	 * @param  array $variations
2579
	 * @param  array $data
2580
	 */
2581
	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...
2582
		global $wpdb;
2583
2584
		foreach ( $variations as $variation_id ) {
2585
			$post_status = get_post_status( $variation_id );
2586
			$new_status  = 'private' === $post_status ? 'publish' : 'private';
2587
			$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $variation_id ) );
2588
		}
2589
	}
2590
2591
	/**
2592
	 * Bulk action - Toggle Downloadable Checkbox.
2593
	 * @access private
2594
	 * @used-by bulk_edit_variations
2595
	 * @param  array $variations
2596
	 * @param  array $data
2597
	 */
2598 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...
2599
		foreach ( $variations as $variation_id ) {
2600
			$_downloadable   = get_post_meta( $variation_id, '_downloadable', true );
2601
			$is_downloadable = 'no' === $_downloadable ? 'yes' : 'no';
2602
			update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
2603
		}
2604
	}
2605
2606
	/**
2607
	 * Bulk action - Toggle Virtual Checkbox.
2608
	 * @access private
2609
	 * @used-by bulk_edit_variations
2610
	 * @param  array $variations
2611
	 * @param  array $data
2612
	 */
2613 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...
2614
		foreach ( $variations as $variation_id ) {
2615
			$_virtual   = get_post_meta( $variation_id, '_virtual', true );
2616
			$is_virtual = 'no' === $_virtual ? 'yes' : 'no';
2617
			update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
2618
		}
2619
	}
2620
2621
	/**
2622
	 * Bulk action - Toggle Manage Stock Checkbox.
2623
	 * @access private
2624
	 * @used-by bulk_edit_variations
2625
	 * @param  array $variations
2626
	 * @param  array $data
2627
	 */
2628
	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...
2629
		foreach ( $variations as $variation_id ) {
2630
			$_manage_stock   = get_post_meta( $variation_id, '_manage_stock', true );
2631
			$is_manage_stock = 'no' === $_manage_stock || '' === $_manage_stock ? 'yes' : 'no';
2632
			update_post_meta( $variation_id, '_manage_stock', $is_manage_stock );
2633
		}
2634
	}
2635
2636
	/**
2637
	 * Bulk action - Set Regular Prices.
2638
	 * @access private
2639
	 * @used-by bulk_edit_variations
2640
	 * @param  array $variations
2641
	 * @param  array $data
2642
	 */
2643 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...
2644
		if ( ! isset( $data['value'] ) ) {
2645
			return;
2646
		}
2647
2648
		foreach ( $variations as $variation_id ) {
2649
			// Price fields
2650
			$regular_price = wc_clean( $data['value'] );
2651
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2652
2653
			// Date fields
2654
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2655
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2656
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2657
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2658
2659
			_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...
2660
		}
2661
	}
2662
2663
	/**
2664
	 * Bulk action - Set Sale Prices.
2665
	 * @access private
2666
	 * @used-by bulk_edit_variations
2667
	 * @param  array $variations
2668
	 * @param  array $data
2669
	 */
2670 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...
2671
		if ( ! isset( $data['value'] ) ) {
2672
			return;
2673
		}
2674
2675
		foreach ( $variations as $variation_id ) {
2676
			// Price fields
2677
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2678
			$sale_price    = wc_clean( $data['value'] );
2679
2680
			// Date fields
2681
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2682
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2683
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2684
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2685
2686
			_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 2678 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...
2687
		}
2688
	}
2689
2690
	/**
2691
	 * Bulk action - Set Stock.
2692
	 * @access private
2693
	 * @used-by bulk_edit_variations
2694
	 * @param  array $variations
2695
	 * @param  array $data
2696
	 */
2697
	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...
2698
		if ( ! isset( $data['value'] ) ) {
2699
			return;
2700
		}
2701
2702
		$value = wc_clean( $data['value'] );
2703
2704
		foreach ( $variations as $variation_id ) {
2705
			if ( 'yes' === get_post_meta( $variation_id, '_manage_stock', true ) ) {
2706
				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...
2707
			} else {
2708
				delete_post_meta( $variation_id, '_stock' );
2709
			}
2710
		}
2711
	}
2712
2713
	/**
2714
	 * Bulk action - Set Weight.
2715
	 * @access private
2716
	 * @used-by bulk_edit_variations
2717
	 * @param  array $variations
2718
	 * @param  array $data
2719
	 */
2720
	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...
2721
		self::variation_bulk_set_meta( $variations, '_weight', wc_clean( $data['value'] ) );
2722
	}
2723
2724
	/**
2725
	 * Bulk action - Set Length.
2726
	 * @access private
2727
	 * @used-by bulk_edit_variations
2728
	 * @param  array $variations
2729
	 * @param  array $data
2730
	 */
2731
	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...
2732
		self::variation_bulk_set_meta( $variations, '_length', wc_clean( $data['value'] ) );
2733
	}
2734
2735
	/**
2736
	 * Bulk action - Set Width.
2737
	 * @access private
2738
	 * @used-by bulk_edit_variations
2739
	 * @param  array $variations
2740
	 * @param  array $data
2741
	 */
2742
	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...
2743
		self::variation_bulk_set_meta( $variations, '_width', wc_clean( $data['value'] ) );
2744
	}
2745
2746
	/**
2747
	 * Bulk action - Set Height.
2748
	 * @access private
2749
	 * @used-by bulk_edit_variations
2750
	 * @param  array $variations
2751
	 * @param  array $data
2752
	 */
2753
	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...
2754
		self::variation_bulk_set_meta( $variations, '_height', wc_clean( $data['value'] ) );
2755
	}
2756
2757
	/**
2758
	 * Bulk action - Set Download Limit.
2759
	 * @access private
2760
	 * @used-by bulk_edit_variations
2761
	 * @param  array $variations
2762
	 * @param  array $data
2763
	 */
2764
	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...
2765
		self::variation_bulk_set_meta( $variations, '_download_limit', wc_clean( $data['value'] ) );
2766
	}
2767
2768
	/**
2769
	 * Bulk action - Set Download Expiry.
2770
	 * @access private
2771
	 * @used-by bulk_edit_variations
2772
	 * @param  array $variations
2773
	 * @param  array $data
2774
	 */
2775
	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...
2776
		self::variation_bulk_set_meta( $variations, '_download_expiry', wc_clean( $data['value'] ) );
2777
	}
2778
2779
	/**
2780
	 * Bulk action - Delete all.
2781
	 * @access private
2782
	 * @used-by bulk_edit_variations
2783
	 * @param  array $variations
2784
	 * @param  array $data
2785
	 */
2786
	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...
2787
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2788
			foreach ( $variations as $variation_id ) {
2789
				wp_delete_post( $variation_id );
2790
			}
2791
		}
2792
	}
2793
2794
	/**
2795
	 * Bulk action - Sale Schedule.
2796
	 * @access private
2797
	 * @used-by bulk_edit_variations
2798
	 * @param  array $variations
2799
	 * @param  array $data
2800
	 */
2801
	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...
2802
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2803
			return;
2804
		}
2805
2806
		foreach ( $variations as $variation_id ) {
2807
			// Price fields
2808
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2809
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2810
2811
			// Date fields
2812
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2813
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2814
2815 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...
2816
				$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2817
			} else {
2818
				$date_from = $data['date_from'];
2819
			}
2820
2821 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...
2822
				$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2823
			} else {
2824
				$date_to = $data['date_to'];
2825
			}
2826
2827
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
2828
		}
2829
	}
2830
2831
	/**
2832
	 * Bulk action - Increase Regular Prices.
2833
	 * @access private
2834
	 * @used-by bulk_edit_variations
2835
	 * @param  array $variations
2836
	 * @param  array $data
2837
	 */
2838
	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...
2839
		self::variation_bulk_adjust_price( $variations, '_regular_price', '+', wc_clean( $data['value'] ) );
2840
	}
2841
2842
	/**
2843
	 * Bulk action - Decrease Regular Prices.
2844
	 * @access private
2845
	 * @used-by bulk_edit_variations
2846
	 * @param  array $variations
2847
	 * @param  array $data
2848
	 */
2849
	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...
2850
		self::variation_bulk_adjust_price( $variations, '_regular_price', '-', wc_clean( $data['value'] ) );
2851
	}
2852
2853
	/**
2854
	 * Bulk action - Increase Sale Prices.
2855
	 * @access private
2856
	 * @used-by bulk_edit_variations
2857
	 * @param  array $variations
2858
	 * @param  array $data
2859
	 */
2860
	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...
2861
		self::variation_bulk_adjust_price( $variations, '_sale_price', '+', wc_clean( $data['value'] ) );
2862
	}
2863
2864
	/**
2865
	 * Bulk action - Decrease Sale Prices.
2866
	 * @access private
2867
	 * @used-by bulk_edit_variations
2868
	 * @param  array $variations
2869
	 * @param  array $data
2870
	 */
2871
	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...
2872
		self::variation_bulk_adjust_price( $variations, '_sale_price', '-', wc_clean( $data['value'] ) );
2873
	}
2874
2875
	/**
2876
	 * Bulk action - Set Price.
2877
	 * @access private
2878
	 * @used-by bulk_edit_variations
2879
	 * @param  array $variations
2880
	 * @param string $operator + or -
2881
	 * @param string $field price being adjusted
2882
	 * @param string $value Price or Percent
2883
	 */
2884
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2885
		foreach ( $variations as $variation_id ) {
2886
			// Get existing data
2887
			$_regular_price = get_post_meta( $variation_id, '_regular_price', true );
2888
			$_sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2889
			$date_from      = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2890
			$date_to        = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2891
			$date_from      = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2892
			$date_to        = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2893
2894
			if ( '%' === substr( $value, -1 ) ) {
2895
				$percent = wc_format_decimal( substr( $value, 0, -1 ) );
2896
				$$field  += ( ( $$field / 100 ) * $percent ) * "{$operator}1";
2897
			} else {
2898
				$$field  += $value * "{$operator}1";
2899
			}
2900
			_wc_save_product_price( $variation_id, $_regular_price, $_sale_price, $date_from, $date_to );
2901
		}
2902
	}
2903
2904
	/**
2905
	 * Bulk action - Set Meta.
2906
	 * @access private
2907
	 * @param array $variations
2908
	 * @param string $field
2909
	 * @param string $value
2910
	 */
2911
	private static function variation_bulk_set_meta( $variations, $field, $value ) {
2912
		foreach ( $variations as $variation_id ) {
2913
			update_post_meta( $variation_id, $field, $value );
2914
		}
2915
	}
2916
2917
	/**
2918
	 * Bulk edit variations via AJAX.
2919
	 * @uses WC_AJAX::variation_bulk_set_meta()
2920
	 * @uses WC_AJAX::variation_bulk_adjust_price()
2921
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price_decrease()
2922
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price_increase()
2923
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price_decrease()
2924
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price_increase()
2925
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_schedule()
2926
	 * @uses WC_AJAX::variation_bulk_action_delete_all()
2927
	 * @uses WC_AJAX::variation_bulk_action_variable_download_expiry()
2928
	 * @uses WC_AJAX::variation_bulk_action_variable_download_limit()
2929
	 * @uses WC_AJAX::variation_bulk_action_variable_height()
2930
	 * @uses WC_AJAX::variation_bulk_action_variable_width()
2931
	 * @uses WC_AJAX::variation_bulk_action_variable_length()
2932
	 * @uses WC_AJAX::variation_bulk_action_variable_weight()
2933
	 * @uses WC_AJAX::variation_bulk_action_variable_stock()
2934
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price()
2935
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price()
2936
	 * @uses WC_AJAX::variation_bulk_action_toggle_manage_stock()
2937
	 * @uses WC_AJAX::variation_bulk_action_toggle_virtual()
2938
	 * @uses WC_AJAX::variation_bulk_action_toggle_downloadable()
2939
	 * @uses WC_AJAX::variation_bulk_action_toggle_enabled
2940
	 */
2941
	public static function bulk_edit_variations() {
2942
		ob_start();
2943
2944
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2945
2946
		// Check permissions again and make sure we have what we need
2947 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...
2948
			die( -1 );
2949
		}
2950
2951
		$product_id  = absint( $_POST['product_id'] );
2952
		$bulk_action = wc_clean( $_POST['bulk_action'] );
2953
		$data        = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array();
2954
		$variations  = array();
2955
2956
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2957
			$variations = get_posts( array(
2958
				'post_parent'    => $product_id,
2959
				'posts_per_page' => -1,
2960
				'post_type'      => 'product_variation',
2961
				'fields'         => 'ids',
2962
				'post_status'    => array( 'publish', 'private' )
2963
			) );
2964
		}
2965
2966
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2967
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2968
		} else {
2969
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2970
		}
2971
2972
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2973
2974
		// Sync and update transients
2975
		WC_Product_Variable::sync( $product_id );
2976
		wc_delete_product_transients( $product_id );
2977
		die();
2978
	}
2979
2980
	/**
2981
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2982
	 */
2983
	public static function tax_rates_save_changes() {
2984
		if ( ! isset( $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2985
			wp_send_json_error( 'missing_fields' );
2986
			exit;
2987
		}
2988
2989
		$current_class = ! empty( $_POST['current_class'] ) ? $_POST['current_class'] : ''; // This is sanitized seven lines later.
2990
2991
		if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) {
2992
			wp_send_json_error( 'bad_nonce' );
2993
			exit;
2994
		}
2995
2996
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
2997
2998
		// Check User Caps
2999
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3000
			wp_send_json_error( 'missing_capabilities' );
3001
			exit;
3002
		}
3003
3004
		$changes = $_POST['changes'];
3005
		foreach ( $changes as $tax_rate_id => $data ) {
3006
			if ( isset( $data['deleted'] ) ) {
3007
				if ( isset( $data['newRow'] ) ) {
3008
					// So the user added and deleted a new row.
3009
					// That's fine, it's not in the database anyways. NEXT!
3010
					continue;
3011
				}
3012
				WC_Tax::_delete_tax_rate( $tax_rate_id );
3013
			}
3014
3015
			$tax_rate = array_intersect_key( $data, array(
3016
				'tax_rate_country'  => 1,
3017
				'tax_rate_state'    => 1,
3018
				'tax_rate'          => 1,
3019
				'tax_rate_name'     => 1,
3020
				'tax_rate_priority' => 1,
3021
				'tax_rate_compound' => 1,
3022
				'tax_rate_shipping' => 1,
3023
				'tax_rate_order'    => 1,
3024
			) );
3025
3026
			if ( isset( $data['newRow'] ) ) {
3027
				// Hurrah, shiny and new!
3028
				$tax_rate['tax_rate_class'] = $current_class;
3029
				$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
3030
			} else {
3031
				// Updating an existing rate ...
3032
				if ( ! empty( $tax_rate ) ) {
3033
					WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
3034
				}
3035
			}
3036
3037
			if ( isset( $data['postcode'] ) ) {
3038
				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...
3039
			}
3040
			if ( isset( $data['city'] ) ) {
3041
				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...
3042
			}
3043
		}
3044
3045
		wp_send_json_success( array(
3046
			'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
3047
		) );
3048
	}
3049
3050
	/**
3051
	 * Handle submissions from assets/js/wc-shipping-zones.js Backbone model.
3052
	 */
3053
	public static function shipping_zones_save_changes() {
3054
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['changes'] ) ) {
3055
			wp_send_json_error( 'missing_fields' );
3056
			exit;
3057
		}
3058
3059
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3060
			wp_send_json_error( 'bad_nonce' );
3061
			exit;
3062
		}
3063
3064
		// Check User Caps
3065
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3066
			wp_send_json_error( 'missing_capabilities' );
3067
			exit;
3068
		}
3069
3070
		$changes = $_POST['changes'];
3071
		foreach ( $changes as $zone_id => $data ) {
3072
			if ( isset( $data['deleted'] ) ) {
3073
				if ( isset( $data['newRow'] ) ) {
3074
					// So the user added and deleted a new row.
3075
					// That's fine, it's not in the database anyways. NEXT!
3076
					continue;
3077
				}
3078
				WC_Shipping_Zones::delete_zone( $zone_id );
3079
				continue;
3080
			}
3081
3082
			$zone_data = array_intersect_key( $data, array(
3083
				'zone_id'        => 1,
3084
				'zone_name'      => 1,
3085
				'zone_order'     => 1,
3086
				'zone_locations' => 1,
3087
				'zone_postcodes' => 1
3088
			) );
3089
3090
			if ( isset( $zone_data['zone_id'] ) ) {
3091
				$zone = new WC_Shipping_Zone( $zone_data['zone_id'] );
3092
3093
				if ( isset( $zone_data['zone_name'] ) ) {
3094
					$zone->set_zone_name( $zone_data['zone_name'] );
3095
				}
3096
3097
				if ( isset( $zone_data['zone_order'] ) ) {
3098
					$zone->set_zone_order( $zone_data['zone_order'] );
3099
				}
3100
3101
				if ( isset( $zone_data['zone_locations'] ) ) {
3102
					$zone->clear_locations( array( 'state', 'country', 'continent' ) );
3103
					$locations = array_filter( array_map( 'wc_clean', (array) $zone_data['zone_locations'] ) );
3104
					foreach ( $locations as $location ) {
3105
						// Each posted location will be in the format type:code
3106
						$location_parts = explode( ':', $location );
3107
						switch ( $location_parts[0] ) {
3108
							case 'state' :
3109
								$zone->add_location( $location_parts[1] . ':' . $location_parts[2], 'state' );
3110
							break;
3111
							case 'country' :
3112
								$zone->add_location( $location_parts[1], 'country' );
3113
							break;
3114
							case 'continent' :
3115
								$zone->add_location( $location_parts[1], 'continent' );
3116
							break;
3117
						}
3118
					}
3119
				}
3120
3121
				if ( isset( $zone_data['zone_postcodes'] ) ) {
3122
					$zone->clear_locations( 'postcode' );
3123
					$postcodes = array_filter( array_map( 'strtoupper', array_map( 'wc_clean', explode( "\n", $zone_data['zone_postcodes'] ) ) ) );
3124
					foreach ( $postcodes as $postcode ) {
3125
						$zone->add_location( $postcode, 'postcode' );
3126
					}
3127
				}
3128
3129
				$zone->save();
3130
			}
3131
		}
3132
3133
		wp_send_json_success( array(
3134
			'zones' => WC_Shipping_Zones::get_zones()
3135
		) );
3136
	}
3137
3138
	/**
3139
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
3140
	 */
3141
	public static function shipping_zone_add_method() {
3142
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['method_id'] ) ) {
3143
			wp_send_json_error( 'missing_fields' );
3144
			exit;
3145
		}
3146
3147
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3148
			wp_send_json_error( 'bad_nonce' );
3149
			exit;
3150
		}
3151
3152
		// Check User Caps
3153
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3154
			wp_send_json_error( 'missing_capabilities' );
3155
			exit;
3156
		}
3157
3158
		$zone_id     = absint( $_POST['zone_id'] );
3159
		$zone        = WC_Shipping_Zones::get_zone( $zone_id );
3160
		$instance_id = $zone->add_shipping_method( wc_clean( $_POST['method_id'] ) );
3161
3162
		wp_send_json_success( array(
3163
			'instance_id' => $instance_id,
3164
			'zone_id'     => $zone_id,
3165
			'methods'     => $zone->get_shipping_methods()
3166
		) );
3167
	}
3168
3169
	/**
3170
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
3171
	 */
3172
	public static function shipping_zone_methods_save_changes() {
3173
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['changes'] ) ) {
3174
			wp_send_json_error( 'missing_fields' );
3175
			exit;
3176
		}
3177
3178
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3179
			wp_send_json_error( 'bad_nonce' );
3180
			exit;
3181
		}
3182
3183
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3184
			wp_send_json_error( 'missing_capabilities' );
3185
			exit;
3186
		}
3187
3188
		global $wpdb;
3189
3190
		$zone_id = absint( $_POST['zone_id'] );
3191
		$zone    = new WC_Shipping_Zone( $zone_id );
3192
		$changes = $_POST['changes'];
3193
3194
		foreach ( $changes as $instance_id => $data ) {
3195
			$method_id = $wpdb->get_var( $wpdb->prepare( "SELECT method_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE instance_id = %d", $instance_id ) );
3196
3197
			if ( isset( $data['deleted'] ) ) {
3198
				$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
3199
				$option_key      = $shipping_method->get_instance_option_key();
3200
				if ( $wpdb->delete( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'instance_id' => $instance_id ) ) ) {
3201
					delete_option( $option_key );
3202
					do_action( 'woocommerce_shipping_zone_method_deleted', $instance_id, $method_id, $zone_id );
3203
				}
3204
				continue;
3205
			}
3206
3207
			$method_data = array_intersect_key( $data, array(
3208
				'method_order' => 1,
3209
				'enabled'      => 1
3210
			) );
3211
3212
			if ( isset( $method_data['method_order'] ) ) {
3213
				$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'method_order' => absint( $method_data['method_order'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
3214
			}
3215
3216
			if ( isset( $method_data['enabled'] ) ) {
3217
				$is_enabled = absint( 'yes' === $method_data['enabled'] );
3218
				if ( $wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'is_enabled' => $is_enabled ), array( 'instance_id' => absint( $instance_id ) ) ) ) {
3219
					do_action( 'woocommerce_shipping_zone_method_status_toggled', $instance_id, $method_id, $zone_id, $is_enabled );
3220
				}
3221
			}
3222
		}
3223
3224
		wp_send_json_success( array(
3225
			'methods' => $zone->get_shipping_methods()
3226
		) );
3227
	}
3228
3229
	/**
3230
	 * Save method settings
3231
	 */
3232
	public static function shipping_zone_methods_save_settings() {
3233
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['instance_id'], $_POST['data'] ) ) {
3234
			wp_send_json_error( 'missing_fields' );
3235
			exit;
3236
		}
3237
3238
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3239
			wp_send_json_error( 'bad_nonce' );
3240
			exit;
3241
		}
3242
3243
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3244
			wp_send_json_error( 'missing_capabilities' );
3245
			exit;
3246
		}
3247
3248
		$instance_id     = absint( $_POST['instance_id'] );
3249
		$zone            = WC_Shipping_Zones::get_zone_by( 'instance_id', $instance_id );
3250
		$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
3251
		$shipping_method->set_post_data( $_POST['data'] );
3252
		$shipping_method->process_admin_options();
3253
3254
		wp_send_json_success( array(
3255
			'methods' => $zone->get_shipping_methods(),
3256
			'errors'  => $shipping_method->get_errors(),
3257
		) );
3258
	}
3259
3260
	/**
3261
	 * Handle submissions from assets/js/wc-shipping-classes.js Backbone model.
3262
	 */
3263
	public static function shipping_classes_save_changes() {
3264
		if ( ! isset( $_POST['wc_shipping_classes_nonce'], $_POST['changes'] ) ) {
3265
			wp_send_json_error( 'missing_fields' );
3266
			exit;
3267
		}
3268
3269
		if ( ! wp_verify_nonce( $_POST['wc_shipping_classes_nonce'], 'wc_shipping_classes_nonce' ) ) {
3270
			wp_send_json_error( 'bad_nonce' );
3271
			exit;
3272
		}
3273
3274
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3275
			wp_send_json_error( 'missing_capabilities' );
3276
			exit;
3277
		}
3278
3279
		$changes = $_POST['changes'];
3280
3281
		foreach ( $changes as $term_id => $data ) {
3282
			$term_id = absint( $term_id );
3283
3284
			if ( isset( $data['deleted'] ) ) {
3285
				if ( isset( $data['newRow'] ) ) {
3286
					// So the user added and deleted a new row.
3287
					// That's fine, it's not in the database anyways. NEXT!
3288
					continue;
3289
				}
3290
				wp_delete_term( $term_id, 'product_shipping_class' );
3291
				continue;
3292
			}
3293
3294
			$update_args = array();
3295
3296
			if ( isset( $data['name'] ) ) {
3297
				$update_args['name'] = wc_clean( $data['name'] );
3298
			}
3299
3300
			if ( isset( $data['slug'] ) ) {
3301
				$update_args['slug'] = wc_clean( $data['slug'] );
3302
			}
3303
3304
			if ( isset( $data['description'] ) ) {
3305
				$update_args['description'] = wc_clean( $data['description'] );
3306
			}
3307
3308
			if ( isset( $data['newRow'] ) ) {
3309
				$update_args = array_filter( $update_args );
3310
				if ( empty( $update_args['name'] ) ) {
3311
					continue;
3312
				}
3313
				$term_id = wp_insert_term( $update_args['name'], 'product_shipping_class', $update_args );
3314
			} else {
3315
				wp_update_term( $term_id, 'product_shipping_class', $update_args );
3316
			}
3317
3318
			do_action( 'woocommerce_shipping_classes_save_class', $term_id, $data );
3319
		}
3320
3321
		$wc_shipping = WC_Shipping::instance();
3322
3323
		wp_send_json_success( array(
3324
			'shipping_classes' => $wc_shipping->get_shipping_classes()
3325
		) );
3326
	}
3327
}
3328
3329
WC_AJAX::init();
3330