Completed
Pull Request — master (#9826)
by Mike
08:43
created

WC_AJAX::shipping_zones_save_changes()   D

Complexity

Conditions 19
Paths 23

Size

Total Lines 84
Code Lines 52

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 84
rs 4.8316
cc 19
eloc 52
nc 23
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 18 and the first side effect is on line 4.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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( add_query_arg( 'wc-ajax', $request, remove_query_arg( array( 'remove_item', 'add-to-cart', 'added-to-cart' ) ) ) );
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 );
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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' ) );
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
64
		@header( 'X-Robots-Tag: noindex' );
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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
			'update_order_review'                              => true,
98
			'add_to_cart'                                      => true,
99
			'checkout'                                         => true,
100
			'get_variation'                                    => true,
101
			'feature_product'                                  => false,
102
			'mark_order_status'                                => false,
103
			'add_attribute'                                    => false,
104
			'add_new_attribute'                                => false,
105
			'remove_variation'                                 => false,
106
			'remove_variations'                                => false,
107
			'save_attributes'                                  => false,
108
			'add_variation'                                    => false,
109
			'link_all_variations'                              => false,
110
			'revoke_access_to_download'                        => false,
111
			'grant_access_to_download'                         => false,
112
			'get_customer_details'                             => false,
113
			'add_order_item'                                   => false,
114
			'add_order_fee'                                    => false,
115
			'add_order_shipping'                               => false,
116
			'add_order_tax'                                    => false,
117
			'remove_order_item'                                => false,
118
			'remove_order_tax'                                 => false,
119
			'reduce_order_item_stock'                          => false,
120
			'increase_order_item_stock'                        => false,
121
			'add_order_item_meta'                              => false,
122
			'remove_order_item_meta'                           => false,
123
			'calc_line_taxes'                                  => false,
124
			'save_order_items'                                 => false,
125
			'load_order_items'                                 => false,
126
			'add_order_note'                                   => false,
127
			'delete_order_note'                                => false,
128
			'json_search_products'                             => false,
129
			'json_search_products_and_variations'              => false,
130
			'json_search_grouped_products'                     => false,
131
			'json_search_downloadable_products_and_variations' => false,
132
			'json_search_customers'                            => false,
133
			'term_ordering'                                    => false,
134
			'product_ordering'                                 => false,
135
			'refund_line_items'                                => false,
136
			'delete_refund'                                    => false,
137
			'rated'                                            => false,
138
			'update_api_key'                                   => false,
139
			'get_customer_location'                            => true,
140
			'load_variations'                                  => false,
141
			'save_variations'                                  => false,
142
			'bulk_edit_variations'                             => false,
143
			'tax_rates_save_changes'                           => false,
144
			'shipping_zones_save_changes'                      => false,
145
		);
146
147
		foreach ( $ajax_events as $ajax_event => $nopriv ) {
148
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
149
150
			if ( $nopriv ) {
151
				add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
152
153
				// WC AJAX can be used for frontend ajax requests
154
				add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
155
			}
156
		}
157
	}
158
159
	/**
160
	 * Get a refreshed cart fragment.
161
	 */
162
	public static function get_refreshed_fragments() {
163
164
		// Get mini cart
165
		ob_start();
166
167
		woocommerce_mini_cart();
168
169
		$mini_cart = ob_get_clean();
170
171
		// Fragments and mini cart are returned
172
		$data = array(
173
			'fragments' => apply_filters( 'woocommerce_add_to_cart_fragments', array(
174
					'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>'
175
				)
176
			),
177
			'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() )
178
		);
179
180
		wp_send_json( $data );
181
182
	}
183
184
	/**
185
	 * AJAX apply coupon on checkout page.
186
	 */
187
	public static function apply_coupon() {
188
189
		check_ajax_referer( 'apply-coupon', 'security' );
190
191
		if ( ! empty( $_POST['coupon_code'] ) ) {
192
			WC()->cart->add_discount( sanitize_text_field( $_POST['coupon_code'] ) );
193
		} else {
194
			wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
195
		}
196
197
		wc_print_notices();
198
199
		die();
200
	}
201
202
	/**
203
	 * AJAX remove coupon on cart and checkout page.
204
	 */
205
	public static function remove_coupon() {
206
207
		check_ajax_referer( 'remove-coupon', 'security' );
208
209
		$coupon = wc_clean( $_POST['coupon'] );
210
211
		if ( ! isset( $coupon ) || empty( $coupon ) ) {
212
			wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ) );
213
214
		} else {
215
216
			WC()->cart->remove_coupon( $coupon );
1 ignored issue
show
Bug introduced by
It seems like $coupon defined by wc_clean($_POST['coupon']) on line 209 can also be of type array; however, WC_Cart::remove_coupon() does only seem to accept string, 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...
217
218
			wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
219
		}
220
221
		wc_print_notices();
222
223
		die();
224
	}
225
226
	/**
227
	 * AJAX update shipping method on cart page.
228
	 */
229
	public static function update_shipping_method() {
230
231
		check_ajax_referer( 'update-shipping-method', 'security' );
232
233
		if ( ! defined('WOOCOMMERCE_CART') ) {
234
			define( 'WOOCOMMERCE_CART', true );
235
		}
236
237
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
238
239 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...
240
			foreach ( $_POST['shipping_method'] as $i => $value ) {
241
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
242
			}
243
		}
244
245
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
246
247
		WC()->cart->calculate_totals();
248
249
		woocommerce_cart_totals();
250
251
		die();
252
	}
253
254
	/**
255
	 * AJAX update order review on checkout.
256
	 */
257
	public static function update_order_review() {
258
		ob_start();
259
260
		check_ajax_referer( 'update-order-review', 'security' );
261
262
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
263
			define( 'WOOCOMMERCE_CHECKOUT', true );
264
		}
265
266
		if ( WC()->cart->is_empty() ) {
267
			$data = array(
268
				'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
269
					'form.woocommerce-checkout' => '<div class="woocommerce-error">' . __( 'Sorry, your session has expired.', 'woocommerce' ) . ' <a href="' . home_url() . '" class="wc-backward">' . __( 'Return to homepage', 'woocommerce' ) . '</a></div>'
270
				) )
271
			);
272
273
			wp_send_json( $data );
274
275
			die();
276
		}
277
278
		do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] );
279
280
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
281
282 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...
283
			foreach ( $_POST['shipping_method'] as $i => $value ) {
284
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
285
			}
286
		}
287
288
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
289
		WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
290
291
		if ( isset( $_POST['country'] ) ) {
292
			WC()->customer->set_country( $_POST['country'] );
293
		}
294
295
		if ( isset( $_POST['state'] ) ) {
296
			WC()->customer->set_state( $_POST['state'] );
297
		}
298
299
		if ( isset( $_POST['postcode'] ) ) {
300
			WC()->customer->set_postcode( $_POST['postcode'] );
301
		}
302
303
		if ( isset( $_POST['city'] ) ) {
304
			WC()->customer->set_city( $_POST['city'] );
305
		}
306
307
		if ( isset( $_POST['address'] ) ) {
308
			WC()->customer->set_address( $_POST['address'] );
309
		}
310
311
		if ( isset( $_POST['address_2'] ) ) {
312
			WC()->customer->set_address_2( $_POST['address_2'] );
313
		}
314
315
		if ( wc_ship_to_billing_address_only() ) {
316
317
			if ( isset( $_POST['country'] ) ) {
318
				WC()->customer->set_shipping_country( $_POST['country'] );
319
			}
320
321
			if ( isset( $_POST['state'] ) ) {
322
				WC()->customer->set_shipping_state( $_POST['state'] );
323
			}
324
325
			if ( isset( $_POST['postcode'] ) ) {
326
				WC()->customer->set_shipping_postcode( $_POST['postcode'] );
327
			}
328
329
			if ( isset( $_POST['city'] ) ) {
330
				WC()->customer->set_shipping_city( $_POST['city'] );
331
			}
332
333
			if ( isset( $_POST['address'] ) ) {
334
				WC()->customer->set_shipping_address( $_POST['address'] );
335
			}
336
337
			if ( isset( $_POST['address_2'] ) ) {
338
				WC()->customer->set_shipping_address_2( $_POST['address_2'] );
339
			}
340
		} else {
341
342
			if ( isset( $_POST['s_country'] ) ) {
343
				WC()->customer->set_shipping_country( $_POST['s_country'] );
344
			}
345
346
			if ( isset( $_POST['s_state'] ) ) {
347
				WC()->customer->set_shipping_state( $_POST['s_state'] );
348
			}
349
350
			if ( isset( $_POST['s_postcode'] ) ) {
351
				WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
352
			}
353
354
			if ( isset( $_POST['s_city'] ) ) {
355
				WC()->customer->set_shipping_city( $_POST['s_city'] );
356
			}
357
358
			if ( isset( $_POST['s_address'] ) ) {
359
				WC()->customer->set_shipping_address( $_POST['s_address'] );
360
			}
361
362
			if ( isset( $_POST['s_address_2'] ) ) {
363
				WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
364
			}
365
		}
366
367
		WC()->cart->calculate_totals();
368
369
		// Get order review fragment
370
		ob_start();
371
		woocommerce_order_review();
372
		$woocommerce_order_review = ob_get_clean();
373
374
		// Get checkout payment fragment
375
		ob_start();
376
		woocommerce_checkout_payment();
377
		$woocommerce_checkout_payment = ob_get_clean();
378
379
		// Get messages if reload checkout is not true
380
		$messages = '';
381
		if ( ! isset( WC()->session->reload_checkout ) ) {
382
			ob_start();
383
			wc_print_notices();
384
			$messages = ob_get_clean();
385
		}
386
387
		$data = array(
388
			'result'    => empty( $messages ) ? 'success' : 'failure',
389
			'messages'  => $messages,
390
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
391
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
392
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
393
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
394
			) )
395
		);
396
397
		wp_send_json( $data );
398
399
		die();
400
	}
401
402
	/**
403
	 * AJAX add to cart.
404
	 */
405
	public static function add_to_cart() {
406
		ob_start();
407
408
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
409
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
410
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
411
		$product_status    = get_post_status( $product_id );
412
413
		if ( $passed_validation && WC()->cart->add_to_cart( $product_id, $quantity ) && 'publish' === $product_status ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression WC()->cart->add_to_cart($product_id, $quantity) of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
414
415
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
416
417
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
418
				wc_add_to_cart_message( $product_id );
419
			}
420
421
			// Return fragments
422
			self::get_refreshed_fragments();
423
424
		} else {
425
426
			// If there was an error adding to the cart, redirect to the product page to show any errors
427
			$data = array(
428
				'error'       => true,
429
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
430
			);
431
432
			wp_send_json( $data );
433
434
		}
435
436
		die();
437
	}
438
439
	/**
440
	 * Process ajax checkout form.
441
	 */
442
	public static function checkout() {
443
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
444
			define( 'WOOCOMMERCE_CHECKOUT', true );
445
		}
446
447
		WC()->checkout()->process_checkout();
448
449
		die(0);
450
	}
451
452
	/**
453
	 * Get a matching variation based on posted attributes.
454
	 */
455
	public static function get_variation() {
456
		ob_start();
457
458
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
459
			die();
460
		}
461
462
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
463
464
		if ( $variation_id ) {
465
			$variation = $variable_product->get_available_variation( $variation_id );
466
		} else {
467
			$variation = false;
468
		}
469
470
		wp_send_json( $variation );
471
472
		die();
473
	}
474
475
	/**
476
	 * Feature a product from admin.
477
	 */
