Completed
Pull Request — master (#9826)
by Mike
10:44
created

WC_AJAX::shipping_zones_save_changes()   D

Complexity

Conditions 17
Paths 23

Size

Total Lines 83
Code Lines 51

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 83
rs 4.8888
cc 17
eloc 51
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
			'shipping_zone_methods_save_changes'               => false,
146
		);
147
148
		foreach ( $ajax_events as $ajax_event => $nopriv ) {
149
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
150
151
			if ( $nopriv ) {
152
				add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
153
154
				// WC AJAX can be used for frontend ajax requests
155
				add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
156
			}
157
		}
158
	}
159
160
	/**
161
	 * Get a refreshed cart fragment.
162
	 */
163
	public static function get_refreshed_fragments() {
164
165
		// Get mini cart
166
		ob_start();
167
168
		woocommerce_mini_cart();
169
170
		$mini_cart = ob_get_clean();
171
172
		// Fragments and mini cart are returned
173
		$data = array(
174
			'fragments' => apply_filters( 'woocommerce_add_to_cart_fragments', array(
175
					'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>'
176
				)
177
			),
178
			'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() )
179
		);
180
181
		wp_send_json( $data );
182
183
	}
184
185
	/**
186
	 * AJAX apply coupon on checkout page.
187
	 */
188
	public static function apply_coupon() {
189
190
		check_ajax_referer( 'apply-coupon', 'security' );
191
192
		if ( ! empty( $_POST['coupon_code'] ) ) {
193
			WC()->cart->add_discount( sanitize_text_field( $_POST['coupon_code'] ) );
194
		} else {
195
			wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
196
		}
197
198
		wc_print_notices();
199
200
		die();
201
	}
202
203
	/**
204
	 * AJAX remove coupon on cart and checkout page.
205
	 */
206
	public static function remove_coupon() {
207
208
		check_ajax_referer( 'remove-coupon', 'security' );
209
210
		$coupon = wc_clean( $_POST['coupon'] );
211
212
		if ( ! isset( $coupon ) || empty( $coupon ) ) {
213
			wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ) );
214
215
		} else {
216
217
			WC()->cart->remove_coupon( $coupon );
1 ignored issue
show
Bug introduced by
It seems like $coupon defined by wc_clean($_POST['coupon']) on line 210 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...
218
219
			wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
220
		}
221
222
		wc_print_notices();
223
224
		die();
225
	}
226
227
	/**
228
	 * AJAX update shipping method on cart page.
229
	 */
230
	public static function update_shipping_method() {
231
232
		check_ajax_referer( 'update-shipping-method', 'security' );
233
234
		if ( ! defined('WOOCOMMERCE_CART') ) {
235
			define( 'WOOCOMMERCE_CART', true );
236
		}
237
238
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
239
240 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...
241
			foreach ( $_POST['shipping_method'] as $i => $value ) {
242
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
243
			}
244
		}
245
246
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
247
248
		WC()->cart->calculate_totals();
249
250
		woocommerce_cart_totals();
251
252
		die();
253
	}
254
255
	/**
256
	 * AJAX update order review on checkout.
257
	 */
258
	public static function update_order_review() {
259
		ob_start();
260
261
		check_ajax_referer( 'update-order-review', 'security' );
262
263
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
264
			define( 'WOOCOMMERCE_CHECKOUT', true );
265
		}
266
267
		if ( WC()->cart->is_empty() ) {
268
			$data = array(
269
				'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
270
					'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>'
271
				) )
272
			);
273
274
			wp_send_json( $data );
275
276
			die();
277
		}
278
279
		do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] );
280
281
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
282
283 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...
284
			foreach ( $_POST['shipping_method'] as $i => $value ) {
285
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
286
			}
287
		}
288
289
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
290
		WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
291
292
		if ( isset( $_POST['country'] ) ) {
293
			WC()->customer->set_country( $_POST['country'] );
294
		}
295
296
		if ( isset( $_POST['state'] ) ) {
297
			WC()->customer->set_state( $_POST['state'] );
298
		}
299
300
		if ( isset( $_POST['postcode'] ) ) {
301
			WC()->customer->set_postcode( $_POST['postcode'] );
302
		}
303
304
		if ( isset( $_POST['city'] ) ) {
305
			WC()->customer->set_city( $_POST['city'] );
306
		}
307
308
		if ( isset( $_POST['address'] ) ) {
309
			WC()->customer->set_address( $_POST['address'] );
310
		}
311
312
		if ( isset( $_POST['address_2'] ) ) {
313
			WC()->customer->set_address_2( $_POST['address_2'] );
314
		}
315
316
		if ( wc_ship_to_billing_address_only() ) {
317
318
			if ( isset( $_POST['country'] ) ) {
319
				WC()->customer->set_shipping_country( $_POST['country'] );
320
			}
321
322
			if ( isset( $_POST['state'] ) ) {
323
				WC()->customer->set_shipping_state( $_POST['state'] );
324
			}
325
326
			if ( isset( $_POST['postcode'] ) ) {
327
				WC()->customer->set_shipping_postcode( $_POST['postcode'] );
328
			}
329
330
			if ( isset( $_POST['city'] ) ) {
331
				WC()->customer->set_shipping_city( $_POST['city'] );
332
			}
333
334
			if ( isset( $_POST['address'] ) ) {
335
				WC()->customer->set_shipping_address( $_POST['address'] );
336
			}
337
338
			if ( isset( $_POST['address_2'] ) ) {
339
				WC()->customer->set_shipping_address_2( $_POST['address_2'] );
340
			}
341
		} else {
342
343
			if ( isset( $_POST['s_country'] ) ) {
344
				WC()->customer->set_shipping_country( $_POST['s_country'] );
345
			}
346
347
			if ( isset( $_POST['s_state'] ) ) {
348
				WC()->customer->set_shipping_state( $_POST['s_state'] );
349
			}
350
351
			if ( isset( $_POST['s_postcode'] ) ) {
352
				WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
353
			}
354
355
			if ( isset( $_POST['s_city'] ) ) {
356
				WC()->customer->set_shipping_city( $_POST['s_city'] );
357
			}
358
359
			if ( isset( $_POST['s_address'] ) ) {
360
				WC()->customer->set_shipping_address( $_POST['s_address'] );
361
			}
362
363
			if ( isset( $_POST['s_address_2'] ) ) {
364
				WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
365
			}
366
		}
