Completed
Pull Request — master (#9826)
by Mike
10:01
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
		$exclude = array();
0 ignored issues
show
Unused Code introduced by
$exclude 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...
1755
1756
		if ( empty( $term ) ) {
1757
			die();
1758
		}
1759
1760
		$like_term = '%' . $wpdb->esc_like( $term ) . '%';
1761
1762
		if ( is_numeric( $term ) ) {
1763
			$query = $wpdb->prepare( "
1764
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1765
				WHERE posts.post_status = 'publish'
1766
				AND (
1767
					posts.post_parent = %s
1768
					OR posts.ID = %s
1769
					OR posts.post_title LIKE %s
1770
					OR (
1771
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1772
					)
1773
				)
1774
			", $term, $term, $term, $like_term );
1775
		} else {
1776
			$query = $wpdb->prepare( "
1777
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1778
				WHERE posts.post_status = 'publish'
1779
				AND (
1780
					posts.post_title LIKE %s
1781
					or posts.post_content LIKE %s
1782
					OR (
1783
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1784
					)
1785
				)
1786
			", $like_term, $like_term, $like_term );
1787
		}
1788
1789
		$query .= " AND posts.post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "')";
1790
1791
		if ( ! empty( $_GET['exclude'] ) ) {
1792
			$query .= " AND posts.ID NOT IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['exclude'] ) ) ) . ")";
1793
		}
1794
1795
		$posts          = array_unique( $wpdb->get_col( $query ) );
1796
		$found_products = array();
1797
1798 View Code Duplication
		if ( ! empty( $posts ) ) {
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...
1799
			foreach ( $posts as $post ) {
1800
				$product = wc_get_product( $post );
1801
1802
				if ( ! current_user_can( 'read_product', $post ) ) {
1803
					continue;
1804
				}
1805
1806
				$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1807
			}
1808
		}
1809
1810
		$found_products = apply_filters( 'woocommerce_json_search_found_products', $found_products );
1811
1812
		wp_send_json( $found_products );
1813
	}
1814
1815
	/**
1816
	 * Search for product variations and return json.
1817
	 *
1818
	 * @see WC_AJAX::json_search_products()
1819
	 */
1820
	public static function json_search_products_and_variations() {
1821
		self::json_search_products( '', array( 'product', 'product_variation' ) );
1822
	}
1823
1824
	/**
1825
	 * Search for gruoped products and return json.
1826
	 */
1827
	public static function json_search_grouped_products() {
1828
		ob_start();
1829
1830
		check_ajax_referer( 'search-products', 'security' );
1831
1832
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1833
		$exclude = array();
1834
1835
		if ( empty( $term ) ) {
1836
			die();
1837
		}
1838
1839 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...
1840
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1841
		}
1842
1843
		$found_products = array();
1844
1845
		if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
1846
1847
			$posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
1848
1849
			if ( sizeof( $posts_in ) > 0 ) {
1850
1851
				$args = array(
1852
					'post_type'        => 'product',
1853
					'post_status'      => 'any',
1854
					'numberposts'      => -1,
1855
					'orderby'          => 'title',
1856
					'order'            => 'asc',
1857
					'post_parent'      => 0,
1858
					'suppress_filters' => 0,
1859
					'include'          => $posts_in,
1860
					's'                => $term,
1861
					'fields'           => 'ids',
1862
					'exclude'          => $exclude
1863
				);
1864
1865
				$posts = get_posts( $args );
1866
1867 View Code Duplication
				if ( ! empty( $posts ) ) {
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...
1868
					foreach ( $posts as $post ) {
1869
						$product = wc_get_product( $post );
1870
1871
						if ( ! current_user_can( 'read_product', $post ) ) {
1872
							continue;
1873
						}
1874
1875
						$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1876
					}
1877
				}
1878
			}
1879
		}
1880
1881
		$found_products = apply_filters( 'woocommerce_json_search_found_grouped_products', $found_products );
1882
1883
		wp_send_json( $found_products );
1884
	}
1885
1886
	/**
1887
	 * Search for downloadable product variations and return json.
1888
	 *
1889
	 * @see WC_AJAX::json_search_products()
1890
	 */
1891
	public static function json_search_downloadable_products_and_variations() {
1892
		ob_start();
1893
1894
		check_ajax_referer( 'search-products', 'security' );
1895
1896
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1897
		$exclude = array();
1898
1899 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...
1900
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1901
		}
1902
1903
		$args = array(
1904
			'post_type'      => array( 'product', 'product_variation' ),
1905
			'posts_per_page' => -1,
1906
			'post_status'    => 'publish',
1907
			'order'          => 'ASC',
1908
			'orderby'        => 'parent title',
1909
			'meta_query'     => array(
1910
				array(
1911
					'key'   => '_downloadable',
1912
					'value' => 'yes'
1913
				)
1914
			),
1915
			's'              => $term,
1916
			'exclude'        => $exclude
1917
		);
1918
1919
		$posts = get_posts( $args );
1920
		$found_products = array();
1921
1922
		if ( ! empty( $posts ) ) {
1923
			foreach ( $posts as $post ) {
1924
				$product = wc_get_product( $post->ID );
1925
1926
				if ( ! current_user_can( 'read_product', $post->ID ) ) {
1927
					continue;
1928
				}
1929
1930
				$found_products[ $post->ID ] = $product->get_formatted_name();
1931
			}
1932
		}
1933
1934
		wp_send_json( $found_products );
1935
	}