478
	public static function feature_product() {
479
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
480
			$product_id = absint( $_GET['product_id'] );
481
482
			if ( 'product' === get_post_type( $product_id ) ) {
483
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
484
485
				delete_transient( 'wc_featured_products' );
486
			}
487
		}
488
489
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=shop_order' ) );
490
		die();
491
	}
492
493
	/**
494
	 * Mark an order with a status.
495
	 */
496
	public static function mark_order_status() {
497
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
498
			$status   = sanitize_text_field( $_GET['status'] );
499
			$order_id = absint( $_GET['order_id'] );
500
501
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
502
				$order = wc_get_order( $order_id );
503
				$order->update_status( $status, '', true );
504
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
505
			}
506
		}
507
508
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
509
		die();
510
	}
511
512
	/**
513
	 * Add an attribute row.
514
	 */
515
	public static function add_attribute() {
516
		ob_start();
517
518
		check_ajax_referer( 'add-attribute', 'security' );
519
520
		if ( ! current_user_can( 'edit_products' ) ) {
521
			die(-1);
522
		}
523
524
		global $wc_product_attributes;
525
526
		$thepostid     = 0;
527
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
528
		$i             = absint( $_POST['i'] );
529
		$position      = 0;
530
		$metabox_class = array();
531
		$attribute     = array(
532
			'name'         => $taxonomy,
533
			'value'        => '',
534
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
535
			'is_variation' => 0,
536
			'is_taxonomy'  => $taxonomy ? 1 : 0
537
		);
538
539
		if ( $taxonomy ) {
540
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
541
			$metabox_class[]    = 'taxonomy';
542
			$metabox_class[]    = $taxonomy;
543
			$attribute_label    = wc_attribute_label( $taxonomy );
544
		} else {
545
			$attribute_label = '';
546
		}
547
548
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
549
		die();
550
	}
551
552
	/**
553
	 * Add a new attribute via ajax function.
554
	 */
555
	public static function add_new_attribute() {
556
		ob_start();
557
558
		check_ajax_referer( 'add-attribute', 'security' );
559
560
		if ( ! current_user_can( 'manage_product_terms' ) ) {
561
			die(-1);
562
		}
563
564
		$taxonomy = esc_attr( $_POST['taxonomy'] );
565
		$term     = wc_clean( $_POST['term'] );
566
567
		if ( taxonomy_exists( $taxonomy ) ) {
568
569
			$result = wp_insert_term( $term, $taxonomy );
570
571
			if ( is_wp_error( $result ) ) {
572
				wp_send_json( array(
573
					'error' => $result->get_error_message()
574
				) );
575
			} else {
576
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
577
				wp_send_json( array(
578
					'term_id' => $term->term_id,
579
					'name'    => $term->name,
580
					'slug'    => $term->slug
581
				) );
582
			}
583
		}
584
585
		die();
586
	}
587
588
	/**
589
	 * Delete variations via ajax function.
590
	 */
591
	public static function remove_variations() {
592
		check_ajax_referer( 'delete-variations', 'security' );
593
594
		if ( ! current_user_can( 'edit_products' ) ) {
595
			die(-1);
596
		}
597
598
		$variation_ids = (array) $_POST['variation_ids'];
599
600
		foreach ( $variation_ids as $variation_id ) {
601
			$variation = get_post( $variation_id );
602
603
			if ( $variation && 'product_variation' == $variation->post_type ) {
604
				wp_delete_post( $variation_id );
605
			}
606
		}
607
608
		die();
609
	}
610
611
	/**
612
	 * Save attributes via ajax.
613
	 */
614
	public static function save_attributes() {
615
616
		check_ajax_referer( 'save-attributes', 'security' );
617
618
		if ( ! current_user_can( 'edit_products' ) ) {
619
			die(-1);
620
		}
621
622
		// Get post data
623
		parse_str( $_POST['data'], $data );
624
		$post_id = absint( $_POST['post_id'] );
625
626
		// Save Attributes
627
		$attributes = array();
628
629
		if ( isset( $data['attribute_names'] ) ) {
630
631
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
632
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
633
634
			if ( isset( $data['attribute_visibility'] ) ) {
635
				$attribute_visibility = $data['attribute_visibility'];
636
			}
637
638
			if ( isset( $data['attribute_variation'] ) ) {
639
				$attribute_variation = $data['attribute_variation'];
640
			}
641
642
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
643
			$attribute_position      = $data['attribute_position'];
644
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
645
646
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
647
				if ( empty( $attribute_names[ $i ] ) ) {
648
					continue;
649
				}
650
651
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
652
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
653
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
654
655
				if ( $is_taxonomy ) {
656
657
					if ( isset( $attribute_values[ $i ] ) ) {
658
659
						// Select based attributes - Format values (posted values are slugs)
660
						if ( is_array( $attribute_values[ $i ] ) ) {
661
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
662
663
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
664
						} else {
665
							$values     = array();
666
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
667
668
							foreach ( $raw_values as $value ) {
669
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
670
								if ( ! $term ) {
671
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
672
673
									if ( $term && ! is_wp_error( $term ) ) {
674
										$values[] = $term['term_id'];
675
									}
676
								} else {
677
									$values[] = $term->term_id;
678
								}
679
							}
680
						}
681
682
						// Remove empty items in the array
683
						$values = array_filter( $values, 'strlen' );
684
685
					} else {
686
						$values = array();
687
					}
688
689
					// Update post terms
690
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
691
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
692
					}
693
694 View Code Duplication
					if ( $values ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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...
695
						// Add attribute to array, but don't set values
696
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
697
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
698
							'value' 		=> '',
699
							'position' 		=> $attribute_position[ $i ],
700
							'is_visible' 	=> $is_visible,
701
							'is_variation' 	=> $is_variation,
702
							'is_taxonomy' 	=> $is_taxonomy
703
						);
704
					}
705
706 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...
707
708
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
709
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
710
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
711
712
					// Custom attribute - Add attribute to array and set the values
713
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
714
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
715
						'value' 		=> $values,
716
						'position' 		=> $attribute_position[ $i ],
717
						'is_visible' 	=> $is_visible,
718
						'is_variation' 	=> $is_variation,
719
						'is_taxonomy' 	=> $is_taxonomy
720
					);
721
				}
722
723
			 }
724
		}
725
726
		if ( ! function_exists( 'attributes_cmp' ) ) {
727
			function attributes_cmp( $a, $b ) {
0 ignored issues
show
Best Practice introduced by
The function attributes_cmp() has been defined more than once; this definition is ignored, only the first definition in includes/admin/meta-boxe...ta-box-product-data.php (L967-973) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
728
				if ( $a['position'] == $b['position'] ) {
729
					return 0;
730
				}
731
732
				return ( $a['position'] < $b['position'] ) ? -1 : 1;
733
			}
734
		}
735
		uasort( $attributes, 'attributes_cmp' );
736
737
		update_post_meta( $post_id, '_product_attributes', $attributes );
738
739
		die();
740
	}
741
742
	/**
743
	 * Add variation via ajax function.
744
	 */
745
	public static function add_variation() {
746
747
		check_ajax_referer( 'add-variation', 'security' );
748
749
		if ( ! current_user_can( 'edit_products' ) ) {
750
			die(-1);
751
		}
752
753
		global $post;
754
755
		$post_id = intval( $_POST['post_id'] );
756
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
757
		$loop    = intval( $_POST['loop'] );
758
759
		$variation = array(
760
			'post_title'   => 'Product #' . $post_id . ' Variation',
761
			'post_content' => '',
762
			'post_status'  => 'publish',
763
			'post_author'  => get_current_user_id(),
764
			'post_parent'  => $post_id,
765
			'post_type'    => 'product_variation',
766
			'menu_order'   => -1
767
		);
768
769
		$variation_id = wp_insert_post( $variation );
770
771
		do_action( 'woocommerce_create_product_variation', $variation_id );
772
773
		if ( $variation_id ) {
774
			$variation        = get_post( $variation_id );
775
			$variation_meta   = get_post_meta( $variation_id );
776
			$variation_data   = array();
777
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
778
			$variation_fields = array(
779
				'_sku'                   => '',
780
				'_stock'                 => '',
781
				'_regular_price'         => '',
782
				'_sale_price'            => '',
783
				'_weight'                => '',
784
				'_length'                => '',
785
				'_width'                 => '',
786
				'_height'                => '',
787
				'_download_limit'        => '',
788
				'_download_expiry'       => '',
789
				'_downloadable_files'    => '',
790
				'_downloadable'          => '',
791
				'_virtual'               => '',
792
				'_thumbnail_id'          => '',
793
				'_sale_price_dates_from' => '',
794
				'_sale_price_dates_to'   => '',
795
				'_manage_stock'          => '',
796
				'_stock_status'          => '',
797
				'_backorders'            => null,
798
				'_tax_class'             => null,
799
				'_variation_description' => ''
800
			);
801
802 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...
803
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
804
			}
805
806
			// Add the variation attributes
807
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
808
809
			// Formatting
810
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
811
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
812
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
813
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
814
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
815
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
816
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
817
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
818
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
819
			$variation_data['menu_order']     = $variation->menu_order;
820
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
821
822
			// Get tax classes
823
			$tax_classes           = WC_Tax::get_tax_classes();
824
			$tax_class_options     = array();
825
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
826
827 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...
828
				foreach ( $tax_classes as $class ) {
829
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
830
				}
831
			}
832
833
			// Set backorder options
834
			$backorder_options = array(
835
				'no'     => __( 'Do not allow', 'woocommerce' ),
836
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
837
				'yes'    => __( 'Allow', 'woocommerce' )
838
			);
839
840
			// set stock status options
841
			$stock_status_options = array(
842
				'instock'    => __( 'In stock', 'woocommerce' ),
843
				'outofstock' => __( 'Out of stock', 'woocommerce' )
844
			);
845
846
			// Get attributes
847
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
848
849
			$parent_data = array(
850
				'id'                   => $post_id,
851
				'attributes'           => $attributes,
852
				'tax_class_options'    => $tax_class_options,
853
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
854
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
855
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
856
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
857
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
858
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
859
				'backorder_options'    => $backorder_options,
860
				'stock_status_options' => $stock_status_options
861
			);
862
863
			if ( ! $parent_data['weight'] ) {
864
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
865
			}
866
867
			if ( ! $parent_data['length'] ) {
868
				$parent_data['length'] = wc_format_localized_decimal( 0 );
869
			}
870
871
			if ( ! $parent_data['width'] ) {
872
				$parent_data['width'] = wc_format_localized_decimal( 0 );
873
			}
874
875
			if ( ! $parent_data['height'] ) {
876
				$parent_data['height'] = wc_format_localized_decimal( 0 );
877
			}
878
879
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
880
		}
881
882
		die();
883
	}
884
885
	/**
886
	 * Link all variations via ajax function.
887
	 */
888
	public static function link_all_variations() {
889
890
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
891
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
892
		}
893
894
		check_ajax_referer( 'link-variations', 'security' );
895
896
		if ( ! current_user_can( 'edit_products' ) ) {
897
			die(-1);
898
		}
899
900 View Code Duplication
		if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
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...
901
			@set_time_limit( 0 );
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
902
		}
903
904
		$post_id = intval( $_POST['post_id'] );
905
906
		if ( ! $post_id ) {
907
			die();
908
		}
909
910
		$variations = array();
911
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
912
913
		// Put variation attributes into an array
914
		foreach ( $_product->get_attributes() as $attribute ) {
915
916
			if ( ! $attribute['is_variation'] ) {
917
				continue;
918
			}
919
920
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
921
922
			if ( $attribute['is_taxonomy'] ) {
923
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
924
			} else {
925
				$options = explode( WC_DELIMITER, $attribute['value'] );
926
			}
927
928
			$options = array_map( 'trim', $options );
929
930
			$variations[ $attribute_field_name ] = $options;
931
		}
