Completed
Pull Request — master (#11645)
by Mike
10:05
created

WC_AJAX::get_customer_details()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 30
rs 8.8571
c 0
b 0
f 0
eloc 21
nc 2
nop 0
1
<?php
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit; // Exit if accessed directly
5
}
6
7
/**
8
 * WooCommerce WC_AJAX.
9
 *
10
 * AJAX Event Handler.
11
 *
12
 * @class    WC_AJAX
13
 * @version  2.4.0
14
 * @package  WooCommerce/Classes
15
 * @category Class
16
 * @author   WooThemes
17
 */
18
class WC_AJAX {
19
20
	/**
21
	 * Hook in ajax handlers.
22
	 */
23
	public static function init() {
24
		add_action( 'init', array( __CLASS__, 'define_ajax' ), 0 );
25
		add_action( 'template_redirect', array( __CLASS__, 'do_wc_ajax' ), 0 );
26
		self::add_ajax_events();
27
	}
28
29
	/**
30
	 * Get WC Ajax Endpoint.
31
	 * @param  string $request Optional
32
	 * @return string
33
	 */
34
	public static function get_endpoint( $request = '' ) {
35
		return esc_url_raw( apply_filters( 'woocommerce_ajax_get_endpoint', add_query_arg( 'wc-ajax', $request, remove_query_arg( array( 'remove_item', 'add-to-cart', 'added-to-cart' ) ) ), $request ) );
36
	}
37
38
	/**
39
	 * Set WC AJAX constant and headers.
40
	 */
41
	public static function define_ajax() {
42
		if ( ! empty( $_GET['wc-ajax'] ) ) {
43
			if ( ! defined( 'DOING_AJAX' ) ) {
44
				define( 'DOING_AJAX', true );
45
			}
46
			if ( ! defined( 'WC_DOING_AJAX' ) ) {
47
				define( 'WC_DOING_AJAX', true );
48
			}
49
			// Turn off display_errors during AJAX events to prevent malformed JSON
50
			if ( ! WP_DEBUG || ( WP_DEBUG && ! WP_DEBUG_DISPLAY ) ) {
51
				@ini_set( 'display_errors', 0 );
52
			}
53
			$GLOBALS['wpdb']->hide_errors();
54
		}
55
	}
56
57
	/**
58
	 * Send headers for WC Ajax Requests
59
	 * @since 2.5.0
60
	 */
61
	private static function wc_ajax_headers() {
62
		send_origin_headers();
63
		@header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
64
		@header( 'X-Robots-Tag: noindex' );
65
		send_nosniff_header();
66
		nocache_headers();
67
		status_header( 200 );
68
	}
69
70
	/**
71
	 * Check for WC Ajax request and fire action.
72
	 */
73
	public static function do_wc_ajax() {
74
		global $wp_query;
75
76
		if ( ! empty( $_GET['wc-ajax'] ) ) {
77
			$wp_query->set( 'wc-ajax', sanitize_text_field( $_GET['wc-ajax'] ) );
78
		}
79
80
		if ( $action = $wp_query->get( 'wc-ajax' ) ) {
81
			self::wc_ajax_headers();
82
			do_action( 'wc_ajax_' . sanitize_text_field( $action ) );
83
			die();
84
		}
85
	}
86
87
	/**
88
	 * Hook in methods - uses WordPress ajax handlers (admin-ajax).
89
	 */
90
	public static function add_ajax_events() {
91
		// woocommerce_EVENT => nopriv
92
		$ajax_events = array(
93
			'get_refreshed_fragments'                          => true,
94
			'apply_coupon'                                     => true,
95
			'remove_coupon'                                    => true,
96
			'update_shipping_method'                           => true,
97
			'get_cart_totals'                                  => true,
98
			'update_order_review'                              => true,
99
			'add_to_cart'                                      => true,
100
			'checkout'                                         => true,
101
			'get_variation'                                    => true,
102
			'feature_product'                                  => false,
103
			'mark_order_status'                                => false,
104
			'add_attribute'                                    => false,
105
			'add_new_attribute'                                => false,
106
			'remove_variation'                                 => false,
107
			'remove_variations'                                => false,
108
			'save_attributes'                                  => false,
109
			'add_variation'                                    => false,
110
			'link_all_variations'                              => false,
111
			'revoke_access_to_download'                        => false,
112
			'grant_access_to_download'                         => false,
113
			'get_customer_details'                             => false,
114
			'add_order_item'                                   => false,
115
			'add_order_fee'                                    => false,
116
			'add_order_shipping'                               => false,
117
			'add_order_tax'                                    => false,
118
			'remove_order_item'                                => false,
119
			'remove_order_tax'                                 => false,
120
			'reduce_order_item_stock'                          => false,
121
			'increase_order_item_stock'                        => false,
122
			'add_order_item_meta'                              => false,
123
			'remove_order_item_meta'                           => false,
124
			'calc_line_taxes'                                  => false,
125
			'save_order_items'                                 => false,
126
			'load_order_items'                                 => false,
127
			'add_order_note'                                   => false,
128
			'delete_order_note'                                => false,
129
			'json_search_products'                             => false,
130
			'json_search_products_and_variations'              => false,
131
			'json_search_grouped_products'                     => false,
132
			'json_search_downloadable_products_and_variations' => false,
133
			'json_search_customers'                            => false,
134
			'term_ordering'                                    => false,
135
			'product_ordering'                                 => false,
136
			'refund_line_items'                                => false,
137
			'delete_refund'                                    => false,
138
			'rated'                                            => false,
139
			'update_api_key'                                   => false,
140
			'get_customer_location'                            => true,
141
			'load_variations'                                  => false,
142
			'save_variations'                                  => false,
143
			'bulk_edit_variations'                             => false,
144
			'tax_rates_save_changes'                           => false,
145
			'shipping_zones_save_changes'                      => false,
146
			'shipping_zone_add_method'                         => false,
147
			'shipping_zone_methods_save_changes'               => false,
148
			'shipping_zone_methods_save_settings'              => false,
149
			'shipping_classes_save_changes'                    => false,
150
		);
151
152
		foreach ( $ajax_events as $ajax_event => $nopriv ) {
153
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
154
155
			if ( $nopriv ) {
156
				add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
157
158
				// WC AJAX can be used for frontend ajax requests
159
				add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
160
			}
161
		}
162
	}
163
164
	/**
165
	 * Get a refreshed cart fragment.
166
	 */
167
	public static function get_refreshed_fragments() {
168
169
		// Get mini cart
170
		ob_start();
171
172
		woocommerce_mini_cart();
173
174
		$mini_cart = ob_get_clean();
175
176
		// Fragments and mini cart are returned
177
		$data = array(
178
			'fragments' => apply_filters( 'woocommerce_add_to_cart_fragments', array(
179
					'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>'
180
				)
181
			),
182
			'cart_hash' => apply_filters( 'woocommerce_add_to_cart_hash', WC()->cart->get_cart_for_session() ? md5( json_encode( WC()->cart->get_cart_for_session() ) ) : '', WC()->cart->get_cart_for_session() )
183
		);
184
185
		wp_send_json( $data );
186
187
	}
188
189
	/**
190
	 * AJAX apply coupon on checkout page.
191
	 */
192
	public static function apply_coupon() {
193
194
		check_ajax_referer( 'apply-coupon', 'security' );
195
196
		if ( ! empty( $_POST['coupon_code'] ) ) {
197
			WC()->cart->add_discount( sanitize_text_field( $_POST['coupon_code'] ) );
198
		} else {
199
			wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
200
		}
201
202
		wc_print_notices();
203
204
		die();
205
	}
206
207
	/**
208
	 * AJAX remove coupon on cart and checkout page.
209
	 */
210
	public static function remove_coupon() {
211
212
		check_ajax_referer( 'remove-coupon', 'security' );
213
214
		$coupon = wc_clean( $_POST['coupon'] );
215
216
		if ( ! isset( $coupon ) || empty( $coupon ) ) {
217
			wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ), 'error' );
218
219
		} else {
220
221
			WC()->cart->remove_coupon( $coupon );
222
223
			wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
224
		}
225
226
		wc_print_notices();
227
228
		die();
229
	}
230
231
	/**
232
	 * AJAX update shipping method on cart page.
233
	 */
234
	public static function update_shipping_method() {
235
236
		check_ajax_referer( 'update-shipping-method', 'security' );
237
238
		if ( ! defined('WOOCOMMERCE_CART') ) {
239
			define( 'WOOCOMMERCE_CART', true );
240
		}
241
242
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
243
244 View Code Duplication
		if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
245
			foreach ( $_POST['shipping_method'] as $i => $value ) {
246
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
247
			}
248
		}
249
250
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
251
252
		WC()->cart->calculate_totals();
253
254
		woocommerce_cart_totals();
255
256
		die();
257
	}
258
259
	/**
260
	 * AJAX receive updated cart_totals div.
261
	 */
262
	public static function get_cart_totals() {
263
264
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
265
			define( 'WOOCOMMERCE_CART', true );
266
		}
267
268
		WC()->cart->calculate_totals();
269
270
		woocommerce_cart_totals();
271
272
		die();
273
	}
274
275
	/**
276
	 * AJAX update order review on checkout.
277
	 */
278
	public static function update_order_review() {
279
		ob_start();
280
281
		check_ajax_referer( 'update-order-review', 'security' );
282
283
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
284
			define( 'WOOCOMMERCE_CHECKOUT', true );
285
		}
286
287
		if ( WC()->cart->is_empty() ) {
288
			$data = array(
289
				'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
290
					'form.woocommerce-checkout' => '<div class="woocommerce-error">' . __( 'Sorry, your session has expired.', 'woocommerce' ) . ' <a href="' . esc_url( wc_get_page_permalink( 'shop' ) ) . '" class="wc-backward">' . __( 'Return to shop', 'woocommerce' ) . '</a></div>'
291
				) )
292
			);
293
294
			wp_send_json( $data );
295
296
			die();
297
		}
298
299
		do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] );
300
301
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
302
303 View Code Duplication
		if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
304
			foreach ( $_POST['shipping_method'] as $i => $value ) {
305
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
306
			}
307
		}
308
309
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
310
		WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
311
312
		if ( isset( $_POST['country'] ) ) {
313
			WC()->customer->set_billing_country( $_POST['country'] );
314
		}
315
316
		if ( isset( $_POST['state'] ) ) {
317
			WC()->customer->set_billing_state( $_POST['state'] );
318
		}
319
320
		if ( isset( $_POST['postcode'] ) ) {
321
			WC()->customer->set_billing_postcode( $_POST['postcode'] );
322
		}
323
324
		if ( isset( $_POST['city'] ) ) {
325
			WC()->customer->set_billing_city( $_POST['city'] );
326
		}
327
328
		if ( isset( $_POST['address'] ) ) {
329
			WC()->customer->set_billing_address( $_POST['address'] );
330
		}
331
332
		if ( isset( $_POST['address_2'] ) ) {
333
			WC()->customer->set_billing_address_2( $_POST['address_2'] );
334
		}
335
336
		if ( wc_ship_to_billing_address_only() ) {
337
338 View Code Duplication
			if ( ! empty( $_POST['country'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
339
				WC()->customer->set_shipping_country( $_POST['country'] );
340
				WC()->customer->set_calculated_shipping( true );
341
			}
342
343
			if ( isset( $_POST['state'] ) ) {
344
				WC()->customer->set_shipping_state( $_POST['state'] );
345
			}
346
347
			if ( isset( $_POST['postcode'] ) ) {
348
				WC()->customer->set_shipping_postcode( $_POST['postcode'] );
349
			}
350
351
			if ( isset( $_POST['city'] ) ) {
352
				WC()->customer->set_shipping_city( $_POST['city'] );
353
			}
354
355
			if ( isset( $_POST['address'] ) ) {
356
				WC()->customer->set_shipping_address( $_POST['address'] );
357
			}
358
359
			if ( isset( $_POST['address_2'] ) ) {
360
				WC()->customer->set_shipping_address_2( $_POST['address_2'] );
361
			}
362
		} else {
363
364 View Code Duplication
			if ( ! empty( $_POST['s_country'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
365
				WC()->customer->set_shipping_country( $_POST['s_country'] );
366
				WC()->customer->set_calculated_shipping( true );
367
			}
368
369
			if ( isset( $_POST['s_state'] ) ) {
370
				WC()->customer->set_shipping_state( $_POST['s_state'] );
371
			}
372
373
			if ( isset( $_POST['s_postcode'] ) ) {
374
				WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
375
			}
376
377
			if ( isset( $_POST['s_city'] ) ) {
378
				WC()->customer->set_shipping_city( $_POST['s_city'] );
379
			}
380
381
			if ( isset( $_POST['s_address'] ) ) {
382
				WC()->customer->set_shipping_address( $_POST['s_address'] );
383
			}
384
385
			if ( isset( $_POST['s_address_2'] ) ) {
386
				WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
387
			}
388
		}
389
390
		WC()->customer->save();
391
		WC()->cart->calculate_totals();
392
393
		// Get order review fragment
394
		ob_start();
395
		woocommerce_order_review();
396
		$woocommerce_order_review = ob_get_clean();
397
398
		// Get checkout payment fragment
399
		ob_start();
400
		woocommerce_checkout_payment();
401
		$woocommerce_checkout_payment = ob_get_clean();
402
403
		// Get messages if reload checkout is not true
404
		$messages = '';
405
		if ( ! isset( WC()->session->reload_checkout ) ) {
406
			ob_start();
407
			wc_print_notices();
408
			$messages = ob_get_clean();
409
		}
410
411
		$data = array(
412
			'result'    => empty( $messages ) ? 'success' : 'failure',
413
			'messages'  => $messages,
414
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
415
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
416
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
417
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
418
			) )
419
		);
420
421
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
422
423
		wp_send_json( $data );
424
425
		die();
426
	}
427
428
	/**
429
	 * AJAX add to cart.
430
	 */
431
	public static function add_to_cart() {
432
		ob_start();
433
434
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
435
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
436
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
437
		$product_status    = get_post_status( $product_id );
438
439
		if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) && 'publish' === $product_status ) {
440
441
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
442
443
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
444
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
445
			}