1936
1937
	/**
1938
	 * Search for customers and return json.
1939
	 */
1940
	public static function json_search_customers() {
1941
		ob_start();
1942
1943
		check_ajax_referer( 'search-customers', 'security' );
1944
1945
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1946
			die(-1);
1947
		}
1948
1949
		$term = wc_clean( stripslashes( $_GET['term'] ) );
1950
1951
		if ( empty( $term ) ) {
1952
			die();
1953
		}
1954
1955
		$found_customers = array();
1956
1957
		add_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1958
1959
		$customers_query = new WP_User_Query( apply_filters( 'woocommerce_json_search_customers_query', array(
1960
			'fields'         => 'all',
1961
			'orderby'        => 'display_name',
1962
			'search'         => '*' . $term . '*',
1963
			'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename' )
1964
		) ) );
1965
1966
		remove_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1967
1968
		$customers = $customers_query->get_results();
1969
1970
		if ( ! empty( $customers ) ) {
1971
			foreach ( $customers as $customer ) {
1972
				$found_customers[ $customer->ID ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
1973
			}
1974
		}
1975
1976
		wp_send_json( $found_customers );
1977
	}
1978
1979
	/**
1980
	 * When searching using the WP_User_Query, search names (user meta) too.
1981
	 * @param  object $query
1982
	 * @return object
1983
	 */
1984
	public static function json_search_customer_name( $query ) {
1985
		global $wpdb;
1986
1987
		$term = wc_clean( stripslashes( $_GET['term'] ) );
1988
		if ( method_exists( $wpdb, 'esc_like' ) ) {
1989
			$term = $wpdb->esc_like( $term );
1990
		} else {
1991
			$term = like_escape( $term );
1992
		}
1993
1994
		$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' ) ";
1995
		$query->query_where .= $wpdb->prepare( " OR user_name.meta_value LIKE %s ", '%' . $term . '%' );
1996
	}
1997
1998
	/**
1999
	 * Ajax request handling for categories ordering.
2000
	 */
2001
	public static function term_ordering() {
2002
2003
		// check permissions again and make sure we have what we need
2004
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
2005
			die(-1);
2006
		}
2007
2008
		$id       = (int) $_POST['id'];
2009
		$next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
2010
		$taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null;
2011
		$term     = get_term_by( 'id', $id, $taxonomy );
2012
2013
		if ( ! $id || ! $term || ! $taxonomy ) {
2014
			die(0);
2015
		}
2016
2017
		wc_reorder_terms( $term, $next_id, $taxonomy );
2018
2019
		$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
2020
2021
		if ( $term && sizeof( $children ) ) {
2022
			echo 'children';
2023
			die();
2024
		}
2025
	}
2026
2027
	/**
2028
	 * Ajax request handling for product ordering.
2029
	 *
2030
	 * Based on Simple Page Ordering by 10up (http://wordpress.org/extend/plugins/simple-page-ordering/).
2031
	 */
2032
	public static function product_ordering() {
2033
		global $wpdb;
2034
2035
		ob_start();
2036
2037
		// check permissions again and make sure we have what we need
2038
		if ( ! current_user_can('edit_products') || empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
2039
			die(-1);
2040
		}
2041
2042
		// real post?
2043
		if ( ! $post = get_post( $_POST['id'] ) ) {
2044
			die(-1);
2045
		}
2046
2047
		$previd  = isset( $_POST['previd'] ) ? $_POST['previd'] : false;
2048
		$nextid  = isset( $_POST['nextid'] ) ? $_POST['nextid'] : false;
2049
		$new_pos = array(); // store new positions for ajax
2050
2051
		$siblings = $wpdb->get_results( $wpdb->prepare( "
2052
			SELECT ID, menu_order FROM {$wpdb->posts} AS posts
2053
			WHERE 	posts.post_type 	= 'product'
2054
			AND 	posts.post_status 	IN ( 'publish', 'pending', 'draft', 'future', 'private' )
2055
			AND 	posts.ID			NOT IN (%d)
2056
			ORDER BY posts.menu_order ASC, posts.ID DESC
2057
		", $post->ID ) );
2058
2059
		$menu_order = 0;
2060
2061
		foreach ( $siblings as $sibling ) {
2062
2063
			// if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
2064 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...
2065
				$wpdb->update(
2066
					$wpdb->posts,
2067
					array(
2068
						'menu_order' => $menu_order
2069
					),
2070
					array( 'ID' => $post->ID ),
2071
					array( '%d' ),
2072
					array( '%d' )
2073
				);
2074
				$new_pos[ $post->ID ] = $menu_order;
2075
				$menu_order++;
2076
			}
2077
2078
			// if repositioned post has been set, and new items are already in the right order, we can stop
2079
			if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $menu_order ) {
2080
				break;
2081
			}
2082
2083
			// set the menu order of the current sibling and increment the menu order
2084
			$wpdb->update(
2085
				$wpdb->posts,
2086
				array(
2087
					'menu_order' => $menu_order
2088
				),
2089
				array( 'ID' => $sibling->ID ),
2090
				array( '%d' ),
2091
				array( '%d' )
2092
			);
2093
			$new_pos[ $sibling->ID ] = $menu_order;
2094
			$menu_order++;
2095
2096 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...
2097
				$wpdb->update(
2098
					$wpdb->posts,
2099
					array(
2100
						'menu_order' => $menu_order
2101
					),
2102
					array( 'ID' => $post->ID ),
2103
					array( '%d' ),
2104
					array( '%d' )
2105
				);
2106
				$new_pos[$post->ID] = $menu_order;
2107
				$menu_order++;
2108
			}
2109
2110
		}