932
933
		// Quit out if none were found
934
		if ( sizeof( $variations ) == 0 ) {
935
			die();
936
		}
937
938
		// Get existing variations so we don't create duplicates
939
		$available_variations = array();
940
941
		foreach( $_product->get_children() as $child_id ) {
942
			$child = $_product->get_child( $child_id );
943
944
			if ( ! empty( $child->variation_id ) ) {
945
				$available_variations[] = $child->get_variation_attributes();
946
			}
947
		}
948
949
		// Created posts will all have the following data
950
		$variation_post_data = array(
951
			'post_title'   => 'Product #' . $post_id . ' Variation',
952
			'post_content' => '',
953
			'post_status'  => 'publish',
954
			'post_author'  => get_current_user_id(),
955
			'post_parent'  => $post_id,
956
			'post_type'    => 'product_variation'
957
		);
958
959
		$variation_ids       = array();
960
		$added               = 0;
961
		$possible_variations = wc_array_cartesian( $variations );
962
963
		foreach ( $possible_variations as $variation ) {
964
965
			// Check if variation already exists
966
			if ( in_array( $variation, $available_variations ) ) {
967
				continue;
968
			}
969
970
			$variation_id = wp_insert_post( $variation_post_data );
971
972
			$variation_ids[] = $variation_id;
973
974
			foreach ( $variation as $key => $value ) {
975
				update_post_meta( $variation_id, $key, $value );
976
			}
977
978
			// Save stock status
979
			update_post_meta( $variation_id, '_stock_status', 'instock' );
980
981
			$added++;
982
983
			do_action( 'product_variation_linked', $variation_id );
984
985
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
986
				break;
987
			}
988
		}
989
990
		delete_transient( 'wc_product_children_' . $post_id );
991
992
		echo $added;
993
994
		die();
995
	}
996
997
	/**
998
	 * Delete download permissions via ajax function.
999
	 */
1000
	public static function revoke_access_to_download() {
1001
1002
		check_ajax_referer( 'revoke-access', 'security' );
1003
1004
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1005
			die(-1);
1006
		}
1007
1008
		global $wpdb;
1009
1010
		$download_id = $_POST['download_id'];
1011
		$product_id  = intval( $_POST['product_id'] );
1012
		$order_id    = intval( $_POST['order_id'] );
1013
1014
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE order_id = %d AND product_id = %d AND download_id = %s;", $order_id, $product_id, $download_id ) );
1015
1016
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
1017
1018
		die();
1019
	}
1020
1021
	/**
1022
	 * Grant download permissions via ajax function.
1023
	 */
1024
	public static function grant_access_to_download() {
1025
1026
		check_ajax_referer( 'grant-access', 'security' );
1027
1028
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1029
			die(-1);
1030
		}
1031
1032
		global $wpdb;
1033
1034
		$wpdb->hide_errors();
1035
1036
		$order_id     = intval( $_POST['order_id'] );
1037
		$product_ids  = $_POST['product_ids'];
1038
		$loop         = intval( $_POST['loop'] );
1039
		$file_counter = 0;
1040
		$order        = wc_get_order( $order_id );
1041
1042
		if ( ! is_array( $product_ids ) ) {
1043
			$product_ids = array( $product_ids );
1044
		}
1045
1046
		foreach ( $product_ids as $product_id ) {
1047
			$product = wc_get_product( $product_id );
1048
			$files   = $product->get_files();
1049
1050
			if ( ! $order->billing_email ) {
1051
				die();
1052
			}
1053
1054
			if ( $files ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1055
				foreach ( $files as $download_id => $file ) {
1056
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
1057
1058
						// insert complete - get inserted data
1059
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1060
1061
						$loop ++;
1062
						$file_counter ++;
1063
1064 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...
1065
							$file_count = $file['name'];
1066
						} else {
1067
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1068
						}
1069
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1070
					}
1071
				}
1072
			}
1073
		}
1074
1075
		die();
1076
	}
1077
1078
	/**
1079
	 * Get customer details via ajax.
1080
	 */
1081
	public static function get_customer_details() {
1082
		ob_start();
1083
1084
		check_ajax_referer( 'get-customer-details', 'security' );
1085
1086
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1087
			die(-1);
1088
		}
1089
1090
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1091
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1092
1093
		$customer_data = array(
1094
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1095
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1096
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1097
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1098
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1099
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1100
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1101
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1102
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1103
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1104
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1105
		);
1106
1107
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1108
1109
		wp_send_json( $customer_data );
1110
	}
1111
1112
	/**
1113
	 * Add order item via ajax.
1114
	 */
1115
	public static function add_order_item() {
1116
		check_ajax_referer( 'order-item', 'security' );
1117
1118
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1119
			die(-1);
1120
		}
1121
1122
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1123
		$order_id    = absint( $_POST['order_id'] );
1124
1125
		// Find the item
1126
		if ( ! is_numeric( $item_to_add ) ) {
1127
			die();
1128
		}
1129
1130
		$post = get_post( $item_to_add );
1131
1132
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1133
			die();
1134
		}
1135
1136
		$_product    = wc_get_product( $post->ID );
1137
		$order       = wc_get_order( $order_id );
1138
		$order_taxes = $order->get_taxes();
1139
		$class       = 'new_row';
1140
1141
		// Set values
1142
		$item = array();
1143
1144
		$item['product_id']        = $_product->id;
1145
		$item['variation_id']      = isset( $_product->variation_id ) ? $_product->variation_id : '';
1146
		$item['variation_data']    = $item['variation_id'] ? $_product->get_variation_attributes() : '';
1147
		$item['name']              = $_product->get_title();
1148
		$item['tax_class']         = $_product->get_tax_class();
1149
		$item['qty']               = 1;
1150
		$item['line_subtotal']     = wc_format_decimal( $_product->get_price_excluding_tax() );
1151
		$item['line_subtotal_tax'] = '';
1152
		$item['line_total']        = wc_format_decimal( $_product->get_price_excluding_tax() );
1153
		$item['line_tax']          = '';
1154
		$item['type']              = 'line_item';
1155
1156
		// Add line item
1157
		$item_id = wc_add_order_item( $order_id, array(
1158
			'order_item_name' 		=> $item['name'],
1159
			'order_item_type' 		=> 'line_item'
1160
		) );
1161
1162
		// Add line item meta
1163
		if ( $item_id ) {
1164
			wc_add_order_item_meta( $item_id, '_qty', $item['qty'] );
1165
			wc_add_order_item_meta( $item_id, '_tax_class', $item['tax_class'] );
1166
			wc_add_order_item_meta( $item_id, '_product_id', $item['product_id'] );
1167
			wc_add_order_item_meta( $item_id, '_variation_id', $item['variation_id'] );
1168
			wc_add_order_item_meta( $item_id, '_line_subtotal', $item['line_subtotal'] );
1169
			wc_add_order_item_meta( $item_id, '_line_subtotal_tax', $item['line_subtotal_tax'] );
1170
			wc_add_order_item_meta( $item_id, '_line_total', $item['line_total'] );
1171
			wc_add_order_item_meta( $item_id, '_line_tax', $item['line_tax'] );
1172
1173
			// Since 2.2
1174
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
1175
1176
			// Store variation data in meta
1177 View Code Duplication
			if ( $item['variation_data'] && is_array( $item['variation_data'] ) ) {
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...
1178
				foreach ( $item['variation_data'] as $key => $value ) {
1179
					wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
1180
				}
1181
			}
1182
1183
			do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1184
		}
1185
1186
		$item['item_meta']       = $order->get_item_meta( $item_id );
1187
		$item['item_meta_array'] = $order->get_item_meta_array( $item_id );
1188
		$item                    = $order->expand_item_meta( $item );
1189
		$item                    = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
1190
1191
		include( 'admin/meta-boxes/views/html-order-item.php' );
1192
1193
		// Quit out
1194
		die();
1195
	}
1196
1197
	/**
1198
	 * Add order fee via ajax.
1199
	 */
1200
	public static function add_order_fee() {
1201
1202
		check_ajax_referer( 'order-item', 'security' );
1203
1204
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1205
			die(-1);
1206
		}
1207
1208
		$order_id      = absint( $_POST['order_id'] );
1209
		$order         = wc_get_order( $order_id );
1210
		$order_taxes   = $order->get_taxes();
1211
		$item          = array();
1212
1213
		// Add new fee
1214
		$fee            = new stdClass();
1215
		$fee->name      = '';
1216
		$fee->tax_class = '';
1217
		$fee->taxable   = $fee->tax_class !== '0';
1218
		$fee->amount    = '';
1219
		$fee->tax       = '';
1220
		$fee->tax_data  = array();
1221
		$item_id        = $order->add_fee( $fee );
1222
1223
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1224
1225
		// Quit out
1226
		die();
1227
	}
1228
1229
	/**
1230
	 * Add order shipping cost via ajax.
1231
	 */
1232
	public static function add_order_shipping() {
1233
1234
		check_ajax_referer( 'order-item', 'security' );
1235
1236
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1237
			die(-1);
1238
		}
1239
1240
		$order_id         = absint( $_POST['order_id'] );
1241
		$order            = wc_get_order( $order_id );
1242
		$order_taxes      = $order->get_taxes();
1243
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1244
		$item             = array();
1245
1246
		// Add new shipping
1247
		$shipping        = new stdClass();
1248
		$shipping->label = '';
1249
		$shipping->id    = '';
1250
		$shipping->cost  = '';
1251
		$shipping->taxes = array();
1252
		$item_id         = $order->add_shipping( $shipping );
1253
1254
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1255
1256
		// Quit out
1257
		die();
1258
	}
1259
1260
	/**
1261
	 * Add order tax column via ajax.
1262
	 */
1263
	public static function add_order_tax() {
1264
		global $wpdb;
1265
1266
		check_ajax_referer( 'order-item', 'security' );
1267
1268
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1269
			die(-1);
1270
		}
1271
1272
		$order_id = absint( $_POST['order_id'] );
1273
		$rate_id  = absint( $_POST['rate_id'] );
1274
		$order    = wc_get_order( $order_id );
1275
		$data     = get_post_meta( $order_id );
1276
1277
		// Add new tax
1278
		$order->add_tax( $rate_id, 0, 0 );
1279
1280
		// Return HTML items
1281
		include( 'admin/meta-boxes/views/html-order-items.php' );
1282
1283
		die();
1284
	}
1285
1286
	/**
1287
	 * Remove an order item.
1288
	 */
1289
	public static function remove_order_item() {
1290
		check_ajax_referer( 'order-item', 'security' );
1291
1292
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1293
			die(-1);
1294
		}
1295
1296
		$order_item_ids = $_POST['order_item_ids'];
1297
1298
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1299
			$order_item_ids = array( $order_item_ids );
1300
		}
1301
1302
		if ( sizeof( $order_item_ids ) > 0 ) {
1303
			foreach( $order_item_ids as $id ) {
1304
				wc_delete_order_item( absint( $id ) );
1305
			}
1306
		}
1307
1308
		die();
1309
	}
1310
1311
	/**
1312
	 * Remove an order tax.
1313
	 */
1314
	public static function remove_order_tax() {
1315
1316
		check_ajax_referer( 'order-item', 'security' );
1317
1318
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1319
			die(-1);
1320
		}
1321
1322
		$order_id = absint( $_POST['order_id'] );
1323
		$rate_id  = absint( $_POST['rate_id'] );
1324
1325
		wc_delete_order_item( $rate_id );
1326
1327
		// Return HTML items
1328
		$order = wc_get_order( $order_id );
