Completed
Pull Request — master (#9826)
by Mike
15:37
created

WC_AJAX::get_refreshed_fragments()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 21
Code Lines 9

Duplication

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