2111
2112
		do_action( 'woocommerce_after_product_ordering' );
2113
2114
		wp_send_json( $new_pos );
2115
	}
2116
2117
	/**
2118
	 * Handle a refund via the edit order screen.
2119
	 */
2120
	public static function refund_line_items() {
2121
		ob_start();
2122
2123
		check_ajax_referer( 'order-item', 'security' );
2124
2125
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2126
			die(-1);
2127
		}
2128
2129
		$order_id               = absint( $_POST['order_id'] );
2130
		$refund_amount          = wc_format_decimal( sanitize_text_field( $_POST['refund_amount'] ) );
2131
		$refund_reason          = sanitize_text_field( $_POST['refund_reason'] );
2132
		$line_item_qtys         = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_qtys'] ) ), true );
2133
		$line_item_totals       = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_totals'] ) ), true );
2134
		$line_item_tax_totals   = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_tax_totals'] ) ), true );
2135
		$api_refund             = $_POST['api_refund'] === 'true' ? true : false;
2136
		$restock_refunded_items = $_POST['restock_refunded_items'] === 'true' ? true : false;
2137
		$refund                 = false;
2138
		$response_data          = array();
2139
2140
		try {
2141
			// Validate that the refund can occur
2142
			$order       = wc_get_order( $order_id );
2143
			$order_items = $order->get_items();
2144
			$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );
2145
2146
			if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) {
2147
				throw new exception( __( 'Invalid refund amount', 'woocommerce' ) );
2148
			}
2149
2150
			// Prepare line items which we are refunding
2151
			$line_items = array();
2152
			$item_ids   = array_unique( array_merge( array_keys( $line_item_qtys, $line_item_totals ) ) );
2153
2154
			foreach ( $item_ids as $item_id ) {
2155
				$line_items[ $item_id ] = array( 'qty' => 0, 'refund_total' => 0, 'refund_tax' => array() );
2156
			}
2157
			foreach ( $line_item_qtys as $item_id => $qty ) {
2158
				$line_items[ $item_id ]['qty'] = max( $qty, 0 );
2159
			}
2160
			foreach ( $line_item_totals as $item_id => $total ) {
2161
				$line_items[ $item_id ]['refund_total'] = wc_format_decimal( $total );
2162
			}
2163
			foreach ( $line_item_tax_totals as $item_id => $tax_totals ) {
2164
				$line_items[ $item_id ]['refund_tax'] = array_map( 'wc_format_decimal', $tax_totals );
2165
			}
2166
2167
			// Create the refund object
2168
			$refund = wc_create_refund( array(
2169
				'amount'     => $refund_amount,
2170
				'reason'     => $refund_reason,
2171
				'order_id'   => $order_id,
2172
				'line_items' => $line_items,
2173
			) );
2174
2175
			if ( is_wp_error( $refund ) ) {
2176
				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...
2177
			}
2178
2179
			// Refund via API
2180 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...
2181
				if ( WC()->payment_gateways() ) {
2182
					$payment_gateways = WC()->payment_gateways->payment_gateways();
2183
				}
2184
				if ( isset( $payment_gateways[ $order->payment_method ] ) && $payment_gateways[ $order->payment_method ]->supports( 'refunds' ) ) {
2185
					$result = $payment_gateways[ $order->payment_method ]->process_refund( $order_id, $refund_amount, $refund_reason );
2186
2187
					do_action( 'woocommerce_refund_processed', $refund, $result );
2188
2189
					if ( is_wp_error( $result ) ) {
2190
						throw new Exception( $result->get_error_message() );
2191
					} elseif ( ! $result ) {
2192
						throw new Exception( __( 'Refund failed', 'woocommerce' ) );
2193
					}
2194
				}
2195
			}
2196
2197
			// restock items
2198
			foreach ( $line_item_qtys as $item_id => $qty ) {
2199
				if ( $restock_refunded_items && $qty && isset( $order_items[ $item_id ] ) ) {
2200
					$order_item = $order_items[ $item_id ];
2201
					$_product   = $order->get_product_from_item( $order_item );
2202
2203
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2204
						$old_stock    = wc_stock_amount( $_product->stock );
2205
						$new_quantity = $_product->increase_stock( $qty );
2206
2207
						$order->add_order_note( sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
2208
2209
						do_action( 'woocommerce_restock_refunded_item', $_product->id, $old_stock, $new_quantity, $order );
2210
					}
2211
				}
2212
			}
2213
2214
			// Trigger notifications and status changes
2215
			if ( $order->get_remaining_refund_amount() > 0 || ( $order->has_free_item() && $order->get_remaining_refund_items() > 0 ) ) {
2216
				/**
2217
				 * woocommerce_order_partially_refunded.
2218
				 *
2219
				 * @since 2.4.0
2220
				 * Note: 3rd arg was added in err. Kept for bw compat. 2.4.3.
2221
				 */
2222
				do_action( 'woocommerce_order_partially_refunded', $order_id, $refund->id, $refund->id );
2223
			} else {
2224
				do_action( 'woocommerce_order_fully_refunded', $order_id, $refund->id );
2225
2226
				$order->update_status( apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order_id, $refund->id ) );
2227
				$response_data['status'] = 'fully_refunded';
2228
			}
2229
2230
			do_action( 'woocommerce_order_refunded', $order_id, $refund->id );
2231
2232
			// Clear transients