367
368
		WC()->cart->calculate_totals();
369
370
		// Get order review fragment
371
		ob_start();
372
		woocommerce_order_review();
373
		$woocommerce_order_review = ob_get_clean();
374
375
		// Get checkout payment fragment
376
		ob_start();
377
		woocommerce_checkout_payment();
378
		$woocommerce_checkout_payment = ob_get_clean();
379
380
		// Get messages if reload checkout is not true
381
		$messages = '';
382
		if ( ! isset( WC()->session->reload_checkout ) ) {
383
			ob_start();
384
			wc_print_notices();
385
			$messages = ob_get_clean();
386
		}
387
388
		$data = array(
389
			'result'    => empty( $messages ) ? 'success' : 'failure',
390
			'messages'  => $messages,
391
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
392
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
393
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
394
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
395
			) )
396
		);
397
398
		wp_send_json( $data );
399
400
		die();
401
	}
402
403
	/**
404
	 * AJAX add to cart.
405
	 */
406
	public static function add_to_cart() {
407
		ob_start();
408
409
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
410
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
411
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
412
		$product_status    = get_post_status( $product_id );
413
414
		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...
415
416
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
417
418
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
419
				wc_add_to_cart_message( $product_id );
420
			}
421
422
			// Return fragments
423
			self::get_refreshed_fragments();
424
425
		} else {
426
427
			// If there was an error adding to the cart, redirect to the product page to show any errors
428
			$data = array(
429
				'error'       => true,
430
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
431
			);
432
433
			wp_send_json( $data );
434
435
		}
436
437
		die();
438
	}
439
440
	/**
441
	 * Process ajax checkout form.
442
	 */
443
	public static function checkout() {
444
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
445
			define( 'WOOCOMMERCE_CHECKOUT', true );
446
		}
447
448
		WC()->checkout()->process_checkout();
449
450
		die(0);
451
	}
452
453
	/**
454
	 * Get a matching variation based on posted attributes.
455
	 */
456
	public static function get_variation() {
457
		ob_start();
458
459
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
460
			die();
461
		}
462
463
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
464
465
		if ( $variation_id ) {
466
			$variation = $variable_product->get_available_variation( $variation_id );
467
		} else {
468
			$variation = false;
469
		}
470
471
		wp_send_json( $variation );
472
473
		die();
474
	}
475
476
	/**
477
	 * Feature a product from admin.
478
	 */
479
	public static function feature_product() {
480
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
481
			$product_id = absint( $_GET['product_id'] );
482
483
			if ( 'product' === get_post_type( $product_id ) ) {
484
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
485
486
				delete_transient( 'wc_featured_products' );
487
			}
488
		}
489
490
		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' ) );
491
		die();
492
	}
493
494
	/**
495
	 * Mark an order with a status.
496
	 */
497
	public static function mark_order_status() {
498
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
499
			$status   = sanitize_text_field( $_GET['status'] );
500
			$order_id = absint( $_GET['order_id'] );
501
502
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
503
				$order = wc_get_order( $order_id );
504
				$order->update_status( $status, '', true );
505
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
506
			}
507
		}
508
509
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
510
		die();
511
	}
512
513
	/**
514
	 * Add an attribute row.
515
	 */
516
	public static function add_attribute() {
517
		ob_start();
518
519
		check_ajax_referer( 'add-attribute', 'security' );
520
521
		if ( ! current_user_can( 'edit_products' ) ) {
522
			die(-1);
523
		}
524
525
		global $wc_product_attributes;
526
527
		$thepostid     = 0;
528
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
529
		$i             = absint( $_POST['i'] );
530
		$position      = 0;
531
		$metabox_class = array();
532
		$attribute     = array(
533
			'name'         => $taxonomy,
534
			'value'        => '',
535
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
536
			'is_variation' => 0,
537
			'is_taxonomy'  => $taxonomy ? 1 : 0
538
		);
539
540
		if ( $taxonomy ) {
541
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
542
			$metabox_class[]    = 'taxonomy';
543
			$metabox_class[]    = $taxonomy;
544
			$attribute_label    = wc_attribute_label( $taxonomy );
545
		} else {
546
			$attribute_label = '';
547
		}
548
549
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
550
		die();
551
	}
552
553
	/**
554
	 * Add a new attribute via ajax function.
555
	 */
556
	public static function add_new_attribute() {
557
		ob_start();
558
559
		check_ajax_referer( 'add-attribute', 'security' );
560
561
		if ( ! current_user_can( 'manage_product_terms' ) ) {
562
			die(-1);
563
		}
564
565
		$taxonomy = esc_attr( $_POST['taxonomy'] );
566
		$term     = wc_clean( $_POST['term'] );
567
568
		if ( taxonomy_exists( $taxonomy ) ) {
569
570
			$result = wp_insert_term( $term, $taxonomy );
571
572
			if ( is_wp_error( $result ) ) {
573
				wp_send_json( array(
574
					'error' => $result->get_error_message()
575
				) );
576
			} else {
577
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
578
				wp_send_json( array(
579
					'term_id' => $term->term_id,
580
					'name'    => $term->name,
581
					'slug'    => $term->slug
582
				) );
583
			}
584
		}
585
586
		die();
587
	}