446
447
			// Return fragments
448
			self::get_refreshed_fragments();
449
450
		} else {
451
452
			// If there was an error adding to the cart, redirect to the product page to show any errors
453
			$data = array(
454
				'error'       => true,
455
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
456
			);
457
458
			wp_send_json( $data );
459
460
		}
461
462
		die();
463
	}
464
465
	/**
466
	 * Process ajax checkout form.
467
	 */
468
	public static function checkout() {
469
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
470
			define( 'WOOCOMMERCE_CHECKOUT', true );
471
		}
472
473
		WC()->checkout()->process_checkout();
474
475
		die(0);
476
	}
477
478
	/**
479
	 * Get a matching variation based on posted attributes.
480
	 */
481
	public static function get_variation() {
482
		ob_start();
483
484
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
485
			die();
486
		}
487
488
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
489
490
		if ( $variation_id ) {
491
			$variation = $variable_product->get_available_variation( $variation_id );
492
		} else {
493
			$variation = false;
494
		}
495
496
		wp_send_json( $variation );
497
498
		die();
499
	}
500
501
	/**
502
	 * Feature a product from admin.
503
	 */
504
	public static function feature_product() {
505
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
506
			$product_id = absint( $_GET['product_id'] );
507
508
			if ( 'product' === get_post_type( $product_id ) ) {
509
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
510
511
				delete_transient( 'wc_featured_products' );
512
			}
513
		}
514
515
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=product' ) );
516
		die();
517
	}
518
519
	/**
520
	 * Mark an order with a status.
521
	 */
522
	public static function mark_order_status() {
523
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
524
			$status   = sanitize_text_field( $_GET['status'] );
525
			$order_id = absint( $_GET['order_id'] );
526
527
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
528
				$order = wc_get_order( $order_id );
529
				$order->update_status( $status, '', true );
530
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
531
			}
532
		}
533
534
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
535
		die();
536
	}
537
538
	/**
539
	 * Add an attribute row.
540
	 */
541
	public static function add_attribute() {
542
		ob_start();
543
544
		check_ajax_referer( 'add-attribute', 'security' );
545
546
		if ( ! current_user_can( 'edit_products' ) ) {
547
			die(-1);
548
		}
549
550
		global $wc_product_attributes;
551
552
		$thepostid     = 0;
553
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
554
		$i             = absint( $_POST['i'] );
555
		$position      = 0;
556
		$metabox_class = array();
557
		$attribute     = array(
558
			'name'         => $taxonomy,
559
			'value'        => '',
560
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
561
			'is_variation' => apply_filters( 'woocommerce_attribute_default_is_variation', 0 ),
562
			'is_taxonomy'  => $taxonomy ? 1 : 0
563
		);
564
565
		if ( $taxonomy ) {
566
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
567
			$metabox_class[]    = 'taxonomy';
568
			$metabox_class[]    = $taxonomy;
569
			$attribute_label    = wc_attribute_label( $taxonomy );
570
		} else {
571
			$attribute_label = '';
572
		}
573
574
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
575
		die();
576
	}
577
578
	/**
579
	 * Add a new attribute via ajax function.
580
	 */
581
	public static function add_new_attribute() {
582
		ob_start();
583
584
		check_ajax_referer( 'add-attribute', 'security' );
585
586
		if ( ! current_user_can( 'manage_product_terms' ) ) {
587
			die(-1);
588
		}
589
590
		$taxonomy = esc_attr( $_POST['taxonomy'] );
591
		$term     = wc_clean( $_POST['term'] );
592
593
		if ( taxonomy_exists( $taxonomy ) ) {
594
595
			$result = wp_insert_term( $term, $taxonomy );
596
597
			if ( is_wp_error( $result ) ) {
598
				wp_send_json( array(
599
					'error' => $result->get_error_message()
600
				) );
601
			} else {
602
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
603
				wp_send_json( array(
604
					'term_id' => $term->term_id,
605
					'name'    => $term->name,
606
					'slug'    => $term->slug
607
				) );
608
			}
609
		}
610
611
		die();
612
	}
613
614
	/**
615
	 * Delete variations via ajax function.
616
	 */
617
	public static function remove_variations() {
618
		check_ajax_referer( 'delete-variations', 'security' );
619
620
		if ( ! current_user_can( 'edit_products' ) ) {
621
			die(-1);
622
		}
623
624
		$variation_ids = (array) $_POST['variation_ids'];
625
626
		foreach ( $variation_ids as $variation_id ) {
627
			$variation = get_post( $variation_id );
628
629
			if ( $variation && 'product_variation' == $variation->post_type ) {
630
				wp_delete_post( $variation_id );
631
			}
632
		}
633
634
		die();
635
	}
636
637
	/**
638
	 * Save attributes via ajax.
639
	 */
640
	public static function save_attributes() {
641
642
		check_ajax_referer( 'save-attributes', 'security' );
643
644
		if ( ! current_user_can( 'edit_products' ) ) {
645
			die(-1);
646
		}
647
648
		// Get post data
649
		parse_str( $_POST['data'], $data );
650
		$post_id = absint( $_POST['post_id'] );
651
652
		// Save Attributes
653
		$attributes = array();
654
655
		if ( isset( $data['attribute_names'] ) ) {
656
657
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
658
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
659
660
			if ( isset( $data['attribute_visibility'] ) ) {
661
				$attribute_visibility = $data['attribute_visibility'];
662
			}
663
664
			if ( isset( $data['attribute_variation'] ) ) {
665
				$attribute_variation = $data['attribute_variation'];
666
			}
667
668
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
669
			$attribute_position      = $data['attribute_position'];
670
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
671
672
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
673
				if ( empty( $attribute_names[ $i ] ) ) {
674
					continue;
675
				}
676
677
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
678
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
679
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
680
681
				if ( $is_taxonomy ) {
682
683
					if ( isset( $attribute_values[ $i ] ) ) {
684
685
						// Select based attributes - Format values (posted values are slugs)
686
						if ( is_array( $attribute_values[ $i ] ) ) {
687
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
688
689
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
690
						} else {
691
							$values     = array();
692
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
693
694
							foreach ( $raw_values as $value ) {
695
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
696
								if ( ! $term ) {
697
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
698
699
									if ( $term && ! is_wp_error( $term ) ) {
700
										$values[] = $term['term_id'];
701
									}
702
								} else {
703
									$values[] = $term->term_id;
704
								}
705
							}
706
						}
707
708
						// Remove empty items in the array
709
						$values = array_filter( $values, 'strlen' );
710
711
					} else {
712
						$values = array();
713
					}
714
715
					// Update post terms
716
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
717
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
718
					}
719
720 View Code Duplication
					if ( ! empty( $values ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
721
						// Add attribute to array, but don't set values
722
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
723
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
724
							'value' 		=> '',
725
							'position' 		=> $attribute_position[ $i ],
726
							'is_visible' 	=> $is_visible,
727
							'is_variation' 	=> $is_variation,
728
							'is_taxonomy' 	=> $is_taxonomy
729
						);
730
					}
731
732 View Code Duplication
				} elseif ( isset( $attribute_values[ $i ] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
733
734
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
735
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
736
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
737
738
					// Custom attribute - Add attribute to array and set the values
739
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
740
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
741
						'value' 		=> $values,
742
						'position' 		=> $attribute_position[ $i ],
743
						'is_visible' 	=> $is_visible,
744
						'is_variation' 	=> $is_variation,
745
						'is_taxonomy' 	=> $is_taxonomy
746
					);
747
				}
748
749
			 }
750
		}
751
752
		uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
753
754
		update_post_meta( $post_id, '_product_attributes', $attributes );
755
756
		die();
757
	}
758
759
	/**
760
	 * Add variation via ajax function.
761
	 */
762
	public static function add_variation() {
763
764
		check_ajax_referer( 'add-variation', 'security' );
765
766
		if ( ! current_user_can( 'edit_products' ) ) {
767
			die(-1);
768
		}
769
770
		global $post;
771
772
		$post_id = intval( $_POST['post_id'] );
773
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
774
		$loop    = intval( $_POST['loop'] );
775
776
		$variation = array(
777
			'post_title'   => 'Product #' . $post_id . ' Variation',
778
			'post_content' => '',
779
			'post_status'  => 'publish',
780
			'post_author'  => get_current_user_id(),
781
			'post_parent'  => $post_id,
782
			'post_type'    => 'product_variation',
783
			'menu_order'   => -1
784
		);
785
786
		$variation_id = wp_insert_post( $variation );
787
788
		do_action( 'woocommerce_create_product_variation', $variation_id );
789
790
		if ( $variation_id ) {
791
			$variation        = get_post( $variation_id );
792
			$variation_meta   = get_post_meta( $variation_id );
793
			$variation_data   = array();
794
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
795
			$variation_fields = array(
796
				'_sku'                   => '',
797
				'_stock'                 => '',
798
				'_regular_price'         => '',
799
				'_sale_price'            => '',
800
				'_weight'                => '',
801
				'_length'                => '',
802
				'_width'                 => '',
803
				'_height'                => '',
804
				'_download_limit'        => '',
805
				'_download_expiry'       => '',
806
				'_downloadable_files'    => '',
807
				'_downloadable'          => '',
808
				'_virtual'               => '',
809
				'_thumbnail_id'          => '',
810
				'_sale_price_dates_from' => '',
811
				'_sale_price_dates_to'   => '',
812
				'_manage_stock'          => '',
813
				'_stock_status'          => '',
814
				'_backorders'            => null,
815
				'_tax_class'             => null,
816
				'_variation_description' => ''
817
			);
818
819 View Code Duplication
			foreach ( $variation_fields as $field => $value ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
820
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
821
			}
822
823
			// Add the variation attributes
824
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
825
826
			// Formatting
827
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
828
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
829
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
830
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
831
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
832
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
833
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
834
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
835
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
836
			$variation_data['menu_order']     = $variation->menu_order;
837
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
838
839
			// Get tax classes
840
			$tax_classes           = WC_Tax::get_tax_classes();
841
			$tax_class_options     = array();
842
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
843
844 View Code Duplication
			if ( ! empty( $tax_classes ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
845
				foreach ( $tax_classes as $class ) {
846
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
847
				}
848
			}
849
850
			// Set backorder options
851
			$backorder_options = array(
852
				'no'     => __( 'Do not allow', 'woocommerce' ),
853
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
854
				'yes'    => __( 'Allow', 'woocommerce' )
855
			);
856
857
			// set stock status options
858
			$stock_status_options = array(
859
				'instock'    => __( 'In stock', 'woocommerce' ),
860
				'outofstock' => __( 'Out of stock', 'woocommerce' )
861
			);
862
863
			// Get attributes
864
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
865
866
			$parent_data = array(
867
				'id'                   => $post_id,
868
				'attributes'           => $attributes,
869
				'tax_class_options'    => $tax_class_options,
870
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
871
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
872
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
873
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
874
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
875
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
876
				'backorder_options'    => $backorder_options,
877
				'stock_status_options' => $stock_status_options
878
			);
879
880
			if ( ! $parent_data['weight'] ) {
881
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
882
			}
883
884
			if ( ! $parent_data['length'] ) {
885
				$parent_data['length'] = wc_format_localized_decimal( 0 );
886
			}
887
888
			if ( ! $parent_data['width'] ) {
889
				$parent_data['width'] = wc_format_localized_decimal( 0 );
890
			}
891
892
			if ( ! $parent_data['height'] ) {
893
				$parent_data['height'] = wc_format_localized_decimal( 0 );
894
			}
895
896
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
897
		}
898
899
		die();
900
	}
901
902
	/**
903
	 * Link all variations via ajax function.
904
	 */
905
	public static function link_all_variations() {
906
907
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
908
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
909
		}
910
911
		check_ajax_referer( 'link-variations', 'security' );
912
913
		if ( ! current_user_can( 'edit_products' ) ) {
914
			die(-1);
915
		}
916
917
		wc_set_time_limit( 0 );
918
919
		$post_id = intval( $_POST['post_id'] );
920
921
		if ( ! $post_id ) {
922
			die();
923
		}
924
925
		$variations = array();
926
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
927
928
		// Put variation attributes into an array
929
		foreach ( $_product->get_attributes() as $attribute ) {
930
931
			if ( ! $attribute['is_variation'] ) {
932
				continue;
933
			}
934
935
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
936
937
			if ( $attribute['is_taxonomy'] ) {
938
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
939
			} else {
940
				$options = explode( WC_DELIMITER, $attribute['value'] );
941
			}
942
943
			$options = array_map( 'trim', $options );
944
945
			$variations[ $attribute_field_name ] = $options;
946
		}
947
948
		// Quit out if none were found
949
		if ( sizeof( $variations ) == 0 ) {
950
			die();
951
		}
952
953
		// Get existing variations so we don't create duplicates
954
		$available_variations = array();
955
956
		foreach( $_product->get_children() as $child_id ) {
957
			$child = $_product->get_child( $child_id );
958
959
			if ( ! empty( $child->variation_id ) ) {
960
				$available_variations[] = $child->get_variation_attributes();
961
			}
962
		}
963
964
		// Created posts will all have the following data
965
		$variation_post_data = array(
966
			'post_title'   => 'Product #' . $post_id . ' Variation',
967
			'post_content' => '',
968
			'post_status'  => 'publish',
969
			'post_author'  => get_current_user_id(),
970
			'post_parent'  => $post_id,
971
			'post_type'    => 'product_variation'
972
		);
973
974
		$variation_ids       = array();
975
		$added               = 0;
976
		$possible_variations = wc_array_cartesian( $variations );
977
978
		foreach ( $possible_variations as $variation ) {
979
980
			// Check if variation already exists
981
			if ( in_array( $variation, $available_variations ) ) {
982
				continue;
983
			}
984
985
			$variation_id = wp_insert_post( $variation_post_data );
986
987
			$variation_ids[] = $variation_id;
988
989
			foreach ( $variation as $key => $value ) {
990
				update_post_meta( $variation_id, $key, $value );
991
			}
992
993
			// Save stock status
994
			update_post_meta( $variation_id, '_stock_status', 'instock' );
995
996
			$added++;
997
998
			do_action( 'product_variation_linked', $variation_id );
999
1000
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
1001
				break;
1002
			}
1003
		}