2233
			wc_delete_shop_order_transients( $order_id );
2234
			wp_send_json_success( $response_data );
2235
2236
		} catch ( Exception $e ) {
2237
			if ( $refund && is_a( $refund, 'WC_Order_Refund' ) ) {
2238
				wp_delete_post( $refund->id, true );
2239
			}
2240
2241
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
2242
		}
2243
	}
2244
2245
	/**
2246
	 * Delete a refund.
2247
	 */
2248
	public static function delete_refund() {
2249
		check_ajax_referer( 'order-item', 'security' );
2250
2251
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2252
			die(-1);
2253
		}
2254
2255
		$refund_id = absint( $_POST['refund_id'] );
2256
2257
		if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
2258
			$order_id = wp_get_post_parent_id( $refund_id );
2259
			wc_delete_shop_order_transients( $order_id );
2260
			wp_delete_post( $refund_id );
2261
2262
			do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
2263
		}
2264
2265
		die();
2266
	}
2267
2268
	/**
2269
	 * Triggered when clicking the rating footer.
2270
	 */
2271
	public static function rated() {
2272
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2273
			die(-1);
2274
		}
2275
2276
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
2277
		die();
2278
	}
2279
2280
	/**
2281
	 * Create/Update API key.
2282
	 */
2283
	public static function update_api_key() {
2284
		ob_start();
2285
2286
		global $wpdb;
2287
2288
		check_ajax_referer( 'update-api-key', 'security' );
2289
2290
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2291
			die(-1);
2292
		}
2293
2294
		try {
2295
			if ( empty( $_POST['description'] ) ) {
2296
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
2297
			}
2298
			if ( empty( $_POST['user'] ) ) {
2299
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
2300
			}
2301
			if ( empty( $_POST['permissions'] ) ) {
2302
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
2303
			}
2304
2305
			$key_id      = absint( $_POST['key_id'] );
2306
			$description = sanitize_text_field( wp_unslash( $_POST['description'] ) );
2307
			$permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read';
2308
			$user_id     = absint( $_POST['user'] );
2309
2310
			if ( 0 < $key_id ) {
2311
				$data = array(
2312
					'user_id'     => $user_id,
2313
					'description' => $description,
2314
					'permissions' => $permissions
2315
				);
2316
2317
				$wpdb->update(
2318
					$wpdb->prefix . 'woocommerce_api_keys',
2319
					$data,
2320
					array( 'key_id' => $key_id ),
2321
					array(
2322
						'%d',
2323
						'%s',
2324
						'%s'
2325
					),
2326
					array( '%d' )
2327
				);
2328
2329
				$data['consumer_key']    = '';
2330
				$data['consumer_secret'] = '';
2331
				$data['message']         = __( 'API Key updated successfully.', 'woocommerce' );
2332
			} else {
2333
				$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...
2334
				$consumer_key    = 'ck_' . wc_rand_hash();
2335
				$consumer_secret = 'cs_' . wc_rand_hash();
2336
2337
				$data = array(
2338
					'user_id'         => $user_id,
2339
					'description'     => $description,
2340
					'permissions'     => $permissions,
2341
					'consumer_key'    => wc_api_hash( $consumer_key ),
2342
					'consumer_secret' => $consumer_secret,
2343
					'truncated_key'   => substr( $consumer_key, -7 )
2344
				);
2345
2346
				$wpdb->insert(
2347
					$wpdb->prefix . 'woocommerce_api_keys',
2348
					$data,
2349
					array(
2350
						'%d',
2351
						'%s',
2352
						'%s',
2353
						'%s',
2354
						'%s',
2355
						'%s'
2356
					)
2357
				);
2358
2359
				$key_id                  = $wpdb->insert_id;
2360
				$data['consumer_key']    = $consumer_key;
2361
				$data['consumer_secret'] = $consumer_secret;
2362
				$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' );
2363
				$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>';
2364
			}
2365
2366
			wp_send_json_success( $data );
2367
		} catch ( Exception $e ) {
2368
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
2369
		}
2370
	}
2371
2372
	/**
2373
	 * Locate user via AJAX.
2374
	 */
2375
	public static function get_customer_location() {
2376
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
2377
		wp_send_json_success( array( 'hash' => $location_hash ) );
2378
	}
2379
2380
	/**
2381
	 * Load variations via AJAX.
2382
	 */
2383
	public static function load_variations() {
2384
		ob_start();
2385
2386
		check_ajax_referer( 'load-variations', 'security' );
2387
2388
		// Check permissions again and make sure we have what we need
2389 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...
2390
			die( -1 );
2391
		}
2392
2393
		global $post;
2394
2395
		$product_id = absint( $_POST['product_id'] );
2396
		$post       = get_post( $product_id ); // Set $post global so its available like within the admin screens
2397
		$per_page   = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
2398
		$page       = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
2399
2400
		// Get attributes
2401
		$attributes        = array();
2402
		$posted_attributes = wp_unslash( $_POST['attributes'] );
2403
2404
		foreach ( $posted_attributes as $key => $value ) {
2405
			$attributes[ $key ] = array_map( 'wc_clean', $value );
2406
		}
2407
2408
		// Get tax classes
2409
		$tax_classes           = WC_Tax::get_tax_classes();
2410
		$tax_class_options     = array();
2411
		$tax_class_options[''] = __( 'Standard', 'woocommerce' );
2412
2413 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...
2414
			foreach ( $tax_classes as $class ) {
2415
				$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
2416
			}
2417
		}