588
589
	/**
590
	 * Delete variations via ajax function.
591
	 */
592
	public static function remove_variations() {
593
		check_ajax_referer( 'delete-variations', 'security' );
594
595
		if ( ! current_user_can( 'edit_products' ) ) {
596
			die(-1);
597
		}
598
599
		$variation_ids = (array) $_POST['variation_ids'];
600
601
		foreach ( $variation_ids as $variation_id ) {
602
			$variation = get_post( $variation_id );
603
604
			if ( $variation && 'product_variation' == $variation->post_type ) {
605
				wp_delete_post( $variation_id );
606
			}
607
		}
608
609
		die();
610
	}
611
612
	/**
613
	 * Save attributes via ajax.
614
	 */
615
	public static function save_attributes() {
616
617
		check_ajax_referer( 'save-attributes', 'security' );
618
619
		if ( ! current_user_can( 'edit_products' ) ) {
620
			die(-1);
621
		}
622
623
		// Get post data
624
		parse_str( $_POST['data'], $data );
625
		$post_id = absint( $_POST['post_id'] );
626
627
		// Save Attributes
628
		$attributes = array();
629
630
		if ( isset( $data['attribute_names'] ) ) {
631
632
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
633
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
634
635
			if ( isset( $data['attribute_visibility'] ) ) {
636
				$attribute_visibility = $data['attribute_visibility'];
637
			}
638
639
			if ( isset( $data['attribute_variation'] ) ) {
640
				$attribute_variation = $data['attribute_variation'];
641
			}
642
643
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
644
			$attribute_position      = $data['attribute_position'];
645
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
646
647
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
648
				if ( empty( $attribute_names[ $i ] ) ) {
649
					continue;
650
				}
651
652
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
653
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
654
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
655
656
				if ( $is_taxonomy ) {
657
658
					if ( isset( $attribute_values[ $i ] ) ) {
659
660
						// Select based attributes - Format values (posted values are slugs)
661
						if ( is_array( $attribute_values[ $i ] ) ) {
662
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
663
664
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
665
						} else {
666
							$values     = array();
667
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
668
669
							foreach ( $raw_values as $value ) {
670
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
671
								if ( ! $term ) {
672
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
673
674
									if ( $term && ! is_wp_error( $term ) ) {
675
										$values[] = $term['term_id'];
676
									}
677
								} else {
678
									$values[] = $term->term_id;
679
								}
680
							}
681
						}
682
683
						// Remove empty items in the array
684
						$values = array_filter( $values, 'strlen' );
685
686
					} else {
687
						$values = array();
688
					}
689
690
					// Update post terms
691
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
692
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
693
					}
694
695 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...
696
						// Add attribute to array, but don't set values
697
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
698
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
699
							'value' 		=> '',
700
							'position' 		=> $attribute_position[ $i ],
701
							'is_visible' 	=> $is_visible,
702
							'is_variation' 	=> $is_variation,
703
							'is_taxonomy' 	=> $is_taxonomy
704
						);
705
					}
706
707 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...
708
709
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
710
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
711
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
712
713
					// Custom attribute - Add attribute to array and set the values
714
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
715
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
716
						'value' 		=> $values,
717
						'position' 		=> $attribute_position[ $i ],
718
						'is_visible' 	=> $is_visible,
719
						'is_variation' 	=> $is_variation,
720
						'is_taxonomy' 	=> $is_taxonomy
721
					);
722
				}
723
724
			 }
725
		}
726
727
		if ( ! function_exists( 'attributes_cmp' ) ) {
728
			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...
729
				if ( $a['position'] == $b['position'] ) {
730
					return 0;
731
				}
732
733
				return ( $a['position'] < $b['position'] ) ? -1 : 1;
734
			}
735
		}
736
		uasort( $attributes, 'attributes_cmp' );
737
738
		update_post_meta( $post_id, '_product_attributes', $attributes );
739
740
		die();
741
	}
742
743
	/**
744
	 * Add variation via ajax function.
745
	 */
746
	public static function add_variation() {
747
748
		check_ajax_referer( 'add-variation', 'security' );
749
750
		if ( ! current_user_can( 'edit_products' ) ) {
751
			die(-1);
752
		}
753
754
		global $post;
755
756
		$post_id = intval( $_POST['post_id'] );
757
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
758
		$loop    = intval( $_POST['loop'] );
759
760
		$variation = array(
761
			'post_title'   => 'Product #' . $post_id . ' Variation',
762
			'post_content' => '',
763
			'post_status'  => 'publish',
764
			'post_author'  => get_current_user_id(),
765
			'post_parent'  => $post_id,
766
			'post_type'    => 'product_variation',
767
			'menu_order'   => -1
768
		);
769
770
		$variation_id = wp_insert_post( $variation );
771
772
		do_action( 'woocommerce_create_product_variation', $variation_id );
773
774
		if ( $variation_id ) {
775
			$variation        = get_post( $variation_id );
776
			$variation_meta   = get_post_meta( $variation_id );
777
			$variation_data   = array();
778
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
779
			$variation_fields = array(
780
				'_sku'                   => '',
781
				'_stock'                 => '',
782
				'_regular_price'         => '',
783
				'_sale_price'            => '',
784
				'_weight'                => '',
785
				'_length'                => '',
786
				'_width'                 => '',
787
				'_height'                => '',
788
				'_download_limit'        => '',
789
				'_download_expiry'       => '',
790
				'_downloadable_files'    => '',
791
				'_downloadable'          => '',
792
				'_virtual'               => '',
793
				'_thumbnail_id'          => '',
794
				'_sale_price_dates_from' => '',
795
				'_sale_price_dates_to'   => '',
796
				'_manage_stock'          => '',
797
				'_stock_status'          => '',
798
				'_backorders'            => null,
799
				'_tax_class'             => null,
800
				'_variation_description' => ''
801
			);
802
803 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...
804
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
805
			}
806
807
			// Add the variation attributes
808
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
809
810
			// Formatting
811
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
812
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
813
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
814
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
815
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
816
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
817
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
818
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
819
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
820
			$variation_data['menu_order']     = $variation->menu_order;
821
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
822
823
			// Get tax classes
824
			$tax_classes           = WC_Tax::get_tax_classes();
825
			$tax_class_options     = array();
826
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
827
828 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...
829
				foreach ( $tax_classes as $class ) {
830
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
831
				}
832
			}