1004
1005
		delete_transient( 'wc_product_children_' . $post_id );
1006
1007
		echo $added;
1008
1009
		die();
1010
	}
1011
1012
	/**
1013
	 * Delete download permissions via ajax function.
1014
	 */
1015
	public static function revoke_access_to_download() {
1016
		check_ajax_referer( 'revoke-access', 'security' );
1017
1018
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1019
			die( -1 );
1020
		}
1021
1022
		global $wpdb;
1023
1024
		$download_id   = $_POST['download_id'];
1025
		$product_id    = intval( $_POST['product_id'] );
1026
		$order_id      = intval( $_POST['order_id'] );
1027
		$permission_id = absint( $_POST['permission_id'] );
1028
1029
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d;", $permission_id ) );
1030
1031
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id, $permission_id );
1032
1033
		die();
1034
	}
1035
1036
	/**
1037
	 * Grant download permissions via ajax function.
1038
	 */
1039
	public static function grant_access_to_download() {
1040
1041
		check_ajax_referer( 'grant-access', 'security' );
1042
1043
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1044
			die(-1);
1045
		}
1046
1047
		global $wpdb;
1048
1049
		$wpdb->hide_errors();
1050
1051
		$order_id     = intval( $_POST['order_id'] );
1052
		$product_ids  = $_POST['product_ids'];
1053
		$loop         = intval( $_POST['loop'] );
1054
		$file_counter = 0;
1055
		$order        = wc_get_order( $order_id );
1056
1057
		if ( ! is_array( $product_ids ) ) {
1058
			$product_ids = array( $product_ids );
1059
		}
1060
1061
		foreach ( $product_ids as $product_id ) {
1062
			$product = wc_get_product( $product_id );
1063
			$files   = $product->get_files();
1064
1065
			if ( ! $order->get_billing_email() ) {
1066
				die();
1067
			}
1068
1069
			if ( ! empty( $files ) ) {
1070
				foreach ( $files as $download_id => $file ) {
1071
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
0 ignored issues
show
Documentation introduced by
$order is of type false|object, but the function expects a object<WC_Order>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1072
1073
						// insert complete - get inserted data
1074
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1075
1076
						$loop ++;
1077
						$file_counter ++;
1078
1079 View Code Duplication
						if ( isset( $file['name'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1080
							$file_count = $file['name'];
1081
						} else {
1082
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1083
						}
1084
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1085
					}
1086
				}
1087
			}
1088
		}
1089
1090
		die();
1091
	}
1092
1093
	/**
1094
	 * Get customer details via ajax.
1095
	 */
1096
	public static function get_customer_details() {
1097
		ob_start();
1098
1099
		check_ajax_referer( 'get-customer-details', 'security' );
1100
1101
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1102
			die(-1);
1103
		}
1104
1105
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1106
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1107
1108
		$customer_data = array(
1109
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1110
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1111
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1112
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1113
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1114
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1115
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1116
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1117
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1118
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1119
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1120
		);
1121
1122
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1123
1124
		wp_send_json( $customer_data );
1125
	}
1126
1127
	/**
1128
	 * Add order item via ajax.
1129
	 */
1130
	public static function add_order_item() {
1131
		check_ajax_referer( 'order-item', 'security' );
1132
1133
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1134
			die(-1);
1135
		}
1136
1137
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1138
		$order_id    = absint( $_POST['order_id'] );
1139
1140
		// Find the item
1141
		if ( ! is_numeric( $item_to_add ) ) {
1142
			die();
1143
		}
1144
1145
		$post = get_post( $item_to_add );
1146
1147
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1148
			die();
1149
		}
1150
1151
		$product     = wc_get_product( $post->ID );
1152
		$order       = wc_get_order( $order_id );
1153
		$order_taxes = $order->get_taxes();
1154
		$class       = 'new_row';
1155
		$item_id     = $order->add_product( $product );
1156
		$item        = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id );
1157
1158
		do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1159
1160
		include( 'admin/meta-boxes/views/html-order-item.php' );
1161
		die();
1162
	}
1163
1164
	/**
1165
	 * Add order fee via ajax.
1166
	 */
1167
	public static function add_order_fee() {
1168
1169
		check_ajax_referer( 'order-item', 'security' );
1170
1171
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1172
			die(-1);
1173
		}
1174
1175
		$order_id      = absint( $_POST['order_id'] );
1176
		$order         = wc_get_order( $order_id );
1177
		$order_taxes   = $order->get_taxes();
1178
		$item          = array();
1179
1180
		// Add new fee
1181
		$fee            = new stdClass();
1182
		$fee->name      = '';
1183
		$fee->tax_class = '';
1184
		$fee->taxable   = $fee->tax_class !== '0';
1185
		$fee->amount    = '';
1186
		$fee->tax       = '';
1187
		$fee->tax_data  = array();
1188
		$item_id        = $order->add_fee( $fee );
1189
1190
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1191
1192
		// Quit out
1193
		die();
1194
	}
1195
1196
	/**
1197
	 * Add order shipping cost via ajax.
1198
	 */
1199
	public static function add_order_shipping() {
1200
1201
		check_ajax_referer( 'order-item', 'security' );
1202
1203
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1204
			die(-1);
1205
		}
1206
1207
		$order_id         = absint( $_POST['order_id'] );
1208
		$order            = wc_get_order( $order_id );
1209
		$order_taxes      = $order->get_taxes();
1210
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1211
1212
		// Add new shipping
1213
		$shipping = new WC_Shipping_Rate();
1214
		$item_id  = $order->add_shipping( $shipping );
1215
		$item     = $order->get_item( $item_id );
1216
1217
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1218
1219
		// Quit out
1220
		die();
1221
	}
1222
1223
	/**
1224
	 * Add order tax column via ajax.
1225
	 */
1226
	public static function add_order_tax() {
1227
		global $wpdb;
1228
1229
		check_ajax_referer( 'order-item', 'security' );
1230
1231
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1232
			die(-1);
1233
		}
1234
1235
		$order_id = absint( $_POST['order_id'] );
1236
		$rate_id  = absint( $_POST['rate_id'] );
1237
		$order    = wc_get_order( $order_id );
1238
		$data     = get_post_meta( $order_id );
1239
1240
		// Add new tax
1241
		$order->add_tax( $rate_id, 0, 0 );
1242
1243
		// Return HTML items
1244
		include( 'admin/meta-boxes/views/html-order-items.php' );
1245
1246
		die();
1247
	}
1248
1249
	/**
1250
	 * Remove an order item.
1251
	 */
1252
	public static function remove_order_item() {
1253
		check_ajax_referer( 'order-item', 'security' );
1254
1255
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1256
			die(-1);
1257
		}
1258
1259
		$order_item_ids = $_POST['order_item_ids'];
1260
1261
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1262
			$order_item_ids = array( $order_item_ids );
1263
		}
1264
1265
		if ( sizeof( $order_item_ids ) > 0 ) {
1266
			foreach( $order_item_ids as $id ) {
1267
				wc_delete_order_item( absint( $id ) );
1268
			}
1269
		}
1270
1271
		die();
1272
	}
1273
1274
	/**
1275
	 * Remove an order tax.
1276
	 */
1277
	public static function remove_order_tax() {
1278
1279
		check_ajax_referer( 'order-item', 'security' );
1280
1281
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1282
			die(-1);
1283
		}
1284
1285
		$order_id = absint( $_POST['order_id'] );
1286
		$rate_id  = absint( $_POST['rate_id'] );
1287
1288
		wc_delete_order_item( $rate_id );
1289
1290
		// Return HTML items
1291
		$order = wc_get_order( $order_id );
1292
		$data  = get_post_meta( $order_id );
1293
		include( 'admin/meta-boxes/views/html-order-items.php' );
1294
1295
		die();
1296
	}
1297
1298
	/**
1299
	 * Reduce order item stock.
1300
	 */
1301
	public static function reduce_order_item_stock() {
1302
		check_ajax_referer( 'order-item', 'security' );
1303
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1304
			die(-1);
1305
		}
1306
		$order_id       = absint( $_POST['order_id'] );
1307
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1308
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1309
		$order          = wc_get_order( $order_id );
1310
		$order_items    = $order->get_items();
1311
		$return         = array();
1312
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1313
			foreach ( $order_items as $item_id => $order_item ) {
1314
				// Only reduce checked items
1315
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1316
					continue;
1317
				}
1318
				$_product = $order->get_product_from_item( $order_item );
1319
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1320
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1321
					$new_stock    = $_product->reduce_stock( $stock_change );
1322
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1323
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1324
					$return[]     = $note;
1325
					$order->add_order_note( $note );
1326
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1327
				}
1328
			}
1329
			do_action( 'woocommerce_reduce_order_stock', $order );
1330
			if ( empty( $return ) ) {
1331
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1332
			}
1333
			echo implode( ', ', $return );
1334
		}
1335
		die();
1336
	}
1337
1338
	/**
1339
	 * Increase order item stock.
1340
	 */
1341
	public static function increase_order_item_stock() {
1342
		check_ajax_referer( 'order-item', 'security' );
1343
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1344
			die(-1);
1345
		}
1346
		$order_id       = absint( $_POST['order_id'] );
1347
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1348
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1349
		$order          = wc_get_order( $order_id );
1350
		$order_items    = $order->get_items();
1351
		$return         = array();
1352
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1353
			foreach ( $order_items as $item_id => $order_item ) {
1354
				// Only reduce checked items
1355
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1356
					continue;
1357
				}
1358
				$_product = $order->get_product_from_item( $order_item );
1359
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1360
					$old_stock    = $_product->get_stock_quantity();
1361
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1362
					$new_quantity = $_product->increase_stock( $stock_change );
1363
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1364
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1365
					$return[]     = $note;
1366
					$order->add_order_note( $note );
1367
				}
1368
			}
1369
			do_action( 'woocommerce_restore_order_stock', $order );
1370
			if ( empty( $return ) ) {
1371
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1372
			}
1373
			echo implode( ', ', $return );
1374
		}
1375
		die();
1376
	}
1377
1378
	/**
1379
	 * Add some meta to a line item.
1380
	 */
1381
	public static function add_order_item_meta() {
1382
		check_ajax_referer( 'order-item', 'security' );
1383
1384
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1385
			die(-1);
1386
		}
1387
1388
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1389
1390
		if ( $meta_id ) {
1391
			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>';
1392
		}
1393
1394
		die();
1395
	}
1396
1397
	/**
1398
	 * Remove meta from a line item.
1399
	 */
1400
	public static function remove_order_item_meta() {
1401
		check_ajax_referer( 'order-item', 'security' );
1402
1403
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1404
			die(-1);
1405
		}
1406
1407
		global $wpdb;
1408
1409
		$wpdb->delete( "{$wpdb->prefix}woocommerce_order_itemmeta", array(
1410
			'meta_id' => absint( $_POST['meta_id'] ),
1411
		) );