2418
2419
		// Set backorder options
2420
		$backorder_options = array(
2421
			'no'     => __( 'Do not allow', 'woocommerce' ),
2422
			'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
2423
			'yes'    => __( 'Allow', 'woocommerce' )
2424
		);
2425
2426
		// set stock status options
2427
		$stock_status_options = array(
2428
			'instock'    => __( 'In stock', 'woocommerce' ),
2429
			'outofstock' => __( 'Out of stock', 'woocommerce' )
2430
		);
2431
2432
		$parent_data = array(
2433
			'id'                   => $product_id,
2434
			'attributes'           => $attributes,
2435
			'tax_class_options'    => $tax_class_options,
2436
			'sku'                  => get_post_meta( $product_id, '_sku', true ),
2437
			'weight'               => wc_format_localized_decimal( get_post_meta( $product_id, '_weight', true ) ),
2438
			'length'               => wc_format_localized_decimal( get_post_meta( $product_id, '_length', true ) ),
2439
			'width'                => wc_format_localized_decimal( get_post_meta( $product_id, '_width', true ) ),
2440
			'height'               => wc_format_localized_decimal( get_post_meta( $product_id, '_height', true ) ),
2441
			'tax_class'            => get_post_meta( $product_id, '_tax_class', true ),
2442
			'backorder_options'    => $backorder_options,
2443
			'stock_status_options' => $stock_status_options
2444
		);
2445
2446
		if ( ! $parent_data['weight'] ) {
2447
			$parent_data['weight'] = wc_format_localized_decimal( 0 );
2448
		}
2449
2450
		if ( ! $parent_data['length'] ) {
2451
			$parent_data['length'] = wc_format_localized_decimal( 0 );
2452
		}
2453
2454
		if ( ! $parent_data['width'] ) {
2455
			$parent_data['width'] = wc_format_localized_decimal( 0 );
2456
		}
2457
2458
		if ( ! $parent_data['height'] ) {
2459
			$parent_data['height'] = wc_format_localized_decimal( 0 );
2460
		}
2461
2462
		// Get variations
2463
		$args = apply_filters( 'woocommerce_ajax_admin_get_variations_args', array(
2464
			'post_type'      => 'product_variation',
2465
			'post_status'    => array( 'private', 'publish' ),
2466
			'posts_per_page' => $per_page,
2467
			'paged'          => $page,
2468
			'orderby'        => array( 'menu_order' => 'ASC', 'ID' => 'DESC' ),
2469
			'post_parent'    => $product_id
2470
		), $product_id );
2471
2472
		$variations = get_posts( $args );
2473
		$loop = 0;
2474
2475
		if ( $variations ) {
2476
2477
			foreach ( $variations as $variation ) {
2478
				$variation_id     = absint( $variation->ID );
2479
				$variation_meta   = get_post_meta( $variation_id );
2480
				$variation_data   = array();
2481
				$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
2482
				$variation_fields = array(
2483
					'_sku'                   => '',
2484
					'_stock'                 => '',
2485
					'_regular_price'         => '',
2486
					'_sale_price'            => '',
2487
					'_weight'                => '',
2488
					'_length'                => '',
2489
					'_width'                 => '',
2490
					'_height'                => '',
2491
					'_download_limit'        => '',
2492
					'_download_expiry'       => '',
2493
					'_downloadable_files'    => '',
2494
					'_downloadable'          => '',
2495
					'_virtual'               => '',
2496
					'_thumbnail_id'          => '',
2497
					'_sale_price_dates_from' => '',
2498
					'_sale_price_dates_to'   => '',
2499
					'_manage_stock'          => '',
2500
					'_stock_status'          => '',
2501
					'_backorders'            => null,
2502
					'_tax_class'             => null,
2503
					'_variation_description' => ''
2504
				);
2505
2506 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...
2507
					$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
2508
				}
2509
2510
				// Add the variation attributes
2511
				$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
2512
2513
				// Formatting
2514
				$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
2515
				$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
2516
				$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
2517
				$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
2518
				$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
2519
				$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
2520
				$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
2521
				$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
2522
				$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
2523
				$variation_data['menu_order']     = $variation->menu_order;
2524
2525
				// Stock BW compat
2526
				if ( '' !== $variation_data['_stock'] ) {
2527
					$variation_data['_manage_stock'] = 'yes';
2528
				}
2529
2530
				include( 'admin/meta-boxes/views/html-variation-admin.php' );
2531
2532
				$loop++;
2533
			}
2534
		}
2535
2536
		die();
2537
	}
2538
2539
	/**
2540
	 * Save variations via AJAX.
2541
	 */
2542
	public static function save_variations() {
2543
		ob_start();
2544
2545
		check_ajax_referer( 'save-variations', 'security' );
2546
2547
		// Check permissions again and make sure we have what we need
2548 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...
2549
			die( -1 );
2550
		}
2551
2552
		// Remove previous meta box errors
2553
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
2554
2555
		$product_id   = absint( $_POST['product_id'] );
2556
		$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
2557
2558
		$product_type_terms = wp_get_object_terms( $product_id, 'product_type' );
2559
2560
		// If the product type hasn't been set or it has changed, update it before saving variations
2561
		if ( empty( $product_type_terms ) || $product_type !== sanitize_title( current( $product_type_terms )->name ) ) {
2562
			wp_set_object_terms( $product_id, $product_type, 'product_type' );
2563
		}
2564
2565
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
2566
2567
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
2568
2569
		// Clear cache/transients