833
834
			// Set backorder options
835
			$backorder_options = array(
836
				'no'     => __( 'Do not allow', 'woocommerce' ),
837
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
838
				'yes'    => __( 'Allow', 'woocommerce' )
839
			);
840
841
			// set stock status options
842
			$stock_status_options = array(
843
				'instock'    => __( 'In stock', 'woocommerce' ),
844
				'outofstock' => __( 'Out of stock', 'woocommerce' )
845
			);
846
847
			// Get attributes
848
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
849
850
			$parent_data = array(
851
				'id'                   => $post_id,
852
				'attributes'           => $attributes,
853
				'tax_class_options'    => $tax_class_options,
854
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
855
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
856
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
857
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
858
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
859
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
860
				'backorder_options'    => $backorder_options,
861
				'stock_status_options' => $stock_status_options
862
			);
863
864
			if ( ! $parent_data['weight'] ) {
865
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
866
			}
867
868
			if ( ! $parent_data['length'] ) {
869
				$parent_data['length'] = wc_format_localized_decimal( 0 );
870
			}
871
872
			if ( ! $parent_data['width'] ) {
873
				$parent_data['width'] = wc_format_localized_decimal( 0 );
874
			}
875
876
			if ( ! $parent_data['height'] ) {
877
				$parent_data['height'] = wc_format_localized_decimal( 0 );
878
			}
879
880
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
881
		}
882
883
		die();
884
	}
885
886
	/**
887
	 * Link all variations via ajax function.
888
	 */
889
	public static function link_all_variations() {
890
891
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
892
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
893
		}
894
895
		check_ajax_referer( 'link-variations', 'security' );
896
897
		if ( ! current_user_can( 'edit_products' ) ) {
898
			die(-1);
899
		}
900
901 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...
902
			@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...
903
		}
904
905
		$post_id = intval( $_POST['post_id'] );
906
907
		if ( ! $post_id ) {
908
			die();
909
		}
910
911
		$variations = array();
912
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
913
914
		// Put variation attributes into an array
915
		foreach ( $_product->get_attributes() as $attribute ) {
916
917
			if ( ! $attribute['is_variation'] ) {
918
				continue;
919
			}
920
921
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
922
923
			if ( $attribute['is_taxonomy'] ) {
924
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
925
			} else {
926
				$options = explode( WC_DELIMITER, $attribute['value'] );
927
			}
928
929
			$options = array_map( 'trim', $options );
930
931
			$variations[ $attribute_field_name ] = $options;
932
		}
933
934
		// Quit out if none were found
935
		if ( sizeof( $variations ) == 0 ) {
936
			die();
937
		}
938
939
		// Get existing variations so we don't create duplicates
940
		$available_variations = array();
941
942
		foreach( $_product->get_children() as $child_id ) {
943
			$child = $_product->get_child( $child_id );
944
945
			if ( ! empty( $child->variation_id ) ) {
946
				$available_variations[] = $child->get_variation_attributes();
947
			}
948
		}
949
950
		// Created posts will all have the following data
951
		$variation_post_data = array(
952
			'post_title'   => 'Product #' . $post_id . ' Variation',
953
			'post_content' => '',
954
			'post_status'  => 'publish',
955
			'post_author'  => get_current_user_id(),
956
			'post_parent'  => $post_id,
957
			'post_type'    => 'product_variation'
958
		);
959
960
		$variation_ids       = array();
961
		$added               = 0;
962
		$possible_variations = wc_array_cartesian( $variations );
963
964
		foreach ( $possible_variations as $variation ) {
965
966
			// Check if variation already exists
967
			if ( in_array( $variation, $available_variations ) ) {
968
				continue;
969
			}
970
971
			$variation_id = wp_insert_post( $variation_post_data );
972
973
			$variation_ids[] = $variation_id;
974
975
			foreach ( $variation as $key => $value ) {
976
				update_post_meta( $variation_id, $key, $value );
977
			}
978
979
			// Save stock status
980
			update_post_meta( $variation_id, '_stock_status', 'instock' );
981
982
			$added++;
983
984
			do_action( 'product_variation_linked', $variation_id );
985
986
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
987
				break;
988
			}
989
		}
990
991
		delete_transient( 'wc_product_children_' . $post_id );
992
993
		echo $added;
994
995
		die();
996
	}
997
998
	/**
999
	 * Delete download permissions via ajax function.
1000
	 */
1001
	public static function revoke_access_to_download() {
1002
1003
		check_ajax_referer( 'revoke-access', 'security' );
1004
1005
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1006
			die(-1);
1007
		}
1008
1009
		global $wpdb;
1010
1011
		$download_id = $_POST['download_id'];
1012
		$product_id  = intval( $_POST['product_id'] );
1013
		$order_id    = intval( $_POST['order_id'] );
1014
1015
		$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 ) );
1016
1017
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
1018
1019
		die();
1020
	}
1021
1022
	/**
1023
	 * Grant download permissions via ajax function.
1024
	 */
1025
	public static function grant_access_to_download() {
1026
1027
		check_ajax_referer( 'grant-access', 'security' );
1028
1029
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1030
			die(-1);
1031
		}