1412
1413
		die();
1414
	}
1415
1416
	/**
1417
	 * Calc line tax.
1418
	 */
1419
	public static function calc_line_taxes() {
1420
		global $wpdb;
1421
1422
		check_ajax_referer( 'calc-totals', 'security' );
1423
1424
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1425
			die(-1);
1426
		}
1427
1428
		$tax                    = new WC_Tax();
1429
		$tax_based_on           = get_option( 'woocommerce_tax_based_on' );
1430
		$order_id               = absint( $_POST['order_id'] );
1431
		$items                  = array();
1432
		$country                = strtoupper( esc_attr( $_POST['country'] ) );
1433
		$state                  = strtoupper( esc_attr( $_POST['state'] ) );
1434
		$postcode               = strtoupper( esc_attr( $_POST['postcode'] ) );
1435
		$city                   = wc_clean( esc_attr( $_POST['city'] ) );
1436
		$order                  = wc_get_order( $order_id );
1437
		$taxes                  = array();
1438
		$shipping_taxes         = array();
1439
		$order_item_tax_classes = array();
1440
1441
		// Default to base
1442
		if ( 'base' === $tax_based_on || empty( $country ) ) {
1443
			$default  = wc_get_base_location();
1444
			$country  = $default['country'];
1445
			$state    = $default['state'];
1446
			$postcode = '';
1447
			$city     = '';
1448
		}
1449
1450
		// Parse the jQuery serialized items
1451
		parse_str( $_POST['items'], $items );
1452
1453
		// Prevent undefined warnings
1454
		if ( ! isset( $items['line_tax'] ) ) {
1455
			$items['line_tax'] = array();
1456
		}
1457
		if ( ! isset( $items['line_subtotal_tax'] ) ) {
1458
			$items['line_subtotal_tax'] = array();
1459
		}
1460
		$items['order_taxes'] = array();
1461
1462
		// Action
1463
		$items = apply_filters( 'woocommerce_ajax_calc_line_taxes', $items, $order_id, $country, $_POST );
1464
1465
		$is_vat_exempt = get_post_meta( $order_id, '_is_vat_exempt', true );
1466
1467
		// Tax is calculated only if tax is enabled and order is not vat exempted
1468
		if ( wc_tax_enabled() && $is_vat_exempt !== 'yes' ) {
1469
1470
			// Get items and fees taxes
1471
			if ( isset( $items['order_item_id'] ) ) {
1472
				$line_total = $line_subtotal = array();
1473
1474
				foreach ( $items['order_item_id'] as $item_id ) {
1475
					$item_id                            = absint( $item_id );
1476
					$line_total[ $item_id ]             = isset( $items['line_total'][ $item_id ] ) ? wc_format_decimal( $items['line_total'][ $item_id ] ) : 0;
1477
					$line_subtotal[ $item_id ]          = isset( $items['line_subtotal'][ $item_id ] ) ? wc_format_decimal( $items['line_subtotal'][ $item_id ] ) : $line_total[ $item_id ];
1478
					$order_item_tax_classes[ $item_id ] = isset( $items['order_item_tax_class'][ $item_id ] ) ? sanitize_text_field( $items['order_item_tax_class'][ $item_id ] ) : '';
1479
					$product_id                         = $order->get_item_meta( $item_id, '_product_id', true );
1480
1481
					// Get product details
1482
					if ( get_post_type( $product_id ) == 'product' ) {
1483
						$_product        = wc_get_product( $product_id );
1484
						$item_tax_status = $_product->get_tax_status();
1485
					} else {
1486
						$item_tax_status = 'taxable';
1487
					}
1488
1489
					if ( '0' !== $order_item_tax_classes[ $item_id ] && 'taxable' === $item_tax_status ) {
1490
						$tax_rates = WC_Tax::find_rates( array(
1491
							'country'   => $country,
1492
							'state'     => $state,
1493
							'postcode'  => $postcode,
1494
							'city'      => $city,
1495
							'tax_class' => $order_item_tax_classes[ $item_id ]
1496
						) );
1497
1498
						$line_taxes          = WC_Tax::calc_tax( $line_total[ $item_id ], $tax_rates, false );
1499
						$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal[ $item_id ], $tax_rates, false );
1500
1501
						// Set the new line_tax
1502
						foreach ( $line_taxes as $_tax_id => $_tax_value ) {
1503
							$items['line_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1504
						}
1505
1506
						// Set the new line_subtotal_tax
1507
						foreach ( $line_subtotal_taxes as $_tax_id => $_tax_value ) {
1508
							$items['line_subtotal_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1509
						}
1510
1511
						// Sum the item taxes
1512
						foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1513
							$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1514
						}
1515
					}
1516
				}
1517
			}
1518
1519
			// Get shipping taxes
1520
			if ( isset( $items['shipping_method_id'] ) ) {
1521
				$matched_tax_rates      = array();
1522
				$order_item_tax_classes = array_unique( array_values( $order_item_tax_classes ) );
1523
1524
				// If multiple classes are found, use the first one. Don't bother with standard rate, we can get that later.
1525 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...
1526
					$tax_classes = WC_Tax::get_tax_classes();
1527
1528
					foreach ( $tax_classes as $tax_class ) {
1529
						$tax_class = sanitize_title( $tax_class );
1530
						if ( in_array( $tax_class, $order_item_tax_classes ) ) {
1531
							$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1532
								'country' 	=> $country,
1533
								'state' 	=> $state,
1534
								'postcode' 	=> $postcode,
1535
								'city' 		=> $city,
1536
								'tax_class' => $tax_class,
1537
							) );
1538
							break;
1539
						}
1540
					}
1541
				// If a single tax class is found, use it
1542
				} elseif ( sizeof( $order_item_tax_classes ) === 1 ) {
1543
					$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1544
						'country' 	=> $country,
1545
						'state' 	=> $state,
1546
						'postcode' 	=> $postcode,
1547
						'city' 		=> $city,
1548
						'tax_class' => $order_item_tax_classes[0]
1549
					) );
1550
				}
1551
1552
				// Get standard rate if no taxes were found
1553
				if ( ! sizeof( $matched_tax_rates ) ) {
1554
					$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1555
						'country' 	=> $country,
1556
						'state' 	=> $state,
1557
						'postcode' 	=> $postcode,
1558
						'city' 		=> $city
1559
					) );
1560
				}
1561
1562
				$shipping_cost = $shipping_taxes = array();
1563
1564
				foreach ( $items['shipping_method_id'] as $item_id ) {
1565
					$item_id                   = absint( $item_id );
1566
					$shipping_cost[ $item_id ] = isset( $items['shipping_cost'][ $item_id ] ) ? wc_format_decimal( $items['shipping_cost'][ $item_id ] ) : 0;
1567
					$_shipping_taxes           = WC_Tax::calc_shipping_tax( $shipping_cost[ $item_id ], $matched_tax_rates );
1568
1569
					// Set the new shipping_taxes
1570
					foreach ( $_shipping_taxes as $_tax_id => $_tax_value ) {
1571
						$items['shipping_taxes'][ $item_id ][ $_tax_id ] = $_tax_value;
1572
1573
						$shipping_taxes[ $_tax_id ] = isset( $shipping_taxes[ $_tax_id ] ) ? $shipping_taxes[ $_tax_id ] + $_tax_value : $_tax_value;
1574
					}
1575
				}
1576
			}
1577
		}
1578
1579
		// Remove old tax rows
1580
		$order->remove_order_items( 'tax' );
1581
1582
		// Add tax rows
1583
		foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_id ) {
1584
			$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 );
1585
		}
1586
1587
		// Create the new order_taxes
1588
		foreach ( $order->get_taxes() as $tax_id => $tax_item ) {
1589
			$items['order_taxes'][ $tax_id ] = absint( $tax_item['rate_id'] );
1590
		}
1591
1592
		$items = apply_filters( 'woocommerce_ajax_after_calc_line_taxes', $items, $order_id, $country, $_POST );
1593
1594
		// Save order items
1595
		wc_save_order_items( $order_id, $items );
1596
1597
		// Return HTML items
1598
		$order = wc_get_order( $order_id );
1599
		$data  = get_post_meta( $order_id );
1600
		include( 'admin/meta-boxes/views/html-order-items.php' );
1601
1602
		die();
1603
	}
1604
1605
	/**
1606
	 * Save order items via ajax.
1607
	 */
1608
	public static function save_order_items() {
1609
		check_ajax_referer( 'order-item', 'security' );
1610
1611
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1612
			die(-1);
1613
		}
1614
1615
		if ( isset( $_POST['order_id'] ) && isset( $_POST['items'] ) ) {
1616
			$order_id = absint( $_POST['order_id'] );
1617
1618
			// Parse the jQuery serialized items
1619
			$items = array();
1620
			parse_str( $_POST['items'], $items );
1621
1622
			// Save order items
1623
			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...
1624
1625
			// Return HTML items
1626
			$order = wc_get_order( $order_id );
1627
			$data  = get_post_meta( $order_id );
1628
			include( 'admin/meta-boxes/views/html-order-items.php' );
1629
		}
1630
1631
		die();
1632
	}
1633
1634
	/**
1635
	 * Load order items via ajax.
1636
	 */
1637
	public static function load_order_items() {
1638
		check_ajax_referer( 'order-item', 'security' );
1639
1640
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1641
			die(-1);
1642
		}
1643
1644
		// Return HTML items
1645
		$order_id = absint( $_POST['order_id'] );
1646
		$order    = wc_get_order( $order_id );
1647
		$data     = get_post_meta( $order_id );
1648
		include( 'admin/meta-boxes/views/html-order-items.php' );
1649
1650
		die();
1651
	}
1652
1653
	/**
1654
	 * Add order note via ajax.
1655
	 */
1656
	public static function add_order_note() {
1657
1658
		check_ajax_referer( 'add-order-note', 'security' );
1659
1660
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1661
			die(-1);
1662
		}
1663
1664
		$post_id   = absint( $_POST['post_id'] );
1665
		$note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
1666
		$note_type = $_POST['note_type'];
1667
1668
		$is_customer_note = $note_type == 'customer' ? 1 : 0;
1669
1670
		if ( $post_id > 0 ) {
1671
			$order      = wc_get_order( $post_id );
1672
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1673
1674
			echo '<li rel="' . esc_attr( $comment_id ) . '" class="note ';
1675
			if ( $is_customer_note ) {
1676
				echo 'customer-note';
1677
			}
1678
			echo '"><div class="note_content">';
1679
			echo wpautop( wptexturize( $note ) );
1680
			echo '</div><p class="meta"><a href="#" class="delete_note">'.__( 'Delete note', 'woocommerce' ).'</a></p>';
1681
			echo '</li>';
1682
		}
1683
1684
		// Quit out
1685
		die();
1686
	}
1687
1688
	/**
1689
	 * Delete order note via ajax.
1690
	 */
1691
	public static function delete_order_note() {
1692
1693
		check_ajax_referer( 'delete-order-note', 'security' );
1694
1695
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1696
			die(-1);
1697
		}
1698
1699
		$note_id = (int) $_POST['note_id'];
1700
1701
		if ( $note_id > 0 ) {
1702
			wp_delete_comment( $note_id );
1703
		}
1704
1705
		// Quit out
1706
		die();
1707
	}
1708
1709
	/**
1710
	 * Search for products and echo json.
1711
	 *
1712
	 * @param string $term (default: '')
1713
	 * @param string $post_types (default: array('product'))
1714
	 */
1715
	public static function json_search_products( $term = '', $post_types = array( 'product' ) ) {
1716
		global $wpdb;
1717
1718
		ob_start();
1719
1720
		check_ajax_referer( 'search-products', 'security' );
1721
1722
		if ( empty( $term ) ) {
1723
			$term = wc_clean( stripslashes( $_GET['term'] ) );
1724
		} else {
1725
			$term = wc_clean( $term );
1726
		}
1727
1728
		if ( empty( $term ) ) {
1729
			die();
1730
		}
1731
1732
		$like_term = '%' . $wpdb->esc_like( $term ) . '%';
1733
1734
		if ( is_numeric( $term ) ) {
1735
			$query = $wpdb->prepare( "
1736
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1737
				WHERE posts.post_status = 'publish'
1738
				AND (
1739
					posts.post_parent = %s
1740
					OR posts.ID = %s
1741
					OR posts.post_title LIKE %s
1742
					OR (
1743
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1744
					)
1745
				)
1746
			", $term, $term, $term, $like_term );
1747
		} else {
1748
			$query = $wpdb->prepare( "
1749
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1750
				WHERE posts.post_status = 'publish'
1751
				AND (
1752
					posts.post_title LIKE %s
1753
					or posts.post_content LIKE %s
1754
					OR (
1755
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1756
					)
1757
				)
1758
			", $like_term, $like_term, $like_term );
1759
		}
1760
1761
		$query .= " AND posts.post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "')";
1762
1763 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...
1764
			$query .= " AND posts.ID NOT IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['exclude'] ) ) ) . ")";
1765
		}
1766
1767 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...
1768
			$query .= " AND posts.ID IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['include'] ) ) ) . ")";
1769
		}