2570
		wc_delete_product_transients( $product_id );
2571
2572
		if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
2573
			echo '<div class="error notice is-dismissible">';
2574
2575
			foreach ( $errors as $error ) {
2576
				echo '<p>' . wp_kses_post( $error ) . '</p>';
2577
			}
2578
2579
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
2580
			echo '</div>';
2581
2582
			delete_option( 'woocommerce_meta_box_errors' );
2583
		}
2584
2585
		die();
2586
	}
2587
2588
	/**
2589
	 * Bulk action - Toggle Enabled.
2590
	 * @access private
2591
	 * @param  array $variations
2592
	 * @param  array $data
2593
	 */
2594
	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...
2595
		global $wpdb;
2596
2597
		foreach ( $variations as $variation_id ) {
2598
			$post_status = get_post_status( $variation_id );
2599
			$new_status  = 'private' === $post_status ? 'publish' : 'private';
2600
			$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $variation_id ) );
2601
		}
2602
	}
2603
2604
	/**
2605
	 * Bulk action - Toggle Downloadable Checkbox.
2606
	 * @access private
2607
	 * @param  array $variations
2608
	 * @param  array $data
2609
	 */
2610 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...
2611
		foreach ( $variations as $variation_id ) {
2612
			$_downloadable   = get_post_meta( $variation_id, '_downloadable', true );
2613
			$is_downloadable = 'no' === $_downloadable ? 'yes' : 'no';
2614
			update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
2615
		}
2616
	}
2617
2618
	/**
2619
	 * Bulk action - Toggle Virtual Checkbox.
2620
	 * @access private
2621
	 * @param  array $variations
2622
	 * @param  array $data
2623
	 */
2624 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...
2625
		foreach ( $variations as $variation_id ) {
2626
			$_virtual   = get_post_meta( $variation_id, '_virtual', true );
2627
			$is_virtual = 'no' === $_virtual ? 'yes' : 'no';
2628
			update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
2629
		}
2630
	}
2631
2632
	/**
2633
	 * Bulk action - Toggle Manage Stock Checkbox.
2634
	 * @access private
2635
	 * @param  array $variations
2636
	 * @param  array $data
2637
	 */
2638
	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...
2639
		foreach ( $variations as $variation_id ) {
2640
			$_manage_stock   = get_post_meta( $variation_id, '_manage_stock', true );
2641
			$is_manage_stock = 'no' === $_manage_stock || '' === $_manage_stock ? 'yes' : 'no';
2642
			update_post_meta( $variation_id, '_manage_stock', $is_manage_stock );
2643
		}
2644
	}
2645
2646
	/**
2647
	 * Bulk action - Set Regular Prices.
2648
	 * @access private
2649
	 * @param  array $variations
2650
	 * @param  array $data
2651
	 */
2652 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...
2653
		if ( ! isset( $data['value'] ) ) {
2654
			return;
2655
		}
2656
2657
		foreach ( $variations as $variation_id ) {
2658
			// Price fields
2659
			$regular_price = wc_clean( $data['value'] );
2660
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2661
2662
			// Date fields
2663
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2664
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2665
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2666
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2667
2668
			_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...
2669
		}
2670
	}
2671
2672
	/**
2673
	 * Bulk action - Set Sale Prices.
2674
	 * @access private
2675
	 * @param  array $variations
2676
	 * @param  array $data
2677
	 */
2678 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...
2679
		if ( ! isset( $data['value'] ) ) {
2680
			return;
2681
		}
2682
2683
		foreach ( $variations as $variation_id ) {
2684
			// Price fields
2685
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2686
			$sale_price    = wc_clean( $data['value'] );
2687
2688
			// Date fields
2689
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2690
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2691
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2692
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2693
2694
			_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 2686 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...
2695
		}
2696
	}
2697
2698
	/**
2699
	 * Bulk action - Set Stock.
2700
	 * @access private
2701
	 * @param  array $variations
2702
	 * @param  array $data
2703
	 */
2704
	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...
2705
		if ( ! isset( $data['value'] ) ) {
2706
			return;
2707
		}
2708
2709
		$value = wc_clean( $data['value'] );
2710
2711
		foreach ( $variations as $variation_id ) {
2712
			if ( 'yes' === get_post_meta( $variation_id, '_manage_stock', true ) ) {
2713
				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...
2714
			} else {
2715
				delete_post_meta( $variation_id, '_stock' );
2716
			}
2717
		}
2718
	}
2719
2720
	/**
2721
	 * Bulk action - Set Weight.
2722
	 * @access private
2723
	 * @param  array $variations
2724
	 * @param  array $data
2725
	 */
2726
	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...
2727
		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...
2728
	}
2729
2730
	/**
2731
	 * Bulk action - Set Length.
2732
	 * @access private
2733
	 * @param  array $variations
2734
	 * @param  array $data
2735
	 */
2736
	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...
2737
		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...
2738
	}
2739
2740
	/**
2741
	 * Bulk action - Set Width.
2742
	 * @access private
2743
	 * @param  array $variations
2744
	 * @param  array $data
2745
	 */
2746
	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...
2747
		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...
2748
	}
2749
2750
	/**
2751
	 * Bulk action - Set Height.
2752
	 * @access private
2753
	 * @param  array $variations
2754
	 * @param  array $data
2755
	 */
2756
	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...
2757
		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...
2758
	}
2759
2760
	/**
2761
	 * Bulk action - Set Download Limit.
2762
	 * @access private
2763
	 * @param  array $variations
2764
	 * @param  array $data
2765
	 */