1329
		$data  = get_post_meta( $order_id );
1330
		include( 'admin/meta-boxes/views/html-order-items.php' );
1331
1332
		die();
1333
	}
1334
1335
	/**
1336
	 * Reduce order item stock.
1337
	 */
1338
	public static function reduce_order_item_stock() {
1339
		check_ajax_referer( 'order-item', 'security' );
1340
1341
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1342
			die(-1);
1343
		}
1344
1345
		$order_id       = absint( $_POST['order_id'] );
1346
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1347
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1348
		$order          = wc_get_order( $order_id );
1349
		$order_items    = $order->get_items();
1350
		$return         = array();
1351
1352
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1353
1354
			foreach ( $order_items as $item_id => $order_item ) {
1355
1356
				// Only reduce checked items
1357
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1358
					continue;
1359
				}
1360
1361
				$_product = $order->get_product_from_item( $order_item );
1362
1363
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1364
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1365
					$new_stock    = $_product->reduce_stock( $stock_change );
1366
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1367
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1368
					$return[]     = $note;
1369
1370
					$order->add_order_note( $note );
1371
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1372
				}
1373
			}
1374
1375
			do_action( 'woocommerce_reduce_order_stock', $order );
1376
1377
			if ( empty( $return ) ) {
1378
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1379
			}
1380
1381
			echo implode( ', ', $return );
1382
		}
1383
1384
		die();
1385
	}
1386
1387
	/**
1388
	 * Increase order item stock.
1389
	 */
1390
	public static function increase_order_item_stock() {
1391
		check_ajax_referer( 'order-item', 'security' );
1392
1393
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1394
			die(-1);
1395
		}
1396
1397
		$order_id       = absint( $_POST['order_id'] );
1398
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1399
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1400
		$order          = wc_get_order( $order_id );
1401
		$order_items    = $order->get_items();
1402
		$return         = array();
1403
1404
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1405
1406
			foreach ( $order_items as $item_id => $order_item ) {
1407
1408
				// Only reduce checked items
1409
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1410
					continue;
1411
				}
1412
1413
				$_product = $order->get_product_from_item( $order_item );
1414
1415
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1416
					$old_stock    = $_product->get_stock_quantity();
1417
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1418
					$new_quantity = $_product->increase_stock( $stock_change );
1419
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1420
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1421
					$return[]     = $note;
1422
1423
					$order->add_order_note( $note );
1424
				}
1425
			}
1426
1427
			do_action( 'woocommerce_restore_order_stock', $order );
1428
1429
			if ( empty( $return ) ) {
1430
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1431
			}
1432
1433
			echo implode( ', ', $return );
1434
		}
1435
1436
		die();
1437
	}
1438
1439
	/**
1440
	 * Add some meta to a line item.
1441
	 */
1442
	public static function add_order_item_meta() {
1443
		check_ajax_referer( 'order-item', 'security' );
1444
1445
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1446
			die(-1);
1447
		}
1448
1449
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1450
1451
		if ( $meta_id ) {
1452
			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>';
1453
		}
1454
1455
		die();
1456
	}
1457
1458
	/**
1459
	 * Remove meta from a line item.
1460
	 */
1461
	public static function remove_order_item_meta() {
1462
		global $wpdb;
1463
1464
		check_ajax_referer( 'order-item', 'security' );
1465
1466
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1467
			die(-1);
1468
		}
1469
1470
		$meta_id = absint( $_POST['meta_id'] );
1471
1472
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_id = %d", $meta_id ) );
1473
1474
		die();
1475
	}
1476
1477
	/**
1478
	 * Calc line tax.
1479
	 */
1480
	public static function calc_line_taxes() {
1481
		global $wpdb;
1482
1483
		check_ajax_referer( 'calc-totals', 'security' );
1484
1485
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1486
			die(-1);
1487
		}
1488
1489
		$tax            = new WC_Tax();
1490
		$tax_based_on   = get_option( 'woocommerce_tax_based_on' );
1491
		$order_id       = absint( $_POST['order_id'] );
1492
		$items          = array();
1493
		$country        = strtoupper( esc_attr( $_POST['country'] ) );
1494
		$state          = strtoupper( esc_attr( $_POST['state'] ) );
1495
		$postcode       = strtoupper( esc_attr( $_POST['postcode'] ) );
1496
		$city           = wc_clean( esc_attr( $_POST['city'] ) );
1497
		$order          = wc_get_order( $order_id );
1498
		$taxes          = array();
1499
		$shipping_taxes = array();
1500
1501
		// Default to base
1502 View Code Duplication
		if ( 'base' === $tax_based_on || empty( $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...
1503
			$default  = wc_get_base_location();
1504
			$country  = $default['country'];
1505
			$state    = $default['state'];
1506
			$postcode = '';
1507
			$city     = '';
1508
		}
1509
1510
		// Parse the jQuery serialized items
1511
		parse_str( $_POST['items'], $items );
1512
1513
		// Prevent undefined warnings
1514
		if ( ! isset( $items['line_tax'] ) ) {
1515
			$items['line_tax'] = array();
1516
		}
1517
		if ( ! isset( $items['line_subtotal_tax'] ) ) {
1518
			$items['line_subtotal_tax'] = array();
1519
		}
1520
		$items['order_taxes'] = array();
1521
1522
		// Action
1523
		$items = apply_filters( 'woocommerce_ajax_calc_line_taxes', $items, $order_id, $country, $_POST );
1524
1525
		// Get items and fees taxes
1526
		if ( isset( $items['order_item_id'] ) ) {
1527
			$line_total = $line_subtotal = $order_item_tax_class = array();
1528
1529
			foreach ( $items['order_item_id'] as $item_id ) {
1530
				$item_id                          = absint( $item_id );
1531
				$line_total[ $item_id ]           = isset( $items['line_total'][ $item_id ] ) ? wc_format_decimal( $items['line_total'][ $item_id ] ) : 0;
1532
				$line_subtotal[ $item_id ]        = isset( $items['line_subtotal'][ $item_id ] ) ? wc_format_decimal( $items['line_subtotal'][ $item_id ] ) : $line_total[ $item_id ];
1533
				$order_item_tax_class[ $item_id ] = isset( $items['order_item_tax_class'][ $item_id ] ) ? sanitize_text_field( $items['order_item_tax_class'][ $item_id ] ) : '';
1534
				$product_id                       = $order->get_item_meta( $item_id, '_product_id', true );
1535
1536
				// Get product details
1537
				if ( get_post_type( $product_id ) == 'product' ) {
1538
					$_product        = wc_get_product( $product_id );
1539
					$item_tax_status = $_product->get_tax_status();
1540
				} else {
1541
					$item_tax_status = 'taxable';
1542
				}
1543
1544
				if ( '0' !== $order_item_tax_class[ $item_id ] && 'taxable' === $item_tax_status ) {
1545
					$tax_rates = WC_Tax::find_rates( array(
1546
						'country'   => $country,
1547
						'state'     => $state,
1548
						'postcode'  => $postcode,
1549
						'city'      => $city,
1550
						'tax_class' => $order_item_tax_class[ $item_id ]
1551
					) );
1552
1553
					$line_taxes          = WC_Tax::calc_tax( $line_total[ $item_id ], $tax_rates, false );
1554
					$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal[ $item_id ], $tax_rates, false );
1555
1556
					// Set the new line_tax
1557
					foreach ( $line_taxes as $_tax_id => $_tax_value ) {
1558
						$items['line_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1559
					}
1560
1561
					// Set the new line_subtotal_tax
1562
					foreach ( $line_subtotal_taxes as $_tax_id => $_tax_value ) {
1563
						$items['line_subtotal_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1564
					}
1565
1566
					// Sum the item taxes
1567
					foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1568
						$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1569
					}
1570
				}
1571
			}
1572
		}
1573
1574
		// Get shipping taxes
1575
		if ( isset( $items['shipping_method_id'] ) ) {
1576
			$matched_tax_rates = array();
1577
1578
			$tax_rates = WC_Tax::find_rates( array(
1579
				'country'   => $country,
1580
				'state'     => $state,
1581
				'postcode'  => $postcode,
1582
				'city'      => $city,
1583
				'tax_class' => ''
1584
			) );
1585
1586 View Code Duplication
			if ( $tax_rates ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tax_rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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...
1587
				foreach ( $tax_rates as $key => $rate ) {
1588
					if ( isset( $rate['shipping'] ) && 'yes' == $rate['shipping'] ) {
1589
						$matched_tax_rates[ $key ] = $rate;
1590
					}
1591
				}
1592
			}
1593
1594
			$shipping_cost = $shipping_taxes = array();
1595
1596
			foreach ( $items['shipping_method_id'] as $item_id ) {
1597
				$item_id                   = absint( $item_id );
1598
				$shipping_cost[ $item_id ] = isset( $items['shipping_cost'][ $item_id ] ) ? wc_format_decimal( $items['shipping_cost'][ $item_id ] ) : 0;
1599
				$_shipping_taxes           = WC_Tax::calc_shipping_tax( $shipping_cost[ $item_id ], $matched_tax_rates );
1600
1601
				// Set the new shipping_taxes
1602
				foreach ( $_shipping_taxes as $_tax_id => $_tax_value ) {
1603
					$items['shipping_taxes'][ $item_id ][ $_tax_id ] = $_tax_value;
1604
1605
					$shipping_taxes[ $_tax_id ] = isset( $shipping_taxes[ $_tax_id ] ) ? $shipping_taxes[ $_tax_id ] + $_tax_value : $_tax_value;
1606
				}
1607
			}
1608
		}
1609
1610
		// Remove old tax rows
1611
		$order->remove_order_items( 'tax' );
1612
1613
		// Add tax rows
1614 View Code Duplication
		foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_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...
1615
			$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 );
1616
		}
1617
1618
		// Create the new order_taxes
1619
		foreach ( $order->get_taxes() as $tax_id => $tax_item ) {
1620
			$items['order_taxes'][ $tax_id ] = absint( $tax_item['rate_id'] );
1621
		}
1622
1623
		$items = apply_filters( 'woocommerce_ajax_after_calc_line_taxes', $items, $order_id, $country, $_POST );
1624
1625
		// Save order items
1626
		wc_save_order_items( $order_id, $items );
1627
1628
		// Return HTML items
1629
		$order = wc_get_order( $order_id );
1630
		$data  = get_post_meta( $order_id );
1631
		include( 'admin/meta-boxes/views/html-order-items.php' );
1632
1633
		die();
1634
	}
1635
1636
	/**
1637
	 * Save order items via ajax.
1638
	 */
1639
	public static function save_order_items() {
1640
		check_ajax_referer( 'order-item', 'security' );
1641
1642
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1643
			die(-1);
1644
		}
1645
1646
		if ( isset( $_POST['order_id'] ) && isset( $_POST['items'] ) ) {
1647
			$order_id = absint( $_POST['order_id'] );
1648
1649
			// Parse the jQuery serialized items
1650
			$items = array();
1651
			parse_str( $_POST['items'], $items );
1652
1653
			// Save order items
1654
			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...
1655
1656
			// Return HTML items
1657
			$order = wc_get_order( $order_id );
1658
			$data  = get_post_meta( $order_id );
1659
			include( 'admin/meta-boxes/views/html-order-items.php' );
1660
		}
1661
1662
		die();
1663
	}
1664
1665
	/**
1666
	 * Load order items via ajax.
1667
	 */
1668
	public static function load_order_items() {
1669
		check_ajax_referer( 'order-item', 'security' );
1670
1671
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1672
			die(-1);
1673
		}
1674
1675
		// Return HTML items
1676
		$order_id = absint( $_POST['order_id'] );