1770
1771
		if ( ! empty( $_GET['limit'] ) ) {
1772
			$query .= " LIMIT " . intval( $_GET['limit'] );
1773
		}
1774
1775
		$posts          = array_unique( $wpdb->get_col( $query ) );
1776
		$found_products = array();
1777
1778
		if ( ! empty( $posts ) ) {
1779
			foreach ( $posts as $post ) {
1780
				$product = wc_get_product( $post );
1781
1782
				if ( ! current_user_can( 'read_product', $post ) ) {
1783
					continue;
1784
				}
1785
1786
				if ( ! $product || ( $product->is_type( 'variation' ) && empty( $product->parent ) ) ) {
1787
					continue;
1788
				}
1789
1790
				$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1791
			}
1792
		}
1793
1794
		$found_products = apply_filters( 'woocommerce_json_search_found_products', $found_products );
1795
1796
		wp_send_json( $found_products );
1797
	}
1798
1799
	/**
1800
	 * Search for product variations and return json.
1801
	 *
1802
	 * @see WC_AJAX::json_search_products()
1803
	 */
1804
	public static function json_search_products_and_variations() {
1805
		self::json_search_products( '', array( 'product', 'product_variation' ) );
1806
	}
1807
1808
	/**
1809
	 * Search for grouped products and return json.
1810
	 */
1811
	public static function json_search_grouped_products() {
1812
		ob_start();
1813
1814
		check_ajax_referer( 'search-products', 'security' );
1815
1816
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1817
		$exclude = array();
1818
1819
		if ( empty( $term ) ) {
1820
			die();
1821
		}
1822
1823 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...
1824
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1825
		}
1826
1827
		$found_products = array();
1828
1829
		if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
1830
1831
			$posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
1832
1833
			if ( sizeof( $posts_in ) > 0 ) {
1834
1835
				$args = array(
1836
					'post_type'        => 'product',
1837
					'post_status'      => 'any',
1838
					'numberposts'      => -1,
1839
					'orderby'          => 'title',
1840
					'order'            => 'asc',
1841
					'post_parent'      => 0,
1842
					'suppress_filters' => 0,
1843
					'include'          => $posts_in,
1844
					's'                => $term,
1845
					'fields'           => 'ids',
1846
					'exclude'          => $exclude
1847
				);
1848
1849
				$posts = get_posts( $args );
1850
1851
				if ( ! empty( $posts ) ) {
1852
					foreach ( $posts as $post ) {
1853
						$product = wc_get_product( $post );
1854
1855
						if ( ! current_user_can( 'read_product', $post ) ) {
1856
							continue;
1857
						}
1858
1859
						$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1860
					}
1861
				}
1862
			}
1863
		}
1864
1865
		$found_products = apply_filters( 'woocommerce_json_search_found_grouped_products', $found_products );
1866
1867
		wp_send_json( $found_products );
1868
	}
1869
1870
	/**
1871
	 * Search for downloadable product variations and return json.
1872
	 *
1873
	 * @see WC_AJAX::json_search_products()
1874
	 */
1875
	public static function json_search_downloadable_products_and_variations() {
1876
		ob_start();
1877
1878
		check_ajax_referer( 'search-products', 'security' );
1879
1880
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1881
		$exclude = array();
1882
1883 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...
1884
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1885
		}
1886
1887
		$args = array(
1888
			'post_type'      => array( 'product', 'product_variation' ),
1889
			'posts_per_page' => -1,
1890
			'post_status'    => 'publish',
1891
			'order'          => 'ASC',
1892
			'orderby'        => 'parent title',
1893
			'meta_query'     => array(
1894
				array(
1895
					'key'   => '_downloadable',
1896
					'value' => 'yes'
1897
				)
1898
			),
1899
			's'              => $term,
1900
			'exclude'        => $exclude
1901
		);
1902
1903
		$posts = get_posts( $args );
1904
		$found_products = array();
1905
1906
		if ( ! empty( $posts ) ) {
1907
			foreach ( $posts as $post ) {
1908
				$product = wc_get_product( $post->ID );
1909
1910
				if ( ! current_user_can( 'read_product', $post->ID ) ) {
1911
					continue;
1912
				}
1913
1914
				$found_products[ $post->ID ] = $product->get_formatted_name();
1915
			}
1916
		}
1917
1918
		wp_send_json( $found_products );
1919
	}
1920
1921
	/**
1922
	 * Search for customers and return json.
1923
	 */
1924
	public static function json_search_customers() {
1925
		ob_start();
1926
1927
		check_ajax_referer( 'search-customers', 'security' );
1928
1929
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1930
			die(-1);
1931
		}
1932
1933
		$term    = wc_clean( stripslashes( $_GET['term'] ) );
1934
		$exclude = array();
1935
1936
		if ( empty( $term ) ) {
1937
			die();
1938
		}
1939
1940
		if ( ! empty( $_GET['exclude'] ) ) {
1941
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1942
		}
1943
1944
		$found_customers = array();
1945
1946
		add_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1947
1948
		$customers_query = new WP_User_Query( apply_filters( 'woocommerce_json_search_customers_query', array(
1949
			'fields'         => 'all',
1950
			'orderby'        => 'display_name',
1951
			'search'         => '*' . $term . '*',
1952
			'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename' )
1953
		) ) );
1954
1955
		remove_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1956
1957
		$customers = $customers_query->get_results();
1958
1959
		if ( ! empty( $customers ) ) {
1960
			foreach ( $customers as $customer ) {
1961
				if ( ! in_array( $customer->ID, $exclude ) ) {
1962
					$found_customers[ $customer->ID ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
1963
				}
1964
			}
1965
		}
1966
1967
		$found_customers = apply_filters( 'woocommerce_json_search_found_customers', $found_customers );
1968
1969
		wp_send_json( $found_customers );
1970
	}
1971
1972
	/**
1973
	 * When searching using the WP_User_Query, search names (user meta) too.
1974
	 * @param  object $query
1975
	 * @return object
1976
	 */
1977
	public static function json_search_customer_name( $query ) {
1978
		global $wpdb;
1979
1980
		$term = wc_clean( stripslashes( $_GET['term'] ) );
1981
		if ( method_exists( $wpdb, 'esc_like' ) ) {
1982
			$term = $wpdb->esc_like( $term );
1983
		} else {
1984
			$term = like_escape( $term );
1985
		}
1986
1987
		$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' ) ";
1988
		$query->query_where .= $wpdb->prepare( " OR user_name.meta_value LIKE %s ", '%' . $term . '%' );
1989
	}
1990
1991
	/**
1992
	 * Ajax request handling for categories ordering.
1993
	 */
1994
	public static function term_ordering() {
1995
1996
		// check permissions again and make sure we have what we need
1997
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
1998
			die(-1);
1999
		}
2000
2001
		$id       = (int) $_POST['id'];
2002
		$next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
2003
		$taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null;
2004
		$term     = get_term_by( 'id', $id, $taxonomy );
2005
2006
		if ( ! $id || ! $term || ! $taxonomy ) {
2007
			die(0);
2008
		}
2009
2010
		wc_reorder_terms( $term, $next_id, $taxonomy );
2011
2012
		$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
2013
2014
		if ( $term && sizeof( $children ) ) {
2015
			echo 'children';
2016
			die();
2017
		}
2018
	}
2019
2020
	/**
2021
	 * Ajax request handling for product ordering.
2022
	 *
2023
	 * Based on Simple Page Ordering by 10up (https://wordpress.org/extend/plugins/simple-page-ordering/).
2024
	 */
2025
	public static function product_ordering() {
2026
		global $wpdb;
2027
2028
		ob_start();
2029
2030
		// check permissions again and make sure we have what we need
2031
		if ( ! current_user_can('edit_products') || empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
2032
			die(-1);
2033
		}
2034
2035
		// real post?
2036
		if ( ! $post = get_post( $_POST['id'] ) ) {
2037
			die(-1);
2038
		}
2039
2040
		$previd  = isset( $_POST['previd'] ) ? $_POST['previd'] : false;
2041
		$nextid  = isset( $_POST['nextid'] ) ? $_POST['nextid'] : false;
2042
		$new_pos = array(); // store new positions for ajax
2043
2044
		$siblings = $wpdb->get_results( $wpdb->prepare( "
2045
			SELECT ID, menu_order FROM {$wpdb->posts} AS posts
2046
			WHERE 	posts.post_type 	= 'product'
2047
			AND 	posts.post_status 	IN ( 'publish', 'pending', 'draft', 'future', 'private' )
2048
			AND 	posts.ID			NOT IN (%d)
2049
			ORDER BY posts.menu_order ASC, posts.ID DESC
2050
		", $post->ID ) );
2051
2052
		$menu_order = 0;
2053
2054
		foreach ( $siblings as $sibling ) {
2055
2056
			// if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
2057 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...
2058
				$wpdb->update(
2059
					$wpdb->posts,
2060
					array(
2061
						'menu_order' => $menu_order
2062
					),
2063
					array( 'ID' => $post->ID ),
2064
					array( '%d' ),
2065
					array( '%d' )
2066
				);
2067
				$new_pos[ $post->ID ] = $menu_order;
2068
				$menu_order++;
2069
			}
2070
2071
			// if repositioned post has been set, and new items are already in the right order, we can stop
2072
			if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $menu_order ) {
2073
				break;
2074
			}
2075
2076
			// set the menu order of the current sibling and increment the menu order
2077
			$wpdb->update(
2078
				$wpdb->posts,
2079
				array(
2080
					'menu_order' => $menu_order
2081
				),
2082
				array( 'ID' => $sibling->ID ),
2083
				array( '%d' ),
2084
				array( '%d' )
2085
			);
2086
			$new_pos[ $sibling->ID ] = $menu_order;
2087
			$menu_order++;
2088
2089 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...
2090
				$wpdb->update(
2091
					$wpdb->posts,
2092
					array(
2093
						'menu_order' => $menu_order
2094
					),
2095
					array( 'ID' => $post->ID ),
2096
					array( '%d' ),
2097
					array( '%d' )
2098
				);
2099
				$new_pos[$post->ID] = $menu_order;
2100
				$menu_order++;
2101
			}
2102
2103
		}
2104
2105
		do_action( 'woocommerce_after_product_ordering' );
2106
2107
		wp_send_json( $new_pos );
2108
	}
2109
2110
	/**
2111
	 * Handle a refund via the edit order screen.
2112
	 */
2113
	public static function refund_line_items() {
2114
		ob_start();
2115
2116
		check_ajax_referer( 'order-item', 'security' );
2117
2118
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2119
			die(-1);
2120
		}
2121
2122
		$order_id               = absint( $_POST['order_id'] );
2123
		$refund_amount          = wc_format_decimal( sanitize_text_field( $_POST['refund_amount'] ), wc_get_price_decimals() );
2124
		$refund_reason          = sanitize_text_field( $_POST['refund_reason'] );
2125
		$line_item_qtys         = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_qtys'] ) ), true );
2126
		$line_item_totals       = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_totals'] ) ), true );
2127
		$line_item_tax_totals   = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_tax_totals'] ) ), true );
2128
		$api_refund             = $_POST['api_refund'] === 'true' ? true : false;
2129
		$restock_refunded_items = $_POST['restock_refunded_items'] === 'true' ? true : false;
2130
		$refund                 = false;
2131
		$response_data          = array();
2132
2133
		try {
2134
			// Validate that the refund can occur
2135
			$order       = wc_get_order( $order_id );
2136
			$order_items = $order->get_items();
2137
			$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded(), wc_get_price_decimals() );
2138
2139
			if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) {
2140
				throw new exception( __( 'Invalid refund amount', 'woocommerce' ) );
2141
			}
2142
2143
			// Prepare line items which we are refunding
2144
			$line_items = array();
2145
			$item_ids   = array_unique( array_merge( array_keys( $line_item_qtys, $line_item_totals ) ) );
2146
2147
			foreach ( $item_ids as $item_id ) {
2148
				$line_items[ $item_id ] = array( 'qty' => 0, 'refund_total' => 0, 'refund_tax' => array() );
2149
			}
2150
			foreach ( $line_item_qtys as $item_id => $qty ) {
2151
				$line_items[ $item_id ]['qty'] = max( $qty, 0 );
2152
			}
2153
			foreach ( $line_item_totals as $item_id => $total ) {
2154
				$line_items[ $item_id ]['refund_total'] = wc_format_decimal( $total );
2155
			}
2156
			foreach ( $line_item_tax_totals as $item_id => $tax_totals ) {
2157
				$line_items[ $item_id ]['refund_tax'] = array_map( 'wc_format_decimal', $tax_totals );
2158
			}
2159
2160
			// Create the refund object
2161
			$refund = wc_create_refund( array(
2162
				'amount'     => $refund_amount,
2163
				'reason'     => $refund_reason,
2164
				'order_id'   => $order_id,
2165
				'line_items' => $line_items,
2166
			) );
2167
2168
			if ( is_wp_error( $refund ) ) {
2169
				throw new Exception( $refund->get_error_message() );
2170
			}
2171
2172
			// Refund via API