2766
	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...
2767
		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...
2768
	}
2769
2770
	/**
2771
	 * Bulk action - Set Download Expiry.
2772
	 * @access private
2773
	 * @param  array $variations
2774
	 * @param  array $data
2775
	 */
2776
	private static function variation_bulk_action_variable_download_expiry( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2777
		self::variation_bulk_set_meta( $variations, '_download_expiry', wc_clean( $data['value'] ) );
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...
2778
	}
2779
2780
	/**
2781
	 * Bulk action - Delete all.
2782
	 * @access private
2783
	 * @param  array $variations
2784
	 * @param  array $data
2785
	 */
2786
	private static function variation_bulk_action_delete_all( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2787
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2788
			foreach ( $variations as $variation_id ) {
2789
				wp_delete_post( $variation_id );
2790
			}
2791
		}
2792
	}
2793
2794
	/**
2795
	 * Bulk action - Sale Schedule.
2796
	 * @access private
2797
	 * @param  array $variations
2798
	 * @param  array $data
2799
	 */
2800
	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...
2801
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2802
			return;
2803
		}
2804
2805
		foreach ( $variations as $variation_id ) {
2806
			// Price fields
2807
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2808
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2809
2810
			// Date fields
2811
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2812
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2813
2814 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...
2815
				$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2816
			} else {
2817
				$date_from = $data['date_from'];
2818
			}
2819
2820 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...
2821
				$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2822
			} else {
2823
				$date_to = $data['date_to'];
2824
			}
2825
2826
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
2827
		}
2828
	}
2829
2830
	/**
2831
	 * Bulk action - Increase Regular Prices.
2832
	 * @access private
2833
	 * @param  array $variations
2834
	 * @param  array $data
2835
	 */
2836
	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...
2837
		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...
2838
	}
2839
2840
	/**
2841
	 * Bulk action - Decrease Regular Prices.
2842
	 * @access private
2843
	 * @param  array $variations
2844
	 * @param  array $data
2845
	 */
2846
	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...
2847
		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...
2848
	}
2849
2850
	/**
2851
	 * Bulk action - Increase Sale Prices.
2852
	 * @access private
2853
	 * @param  array $variations
2854
	 * @param  array $data
2855
	 */
2856
	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...
2857
		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...
2858
	}
2859
2860
	/**
2861
	 * Bulk action - Decrease Sale Prices.
2862
	 * @access private
2863
	 * @param  array $variations
2864
	 * @param  array $data
2865
	 */
2866
	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...
2867
		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...
2868
	}
2869
2870
	/**
2871
	 * Bulk action - Set Price.
2872
	 * @access private
2873
	 * @param  array $variations
2874
	 * @param string $operator + or -
2875
	 * @param string $field price being adjusted
2876
	 * @param string $value Price or Percent
2877
	 */
2878
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2879
		foreach ( $variations as $variation_id ) {
2880
			// Get existing data
2881
			$_regular_price = get_post_meta( $variation_id, '_regular_price', true );
2882
			$_sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2883
			$date_from      = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2884
			$date_to        = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2885
			$date_from      = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2886
			$date_to        = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2887
2888
			if ( '%' === substr( $value, -1 ) ) {
2889
				$percent = wc_format_decimal( substr( $value, 0, -1 ) );
2890
				$$field  += ( ( $$field / 100 ) * $percent ) * "{$operator}1";
2891
			} else {
2892
				$$field  += $value * "{$operator}1";
2893
			}
2894
			_wc_save_product_price( $variation_id, $_regular_price, $_sale_price, $date_from, $date_to );
2895
		}
2896
	}
2897
2898
	/**
2899
	 * Bulk action - Set Meta.
2900
	 * @access private
2901
	 * @param array $variations
2902
	 * @param string $field
2903
	 * @param string $value
2904
	 */
2905
	private static function variation_bulk_set_meta( $variations, $field, $value ) {
2906
		foreach ( $variations as $variation_id ) {
2907
			update_post_meta( $variation_id, $field, $value );
2908
		}
2909
	}
2910
2911
2912
	/**
2913
	 * Bulk edit variations via AJAX.
2914
	 */
2915
	public static function bulk_edit_variations() {
2916
		ob_start();
2917
2918
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2919
2920
		// Check permissions again and make sure we have what we need
2921 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...
2922
			die( -1 );
2923
		}
2924
2925
		$product_id  = absint( $_POST['product_id'] );
2926
		$bulk_action = wc_clean( $_POST['bulk_action'] );
2927
		$data        = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array();
2928
		$variations  = array();
2929
2930
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2931
			$variations = get_posts( array(
2932
				'post_parent'    => $product_id,
2933
				'posts_per_page' => -1,
2934
				'post_type'      => 'product_variation',
2935
				'fields'         => 'ids',
2936
				'post_status'    => array( 'publish', 'private' )
2937
			) );
2938
		}
2939
2940
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2941
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2942
		} else {
2943
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2944
		}
2945
2946
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2947
2948
		// Sync and update transients
2949
		WC_Product_Variable::sync( $product_id );
2950
		wc_delete_product_transients( $product_id );
2951
		die();
2952
	}
2953
2954
	/**
2955
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2956
	 */