1677
		$order    = wc_get_order( $order_id );
1678
		$data     = get_post_meta( $order_id );
1679
		include( 'admin/meta-boxes/views/html-order-items.php' );
1680
1681
		die();
1682
	}
1683
1684
	/**
1685
	 * Add order note via ajax.
1686
	 */
1687
	public static function add_order_note() {
1688
1689
		check_ajax_referer( 'add-order-note', 'security' );
1690
1691
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1692
			die(-1);
1693
		}
1694
1695
		$post_id   = absint( $_POST['post_id'] );
1696
		$note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
1697
		$note_type = $_POST['note_type'];
1698
1699
		$is_customer_note = $note_type == 'customer' ? 1 : 0;
1700
1701
		if ( $post_id > 0 ) {
1702
			$order      = wc_get_order( $post_id );
1703
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1704
1705
			echo '<li rel="' . esc_attr( $comment_id ) . '" class="note ';
1706
			if ( $is_customer_note ) {
1707
				echo 'customer-note';
1708
			}
1709
			echo '"><div class="note_content">';
1710
			echo wpautop( wptexturize( $note ) );
1711
			echo '</div><p class="meta"><a href="#" class="delete_note">'.__( 'Delete note', 'woocommerce' ).'</a></p>';
1712
			echo '</li>';
1713
		}
1714
1715
		// Quit out
1716
		die();
1717
	}
1718
1719
	/**
1720
	 * Delete order note via ajax.
1721
	 */
1722
	public static function delete_order_note() {
1723
1724
		check_ajax_referer( 'delete-order-note', 'security' );
1725
1726
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1727
			die(-1);
1728
		}
1729
1730
		$note_id = (int) $_POST['note_id'];
1731
1732
		if ( $note_id > 0 ) {
1733
			wp_delete_comment( $note_id );
1734
		}
1735
1736
		// Quit out
1737
		die();
1738
	}
1739
1740
	/**
1741
	 * Search for products and echo json.
1742
	 *
1743
	 * @param string $x (default: '')
1744
	 * @param string $post_types (default: array('product'))
1745
	 */
1746
	public static function json_search_products( $x = '', $post_types = array( 'product' ) ) {
0 ignored issues
show
Unused Code introduced by
The parameter $x 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...
1747
		global $wpdb;
1748
1749
		ob_start();
1750
1751
		check_ajax_referer( 'search-products', 'security' );
1752
1753
		$term = (string) wc_clean( stripslashes( $_GET['term'] ) );
1754
1755
		if ( empty( $term ) ) {
1756
			die();
1757
		}
1758
1759
		$like_term = '%' . $wpdb->esc_like( $term ) . '%';
1760
1761
		if ( is_numeric( $term ) ) {
1762
			$query = $wpdb->prepare( "
1763
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1764
				WHERE posts.post_status = 'publish'
1765
				AND (
1766
					posts.post_parent = %s
1767
					OR posts.ID = %s
1768
					OR posts.post_title LIKE %s
1769
					OR (
1770
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1771
					)
1772
				)
1773
			", $term, $term, $term, $like_term );
1774
		} else {
1775
			$query = $wpdb->prepare( "
1776
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1777
				WHERE posts.post_status = 'publish'
1778
				AND (
1779
					posts.post_title LIKE %s
1780
					or posts.post_content LIKE %s
1781
					OR (
1782
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1783
					)
1784
				)
1785
			", $like_term, $like_term, $like_term );
1786
		}
1787
1788
		$query .= " AND posts.post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "')";
1789
1790 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...
1791
			$query .= " AND posts.ID NOT IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['exclude'] ) ) ) . ")";
1792
		}
1793
1794 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...
1795
			$query .= " AND posts.ID IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['include'] ) ) ) . ")";
1796
		}
1797
1798
		if ( ! empty( $_GET['limit'] ) ) {
1799
			$query .= " LIMIT " . intval( $_GET['limit'] );
1800
		}
1801
1802
		$posts          = array_unique( $wpdb->get_col( $query ) );
1803
		$found_products = array();
1804
1805
		if ( ! empty( $posts ) ) {
1806
			foreach ( $posts as $post ) {
1807
				$product = wc_get_product( $post );
1808
1809
				if ( ! current_user_can( 'read_product', $post ) ) {
1810
					continue;
1811
				}
1812
1813
				if ( ! $product || ( $product->is_type( 'variation' ) && empty( $product->parent ) ) ) {
1814
					continue;
1815
				}
1816
1817
				$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1818
			}
1819
		}
1820
1821
		$found_products = apply_filters( 'woocommerce_json_search_found_products', $found_products );
1822
1823
		wp_send_json( $found_products );
1824
	}
1825
1826
	/**
1827
	 * Search for product variations and return json.
1828
	 *
1829
	 * @see WC_AJAX::json_search_products()
1830
	 */
1831
	public static function json_search_products_and_variations() {
1832
		self::json_search_products( '', array( 'product', 'product_variation' ) );
1833
	}
1834
1835
	/**
1836
	 * Search for gruoped products and return json.
1837
	 */
1838
	public static function json_search_grouped_products() {
1839
		ob_start();
1840
1841
		check_ajax_referer( 'search-products', 'security' );
1842
1843
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1844
		$exclude = array();
1845
1846
		if ( empty( $term ) ) {
1847
			die();
1848
		}
1849
1850 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...
1851
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1852
		}
1853
1854
		$found_products = array();
1855
1856
		if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
1857
1858
			$posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
1859
1860
			if ( sizeof( $posts_in ) > 0 ) {
1861
1862
				$args = array(
1863
					'post_type'        => 'product',
1864
					'post_status'      => 'any',
1865
					'numberposts'      => -1,
1866
					'orderby'          => 'title',
1867
					'order'            => 'asc',
1868
					'post_parent'      => 0,
1869
					'suppress_filters' => 0,
1870
					'include'          => $posts_in,
1871
					's'                => $term,
1872
					'fields'           => 'ids',
1873
					'exclude'          => $exclude
1874
				);
1875
1876
				$posts = get_posts( $args );
1877
1878
				if ( ! empty( $posts ) ) {
1879
					foreach ( $posts as $post ) {
1880
						$product = wc_get_product( $post );
1881
1882
						if ( ! current_user_can( 'read_product', $post ) ) {
1883
							continue;
1884
						}
1885
1886
						$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1887
					}
1888
				}
1889
			}
1890
		}
1891
1892
		$found_products = apply_filters( 'woocommerce_json_search_found_grouped_products', $found_products );
1893
1894
		wp_send_json( $found_products );
1895
	}
1896
1897
	/**
1898
	 * Search for downloadable product variations and return json.
1899
	 *
1900
	 * @see WC_AJAX::json_search_products()
1901
	 */
1902
	public static function json_search_downloadable_products_and_variations() {
1903
		ob_start();
1904
1905
		check_ajax_referer( 'search-products', 'security' );
1906
1907
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1908
		$exclude = array();
1909
1910 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...
1911
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1912
		}
1913
1914
		$args = array(
1915
			'post_type'      => array( 'product', 'product_variation' ),
1916
			'posts_per_page' => -1,
1917
			'post_status'    => 'publish',
1918
			'order'          => 'ASC',
1919
			'orderby'        => 'parent title',
1920
			'meta_query'     => array(
1921
				array(
1922
					'key'   => '_downloadable',
1923
					'value' => 'yes'
1924
				)
1925
			),
1926
			's'              => $term,
1927
			'exclude'        => $exclude
1928
		);
1929
1930
		$posts = get_posts( $args );
1931
		$found_products = array();
1932
1933
		if ( ! empty( $posts ) ) {
1934
			foreach ( $posts as $post ) {
1935
				$product = wc_get_product( $post->ID );
1936
1937
				if ( ! current_user_can( 'read_product', $post->ID ) ) {
1938
					continue;
1939
				}
1940
1941
				$found_products[ $post->ID ] = $product->get_formatted_name();
1942
			}
1943
		}
1944
1945
		wp_send_json( $found_products );
1946
	}
1947
1948
	/**
1949
	 * Search for customers and return json.
1950
	 */
1951
	public static function json_search_customers() {
1952
		ob_start();
1953
1954
		check_ajax_referer( 'search-customers', 'security' );
1955
1956
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1957
			die(-1);
1958
		}
1959
1960
		$term = wc_clean( stripslashes( $_GET['term'] ) );
1961
1962
		if ( empty( $term ) ) {
1963
			die();
1964
		}
1965
1966
		$found_customers = array();
1967
1968
		add_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1969
1970
		$customers_query = new WP_User_Query( apply_filters( 'woocommerce_json_search_customers_query', array(
1971
			'fields'         => 'all',
1972
			'orderby'        => 'display_name',
1973
			'search'         => '*' . $term . '*',
1974
			'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename' )
1975
		) ) );
1976
1977
		remove_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1978
1979
		$customers = $customers_query->get_results();
1980
1981
		if ( ! empty( $customers ) ) {
1982
			foreach ( $customers as $customer ) {
1983
				$found_customers[ $customer->ID ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
1984
			}
1985
		}
1986
1987
		wp_send_json( $found_customers );
1988
	}
1989
1990
	/**
1991
	 * When searching using the WP_User_Query, search names (user meta) too.
1992
	 * @param  object $query
1993
	 * @return object
1994
	 */
1995
	public static function json_search_customer_name( $query ) {
1996
		global $wpdb;
1997
1998
		$term = wc_clean( stripslashes( $_GET['term'] ) );
1999
		if ( method_exists( $wpdb, 'esc_like' ) ) {
2000
			$term = $wpdb->esc_like( $term );
2001
		} else {
2002
			$term = like_escape( $term );
2003
		}
2004
2005
		$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' ) ";
2006
		$query->query_where .= $wpdb->prepare( " OR user_name.meta_value LIKE %s ", '%' . $term . '%' );
2007
	}
2008
2009
	/**
2010
	 * Ajax request handling for categories ordering.
2011
	 */
2012
	public static function term_ordering() {
2013
2014
		// check permissions again and make sure we have what we need
2015
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
2016
			die(-1);
2017
		}
2018
2019
		$id       = (int) $_POST['id'];
2020
		$next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
2021
		$taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null;
2022
		$term     = get_term_by( 'id', $id, $taxonomy );
2023
2024
		if ( ! $id || ! $term || ! $taxonomy ) {
2025
			die(0);
2026
		}
2027
2028
		wc_reorder_terms( $term, $next_id, $taxonomy );
2029
2030
		$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
2031
2032
		if ( $term && sizeof( $children ) ) {
2033
			echo 'children';
2034
			die();
2035
		}
2036
	}
2037
2038
	/**
2039
	 * Ajax request handling for product ordering.
2040
	 *
2041
	 * Based on Simple Page Ordering by 10up (http://wordpress.org/extend/plugins/simple-page-ordering/).
2042
	 */