2173
			if ( $api_refund ) {
2174
				if ( WC()->payment_gateways() ) {
2175
					$payment_gateways = WC()->payment_gateways->payment_gateways();
2176
				}
2177
				if ( isset( $payment_gateways[ $order->get_payment_method() ] ) && $payment_gateways[ $order->get_payment_method() ]->supports( 'refunds' ) ) {
2178
					$result = $payment_gateways[ $order->get_payment_method() ]->process_refund( $order_id, $refund_amount, $refund_reason );
2179
2180
					do_action( 'woocommerce_refund_processed', $refund, $result );
2181
2182
					if ( is_wp_error( $result ) ) {
2183
						throw new Exception( $result->get_error_message() );
2184
					} elseif ( ! $result ) {
2185
						throw new Exception( __( 'Refund failed', 'woocommerce' ) );
2186
					}
2187
				}
2188
			}
2189
2190
			// restock items
2191
			foreach ( $line_item_qtys as $item_id => $qty ) {
2192
				if ( $restock_refunded_items && $qty && isset( $order_items[ $item_id ] ) ) {
2193
					$order_item = $order_items[ $item_id ];
2194
					$_product   = $order->get_product_from_item( $order_item );
2195
2196
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2197
						$old_stock    = wc_stock_amount( $_product->stock );
2198
						$new_quantity = $_product->increase_stock( $qty );
2199
2200
						$order->add_order_note( sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
2201
2202
						do_action( 'woocommerce_restock_refunded_item', $_product->id, $old_stock, $new_quantity, $order, $_product );
2203
					}
2204
				}
2205
			}
2206
2207
			// Trigger notifications and status changes
2208
			if ( $order->get_remaining_refund_amount() > 0 || ( $order->has_free_item() && $order->get_remaining_refund_items() > 0 ) ) {
2209
				/**
2210
				 * woocommerce_order_partially_refunded.
2211
				 *
2212
				 * @since 2.4.0
2213
				 * Note: 3rd arg was added in err. Kept for bw compat. 2.4.3.
2214
				 */
2215
				do_action( 'woocommerce_order_partially_refunded', $order_id, $refund->id, $refund->id );
2216
			} else {
2217
				do_action( 'woocommerce_order_fully_refunded', $order_id, $refund->id );
2218
2219
				$order->update_status( apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order_id, $refund->id ) );
2220
				$response_data['status'] = 'fully_refunded';
2221
			}
2222
2223
			do_action( 'woocommerce_order_refunded', $order_id, $refund->id );
2224
2225
			// Clear transients
2226
			wc_delete_shop_order_transients( $order_id );
2227
			wp_send_json_success( $response_data );
2228
2229
		} catch ( Exception $e ) {
2230
			if ( $refund && is_a( $refund, 'WC_Order_Refund' ) ) {
2231
				wp_delete_post( $refund->id, true );
2232
			}
2233
2234
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
2235
		}
2236
	}
2237
2238
	/**
2239
	 * Delete a refund.
2240
	 */
2241
	public static function delete_refund() {
2242
		check_ajax_referer( 'order-item', 'security' );
2243
2244
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2245
			die(-1);
2246
		}
2247
2248
		$refund_ids = array_map( 'absint', is_array( $_POST['refund_id'] ) ? $_POST['refund_id'] : array( $_POST['refund_id'] ) );
2249
		foreach ( $refund_ids as $refund_id ) {
2250
			if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
2251
				$order_id = wp_get_post_parent_id( $refund_id );
2252
				wc_delete_shop_order_transients( $order_id );
2253
				wp_delete_post( $refund_id );
2254
				do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
2255
			}
2256
		}
2257
		die();
2258
	}
2259
2260
	/**
2261
	 * Triggered when clicking the rating footer.
2262
	 */
2263
	public static function rated() {
2264
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2265
			die(-1);
2266
		}
2267
2268
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
2269
		die();
2270
	}
2271
2272
	/**
2273
	 * Create/Update API key.
2274
	 */
2275
	public static function update_api_key() {
2276
		ob_start();
2277
2278
		global $wpdb;
2279
2280
		check_ajax_referer( 'update-api-key', 'security' );
2281
2282
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2283
			die(-1);
2284
		}
2285
2286
		try {
2287
			if ( empty( $_POST['description'] ) ) {
2288
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
2289
			}
2290
			if ( empty( $_POST['user'] ) ) {
2291
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
2292
			}
2293
			if ( empty( $_POST['permissions'] ) ) {
2294
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
2295
			}
2296
2297
			$key_id      = absint( $_POST['key_id'] );
2298
			$description = sanitize_text_field( wp_unslash( $_POST['description'] ) );
2299
			$permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read';
2300
			$user_id     = absint( $_POST['user'] );
2301
2302
			if ( 0 < $key_id ) {
2303
				$data = array(
2304
					'user_id'     => $user_id,
2305
					'description' => $description,
2306
					'permissions' => $permissions
2307
				);
2308
2309
				$wpdb->update(
2310
					$wpdb->prefix . 'woocommerce_api_keys',
2311
					$data,
2312
					array( 'key_id' => $key_id ),
2313
					array(
2314
						'%d',
2315
						'%s',
2316
						'%s'
2317
					),
2318
					array( '%d' )
2319
				);
2320
2321
				$data['consumer_key']    = '';
2322
				$data['consumer_secret'] = '';
2323
				$data['message']         = __( 'API Key updated successfully.', 'woocommerce' );
2324
			} else {
2325
				$consumer_key    = 'ck_' . wc_rand_hash();
2326
				$consumer_secret = 'cs_' . wc_rand_hash();
2327
2328
				$data = array(
2329
					'user_id'         => $user_id,
2330
					'description'     => $description,
2331
					'permissions'     => $permissions,
2332
					'consumer_key'    => wc_api_hash( $consumer_key ),
2333
					'consumer_secret' => $consumer_secret,
2334
					'truncated_key'   => substr( $consumer_key, -7 )
2335
				);
2336
2337
				$wpdb->insert(
2338
					$wpdb->prefix . 'woocommerce_api_keys',
2339
					$data,
2340
					array(
2341
						'%d',
2342
						'%s',
2343
						'%s',
2344
						'%s',
2345
						'%s',
2346
						'%s'
2347
					)
2348
				);
2349
2350
				$key_id                  = $wpdb->insert_id;
2351
				$data['consumer_key']    = $consumer_key;
2352
				$data['consumer_secret'] = $consumer_secret;
2353
				$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' );
2354
				$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>';
2355
			}
2356
2357
			wp_send_json_success( $data );
2358
		} catch ( Exception $e ) {
2359
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
2360
		}
2361
	}
2362
2363
	/**
2364
	 * Locate user via AJAX.
2365
	 */
2366
	public static function get_customer_location() {
2367
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
2368
		wp_send_json_success( array( 'hash' => $location_hash ) );
2369
	}
2370
2371
	/**
2372
	 * Load variations via AJAX.
2373
	 */
2374
	public static function load_variations() {
2375
		ob_start();
2376
2377
		check_ajax_referer( 'load-variations', 'security' );
2378
2379
		// Check permissions again and make sure we have what we need
2380 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...
2381
			die( -1 );
2382
		}
2383
2384
		global $post;
2385
2386
		$product_id = absint( $_POST['product_id'] );
2387
		$post       = get_post( $product_id ); // Set $post global so its available like within the admin screens
2388
		$per_page   = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
2389
		$page       = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
2390
2391
		// Get attributes
2392
		$attributes        = array();
2393
		$posted_attributes = wp_unslash( $_POST['attributes'] );
2394
2395
		foreach ( $posted_attributes as $key => $value ) {
2396
			$attributes[ $key ] = array_map( 'wc_clean', $value );
2397
		}
2398
2399
		// Get tax classes
2400
		$tax_classes           = WC_Tax::get_tax_classes();
2401
		$tax_class_options     = array();
2402
		$tax_class_options[''] = __( 'Standard', 'woocommerce' );
2403
2404 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...
2405
			foreach ( $tax_classes as $class ) {
2406
				$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
2407
			}
2408
		}
2409
2410
		// Set backorder options
2411
		$backorder_options = array(
2412
			'no'     => __( 'Do not allow', 'woocommerce' ),
2413
			'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
2414
			'yes'    => __( 'Allow', 'woocommerce' )
2415
		);
2416
2417
		// set stock status options
2418
		$stock_status_options = array(
2419
			'instock'    => __( 'In stock', 'woocommerce' ),
2420
			'outofstock' => __( 'Out of stock', 'woocommerce' )
2421
		);
2422
2423
		$parent_data = array(
2424
			'id'                   => $product_id,
2425
			'attributes'           => $attributes,
2426
			'tax_class_options'    => $tax_class_options,
2427
			'sku'                  => get_post_meta( $product_id, '_sku', true ),
2428
			'weight'               => wc_format_localized_decimal( get_post_meta( $product_id, '_weight', true ) ),
2429
			'length'               => wc_format_localized_decimal( get_post_meta( $product_id, '_length', true ) ),
2430
			'width'                => wc_format_localized_decimal( get_post_meta( $product_id, '_width', true ) ),
2431
			'height'               => wc_format_localized_decimal( get_post_meta( $product_id, '_height', true ) ),
2432
			'tax_class'            => get_post_meta( $product_id, '_tax_class', true ),
2433
			'backorder_options'    => $backorder_options,
2434
			'stock_status_options' => $stock_status_options
2435
		);
2436
2437
		if ( ! $parent_data['weight'] ) {
2438
			$parent_data['weight'] = wc_format_localized_decimal( 0 );
2439
		}
2440
2441
		if ( ! $parent_data['length'] ) {
2442
			$parent_data['length'] = wc_format_localized_decimal( 0 );
2443
		}
2444
2445
		if ( ! $parent_data['width'] ) {
2446
			$parent_data['width'] = wc_format_localized_decimal( 0 );
2447
		}
2448
2449
		if ( ! $parent_data['height'] ) {
2450
			$parent_data['height'] = wc_format_localized_decimal( 0 );
2451
		}
2452
2453
		// Get variations
2454
		$args = apply_filters( 'woocommerce_ajax_admin_get_variations_args', array(
2455
			'post_type'      => 'product_variation',
2456
			'post_status'    => array( 'private', 'publish' ),
2457
			'posts_per_page' => $per_page,
2458
			'paged'          => $page,
2459
			'orderby'        => array( 'menu_order' => 'ASC', 'ID' => 'DESC' ),
2460
			'post_parent'    => $product_id
2461
		), $product_id );
2462
2463
		$variations = get_posts( $args );
2464
		$loop = 0;
2465
2466
		if ( $variations ) {
2467
2468
			foreach ( $variations as $variation ) {
2469
				$variation_id     = absint( $variation->ID );
2470
				$variation_meta   = get_post_meta( $variation_id );
2471
				$variation_data   = array();
2472
				$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
2473
				$variation_fields = array(
2474
					'_sku'                   => '',
2475
					'_stock'                 => '',
2476
					'_regular_price'         => '',
2477
					'_sale_price'            => '',
2478
					'_weight'                => '',
2479
					'_length'                => '',
2480
					'_width'                 => '',
2481
					'_height'                => '',
2482
					'_download_limit'        => '',
2483
					'_download_expiry'       => '',
2484
					'_downloadable_files'    => '',
2485
					'_downloadable'          => '',
2486
					'_virtual'               => '',
2487
					'_thumbnail_id'          => '',
2488
					'_sale_price_dates_from' => '',
2489
					'_sale_price_dates_to'   => '',
2490
					'_manage_stock'          => '',
2491
					'_stock_status'          => '',
2492
					'_backorders'            => null,
2493
					'_tax_class'             => null,
2494
					'_variation_description' => ''
2495
				);
2496
2497 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...
2498
					$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
2499
				}
2500
2501
				// Add the variation attributes
2502
				$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
2503
2504
				// Formatting
2505
				$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
2506
				$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
2507
				$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
2508
				$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
2509
				$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
2510
				$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
2511
				$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
2512
				$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
2513
				$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
2514
				$variation_data['menu_order']     = $variation->menu_order;
2515
				$variation_data['_stock']         = '' === $variation_data['_stock'] ? '' : wc_stock_amount( $variation_data['_stock'] );
2516
2517
				include( 'admin/meta-boxes/views/html-variation-admin.php' );
2518
2519
				$loop++;
2520
			}
2521
		}
2522
2523
		die();
2524
	}
2525
2526
	/**
2527
	 * Save variations via AJAX.
2528
	 */
2529
	public static function save_variations() {
2530
		ob_start();
2531
2532
		check_ajax_referer( 'save-variations', 'security' );
2533
2534
		// Check permissions again and make sure we have what we need
2535 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...
2536
			die( -1 );
2537
		}
2538
2539
		// Remove previous meta box errors
2540
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
2541
2542
		$product_id   = absint( $_POST['product_id'] );
2543
		$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
2544
2545
		$product_type_terms = wp_get_object_terms( $product_id, 'product_type' );
2546
2547
		// If the product type hasn't been set or it has changed, update it before saving variations