2957
	public static function tax_rates_save_changes() {
2958
		if ( ! isset( $_POST['current_class'], $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2959
			wp_send_json_error( 'missing_fields' );
2960
			exit;
2961
		}
2962
2963
		$current_class = $_POST['current_class']; // This is sanitized seven lines later.
2964
2965
		if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) {
2966
			wp_send_json_error( 'bad_nonce' );
2967
			exit;
2968
		}
2969
2970
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
2971
2972
		// Check User Caps
2973
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2974
			wp_send_json_error( 'missing_capabilities' );
2975
			exit;
2976
		}
2977
2978
		$changes = $_POST['changes'];
2979
		foreach ( $changes as $tax_rate_id => $data ) {
2980
			if ( isset( $data['deleted'] ) ) {
2981
				if ( isset( $data['newRow'] ) ) {
2982
					// So the user added and deleted a new row.
2983
					// That's fine, it's not in the database anyways. NEXT!
2984
					continue;
2985
				}
2986
				WC_Tax::_delete_tax_rate( $tax_rate_id );
2987
			}
2988
2989
			$tax_rate = array_intersect_key( $data, array(
2990
				'tax_rate_country'  => 1,
2991
				'tax_rate_state'    => 1,
2992
				'tax_rate'          => 1,
2993
				'tax_rate_name'     => 1,
2994
				'tax_rate_priority' => 1,
2995
				'tax_rate_compound' => 1,
2996
				'tax_rate_shipping' => 1,
2997
				'tax_rate_order'    => 1,
2998
			) );
2999
3000
			if ( isset( $data['newRow'] ) ) {
3001
				// 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...
3002
				$tax_rate['tax_rate_class'] = $current_class;
3003
				$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
3004
			} else {
3005
				// Updating an existing rate ...
3006
				if ( ! empty( $tax_rate ) ) {
3007
					WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
3008
				}
3009
			}
3010
3011
			if ( isset( $data['postcode'] ) ) {
3012
				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...
3013
			}
3014
			if ( isset( $data['city'] ) ) {
3015
				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...
3016
			}
3017
		}
3018
3019
		wp_send_json_success( array(
3020
			'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
3021
		) );
3022
	}
3023
3024
	/**
3025
	 * Handle submissions from assets/js/wc-shipping-zones.js Backbone model.
3026
	 */
3027
	public static function shipping_zones_save_changes() {
3028
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['changes'] ) ) {
3029
			wp_send_json_error( 'missing_fields' . (isset( $_POST['wc_shipping_zones_nonce'] ) ? 1 : 0). (isset( $_POST['changes'] ) ? 1 : 0) );
3030
			exit;
3031
		}
3032
3033
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3034
			wp_send_json_error( 'bad_nonce' );
3035
			exit;
3036
		}
3037
3038
		// Check User Caps
3039
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3040
			wp_send_json_error( 'missing_capabilities' );
3041
			exit;
3042
		}
3043
3044
		$changes = $_POST['changes'];
3045
		foreach ( $changes as $zone_id => $data ) {
3046
			if ( isset( $data['deleted'] ) ) {
3047
				if ( isset( $data['newRow'] ) ) {
3048
					// So the user added and deleted a new row.
3049
					// That's fine, it's not in the database anyways. NEXT!
3050
					continue;
3051
				}
3052
				WC_Shipping_Zones::delete_zone( $zone_id );
3053
				continue;
3054
			}
3055
3056
			$zone_data = array_intersect_key( $data, array(
3057
				'zone_id'        => 1,
3058
				'zone_name'      => 1,
3059
				'zone_order'     => 1,
3060
				'zone_locations' => 1,
3061
				'zone_postcodes' => 1
3062
			) );
3063
3064
			if ( isset( $zone_data['zone_id'] ) ) {
3065
				$zone = new WC_Shipping_Zone( $zone_data['zone_id'] );
3066
3067
				if ( isset( $zone_data['zone_name'] ) ) {
3068
					$zone->set_zone_name( $zone_data['zone_name'] );
3069
				}
3070
3071
				if ( isset( $zone_data['zone_order'] ) ) {
3072
					$zone->set_zone_order( $zone_data['zone_order'] );
3073
				}
3074
3075
				if ( isset( $zone_data['zone_locations'] ) ) {
3076
					$locations = array_filter( array_map( 'wc_clean', (array) $zone_data['zone_locations'] ) );
3077
					$zone->clear_locations();
3078
					foreach ( $locations as $location ) {
3079
						// Each posted location will be in the format type:code
3080
						$location_parts = explode( ':', $location );
3081
						switch ( $location_parts[0] ) {
3082
							case 'state' :
3083
								$zone->add_location( $location_parts[1] . ':' . $location_parts[2], 'state' );
3084
							break;
3085
							case 'country' :
3086
								$zone->add_location( $location_parts[1], 'country' );
3087
							break;
3088
							case 'continent' :
3089
								$zone->add_location( $location_parts[1], 'continent' );
3090
							break;
3091
						}
3092
					}
3093
				}
3094
3095
				if ( isset( $zone_data['zone_postcodes'] ) ) {
3096
					$postcodes = array_filter( array_map( 'strtoupper', array_map( 'wc_clean', explode( "\n", $zone_data['zone_postcodes'] ) ) ) );
3097
					foreach ( $postcodes as $postcode ) {
3098
						$zone->add_location( $postcode, 'postcode' );
3099
					}
3100
				}
3101
3102
				$zone->save();
3103
			}
3104
		}
3105
3106
		wp_send_json_success( array(
3107
			'zones' => WC_Shipping_Zones::get_zones(),
3108
			'test' => print_r($_POST['changes'], true)
3109
		) );
3110
	}
3111
}
3112
3113
WC_AJAX::init();
3114