1032
1033
		global $wpdb;
1034
1035
		$wpdb->hide_errors();
1036
1037
		$order_id     = intval( $_POST['order_id'] );
1038
		$product_ids  = $_POST['product_ids'];
1039
		$loop         = intval( $_POST['loop'] );
1040
		$file_counter = 0;
1041
		$order        = wc_get_order( $order_id );
1042
1043
		if ( ! is_array( $product_ids ) ) {
1044
			$product_ids = array( $product_ids );
1045
		}
1046
1047
		foreach ( $product_ids as $product_id ) {
1048
			$product = wc_get_product( $product_id );
1049
			$files   = $product->get_files();
1050
1051
			if ( ! $order->billing_email ) {
1052
				die();
1053
			}
1054
1055
			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...
1056
				foreach ( $files as $download_id => $file ) {
1057
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
1058
1059
						// insert complete - get inserted data
1060
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1061
1062
						$loop ++;
1063
						$file_counter ++;
1064
1065 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...
1066
							$file_count = $file['name'];
1067
						} else {
1068
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1069
						}
1070
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1071
					}
1072
				}
1073
			}
1074
		}
1075
1076
		die();
1077
	}
1078
1079
	/**
1080
	 * Get customer details via ajax.
1081
	 */
1082
	public static function get_customer_details() {
1083
		ob_start();
1084
1085
		check_ajax_referer( 'get-customer-details', 'security' );
1086
1087
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1088
			die(-1);
1089
		}
1090
1091
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1092
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1093
1094
		$customer_data = array(
1095
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1096
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1097
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1098
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1099
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1100
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1101
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1102
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1103
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1104
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1105
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1106
		);
1107
1108
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1109
1110
		wp_send_json( $customer_data );
1111
	}
1112
1113
	/**
1114
	 * Add order item via ajax.
1115
	 */
1116
	public static function add_order_item() {
1117
		check_ajax_referer( 'order-item', 'security' );
1118
1119
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1120
			die(-1);
1121
		}
1122
1123
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1124
		$order_id    = absint( $_POST['order_id'] );
1125
1126
		// Find the item
1127
		if ( ! is_numeric( $item_to_add ) ) {
1128
			die();
1129
		}
1130
1131
		$post = get_post( $item_to_add );
1132
1133
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1134
			die();
1135
		}
1136
1137
		$_product    = wc_get_product( $post->ID );
1138
		$order       = wc_get_order( $order_id );
1139
		$order_taxes = $order->get_taxes();
1140
		$class       = 'new_row';
1141
1142
		// Set values
1143
		$item = array();
1144
1145
		$item['product_id']        = $_product->id;
1146
		$item['variation_id']      = isset( $_product->variation_id ) ? $_product->variation_id : '';
1147
		$item['variation_data']    = $item['variation_id'] ? $_product->get_variation_attributes() : '';
1148
		$item['name']              = $_product->get_title();
1149
		$item['tax_class']         = $_product->get_tax_class();
1150
		$item['qty']               = 1;
1151
		$item['line_subtotal']     = wc_format_decimal( $_product->get_price_excluding_tax() );
1152
		$item['line_subtotal_tax'] = '';
1153
		$item['line_total']        = wc_format_decimal( $_product->get_price_excluding_tax() );
1154
		$item['line_tax']          = '';
1155
		$item['type']              = 'line_item';
1156
1157
		// Add line item
1158
		$item_id = wc_add_order_item( $order_id, array(
1159
			'order_item_name' 		=> $item['name'],
1160
			'order_item_type' 		=> 'line_item'
1161
		) );
1162
1163
		// Add line item meta
1164
		if ( $item_id ) {
1165
			wc_add_order_item_meta( $item_id, '_qty', $item['qty'] );
1166
			wc_add_order_item_meta( $item_id, '_tax_class', $item['tax_class'] );
1167
			wc_add_order_item_meta( $item_id, '_product_id', $item['product_id'] );
1168
			wc_add_order_item_meta( $item_id, '_variation_id', $item['variation_id'] );
1169
			wc_add_order_item_meta( $item_id, '_line_subtotal', $item['line_subtotal'] );
1170
			wc_add_order_item_meta( $item_id, '_line_subtotal_tax', $item['line_subtotal_tax'] );
1171
			wc_add_order_item_meta( $item_id, '_line_total', $item['line_total'] );
1172
			wc_add_order_item_meta( $item_id, '_line_tax', $item['line_tax'] );
1173
1174
			// Since 2.2
1175
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
1176
1177
			// Store variation data in meta
1178 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...
1179
				foreach ( $item['variation_data'] as $key => $value ) {
1180
					wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
1181
				}
1182
			}
1183
1184
			do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1185
		}
1186
1187
		$item['item_meta']       = $order->get_item_meta( $item_id );
1188
		$item['item_meta_array'] = $order->get_item_meta_array( $item_id );
1189
		$item                    = $order->expand_item_meta( $item );
1190
		$item                    = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
1191
1192
		include( 'admin/meta-boxes/views/html-order-item.php' );
1193
1194
		// Quit out
1195
		die();
1196
	}
1197
1198
	/**
1199
	 * Add order fee via ajax.
1200
	 */
1201
	public static function add_order_fee() {
1202
1203
		check_ajax_referer( 'order-item', 'security' );
1204
1205
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1206
			die(-1);
1207
		}
1208
1209
		$order_id      = absint( $_POST['order_id'] );
1210
		$order         = wc_get_order( $order_id );
1211
		$order_taxes   = $order->get_taxes();
1212
		$item          = array();
1213
1214
		// Add new fee
1215
		$fee            = new stdClass();
1216
		$fee->name      = '';
1217
		$fee->tax_class = '';