2043
	public static function product_ordering() {
2044
		global $wpdb;
2045
2046
		ob_start();
2047
2048
		// check permissions again and make sure we have what we need
2049
		if ( ! current_user_can('edit_products') || empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
2050
			die(-1);
2051
		}
2052
2053
		// real post?
2054
		if ( ! $post = get_post( $_POST['id'] ) ) {
2055
			die(-1);
2056
		}
2057
2058
		$previd  = isset( $_POST['previd'] ) ? $_POST['previd'] : false;
2059
		$nextid  = isset( $_POST['nextid'] ) ? $_POST['nextid'] : false;
2060
		$new_pos = array(); // store new positions for ajax
2061
2062
		$siblings = $wpdb->get_results( $wpdb->prepare( "
2063
			SELECT ID, menu_order FROM {$wpdb->posts} AS posts
2064
			WHERE 	posts.post_type 	= 'product'
2065
			AND 	posts.post_status 	IN ( 'publish', 'pending', 'draft', 'future', 'private' )
2066
			AND 	posts.ID			NOT IN (%d)
2067
			ORDER BY posts.menu_order ASC, posts.ID DESC
2068
		", $post->ID ) );
2069
2070
		$menu_order = 0;
2071
2072
		foreach ( $siblings as $sibling ) {
2073
2074
			// if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
2075 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...
2076
				$wpdb->update(
2077
					$wpdb->posts,
2078
					array(
2079
						'menu_order' => $menu_order
2080
					),
2081
					array( 'ID' => $post->ID ),
2082
					array( '%d' ),
2083
					array( '%d' )
2084
				);
2085
				$new_pos[ $post->ID ] = $menu_order;
2086
				$menu_order++;
2087
			}
2088
2089
			// if repositioned post has been set, and new items are already in the right order, we can stop
2090
			if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $menu_order ) {
2091
				break;
2092
			}
2093
2094
			// set the menu order of the current sibling and increment the menu order
2095
			$wpdb->update(
2096
				$wpdb->posts,
2097
				array(
2098
					'menu_order' => $menu_order
2099
				),
2100
				array( 'ID' => $sibling->ID ),
2101
				array( '%d' ),
2102
				array( '%d' )
2103
			);
2104
			$new_pos[ $sibling->ID ] = $menu_order;
2105
			$menu_order++;
2106
2107 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...
2108
				$wpdb->update(
2109
					$wpdb->posts,
2110
					array(
2111
						'menu_order' => $menu_order
2112
					),
2113
					array( 'ID' => $post->ID ),
2114
					array( '%d' ),
2115
					array( '%d' )
2116
				);
2117
				$new_pos[$post->ID] = $menu_order;
2118
				$menu_order++;
2119
			}
2120
2121
		}
2122
2123
		do_action( 'woocommerce_after_product_ordering' );
2124
2125
		wp_send_json( $new_pos );
2126
	}
2127
2128
	/**
2129
	 * Handle a refund via the edit order screen.
2130
	 */
2131
	public static function refund_line_items() {
2132
		ob_start();
2133
2134
		check_ajax_referer( 'order-item', 'security' );
2135
2136
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2137
			die(-1);
2138
		}
2139
2140
		$order_id               = absint( $_POST['order_id'] );
2141
		$refund_amount          = wc_format_decimal( sanitize_text_field( $_POST['refund_amount'] ) );
2142
		$refund_reason          = sanitize_text_field( $_POST['refund_reason'] );
2143
		$line_item_qtys         = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_qtys'] ) ), true );
2144
		$line_item_totals       = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_totals'] ) ), true );
2145
		$line_item_tax_totals   = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_tax_totals'] ) ), true );
2146
		$api_refund             = $_POST['api_refund'] === 'true' ? true : false;
2147
		$restock_refunded_items = $_POST['restock_refunded_items'] === 'true' ? true : false;
2148
		$refund                 = false;
2149
		$response_data          = array();
2150
2151
		try {
2152
			// Validate that the refund can occur
2153
			$order       = wc_get_order( $order_id );
2154
			$order_items = $order->get_items();
2155
			$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );
2156
2157
			if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) {
2158
				throw new exception( __( 'Invalid refund amount', 'woocommerce' ) );
2159
			}
2160
2161
			// Prepare line items which we are refunding
2162
			$line_items = array();
2163
			$item_ids   = array_unique( array_merge( array_keys( $line_item_qtys, $line_item_totals ) ) );
2164
2165
			foreach ( $item_ids as $item_id ) {
2166
				$line_items[ $item_id ] = array( 'qty' => 0, 'refund_total' => 0, 'refund_tax' => array() );
2167
			}
2168
			foreach ( $line_item_qtys as $item_id => $qty ) {
2169
				$line_items[ $item_id ]['qty'] = max( $qty, 0 );
2170
			}
2171
			foreach ( $line_item_totals as $item_id => $total ) {
2172
				$line_items[ $item_id ]['refund_total'] = wc_format_decimal( $total );
2173
			}
2174
			foreach ( $line_item_tax_totals as $item_id => $tax_totals ) {
2175
				$line_items[ $item_id ]['refund_tax'] = array_map( 'wc_format_decimal', $tax_totals );
2176
			}
2177
2178
			// Create the refund object
2179
			$refund = wc_create_refund( array(
2180
				'amount'     => $refund_amount,
2181
				'reason'     => $refund_reason,
2182
				'order_id'   => $order_id,
2183
				'line_items' => $line_items,
2184
			) );
2185
2186
			if ( is_wp_error( $refund ) ) {
2187
				throw new Exception( $refund->get_error_message() );
1 ignored issue
show
Bug introduced by
The method get_error_message() does not seem to exist on object<WC_Order_Refund>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2188
			}
2189
2190
			// Refund via API
2191 View Code Duplication
			if ( $api_refund ) {
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...
2192
				if ( WC()->payment_gateways() ) {
2193
					$payment_gateways = WC()->payment_gateways->payment_gateways();
2194
				}
2195
				if ( isset( $payment_gateways[ $order->payment_method ] ) && $payment_gateways[ $order->payment_method ]->supports( 'refunds' ) ) {
2196
					$result = $payment_gateways[ $order->payment_method ]->process_refund( $order_id, $refund_amount, $refund_reason );
2197
2198
					do_action( 'woocommerce_refund_processed', $refund, $result );
2199
2200
					if ( is_wp_error( $result ) ) {
2201
						throw new Exception( $result->get_error_message() );
2202
					} elseif ( ! $result ) {
2203
						throw new Exception( __( 'Refund failed', 'woocommerce' ) );
2204
					}
2205
				}
2206
			}
2207
2208
			// restock items
2209
			foreach ( $line_item_qtys as $item_id => $qty ) {
2210
				if ( $restock_refunded_items && $qty && isset( $order_items[ $item_id ] ) ) {
2211
					$order_item = $order_items[ $item_id ];
2212
					$_product   = $order->get_product_from_item( $order_item );
2213
2214
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2215
						$old_stock    = wc_stock_amount( $_product->stock );
2216
						$new_quantity = $_product->increase_stock( $qty );
2217
2218
						$order->add_order_note( sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
2219
2220
						do_action( 'woocommerce_restock_refunded_item', $_product->id, $old_stock, $new_quantity, $order );
2221
					}
2222
				}
2223
			}
2224
2225
			// Trigger notifications and status changes
2226
			if ( $order->get_remaining_refund_amount() > 0 || ( $order->has_free_item() && $order->get_remaining_refund_items() > 0 ) ) {
2227
				/**
2228
				 * woocommerce_order_partially_refunded.
2229
				 *
2230
				 * @since 2.4.0
2231
				 * Note: 3rd arg was added in err. Kept for bw compat. 2.4.3.
2232
				 */
2233
				do_action( 'woocommerce_order_partially_refunded', $order_id, $refund->id, $refund->id );
2234
			} else {
2235
				do_action( 'woocommerce_order_fully_refunded', $order_id, $refund->id );
2236
2237
				$order->update_status( apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order_id, $refund->id ) );
2238
				$response_data['status'] = 'fully_refunded';
2239
			}
2240
2241
			do_action( 'woocommerce_order_refunded', $order_id, $refund->id );
2242
2243
			// Clear transients
2244
			wc_delete_shop_order_transients( $order_id );
2245
			wp_send_json_success( $response_data );
2246
2247
		} catch ( Exception $e ) {
2248
			if ( $refund && is_a( $refund, 'WC_Order_Refund' ) ) {
2249
				wp_delete_post( $refund->id, true );
2250
			}
2251
2252
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
2253
		}
2254
	}
2255
2256
	/**
2257
	 * Delete a refund.
2258
	 */
2259
	public static function delete_refund() {
2260
		check_ajax_referer( 'order-item', 'security' );
2261
2262
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2263
			die(-1);
2264
		}
2265
2266
		$refund_id = absint( $_POST['refund_id'] );
2267
2268
		if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
2269
			$order_id = wp_get_post_parent_id( $refund_id );
2270
			wc_delete_shop_order_transients( $order_id );
2271
			wp_delete_post( $refund_id );
2272
2273
			do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
2274
		}
2275
2276
		die();
2277
	}
2278
2279
	/**
2280
	 * Triggered when clicking the rating footer.
2281
	 */
2282
	public static function rated() {
2283
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2284
			die(-1);
2285
		}
2286
2287
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
2288
		die();
2289
	}
2290
2291
	/**
2292
	 * Create/Update API key.
2293
	 */
2294
	public static function update_api_key() {
2295
		ob_start();
2296
2297
		global $wpdb;
2298
2299
		check_ajax_referer( 'update-api-key', 'security' );
2300
2301
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2302
			die(-1);
2303
		}
2304
2305
		try {
2306
			if ( empty( $_POST['description'] ) ) {
2307
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
2308
			}
2309
			if ( empty( $_POST['user'] ) ) {
2310
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
2311
			}
2312
			if ( empty( $_POST['permissions'] ) ) {
2313
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
2314
			}
2315
2316
			$key_id      = absint( $_POST['key_id'] );
2317
			$description = sanitize_text_field( wp_unslash( $_POST['description'] ) );
2318
			$permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read';
2319
			$user_id     = absint( $_POST['user'] );
2320
2321
			if ( 0 < $key_id ) {
2322
				$data = array(
2323
					'user_id'     => $user_id,
2324
					'description' => $description,
2325
					'permissions' => $permissions
2326
				);
2327
2328
				$wpdb->update(
2329
					$wpdb->prefix . 'woocommerce_api_keys',
2330
					$data,
2331
					array( 'key_id' => $key_id ),
2332
					array(
2333
						'%d',
2334
						'%s',
2335
						'%s'
2336
					),
2337
					array( '%d' )
2338
				);
2339
2340
				$data['consumer_key']    = '';
2341
				$data['consumer_secret'] = '';
2342
				$data['message']         = __( 'API Key updated successfully.', 'woocommerce' );
2343
			} else {
2344
				$status          = 2;
0 ignored issues
show
Unused Code introduced by
$status is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2345
				$consumer_key    = 'ck_' . wc_rand_hash();
2346
				$consumer_secret = 'cs_' . wc_rand_hash();
2347
2348
				$data = array(
2349
					'user_id'         => $user_id,
2350
					'description'     => $description,
2351
					'permissions'     => $permissions,
2352
					'consumer_key'    => wc_api_hash( $consumer_key ),
2353
					'consumer_secret' => $consumer_secret,
2354
					'truncated_key'   => substr( $consumer_key, -7 )
2355
				);
2356
2357
				$wpdb->insert(
2358
					$wpdb->prefix . 'woocommerce_api_keys',
2359
					$data,
2360
					array(
2361
						'%d',
2362
						'%s',
2363
						'%s',
2364
						'%s',
2365
						'%s',
2366
						'%s'
2367
					)
2368
				);
2369
2370
				$key_id                  = $wpdb->insert_id;
2371
				$data['consumer_key']    = $consumer_key;
2372
				$data['consumer_secret'] = $consumer_secret;
2373
				$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' );
2374
				$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>';
2375
			}
2376
2377
			wp_send_json_success( $data );
2378
		} catch ( Exception $e ) {
2379
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
2380
		}
2381
	}
2382
2383
	/**
2384
	 * Locate user via AJAX.
2385
	 */
2386
	public static function get_customer_location() {
2387
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
2388
		wp_send_json_success( array( 'hash' => $location_hash ) );
2389
	}
2390
2391
	/**
2392
	 * Load variations via AJAX.
2393
	 */