2548
		if ( empty( $product_type_terms ) || $product_type !== sanitize_title( current( $product_type_terms )->name ) ) {
2549
			wp_set_object_terms( $product_id, $product_type, 'product_type' );
2550
		}
2551
2552
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
2553
2554
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
2555
2556
		// Clear cache/transients
2557
		wc_delete_product_transients( $product_id );
2558
2559
		if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
2560
			echo '<div class="error notice is-dismissible">';
2561
2562
			foreach ( $errors as $error ) {
2563
				echo '<p>' . wp_kses_post( $error ) . '</p>';
2564
			}
2565
2566
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
2567
			echo '</div>';
2568
2569
			delete_option( 'woocommerce_meta_box_errors' );
2570
		}
2571
2572
		die();
2573
	}
2574
2575
	/**
2576
	 * Bulk action - Toggle Enabled.
2577
	 * @access private
2578
	 * @used-by bulk_edit_variations
2579
	 * @param  array $variations
2580
	 * @param  array $data
2581
	 */
2582
	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...
2583
		global $wpdb;
2584
2585
		foreach ( $variations as $variation_id ) {
2586
			$post_status = get_post_status( $variation_id );
2587
			$new_status  = 'private' === $post_status ? 'publish' : 'private';
2588
			$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $variation_id ) );
2589
		}
2590
	}
2591
2592
	/**
2593
	 * Bulk action - Toggle Downloadable Checkbox.
2594
	 * @access private
2595
	 * @used-by bulk_edit_variations
2596
	 * @param  array $variations
2597
	 * @param  array $data
2598
	 */
2599 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...
2600
		foreach ( $variations as $variation_id ) {
2601
			$_downloadable   = get_post_meta( $variation_id, '_downloadable', true );
2602
			$is_downloadable = 'no' === $_downloadable ? 'yes' : 'no';
2603
			update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
2604
		}
2605
	}
2606
2607
	/**
2608
	 * Bulk action - Toggle Virtual Checkbox.
2609
	 * @access private
2610
	 * @used-by bulk_edit_variations
2611
	 * @param  array $variations
2612
	 * @param  array $data
2613
	 */
2614 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...
2615
		foreach ( $variations as $variation_id ) {
2616
			$_virtual   = get_post_meta( $variation_id, '_virtual', true );
2617
			$is_virtual = 'no' === $_virtual ? 'yes' : 'no';
2618
			update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
2619
		}
2620
	}
2621
2622
	/**
2623
	 * Bulk action - Toggle Manage Stock Checkbox.
2624
	 * @access private
2625
	 * @used-by bulk_edit_variations
2626
	 * @param  array $variations
2627
	 * @param  array $data
2628
	 */
2629
	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...
2630
		foreach ( $variations as $variation_id ) {
2631
			$_manage_stock   = get_post_meta( $variation_id, '_manage_stock', true );
2632
			$is_manage_stock = 'no' === $_manage_stock || '' === $_manage_stock ? 'yes' : 'no';
2633
			update_post_meta( $variation_id, '_manage_stock', $is_manage_stock );
2634
		}
2635
	}
2636
2637
	/**
2638
	 * Bulk action - Set Regular Prices.
2639
	 * @access private
2640
	 * @used-by bulk_edit_variations
2641
	 * @param  array $variations
2642
	 * @param  array $data
2643
	 */
2644 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...
2645
		if ( ! isset( $data['value'] ) ) {
2646
			return;
2647
		}
2648
2649
		foreach ( $variations as $variation_id ) {
2650
			// Price fields
2651
			$regular_price = wc_clean( $data['value'] );
2652
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2653
2654
			// Date fields
2655
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2656
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2657
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2658
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2659
2660
			_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...
2661
		}
2662
	}
2663
2664
	/**
2665
	 * Bulk action - Set Sale Prices.
2666
	 * @access private
2667
	 * @used-by bulk_edit_variations
2668
	 * @param  array $variations
2669
	 * @param  array $data
2670
	 */
2671 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...
2672
		if ( ! isset( $data['value'] ) ) {
2673
			return;
2674
		}
2675
2676
		foreach ( $variations as $variation_id ) {
2677
			// Price fields
2678
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2679
			$sale_price    = wc_clean( $data['value'] );
2680
2681
			// Date fields
2682
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2683
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2684
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2685
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2686
2687
			_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 2679 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...
2688
		}
2689
	}
2690
2691
	/**
2692
	 * Bulk action - Set Stock.
2693
	 * @access private
2694
	 * @used-by bulk_edit_variations
2695
	 * @param  array $variations
2696
	 * @param  array $data
2697
	 */
2698
	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...
2699
		if ( ! isset( $data['value'] ) ) {
2700
			return;
2701
		}
2702
2703
		$value = wc_clean( $data['value'] );
2704
2705
		foreach ( $variations as $variation_id ) {
2706
			if ( 'yes' === get_post_meta( $variation_id, '_manage_stock', true ) ) {
2707
				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...
2708
			} else {
2709
				delete_post_meta( $variation_id, '_stock' );
2710
			}
2711
		}
2712
	}
2713
2714
	/**
2715
	 * Bulk action - Set Weight.
2716
	 * @access private
2717
	 * @used-by bulk_edit_variations
2718
	 * @param  array $variations
2719
	 * @param  array $data
2720
	 */
2721
	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...
2722
		self::variation_bulk_set_meta( $variations, '_weight', wc_clean( $data['value'] ) );
2723
	}
2724
2725
	/**
2726
	 * Bulk action - Set Length.
2727
	 * @access private
2728
	 * @used-by bulk_edit_variations
2729
	 * @param  array $variations
2730
	 * @param  array $data
2731
	 */
2732
	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...
2733
		self::variation_bulk_set_meta( $variations, '_length', wc_clean( $data['value'] ) );
2734
	}
2735
2736
	/**
2737
	 * Bulk action - Set Width.
2738
	 * @access private
2739
	 * @used-by bulk_edit_variations
2740
	 * @param  array $variations
2741
	 * @param  array $data
2742
	 */
2743
	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...
2744
		self::variation_bulk_set_meta( $variations, '_width', wc_clean( $data['value'] ) );
2745
	}
2746
2747
	/**
2748
	 * Bulk action - Set Height.
2749
	 * @access private
2750
	 * @used-by bulk_edit_variations
2751
	 * @param  array $variations
2752
	 * @param  array $data
2753
	 */
2754
	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...
2755
		self::variation_bulk_set_meta( $variations, '_height', wc_clean( $data['value'] ) );
2756
	}
2757
2758
	/**
2759
	 * Bulk action - Set Download Limit.
2760
	 * @access private
2761
	 * @used-by bulk_edit_variations
2762
	 * @param  array $variations
2763
	 * @param  array $data
2764
	 */
2765
	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...
2766
		self::variation_bulk_set_meta( $variations, '_download_limit', wc_clean( $data['value'] ) );
2767
	}
2768
2769
	/**
2770
	 * Bulk action - Set Download Expiry.
2771
	 * @access private
2772
	 * @used-by bulk_edit_variations
2773
	 * @param  array $variations
2774
	 * @param  array $data
2775
	 */
2776
	private static function variation_bulk_action_variable_download_expiry( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2777
		self::variation_bulk_set_meta( $variations, '_download_expiry', wc_clean( $data['value'] ) );
2778
	}
2779
2780
	/**
2781
	 * Bulk action - Delete all.
2782
	 * @access private
2783
	 * @used-by bulk_edit_variations
2784
	 * @param  array $variations
2785
	 * @param  array $data
2786
	 */
2787
	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...
2788
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2789
			foreach ( $variations as $variation_id ) {
2790
				wp_delete_post( $variation_id );
2791
			}
2792
		}
2793
	}
2794
2795
	/**
2796
	 * Bulk action - Sale Schedule.
2797
	 * @access private
2798
	 * @used-by bulk_edit_variations
2799
	 * @param  array $variations
2800
	 * @param  array $data
2801
	 */
2802
	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...
2803
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2804
			return;
2805
		}
2806
2807
		foreach ( $variations as $variation_id ) {
2808
			// Price fields
2809
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2810
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2811
2812
			// Date fields
2813
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2814
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2815
2816 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...
2817
				$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2818
			} else {
2819
				$date_from = $data['date_from'];
2820
			}
2821
2822 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...
2823
				$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2824
			} else {
2825
				$date_to = $data['date_to'];
2826
			}
2827
2828
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
2829
		}
2830
	}
2831
2832
	/**
2833
	 * Bulk action - Increase Regular Prices.
2834
	 * @access private
2835
	 * @used-by bulk_edit_variations
2836
	 * @param  array $variations
2837
	 * @param  array $data
2838
	 */
2839
	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...
2840
		self::variation_bulk_adjust_price( $variations, '_regular_price', '+', wc_clean( $data['value'] ) );
2841
	}
2842
2843
	/**
2844
	 * Bulk action - Decrease Regular Prices.
2845
	 * @access private
2846
	 * @used-by bulk_edit_variations
2847
	 * @param  array $variations
2848
	 * @param  array $data
2849
	 */
2850
	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...
2851
		self::variation_bulk_adjust_price( $variations, '_regular_price', '-', wc_clean( $data['value'] ) );
2852
	}
2853
2854
	/**
2855
	 * Bulk action - Increase Sale Prices.
2856
	 * @access private
2857
	 * @used-by bulk_edit_variations
2858
	 * @param  array $variations
2859
	 * @param  array $data
2860
	 */
2861
	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...
2862
		self::variation_bulk_adjust_price( $variations, '_sale_price', '+', wc_clean( $data['value'] ) );
2863
	}
2864
2865
	/**
2866
	 * Bulk action - Decrease Sale Prices.
2867
	 * @access private
2868
	 * @used-by bulk_edit_variations
2869
	 * @param  array $variations
2870
	 * @param  array $data
2871
	 */
2872
	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...
2873
		self::variation_bulk_adjust_price( $variations, '_sale_price', '-', wc_clean( $data['value'] ) );
2874
	}
2875
2876
	/**
2877
	 * Bulk action - Set Price.
2878
	 * @access private
2879
	 * @used-by bulk_edit_variations
2880
	 * @param  array $variations
2881
	 * @param string $operator + or -
2882
	 * @param string $field price being adjusted
2883
	 * @param string $value Price or Percent
2884
	 */
2885
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2886
		foreach ( $variations as $variation_id ) {
2887
			// Get existing data
2888
			$_regular_price = get_post_meta( $variation_id, '_regular_price', true );
2889
			$_sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2890
			$date_from      = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2891
			$date_to        = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2892
			$date_from      = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2893
			$date_to        = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2894
2895
			if ( '%' === substr( $value, -1 ) ) {
2896
				$percent = wc_format_decimal( substr( $value, 0, -1 ) );
2897
				$$field  += ( ( $$field / 100 ) * $percent ) * "{$operator}1";
2898
			} else {
2899
				$$field  += $value * "{$operator}1";
2900
			}
2901
			_wc_save_product_price( $variation_id, $_regular_price, $_sale_price, $date_from, $date_to );
2902
		}
2903
	}
2904
2905
	/**
2906
	 * Bulk action - Set Meta.
2907
	 * @access private
2908
	 * @param array $variations
2909
	 * @param string $field
2910
	 * @param string $value
2911
	 */
2912
	private static function variation_bulk_set_meta( $variations, $field, $value ) {
2913
		foreach ( $variations as $variation_id ) {
2914
			update_post_meta( $variation_id, $field, $value );
2915
		}
2916
	}
2917
2918
	/**
2919
	 * Bulk edit variations via AJAX.
2920
	 * @uses WC_AJAX::variation_bulk_set_meta()
2921
	 * @uses WC_AJAX::variation_bulk_adjust_price()
2922
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price_decrease()
2923
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price_increase()
2924
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price_decrease()
2925
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price_increase()
2926
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_schedule()
2927
	 * @uses WC_AJAX::variation_bulk_action_delete_all()
2928
	 * @uses WC_AJAX::variation_bulk_action_variable_download_expiry()
2929
	 * @uses WC_AJAX::variation_bulk_action_variable_download_limit()
2930
	 * @uses WC_AJAX::variation_bulk_action_variable_height()
2931
	 * @uses WC_AJAX::variation_bulk_action_variable_width()
2932
	 * @uses WC_AJAX::variation_bulk_action_variable_length()
2933
	 * @uses WC_AJAX::variation_bulk_action_variable_weight()
2934
	 * @uses WC_AJAX::variation_bulk_action_variable_stock()
2935
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price()
2936
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price()
2937
	 * @uses WC_AJAX::variation_bulk_action_toggle_manage_stock()
2938
	 * @uses WC_AJAX::variation_bulk_action_toggle_virtual()
2939
	 * @uses WC_AJAX::variation_bulk_action_toggle_downloadable()
2940
	 * @uses WC_AJAX::variation_bulk_action_toggle_enabled
2941
	 */
2942
	public static function bulk_edit_variations() {
2943
		ob_start();
2944
2945
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2946
2947
		// Check permissions again and make sure we have what we need
2948 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...
2949
			die( -1 );
2950
		}
2951
2952
		$product_id  = absint( $_POST['product_id'] );