1218
		$fee->taxable   = $fee->tax_class !== '0';
1219
		$fee->amount    = '';
1220
		$fee->tax       = '';
1221
		$fee->tax_data  = array();
1222
		$item_id        = $order->add_fee( $fee );
1223
1224
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1225
1226
		// Quit out
1227
		die();
1228
	}
1229
1230
	/**
1231
	 * Add order shipping cost via ajax.
1232
	 */
1233
	public static function add_order_shipping() {
1234
1235
		check_ajax_referer( 'order-item', 'security' );
1236
1237
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1238
			die(-1);
1239
		}
1240
1241
		$order_id         = absint( $_POST['order_id'] );
1242
		$order            = wc_get_order( $order_id );
1243
		$order_taxes      = $order->get_taxes();
1244
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1245
		$item             = array();
1246
1247
		// Add new shipping
1248
		$shipping        = new stdClass();
1249
		$shipping->label = '';
1250
		$shipping->id    = '';
1251
		$shipping->cost  = '';
1252
		$shipping->taxes = array();
1253
		$item_id         = $order->add_shipping( $shipping );
1254
1255
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1256
1257
		// Quit out
1258
		die();
1259
	}
1260
1261
	/**
1262
	 * Add order tax column via ajax.
1263
	 */
1264
	public static function add_order_tax() {
1265
		global $wpdb;
1266
1267
		check_ajax_referer( 'order-item', 'security' );
1268
1269
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1270
			die(-1);
1271
		}
1272
1273
		$order_id = absint( $_POST['order_id'] );
1274
		$rate_id  = absint( $_POST['rate_id'] );
1275
		$order    = wc_get_order( $order_id );
1276
		$data     = get_post_meta( $order_id );
1277
1278
		// Add new tax
1279
		$order->add_tax( $rate_id, 0, 0 );
1280
1281
		// Return HTML items
1282
		include( 'admin/meta-boxes/views/html-order-items.php' );
1283
1284
		die();
1285
	}
1286
1287
	/**
1288
	 * Remove an order item.
1289
	 */
1290
	public static function remove_order_item() {
1291
		check_ajax_referer( 'order-item', 'security' );
1292
1293
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1294
			die(-1);
1295
		}
1296
1297
		$order_item_ids = $_POST['order_item_ids'];
1298
1299
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1300
			$order_item_ids = array( $order_item_ids );
1301
		}
1302
1303
		if ( sizeof( $order_item_ids ) > 0 ) {
1304
			foreach( $order_item_ids as $id ) {
1305
				wc_delete_order_item( absint( $id ) );
1306
			}
1307
		}
1308
1309
		die();
1310
	}
1311
1312
	/**
1313
	 * Remove an order tax.
1314
	 */
1315
	public static function remove_order_tax() {
1316
1317
		check_ajax_referer( 'order-item', 'security' );
1318
1319
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1320
			die(-1);
1321
		}
1322
1323
		$order_id = absint( $_POST['order_id'] );
1324
		$rate_id  = absint( $_POST['rate_id'] );
1325
1326
		wc_delete_order_item( $rate_id );
1327
1328
		// Return HTML items
1329
		$order = wc_get_order( $order_id );
1330
		$data  = get_post_meta( $order_id );
1331
		include( 'admin/meta-boxes/views/html-order-items.php' );
1332
1333
		die();
1334
	}
1335
1336
	/**
1337
	 * Reduce order item stock.
1338
	 */
1339
	public static function reduce_order_item_stock() {
1340
		check_ajax_referer( 'order-item', 'security' );
1341
1342
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1343
			die(-1);
1344
		}
1345
1346
		$order_id       = absint( $_POST['order_id'] );
1347
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1348
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1349
		$order          = wc_get_order( $order_id );
1350
		$order_items    = $order->get_items();
1351
		$return         = array();
1352
1353
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1354
1355
			foreach ( $order_items as $item_id => $order_item ) {
1356
1357
				// Only reduce checked items
1358
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1359
					continue;
1360
				}
1361
1362
				$_product = $order->get_product_from_item( $order_item );
1363
1364
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1365
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1366
					$new_stock    = $_product->reduce_stock( $stock_change );
1367
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1368
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1369
					$return[]     = $note;
1370
1371
					$order->add_order_note( $note );
1372
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1373
				}
1374
			}
1375
1376
			do_action( 'woocommerce_reduce_order_stock', $order );
1377
1378
			if ( empty( $return ) ) {
1379
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1380
			}
1381
1382
			echo implode( ', ', $return );
1383
		}
1384
1385
		die();
1386
	}
1387
1388
	/**
1389
	 * Increase order item stock.
1390
	 */
1391
	public static function increase_order_item_stock() {
1392
		check_ajax_referer( 'order-item', 'security' );
1393
1394
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1395
			die(-1);
1396
		}
1397
1398
		$order_id       = absint( $_POST['order_id'] );
1399
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1400
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1401
		$order          = wc_get_order( $order_id );
1402
		$order_items    = $order->get_items();
1403
		$return         = array();
1404
1405
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1406
1407
			foreach ( $order_items as $item_id => $order_item ) {
1408
1409
				// Only reduce checked items
1410
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1411
					continue;
1412
				}
1413
1414
				$_product = $order->get_product_from_item( $order_item );
1415
1416
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1417
					$old_stock    = $_product->get_stock_quantity();
1418
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1419
					$new_quantity = $_product->increase_stock( $stock_change );
1420
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1421
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1422
					$return[]     = $note;
1423
1424
					$order->add_order_note( $note );
1425
				}
1426
			}
1427
1428
			do_action( 'woocommerce_restore_order_stock', $order );
1429
1430
			if ( empty( $return ) ) {
1431
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1432
			}
1433
1434
			echo implode( ', ', $return );
1435
		}