2394
	public static function load_variations() {
2395
		ob_start();
2396
2397
		check_ajax_referer( 'load-variations', 'security' );
2398
2399
		// Check permissions again and make sure we have what we need
2400 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...
2401
			die( -1 );
2402
		}
2403
2404
		global $post;
2405
2406
		$product_id = absint( $_POST['product_id'] );
2407
		$post       = get_post( $product_id ); // Set $post global so its available like within the admin screens
2408
		$per_page   = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
2409
		$page       = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
2410
2411
		// Get attributes
2412
		$attributes        = array();
2413
		$posted_attributes = wp_unslash( $_POST['attributes'] );
2414
2415
		foreach ( $posted_attributes as $key => $value ) {
2416
			$attributes[ $key ] = array_map( 'wc_clean', $value );
2417
		}
2418
2419
		// Get tax classes
2420
		$tax_classes           = WC_Tax::get_tax_classes();
2421
		$tax_class_options     = array();
2422
		$tax_class_options[''] = __( 'Standard', 'woocommerce' );
2423
2424 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...
2425
			foreach ( $tax_classes as $class ) {
2426
				$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
2427
			}
2428
		}
2429
2430
		// Set backorder options
2431
		$backorder_options = array(
2432
			'no'     => __( 'Do not allow', 'woocommerce' ),
2433
			'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
2434
			'yes'    => __( 'Allow', 'woocommerce' )
2435
		);
2436
2437
		// set stock status options
2438
		$stock_status_options = array(
2439
			'instock'    => __( 'In stock', 'woocommerce' ),
2440
			'outofstock' => __( 'Out of stock', 'woocommerce' )
2441
		);
2442
2443
		$parent_data = array(
2444
			'id'                   => $product_id,
2445
			'attributes'           => $attributes,
2446
			'tax_class_options'    => $tax_class_options,
2447
			'sku'                  => get_post_meta( $product_id, '_sku', true ),
2448
			'weight'               => wc_format_localized_decimal( get_post_meta( $product_id, '_weight', true ) ),
2449
			'length'               => wc_format_localized_decimal( get_post_meta( $product_id, '_length', true ) ),
2450
			'width'                => wc_format_localized_decimal( get_post_meta( $product_id, '_width', true ) ),
2451
			'height'               => wc_format_localized_decimal( get_post_meta( $product_id, '_height', true ) ),
2452
			'tax_class'            => get_post_meta( $product_id, '_tax_class', true ),
2453
			'backorder_options'    => $backorder_options,
2454
			'stock_status_options' => $stock_status_options
2455
		);
2456
2457
		if ( ! $parent_data['weight'] ) {
2458
			$parent_data['weight'] = wc_format_localized_decimal( 0 );
2459
		}
2460
2461
		if ( ! $parent_data['length'] ) {
2462
			$parent_data['length'] = wc_format_localized_decimal( 0 );
2463
		}
2464
2465
		if ( ! $parent_data['width'] ) {
2466
			$parent_data['width'] = wc_format_localized_decimal( 0 );
2467
		}
2468
2469
		if ( ! $parent_data['height'] ) {
2470
			$parent_data['height'] = wc_format_localized_decimal( 0 );
2471
		}
2472
2473
		// Get variations
2474
		$args = apply_filters( 'woocommerce_ajax_admin_get_variations_args', array(
2475
			'post_type'      => 'product_variation',
2476
			'post_status'    => array( 'private', 'publish' ),
2477
			'posts_per_page' => $per_page,
2478
			'paged'          => $page,
2479
			'orderby'        => array( 'menu_order' => 'ASC', 'ID' => 'DESC' ),
2480
			'post_parent'    => $product_id
2481
		), $product_id );
2482
2483
		$variations = get_posts( $args );
2484
		$loop = 0;
2485
2486
		if ( $variations ) {
2487
2488
			foreach ( $variations as $variation ) {
2489
				$variation_id     = absint( $variation->ID );
2490
				$variation_meta   = get_post_meta( $variation_id );
2491
				$variation_data   = array();
2492
				$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
2493
				$variation_fields = array(
2494
					'_sku'                   => '',
2495
					'_stock'                 => '',
2496
					'_regular_price'         => '',
2497
					'_sale_price'            => '',
2498
					'_weight'                => '',
2499
					'_length'                => '',
2500
					'_width'                 => '',
2501
					'_height'                => '',
2502
					'_download_limit'        => '',
2503
					'_download_expiry'       => '',
2504
					'_downloadable_files'    => '',
2505
					'_downloadable'          => '',
2506
					'_virtual'               => '',
2507
					'_thumbnail_id'          => '',
2508
					'_sale_price_dates_from' => '',
2509
					'_sale_price_dates_to'   => '',
2510
					'_manage_stock'          => '',
2511
					'_stock_status'          => '',
2512
					'_backorders'            => null,
2513
					'_tax_class'             => null,
2514
					'_variation_description' => ''
2515
				);
2516
2517 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...
2518
					$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
2519
				}
2520
2521
				// Add the variation attributes
2522
				$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
2523
2524
				// Formatting
2525
				$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
2526
				$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
2527
				$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
2528
				$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
2529
				$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
2530
				$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
2531
				$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
2532
				$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
2533
				$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
2534
				$variation_data['menu_order']     = $variation->menu_order;
2535
2536
				// Stock BW compat
2537
				if ( '' !== $variation_data['_stock'] ) {
2538
					$variation_data['_manage_stock'] = 'yes';
2539
				}
2540
2541
				include( 'admin/meta-boxes/views/html-variation-admin.php' );
2542
2543
				$loop++;
2544
			}
2545
		}
2546
2547
		die();
2548
	}
2549
2550
	/**
2551
	 * Save variations via AJAX.
2552
	 */
2553
	public static function save_variations() {
2554
		ob_start();
2555
2556
		check_ajax_referer( 'save-variations', 'security' );
2557
2558
		// Check permissions again and make sure we have what we need
2559 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...
2560
			die( -1 );
2561
		}
2562
2563
		// Remove previous meta box errors
2564
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
2565
2566
		$product_id   = absint( $_POST['product_id'] );
2567
		$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
2568
2569
		$product_type_terms = wp_get_object_terms( $product_id, 'product_type' );
2570
2571
		// If the product type hasn't been set or it has changed, update it before saving variations
2572
		if ( empty( $product_type_terms ) || $product_type !== sanitize_title( current( $product_type_terms )->name ) ) {
2573
			wp_set_object_terms( $product_id, $product_type, 'product_type' );
2574
		}
2575
2576
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
2577
2578
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
2579
2580
		// Clear cache/transients
2581
		wc_delete_product_transients( $product_id );
2582
2583
		if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
2584
			echo '<div class="error notice is-dismissible">';
2585
2586
			foreach ( $errors as $error ) {
2587
				echo '<p>' . wp_kses_post( $error ) . '</p>';
2588
			}
2589
2590
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
2591
			echo '</div>';
2592
2593
			delete_option( 'woocommerce_meta_box_errors' );
2594
		}
2595
2596
		die();
2597
	}
2598
2599
	/**
2600
	 * Bulk action - Toggle Enabled.
2601
	 * @access private
2602
	 * @param  array $variations
2603
	 * @param  array $data
2604
	 */
2605
	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...
2606
		global $wpdb;
2607
2608
		foreach ( $variations as $variation_id ) {
2609
			$post_status = get_post_status( $variation_id );
2610
			$new_status  = 'private' === $post_status ? 'publish' : 'private';
2611
			$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $variation_id ) );
2612
		}
2613
	}
2614
2615
	/**
2616
	 * Bulk action - Toggle Downloadable Checkbox.
2617
	 * @access private
2618
	 * @param  array $variations
2619
	 * @param  array $data
2620
	 */
2621 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...
2622
		foreach ( $variations as $variation_id ) {
2623
			$_downloadable   = get_post_meta( $variation_id, '_downloadable', true );
2624
			$is_downloadable = 'no' === $_downloadable ? 'yes' : 'no';
2625
			update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
2626
		}
2627
	}
2628
2629
	/**
2630
	 * Bulk action - Toggle Virtual Checkbox.
2631
	 * @access private
2632
	 * @param  array $variations
2633
	 * @param  array $data
2634
	 */
2635 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...
2636
		foreach ( $variations as $variation_id ) {
2637
			$_virtual   = get_post_meta( $variation_id, '_virtual', true );
2638
			$is_virtual = 'no' === $_virtual ? 'yes' : 'no';
2639
			update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
2640
		}
2641
	}
2642
2643
	/**
2644
	 * Bulk action - Toggle Manage Stock Checkbox.
2645
	 * @access private
2646
	 * @param  array $variations
2647
	 * @param  array $data
2648
	 */
2649
	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...
2650
		foreach ( $variations as $variation_id ) {
2651
			$_manage_stock   = get_post_meta( $variation_id, '_manage_stock', true );
2652
			$is_manage_stock = 'no' === $_manage_stock || '' === $_manage_stock ? 'yes' : 'no';
2653
			update_post_meta( $variation_id, '_manage_stock', $is_manage_stock );
2654
		}
2655
	}
2656
2657
	/**
2658
	 * Bulk action - Set Regular Prices.
2659
	 * @access private
2660
	 * @param  array $variations
2661
	 * @param  array $data
2662
	 */
2663 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...
2664
		if ( ! isset( $data['value'] ) ) {
2665
			return;
2666
		}
2667
2668
		foreach ( $variations as $variation_id ) {
2669
			// Price fields
2670
			$regular_price = wc_clean( $data['value'] );
2671
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2672
2673
			// Date fields
2674
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2675
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2676
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2677
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2678
2679
			_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...
2680
		}
2681
	}
2682
2683
	/**
2684
	 * Bulk action - Set Sale Prices.
2685
	 * @access private
2686
	 * @param  array $variations
2687
	 * @param  array $data
2688
	 */
2689 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...
2690
		if ( ! isset( $data['value'] ) ) {
2691
			return;
2692
		}
2693
2694
		foreach ( $variations as $variation_id ) {
2695
			// Price fields
2696
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2697
			$sale_price    = wc_clean( $data['value'] );
2698
2699
			// Date fields
2700
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2701
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2702
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2703
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2704
2705
			_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 2697 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...
2706
		}
2707
	}
2708
2709
	/**
2710
	 * Bulk action - Set Stock.
2711
	 * @access private
2712
	 * @param  array $variations
2713
	 * @param  array $data
2714
	 */
2715
	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...
2716
		if ( ! isset( $data['value'] ) ) {
2717
			return;
2718
		}
2719
2720
		$value = wc_clean( $data['value'] );
2721
2722
		foreach ( $variations as $variation_id ) {
2723
			if ( 'yes' === get_post_meta( $variation_id, '_manage_stock', true ) ) {
2724
				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...
2725
			} else {
2726
				delete_post_meta( $variation_id, '_stock' );
2727
			}
2728
		}
2729
	}
2730
2731
	/**
2732
	 * Bulk action - Set Weight.
2733
	 * @access private
2734
	 * @param  array $variations
2735
	 * @param  array $data
2736
	 */
2737
	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...
2738
		self::variation_bulk_set_meta( $variations, '_weight', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2739
	}
2740
2741
	/**
2742
	 * Bulk action - Set Length.
2743
	 * @access private
2744
	 * @param  array $variations
2745
	 * @param  array $data
2746
	 */
2747
	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...
2748
		self::variation_bulk_set_meta( $variations, '_length', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2749
	}
2750
2751
	/**
2752
	 * Bulk action - Set Width.
2753
	 * @access private
2754
	 * @param  array $variations
2755
	 * @param  array $data
2756
	 */