2953
		$bulk_action = wc_clean( $_POST['bulk_action'] );
2954
		$data        = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array();
2955
		$variations  = array();
2956
2957
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2958
			$variations = get_posts( array(
2959
				'post_parent'    => $product_id,
2960
				'posts_per_page' => -1,
2961
				'post_type'      => 'product_variation',
2962
				'fields'         => 'ids',
2963
				'post_status'    => array( 'publish', 'private' )
2964
			) );
2965
		}
2966
2967
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2968
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2969
		} else {
2970
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2971
		}
2972
2973
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2974
2975
		// Sync and update transients
2976
		WC_Product_Variable::sync( $product_id );
2977
		wc_delete_product_transients( $product_id );
2978
		die();
2979
	}
2980
2981
	/**
2982
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2983
	 */
2984
	public static function tax_rates_save_changes() {
2985
		if ( ! isset( $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2986
			wp_send_json_error( 'missing_fields' );
2987
			exit;
2988
		}
2989
2990
		$current_class = ! empty( $_POST['current_class'] ) ? $_POST['current_class'] : ''; // This is sanitized seven lines later.
2991
2992
		if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) {
2993
			wp_send_json_error( 'bad_nonce' );
2994
			exit;
2995
		}
2996
2997
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
2998
2999
		// Check User Caps
3000
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3001
			wp_send_json_error( 'missing_capabilities' );
3002
			exit;
3003
		}
3004
3005
		$changes = $_POST['changes'];
3006
		foreach ( $changes as $tax_rate_id => $data ) {
3007
			if ( isset( $data['deleted'] ) ) {
3008
				if ( isset( $data['newRow'] ) ) {
3009
					// So the user added and deleted a new row.
3010
					// That's fine, it's not in the database anyways. NEXT!
3011
					continue;
3012
				}
3013
				WC_Tax::_delete_tax_rate( $tax_rate_id );
3014
			}
3015
3016
			$tax_rate = array_intersect_key( $data, array(
3017
				'tax_rate_country'  => 1,
3018
				'tax_rate_state'    => 1,
3019
				'tax_rate'          => 1,
3020
				'tax_rate_name'     => 1,
3021
				'tax_rate_priority' => 1,
3022
				'tax_rate_compound' => 1,
3023
				'tax_rate_shipping' => 1,
3024
				'tax_rate_order'    => 1,
3025
			) );
3026
3027
			if ( isset( $data['newRow'] ) ) {
3028
				// Hurrah, shiny and new!
3029
				$tax_rate['tax_rate_class'] = $current_class;
3030
				$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
3031
			} else {
3032
				// Updating an existing rate ...
3033
				if ( ! empty( $tax_rate ) ) {
3034
					WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
3035
				}
3036
			}
3037
3038
			if ( isset( $data['postcode'] ) ) {
3039
				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...
3040
			}
3041
			if ( isset( $data['city'] ) ) {
3042
				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...
3043
			}
3044
		}
3045
3046
		wp_send_json_success( array(
3047
			'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
3048
		) );
3049
	}
3050
3051
	/**
3052
	 * Handle submissions from assets/js/wc-shipping-zones.js Backbone model.
3053
	 */
3054
	public static function shipping_zones_save_changes() {
3055
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['changes'] ) ) {
3056
			wp_send_json_error( 'missing_fields' );
3057
			exit;
3058
		}
3059
3060
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3061
			wp_send_json_error( 'bad_nonce' );
3062
			exit;
3063
		}
3064
3065
		// Check User Caps
3066
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3067
			wp_send_json_error( 'missing_capabilities' );
3068
			exit;
3069
		}
3070
3071
		$changes = $_POST['changes'];
3072
		foreach ( $changes as $zone_id => $data ) {
3073
			if ( isset( $data['deleted'] ) ) {
3074
				if ( isset( $data['newRow'] ) ) {
3075
					// So the user added and deleted a new row.
3076
					// That's fine, it's not in the database anyways. NEXT!
3077
					continue;
3078
				}
3079
				WC_Shipping_Zones::delete_zone( $zone_id );
3080
				continue;
3081
			}
3082
3083
			$zone_data = array_intersect_key( $data, array(
3084
				'zone_id'        => 1,
3085
				'zone_name'      => 1,
3086
				'zone_order'     => 1,
3087
				'zone_locations' => 1,
3088
				'zone_postcodes' => 1
3089
			) );
3090
3091
			if ( isset( $zone_data['zone_id'] ) ) {
3092
				$zone = new WC_Shipping_Zone( $zone_data['zone_id'] );
3093
3094
				if ( isset( $zone_data['zone_name'] ) ) {
3095
					$zone->set_zone_name( $zone_data['zone_name'] );
3096
				}
3097
3098
				if ( isset( $zone_data['zone_order'] ) ) {
3099
					$zone->set_zone_order( $zone_data['zone_order'] );
3100
				}
3101
3102
				if ( isset( $zone_data['zone_locations'] ) ) {
3103
					$zone->clear_locations( array( 'state', 'country', 'continent' ) );
3104
					$locations = array_filter( array_map( 'wc_clean', (array) $zone_data['zone_locations'] ) );
3105
					foreach ( $locations as $location ) {
3106
						// Each posted location will be in the format type:code
3107
						$location_parts = explode( ':', $location );
3108
						switch ( $location_parts[0] ) {
3109
							case 'state' :
3110
								$zone->add_location( $location_parts[1] . ':' . $location_parts[2], 'state' );
3111
							break;
3112
							case 'country' :
3113
								$zone->add_location( $location_parts[1], 'country' );
3114
							break;
3115
							case 'continent' :
3116
								$zone->add_location( $location_parts[1], 'continent' );
3117
							break;
3118
						}
3119
					}
3120
				}
3121
3122
				if ( isset( $zone_data['zone_postcodes'] ) ) {
3123
					$zone->clear_locations( 'postcode' );
3124
					$postcodes = array_filter( array_map( 'strtoupper', array_map( 'wc_clean', explode( "\n", $zone_data['zone_postcodes'] ) ) ) );
3125
					foreach ( $postcodes as $postcode ) {
3126
						$zone->add_location( $postcode, 'postcode' );
3127
					}
3128
				}
3129
3130
				$zone->save();
3131
			}
3132
		}
3133
3134
		wp_send_json_success( array(
3135
			'zones' => WC_Shipping_Zones::get_zones()
3136
		) );
3137
	}
3138
3139
	/**
3140
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
3141
	 */
3142
	public static function shipping_zone_add_method() {
3143
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['method_id'] ) ) {
3144
			wp_send_json_error( 'missing_fields' );
3145
			exit;
3146
		}
3147
3148
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3149
			wp_send_json_error( 'bad_nonce' );
3150
			exit;
3151
		}
3152
3153
		// Check User Caps
3154
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3155
			wp_send_json_error( 'missing_capabilities' );
3156
			exit;
3157
		}
3158
3159
		$zone_id     = absint( $_POST['zone_id'] );
3160
		$zone        = WC_Shipping_Zones::get_zone( $zone_id );
3161
		$instance_id = $zone->add_shipping_method( wc_clean( $_POST['method_id'] ) );
3162
3163
		wp_send_json_success( array(
3164
			'instance_id' => $instance_id,
3165
			'zone_id'     => $zone_id,
3166
			'methods'     => $zone->get_shipping_methods()
3167
		) );
3168
	}
3169
3170
	/**
3171
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
3172
	 */
3173
	public static function shipping_zone_methods_save_changes() {
3174
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['changes'] ) ) {
3175
			wp_send_json_error( 'missing_fields' );
3176
			exit;
3177
		}
3178
3179
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3180
			wp_send_json_error( 'bad_nonce' );
3181
			exit;
3182
		}
3183
3184
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3185
			wp_send_json_error( 'missing_capabilities' );
3186
			exit;
3187
		}
3188
3189
		global $wpdb;
3190
3191
		$zone_id = absint( $_POST['zone_id'] );
3192
		$zone    = new WC_Shipping_Zone( $zone_id );
3193
		$changes = $_POST['changes'];
3194
3195
		foreach ( $changes as $instance_id => $data ) {
3196
			$method_id = $wpdb->get_var( $wpdb->prepare( "SELECT method_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE instance_id = %d", $instance_id ) );
3197
3198
			if ( isset( $data['deleted'] ) ) {
3199
				$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
3200
				$option_key      = $shipping_method->get_instance_option_key();
3201
				if ( $wpdb->delete( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'instance_id' => $instance_id ) ) ) {
3202
					delete_option( $option_key );
3203
					do_action( 'woocommerce_shipping_zone_method_deleted', $instance_id, $method_id, $zone_id );
3204
				}
3205
				continue;
3206
			}
3207
3208
			$method_data = array_intersect_key( $data, array(
3209
				'method_order' => 1,
3210
				'enabled'      => 1
3211
			) );
3212
3213
			if ( isset( $method_data['method_order'] ) ) {
3214
				$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'method_order' => absint( $method_data['method_order'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
3215
			}
3216
3217
			if ( isset( $method_data['enabled'] ) ) {
3218
				$is_enabled = absint( 'yes' === $method_data['enabled'] );
3219
				if ( $wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'is_enabled' => $is_enabled ), array( 'instance_id' => absint( $instance_id ) ) ) ) {
3220
					do_action( 'woocommerce_shipping_zone_method_status_toggled', $instance_id, $method_id, $zone_id, $is_enabled );
3221
				}
3222
			}
3223
		}
3224
3225
		wp_send_json_success( array(
3226
			'methods' => $zone->get_shipping_methods()
3227
		) );
3228
	}
3229
3230
	/**
3231
	 * Save method settings
3232
	 */
3233
	public static function shipping_zone_methods_save_settings() {
3234
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['instance_id'], $_POST['data'] ) ) {
3235
			wp_send_json_error( 'missing_fields' );
3236
			exit;
3237
		}
3238
3239
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3240
			wp_send_json_error( 'bad_nonce' );
3241
			exit;
3242
		}
3243
3244
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3245
			wp_send_json_error( 'missing_capabilities' );
3246
			exit;
3247
		}
3248
3249
		$instance_id     = absint( $_POST['instance_id'] );
3250
		$zone            = WC_Shipping_Zones::get_zone_by( 'instance_id', $instance_id );
3251
		$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
3252
		$shipping_method->set_post_data( $_POST['data'] );
3253
		$shipping_method->process_admin_options();
3254
3255
		wp_send_json_success( array(
3256
			'methods' => $zone->get_shipping_methods(),
3257
			'errors'  => $shipping_method->get_errors(),
3258
		) );
3259
	}
3260
3261
	/**
3262
	 * Handle submissions from assets/js/wc-shipping-classes.js Backbone model.
3263
	 */
3264
	public static function shipping_classes_save_changes() {
3265
		if ( ! isset( $_POST['wc_shipping_classes_nonce'], $_POST['changes'] ) ) {
3266
			wp_send_json_error( 'missing_fields' );
3267
			exit;
3268
		}
3269
3270
		if ( ! wp_verify_nonce( $_POST['wc_shipping_classes_nonce'], 'wc_shipping_classes_nonce' ) ) {
3271
			wp_send_json_error( 'bad_nonce' );
3272
			exit;
3273
		}
3274
3275
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3276
			wp_send_json_error( 'missing_capabilities' );
3277
			exit;
3278
		}
3279
3280
		$changes = $_POST['changes'];
3281
3282
		foreach ( $changes as $term_id => $data ) {
3283
			$term_id = absint( $term_id );
3284
3285
			if ( isset( $data['deleted'] ) ) {
3286
				if ( isset( $data['newRow'] ) ) {
3287
					// So the user added and deleted a new row.
3288
					// That's fine, it's not in the database anyways. NEXT!
3289
					continue;
3290
				}
3291
				wp_delete_term( $term_id, 'product_shipping_class' );
3292
				continue;
3293
			}
3294
3295
			$update_args = array();
3296
3297
			if ( isset( $data['name'] ) ) {
3298
				$update_args['name'] = wc_clean( $data['name'] );
3299
			}
3300
3301
			if ( isset( $data['slug'] ) ) {
3302
				$update_args['slug'] = wc_clean( $data['slug'] );
3303
			}
3304
3305
			if ( isset( $data['description'] ) ) {
3306
				$update_args['description'] = wc_clean( $data['description'] );
3307
			}
3308
3309
			if ( isset( $data['newRow'] ) ) {
3310
				$update_args = array_filter( $update_args );
3311
				if ( empty( $update_args['name'] ) ) {
3312
					continue;
3313
				}
3314
				$term_id = wp_insert_term( $update_args['name'], 'product_shipping_class', $update_args );
3315
			} else {
3316
				wp_update_term( $term_id, 'product_shipping_class', $update_args );
3317
			}
3318
3319
			do_action( 'woocommerce_shipping_classes_save_class', $term_id, $data );
3320
		}
3321
3322
		$wc_shipping = WC_Shipping::instance();
3323
3324
		wp_send_json_success( array(
3325
			'shipping_classes' => $wc_shipping->get_shipping_classes()
3326
		) );
3327
	}
3328
}
3329
3330
WC_AJAX::init();
3331