1436
1437
		die();
1438
	}
1439
1440
	/**
1441
	 * Add some meta to a line item.
1442
	 */
1443
	public static function add_order_item_meta() {
1444
		check_ajax_referer( 'order-item', 'security' );
1445
1446
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1447
			die(-1);
1448
		}
1449
1450
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1451
1452
		if ( $meta_id ) {
1453
			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>';
1454
		}
1455
1456
		die();
1457
	}
1458
1459
	/**
1460
	 * Remove meta from a line item.
1461
	 */
1462
	public static function remove_order_item_meta() {
1463
		global $wpdb;
1464
1465
		check_ajax_referer( 'order-item', 'security' );
1466
1467
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1468
			die(-1);
1469
		}
1470
1471
		$meta_id = absint( $_POST['meta_id'] );
1472
1473
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_id = %d", $meta_id ) );
1474
1475
		die();
1476
	}
1477
1478
	/**
1479
	 * Calc line tax.
1480
	 */
1481
	public static function calc_line_taxes() {
1482
		global $wpdb;
1483
1484
		check_ajax_referer( 'calc-totals', 'security' );
1485
1486
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1487
			die(-1);
1488
		}
1489
1490
		$tax            = new WC_Tax();
1491
		$tax_based_on   = get_option( 'woocommerce_tax_based_on' );
1492
		$order_id       = absint( $_POST['order_id'] );
1493
		$items          = array();
1494
		$country        = strtoupper( esc_attr( $_POST['country'] ) );
1495
		$state          = strtoupper( esc_attr( $_POST['state'] ) );
1496
		$postcode       = strtoupper( esc_attr( $_POST['postcode'] ) );
1497
		$city           = wc_clean( esc_attr( $_POST['city'] ) );
1498
		$order          = wc_get_order( $order_id );
1499
		$taxes          = array();
1500
		$shipping_taxes = array();
1501
1502
		// Default to base
1503 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...
1504
			$default  = wc_get_base_location();
1505
			$country  = $default['country'];
1506
			$state    = $default['state'];
1507
			$postcode = '';
1508
			$city     = '';
1509
		}
1510
1511
		// Parse the jQuery serialized items
1512
		parse_str( $_POST['items'], $items );
1513
1514
		// Prevent undefined warnings
1515
		if ( ! isset( $items['line_tax'] ) ) {
1516
			$items['line_tax'] = array();
1517
		}
1518
		if ( ! isset( $items['line_subtotal_tax'] ) ) {
1519
			$items['line_subtotal_tax'] = array();
1520
		}
1521
		$items['order_taxes'] = array();
1522
1523
		// Action
1524
		$items = apply_filters( 'woocommerce_ajax_calc_line_taxes', $items, $order_id, $country, $_POST );
1525
1526
		// Get items and fees taxes
1527
		if ( isset( $items['order_item_id'] ) ) {
1528
			$line_total = $line_subtotal = $order_item_tax_class = array();
1529
1530
			foreach ( $items['order_item_id'] as $item_id ) {
1531
				$item_id                          = absint( $item_id );
1532
				$line_total[ $item_id ]           = isset( $items['line_total'][ $item_id ] ) ? wc_format_decimal( $items['line_total'][ $item_id ] ) : 0;
1533
				$line_subtotal[ $item_id ]        = isset( $items['line_subtotal'][ $item_id ] ) ? wc_format_decimal( $items['line_subtotal'][ $item_id ] ) : $line_total[ $item_id ];
1534
				$order_item_tax_class[ $item_id ] = isset( $items['order_item_tax_class'][ $item_id ] ) ? sanitize_text_field( $items['order_item_tax_class'][ $item_id ] ) : '';
1535
				$product_id                       = $order->get_item_meta( $item_id, '_product_id', true );
1536
1537
				// Get product details
1538
				if ( get_post_type( $product_id ) == 'product' ) {
1539
					$_product        = wc_get_product( $product_id );
1540
					$item_tax_status = $_product->get_tax_status();
1541
				} else {
1542
					$item_tax_status = 'taxable';
1543
				}
1544
1545
				if ( '0' !== $order_item_tax_class[ $item_id ] && 'taxable' === $item_tax_status ) {
1546
					$tax_rates = WC_Tax::find_rates( array(
1547
						'country'   => $country,
1548
						'state'     => $state,
1549
						'postcode'  => $postcode,
1550
						'city'      => $city,
1551
						'tax_class' => $order_item_tax_class[ $item_id ]
1552
					) );
1553
1554
					$line_taxes          = WC_Tax::calc_tax( $line_total[ $item_id ], $tax_rates, false );
1555
					$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal[ $item_id ], $tax_rates, false );
1556
1557
					// Set the new line_tax
1558
					foreach ( $line_taxes as $_tax_id => $_tax_value ) {
1559
						$items['line_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1560
					}
1561
1562
					// Set the new line_subtotal_tax
1563
					foreach ( $line_subtotal_taxes as $_tax_id => $_tax_value ) {
1564
						$items['line_subtotal_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1565
					}
1566
1567
					// Sum the item taxes
1568
					foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1569
						$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1570
					}
1571
				}
1572
			}
1573
		}
1574
1575
		// Get shipping taxes
1576
		if ( isset( $items['shipping_method_id'] ) ) {
1577
			$matched_tax_rates = array();
1578
1579
			$tax_rates = WC_Tax::find_rates( array(
1580
				'country'   => $country,
1581
				'state'     => $state,
1582
				'postcode'  => $postcode,
1583
				'city'      => $city,
1584
				'tax_class' => ''
1585
			) );
1586
1587 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...
1588
				foreach ( $tax_rates as $key => $rate ) {
1589
					if ( isset( $rate['shipping'] ) && 'yes' == $rate['shipping'] ) {
1590
						$matched_tax_rates[ $key ] = $rate;
1591
					}
1592
				}
1593
			}