2757
	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...
2758
		self::variation_bulk_set_meta( $variations, '_width', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2759
	}
2760
2761
	/**
2762
	 * Bulk action - Set Height.
2763
	 * @access private
2764
	 * @param  array $variations
2765
	 * @param  array $data
2766
	 */
2767
	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...
2768
		self::variation_bulk_set_meta( $variations, '_height', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2769
	}
2770
2771
	/**
2772
	 * Bulk action - Set Download Limit.
2773
	 * @access private
2774
	 * @param  array $variations
2775
	 * @param  array $data
2776
	 */
2777
	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...
2778
		self::variation_bulk_set_meta( $variations, '_download_limit', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2779
	}
2780
2781
	/**
2782
	 * Bulk action - Set Download Expiry.
2783
	 * @access private
2784
	 * @param  array $variations
2785
	 * @param  array $data
2786
	 */
2787
	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...
2788
		self::variation_bulk_set_meta( $variations, '_download_expiry', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2789
	}
2790
2791
	/**
2792
	 * Bulk action - Delete all.
2793
	 * @access private
2794
	 * @param  array $variations
2795
	 * @param  array $data
2796
	 */
2797
	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...
2798
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2799
			foreach ( $variations as $variation_id ) {
2800
				wp_delete_post( $variation_id );
2801
			}
2802
		}
2803
	}
2804
2805
	/**
2806
	 * Bulk action - Sale Schedule.
2807
	 * @access private
2808
	 * @param  array $variations
2809
	 * @param  array $data
2810
	 */
2811
	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...
2812
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2813
			return;
2814
		}
2815
2816
		foreach ( $variations as $variation_id ) {
2817
			// Price fields
2818
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2819
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2820
2821
			// Date fields
2822
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2823
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2824
2825 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...
2826
				$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2827
			} else {
2828
				$date_from = $data['date_from'];
2829
			}
2830
2831 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...
2832
				$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2833
			} else {
2834
				$date_to = $data['date_to'];
2835
			}
2836
2837
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
2838
		}
2839
	}
2840
2841
	/**
2842
	 * Bulk action - Increase Regular Prices.
2843
	 * @access private
2844
	 * @param  array $variations
2845
	 * @param  array $data
2846
	 */
2847
	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...
2848
		self::variation_bulk_adjust_price( $variations, '_regular_price', '+', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_adjust_price() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2849
	}
2850
2851
	/**
2852
	 * Bulk action - Decrease Regular Prices.
2853
	 * @access private
2854
	 * @param  array $variations
2855
	 * @param  array $data
2856
	 */
2857
	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...
2858
		self::variation_bulk_adjust_price( $variations, '_regular_price', '-', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_adjust_price() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2859
	}
2860
2861
	/**
2862
	 * Bulk action - Increase Sale Prices.
2863
	 * @access private
2864
	 * @param  array $variations
2865
	 * @param  array $data
2866
	 */
2867
	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...
2868
		self::variation_bulk_adjust_price( $variations, '_sale_price', '+', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_adjust_price() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2869
	}
2870
2871
	/**
2872
	 * Bulk action - Decrease Sale Prices.
2873
	 * @access private
2874
	 * @param  array $variations
2875
	 * @param  array $data
2876
	 */
2877
	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...
2878
		self::variation_bulk_adjust_price( $variations, '_sale_price', '-', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_adjust_price() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2879
	}
2880
2881
	/**
2882
	 * Bulk action - Set Price.
2883
	 * @access private
2884
	 * @param  array $variations
2885
	 * @param string $operator + or -
2886
	 * @param string $field price being adjusted
2887
	 * @param string $value Price or Percent
2888
	 */
2889
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2890
		foreach ( $variations as $variation_id ) {
2891
			// Get existing data
2892
			$_regular_price = get_post_meta( $variation_id, '_regular_price', true );
2893
			$_sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2894
			$date_from      = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2895
			$date_to        = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2896
			$date_from      = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2897
			$date_to        = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2898
2899
			if ( '%' === substr( $value, -1 ) ) {
2900
				$percent = wc_format_decimal( substr( $value, 0, -1 ) );
2901
				$$field  += ( ( $$field / 100 ) * $percent ) * "{$operator}1";
2902
			} else {
2903
				$$field  += $value * "{$operator}1";
2904
			}
2905
			_wc_save_product_price( $variation_id, $_regular_price, $_sale_price, $date_from, $date_to );
2906
		}
2907
	}
2908
2909
	/**
2910
	 * Bulk action - Set Meta.
2911
	 * @access private
2912
	 * @param array $variations
2913
	 * @param string $field
2914
	 * @param string $value
2915
	 */
2916
	private static function variation_bulk_set_meta( $variations, $field, $value ) {
2917
		foreach ( $variations as $variation_id ) {
2918
			update_post_meta( $variation_id, $field, $value );
2919
		}
2920
	}
2921
2922
2923
	/**
2924
	 * Bulk edit variations via AJAX.
2925
	 */
2926
	public static function bulk_edit_variations() {
2927
		ob_start();
2928
2929
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2930
2931
		// Check permissions again and make sure we have what we need
2932 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...
2933
			die( -1 );
2934
		}
2935
2936
		$product_id  = absint( $_POST['product_id'] );
2937
		$bulk_action = wc_clean( $_POST['bulk_action'] );
2938
		$data        = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array();
2939
		$variations  = array();
2940
2941
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2942
			$variations = get_posts( array(
2943
				'post_parent'    => $product_id,
2944
				'posts_per_page' => -1,
2945
				'post_type'      => 'product_variation',
2946
				'fields'         => 'ids',
2947
				'post_status'    => array( 'publish', 'private' )
2948
			) );
2949
		}
2950
2951
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2952
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2953
		} else {
2954
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2955
		}
2956
2957
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2958
2959
		// Sync and update transients
2960
		WC_Product_Variable::sync( $product_id );
2961
		wc_delete_product_transients( $product_id );
2962
		die();
2963
	}
2964
2965
	/**
2966
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2967
	 */
2968
	public static function tax_rates_save_changes() {
2969
		if ( ! isset( $_POST['current_class'], $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2970
			wp_send_json_error( 'missing_fields' );
2971
			exit;
2972
		}
2973
2974
		$current_class = $_POST['current_class']; // This is sanitized seven lines later.
2975
2976
		if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) {
2977
			wp_send_json_error( 'bad_nonce' );
2978
			exit;
2979
		}
2980
2981
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
2982
2983
		// Check User Caps
2984
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2985
			wp_send_json_error( 'missing_capabilities' );
2986
			exit;
2987
		}
2988
2989
		$changes = $_POST['changes'];
2990
		foreach ( $changes as $tax_rate_id => $data ) {
2991
			if ( isset( $data['deleted'] ) ) {
2992
				if ( isset( $data['newRow'] ) ) {
2993
					// So the user added and deleted a new row.
2994
					// That's fine, it's not in the database anyways. NEXT!
2995
					continue;
2996
				}
2997
				WC_Tax::_delete_tax_rate( $tax_rate_id );
2998
			}
2999
3000
			$tax_rate = array_intersect_key( $data, array(
3001
				'tax_rate_country'  => 1,
3002
				'tax_rate_state'    => 1,
3003
				'tax_rate'          => 1,
3004
				'tax_rate_name'     => 1,
3005
				'tax_rate_priority' => 1,
3006
				'tax_rate_compound' => 1,
3007
				'tax_rate_shipping' => 1,
3008
				'tax_rate_order'    => 1,
3009
			) );
3010
3011
			if ( isset( $data['newRow'] ) ) {
3012
				// Hurrah, shiny and new!
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3013
				$tax_rate['tax_rate_class'] = $current_class;
3014
				$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
3015
			} else {
3016
				// Updating an existing rate ...
3017
				if ( ! empty( $tax_rate ) ) {
3018
					WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
3019
				}
3020
			}
3021
3022
			if ( isset( $data['postcode'] ) ) {
3023
				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...
3024
			}
3025
			if ( isset( $data['city'] ) ) {
3026
				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...
3027
			}
3028
		}
3029
3030
		wp_send_json_success( array(
3031
			'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
3032
		) );
3033
	}
3034
3035
	/**
3036
	 * Handle submissions from assets/js/wc-shipping-zones.js Backbone model.
3037
	 */
3038
	public static function shipping_zones_save_changes() {
3039
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['changes'] ) ) {
3040
			wp_send_json_error( 'missing_fields' . (isset( $_POST['wc_shipping_zones_nonce'] ) ? 1 : 0). (isset( $_POST['changes'] ) ? 1 : 0) );
3041
			exit;
3042
		}
3043
3044
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3045
			wp_send_json_error( 'bad_nonce' );
3046
			exit;
3047
		}
3048
3049
		// Check User Caps
3050
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3051
			wp_send_json_error( 'missing_capabilities' );
3052
			exit;
3053
		}
3054
3055
		$changes = $_POST['changes'];
3056
		foreach ( $changes as $zone_id => $data ) {
3057
			if ( isset( $data['deleted'] ) ) {
3058
				if ( isset( $data['newRow'] ) ) {
3059
					// So the user added and deleted a new row.
3060
					// That's fine, it's not in the database anyways. NEXT!
3061
					continue;
3062
				}
3063
				WC_Shipping_Zones::delete_zone( $zone_id );
3064
				continue;
3065
			}
3066
3067
			$zone_data = array_intersect_key( $data, array(
3068
				'zone_id'        => 1,
3069
				'zone_name'      => 1,
3070
				'zone_order'     => 1,
3071
				'zone_locations' => 1,
3072
				'zone_postcodes' => 1
3073
			) );
3074
3075
			if ( isset( $zone_data['zone_id'] ) ) {
3076
				$zone = new WC_Shipping_Zone( $zone_data['zone_id'] );
3077
3078
				if ( isset( $zone_data['zone_name'] ) ) {
3079
					$zone->set_zone_name( $zone_data['zone_name'] );
3080
				}
3081
3082
				if ( isset( $zone_data['zone_order'] ) ) {
3083
					$zone->set_zone_order( $zone_data['zone_order'] );
3084
				}
3085
3086
				if ( isset( $zone_data['zone_locations'] ) ) {
3087
					$locations = array_filter( array_map( 'wc_clean', (array) $zone_data['zone_locations'] ) );
3088
					$zone->clear_locations();
3089
					foreach ( $locations as $location ) {
3090
						// Each posted location will be in the format type:code
3091
						$location_parts = explode( ':', $location );
3092
						switch ( $location_parts[0] ) {
3093
							case 'state' :
3094
								$zone->add_location( $location_parts[1] . ':' . $location_parts[2], 'state' );
3095
							break;
3096
							case 'country' :
3097
								$zone->add_location( $location_parts[1], 'country' );
3098
							break;
3099
							case 'continent' :
3100
								$zone->add_location( $location_parts[1], 'continent' );
3101
							break;
3102
						}
3103
					}
3104
				}
3105
3106
				if ( isset( $zone_data['zone_postcodes'] ) ) {
3107
					$postcodes = array_filter( array_map( 'strtoupper', array_map( 'wc_clean', explode( "\n", $zone_data['zone_postcodes'] ) ) ) );
3108
					foreach ( $postcodes as $postcode ) {
3109
						$zone->add_location( $postcode, 'postcode' );
3110
					}
3111
				}
3112
3113
				$zone->save();
3114
			}
3115
		}
3116
3117
		wp_send_json_success( array(
3118
			'zones' => WC_Shipping_Zones::get_zones(),
3119
			'test' => print_r($_POST['changes'], true)
3120
		) );
3121
	}
3122
}
3123
3124
WC_AJAX::init();
3125