1594
1595
			$shipping_cost = $shipping_taxes = array();
1596
1597
			foreach ( $items['shipping_method_id'] as $item_id ) {
1598
				$item_id                   = absint( $item_id );
1599
				$shipping_cost[ $item_id ] = isset( $items['shipping_cost'][ $item_id ] ) ? wc_format_decimal( $items['shipping_cost'][ $item_id ] ) : 0;
1600
				$_shipping_taxes           = WC_Tax::calc_shipping_tax( $shipping_cost[ $item_id ], $matched_tax_rates );
1601
1602
				// Set the new shipping_taxes
1603
				foreach ( $_shipping_taxes as $_tax_id => $_tax_value ) {
1604
					$items['shipping_taxes'][ $item_id ][ $_tax_id ] = $_tax_value;
1605
1606
					$shipping_taxes[ $_tax_id ] = isset( $shipping_taxes[ $_tax_id ] ) ? $shipping_taxes[ $_tax_id ] + $_tax_value : $_tax_value;
1607
				}
1608
			}
1609
		}
1610
1611
		// Remove old tax rows
1612
		$order->remove_order_items( 'tax' );
1613
1614
		// Add tax rows
1615 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...
1616
			$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 );
1617
		}
1618
1619
		// Create the new order_taxes
1620
		foreach ( $order->get_taxes() as $tax_id => $tax_item ) {
1621
			$items['order_taxes'][ $tax_id ] = absint( $tax_item['rate_id'] );
1622
		}
1623
1624
		$items = apply_filters( 'woocommerce_ajax_after_calc_line_taxes', $items, $order_id, $country, $_POST );
1625
1626
		// Save order items
1627
		wc_save_order_items( $order_id, $items );
1628
1629
		// Return HTML items
1630
		$order = wc_get_order( $order_id );
1631
		$data  = get_post_meta( $order_id );
1632
		include( 'admin/meta-boxes/views/html-order-items.php' );
1633
1634
		die();
1635
	}
1636
1637
	/**
1638
	 * Save order items via ajax.
1639
	 */
1640
	public static function save_order_items() {
1641
		check_ajax_referer( 'order-item', 'security' );
1642
1643
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1644
			die(-1);
1645
		}
1646
1647
		if ( isset( $_POST['order_id'] ) && isset( $_POST['items'] ) ) {
1648
			$order_id = absint( $_POST['order_id'] );
1649
1650
			// Parse the jQuery serialized items
1651
			$items = array();
1652
			parse_str( $_POST['items'], $items );
1653
1654
			// Save order items
1655
			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...
1656
1657
			// Return HTML items
1658
			$order = wc_get_order( $order_id );
1659
			$data  = get_post_meta( $order_id );
1660
			include( 'admin/meta-boxes/views/html-order-items.php' );
1661
		}
1662
1663
		die();
1664
	}
1665
1666
	/**
1667
	 * Load order items via ajax.
1668
	 */
1669
	public static function load_order_items() {
1670
		check_ajax_referer( 'order-item', 'security' );
1671
1672
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1673
			die(-1);
1674
		}
1675
1676
		// Return HTML items
1677
		$order_id = absint( $_POST['order_id'] );
1678
		$order    = wc_get_order( $order_id );
1679
		$data     = get_post_meta( $order_id );
1680
		include( 'admin/meta-boxes/views/html-order-items.php' );
1681
1682
		die();
1683
	}
1684
1685
	/**
1686
	 * Add order note via ajax.
1687
	 */
1688
	public static function add_order_note() {
1689
1690
		check_ajax_referer( 'add-order-note', 'security' );
1691
1692
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1693
			die(-1);
1694
		}
1695
1696
		$post_id   = absint( $_POST['post_id'] );
1697
		$note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
1698
		$note_type = $_POST['note_type'];
1699
1700
		$is_customer_note = $note_type == 'customer' ? 1 : 0;
1701
1702
		if ( $post_id > 0 ) {
1703
			$order      = wc_get_order( $post_id );
1704
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1705
1706
			echo '<li rel="' . esc_attr( $comment_id ) . '" class="note ';
1707
			if ( $is_customer_note ) {
1708
				echo 'customer-note';
1709
			}
1710
			echo '"><div class="note_content">';
1711
			echo wpautop( wptexturize( $note ) );
1712
			echo '</div><p class="meta"><a href="#" class="delete_note">'.__( 'Delete note', 'woocommerce' ).'</a></p>';
1713
			echo '</li>';
1714
		}
1715
1716
		// Quit out
1717
		die();
1718
	}
1719
1720
	/**
1721
	 * Delete order note via ajax.
1722
	 */
1723
	public static function delete_order_note() {
1724
1725
		check_ajax_referer( 'delete-order-note', 'security' );
1726
1727
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1728
			die(-1);
1729
		}
1730
1731
		$note_id = (int) $_POST['note_id'];
1732
1733
		if ( $note_id > 0 ) {
1734
			wp_delete_comment( $note_id );
1735
		}
1736
1737
		// Quit out
1738
		die();
1739
	}
1740
1741
	/**
1742
	 * Search for products and echo json.
1743
	 *
1744
	 * @param string $x (default: '')
1745
	 * @param string $post_types (default: array('product'))
1746
	 */
1747
	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...
1748
		global $wpdb;
1749
1750
		ob_start();
1751
1752
		check_ajax_referer( 'search-products', 'security' );
1753
1754
		$term = (string) wc_clean( stripslashes( $_GET['term'] ) );
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 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...
1792
			$query .= " AND posts.ID NOT IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['exclude'] ) ) ) . ")";
1793
		}
1794
1795 View Code Duplication
		if ( ! empty( $_GET['include'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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