Completed
Push — master ( e4380d...bbc130 )
by Mike
07:56
created

WC_AJAX::link_all_variations()   D

Complexity

Conditions 14
Paths 156

Size

Total Lines 106
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 54
c 1
b 0
f 0
nc 156
nop 0
dl 0
loc 106
rs 4.6283

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
365
				WC()->customer->set_shipping_country( $_POST['s_country'] );
366
				WC()->customer->calculated_shipping( true );
367
			}
368
369
			if ( isset( $_POST['s_state'] ) ) {
370
				WC()->customer->set_shipping_state( $_POST['s_state'] );
371
			}
372
373
			if ( isset( $_POST['s_postcode'] ) ) {
374
				WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
375
			}
376
377
			if ( isset( $_POST['s_city'] ) ) {
378
				WC()->customer->set_shipping_city( $_POST['s_city'] );
379
			}
380
381
			if ( isset( $_POST['s_address'] ) ) {
382
				WC()->customer->set_shipping_address( $_POST['s_address'] );
383
			}
384
385
			if ( isset( $_POST['s_address_2'] ) ) {
386
				WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
387
			}
388
		}
389
390
		WC()->cart->calculate_totals();
391
392
		// Get order review fragment
393
		ob_start();
394
		woocommerce_order_review();
395
		$woocommerce_order_review = ob_get_clean();
396
397
		// Get checkout payment fragment
398
		ob_start();
399
		woocommerce_checkout_payment();
400
		$woocommerce_checkout_payment = ob_get_clean();
401
402
		// Get messages if reload checkout is not true
403
		$messages = '';
404
		if ( ! isset( WC()->session->reload_checkout ) ) {
405
			ob_start();
406
			wc_print_notices();
407
			$messages = ob_get_clean();
408
		}
409
410
		$data = array(
411
			'result'    => empty( $messages ) ? 'success' : 'failure',
412
			'messages'  => $messages,
413
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
414
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
415
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
416
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
417
			) )
418
		);
419
420
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
421
422
		wp_send_json( $data );
423
424
		die();
425
	}
426
427
	/**
428
	 * AJAX add to cart.
429
	 */
430
	public static function add_to_cart() {
431
		ob_start();
432
433
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
434
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
435
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
436
		$product_status    = get_post_status( $product_id );
437
438
		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...
439
440
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
441
442
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
443
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
444
			}
445
446
			// Return fragments
447
			self::get_refreshed_fragments();
448
449
		} else {
450
451
			// If there was an error adding to the cart, redirect to the product page to show any errors
452
			$data = array(
453
				'error'       => true,
454
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
455
			);
456
457
			wp_send_json( $data );
458
459
		}
460
461
		die();
462
	}
463
464
	/**
465
	 * Process ajax checkout form.
466
	 */
467
	public static function checkout() {
468
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
469
			define( 'WOOCOMMERCE_CHECKOUT', true );
470
		}
471
472
		WC()->checkout()->process_checkout();
473
474
		die(0);
475
	}
476
477
	/**
478
	 * Get a matching variation based on posted attributes.
479
	 */
480
	public static function get_variation() {
481
		ob_start();
482
483
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
484
			die();
485
		}
486
487
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
488
489
		if ( $variation_id ) {
490
			$variation = $variable_product->get_available_variation( $variation_id );
491
		} else {
492
			$variation = false;
493
		}
494
495
		wp_send_json( $variation );
496
497
		die();
498
	}
499
500
	/**
501
	 * Feature a product from admin.
502
	 */
503
	public static function feature_product() {
504
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
505
			$product_id = absint( $_GET['product_id'] );
506
507
			if ( 'product' === get_post_type( $product_id ) ) {
508
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
509
510
				delete_transient( 'wc_featured_products' );
511
			}
512
		}
513
514
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=product' ) );
515
		die();
516
	}
517
518
	/**
519
	 * Mark an order with a status.
520
	 */
521
	public static function mark_order_status() {
522
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
523
			$status   = sanitize_text_field( $_GET['status'] );
524
			$order_id = absint( $_GET['order_id'] );
525
526
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
527
				$order = wc_get_order( $order_id );
528
				$order->update_status( $status, '', true );
529
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
530
			}
531
		}
532
533
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
534
		die();
535
	}
536
537
	/**
538
	 * Add an attribute row.
539
	 */
540
	public static function add_attribute() {
541
		ob_start();
542
543
		check_ajax_referer( 'add-attribute', 'security' );
544
545
		if ( ! current_user_can( 'edit_products' ) ) {
546
			die(-1);
547
		}
548
549
		global $wc_product_attributes;
550
551
		$thepostid     = 0;
552
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
553
		$i             = absint( $_POST['i'] );
554
		$position      = 0;
555
		$metabox_class = array();
556
		$attribute     = array(
557
			'name'         => $taxonomy,
558
			'value'        => '',
559
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
560
			'is_variation' => apply_filters( 'woocommerce_attribute_default_is_variation', 0 ),
561
			'is_taxonomy'  => $taxonomy ? 1 : 0
562
		);
563
564
		if ( $taxonomy ) {
565
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
566
			$metabox_class[]    = 'taxonomy';
567
			$metabox_class[]    = $taxonomy;
568
			$attribute_label    = wc_attribute_label( $taxonomy );
569
		} else {
570
			$attribute_label = '';
571
		}
572
573
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
574
		die();
575
	}
576
577
	/**
578
	 * Add a new attribute via ajax function.
579
	 */
580
	public static function add_new_attribute() {
581
		ob_start();
582
583
		check_ajax_referer( 'add-attribute', 'security' );
584
585
		if ( ! current_user_can( 'manage_product_terms' ) ) {
586
			die(-1);
587
		}
588
589
		$taxonomy = esc_attr( $_POST['taxonomy'] );
590
		$term     = wc_clean( $_POST['term'] );
591
592
		if ( taxonomy_exists( $taxonomy ) ) {
593
594
			$result = wp_insert_term( $term, $taxonomy );
595
596
			if ( is_wp_error( $result ) ) {
597
				wp_send_json( array(
598
					'error' => $result->get_error_message()
599
				) );
600
			} else {
601
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
602
				wp_send_json( array(
603
					'term_id' => $term->term_id,
604
					'name'    => $term->name,
605
					'slug'    => $term->slug
606
				) );
607
			}
608
		}
609
610
		die();
611
	}
612
613
	/**
614
	 * Delete variations via ajax function.
615
	 */
616
	public static function remove_variations() {
617
		check_ajax_referer( 'delete-variations', 'security' );
618
619
		if ( ! current_user_can( 'edit_products' ) ) {
620
			die(-1);
621
		}
622
623
		$variation_ids = (array) $_POST['variation_ids'];
624
625
		foreach ( $variation_ids as $variation_id ) {
626
			$variation = get_post( $variation_id );
627
628
			if ( $variation && 'product_variation' == $variation->post_type ) {
629
				wp_delete_post( $variation_id );
630
			}
631
		}
632
633
		die();
634
	}
635
636
	/**
637
	 * Save attributes via ajax.
638
	 */
639
	public static function save_attributes() {
640
641
		check_ajax_referer( 'save-attributes', 'security' );
642
643
		if ( ! current_user_can( 'edit_products' ) ) {
644
			die(-1);
645
		}
646
647
		// Get post data
648
		parse_str( $_POST['data'], $data );
649
		$post_id = absint( $_POST['post_id'] );
650
651
		// Save Attributes
652
		$attributes = array();
653
654
		if ( isset( $data['attribute_names'] ) ) {
655
656
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
657
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
658
659
			if ( isset( $data['attribute_visibility'] ) ) {
660
				$attribute_visibility = $data['attribute_visibility'];
661
			}
662
663
			if ( isset( $data['attribute_variation'] ) ) {
664
				$attribute_variation = $data['attribute_variation'];
665
			}
666
667
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
668
			$attribute_position      = $data['attribute_position'];
669
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
670
671
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
672
				if ( empty( $attribute_names[ $i ] ) ) {
673
					continue;
674
				}
675
676
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
677
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
678
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
679
680
				if ( $is_taxonomy ) {
681
682
					if ( isset( $attribute_values[ $i ] ) ) {
683
684
						// Select based attributes - Format values (posted values are slugs)
685
						if ( is_array( $attribute_values[ $i ] ) ) {
686
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
687
688
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
689
						} else {
690
							$values     = array();
691
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
692
693
							foreach ( $raw_values as $value ) {
694
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
695
								if ( ! $term ) {
696
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
697
698
									if ( $term && ! is_wp_error( $term ) ) {
699
										$values[] = $term['term_id'];
700
									}
701
								} else {
702
									$values[] = $term->term_id;
703
								}
704
							}
705
						}
706
707
						// Remove empty items in the array
708
						$values = array_filter( $values, 'strlen' );
709
710
					} else {
711
						$values = array();
712
					}
713
714
					// Update post terms
715
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
716
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
717
					}
718
719 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...
720
						// Add attribute to array, but don't set values
721
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
722
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
723
							'value' 		=> '',
724
							'position' 		=> $attribute_position[ $i ],
725
							'is_visible' 	=> $is_visible,
726
							'is_variation' 	=> $is_variation,
727
							'is_taxonomy' 	=> $is_taxonomy
728
						);
729
					}
730
731 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...
732
733
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
734
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
735
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
736
737
					// Custom attribute - Add attribute to array and set the values
738
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
739
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
740
						'value' 		=> $values,
741
						'position' 		=> $attribute_position[ $i ],
742
						'is_visible' 	=> $is_visible,
743
						'is_variation' 	=> $is_variation,
744
						'is_taxonomy' 	=> $is_taxonomy
745
					);
746
				}
747
748
			 }
749
		}
750
751
		if ( ! function_exists( 'attributes_cmp' ) ) {
752
			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 (L964-970) 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...
753
				if ( $a['position'] == $b['position'] ) {
754
					return 0;
755
				}
756
757
				return ( $a['position'] < $b['position'] ) ? -1 : 1;
758
			}
759
		}
760
		uasort( $attributes, 'attributes_cmp' );
761
762
		update_post_meta( $post_id, '_product_attributes', $attributes );
763
764
		die();
765
	}
766
767
	/**
768
	 * Add variation via ajax function.
769
	 */
770
	public static function add_variation() {
771
772
		check_ajax_referer( 'add-variation', 'security' );
773
774
		if ( ! current_user_can( 'edit_products' ) ) {
775
			die(-1);
776
		}
777
778
		global $post;
779
780
		$post_id = intval( $_POST['post_id'] );
781
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
782
		$loop    = intval( $_POST['loop'] );
783
784
		$variation = array(
785
			'post_title'   => 'Product #' . $post_id . ' Variation',
786
			'post_content' => '',
787
			'post_status'  => 'publish',
788
			'post_author'  => get_current_user_id(),
789
			'post_parent'  => $post_id,
790
			'post_type'    => 'product_variation',
791
			'menu_order'   => -1
792
		);
793
794
		$variation_id = wp_insert_post( $variation );
795
796
		do_action( 'woocommerce_create_product_variation', $variation_id );
797
798
		if ( $variation_id ) {
799
			$variation        = get_post( $variation_id );
800
			$variation_meta   = get_post_meta( $variation_id );
801
			$variation_data   = array();
802
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
803
			$variation_fields = array(
804
				'_sku'                   => '',
805
				'_stock'                 => '',
806
				'_regular_price'         => '',
807
				'_sale_price'            => '',
808
				'_weight'                => '',
809
				'_length'                => '',
810
				'_width'                 => '',
811
				'_height'                => '',
812
				'_download_limit'        => '',
813
				'_download_expiry'       => '',
814
				'_downloadable_files'    => '',
815
				'_downloadable'          => '',
816
				'_virtual'               => '',
817
				'_thumbnail_id'          => '',
818
				'_sale_price_dates_from' => '',
819
				'_sale_price_dates_to'   => '',
820
				'_manage_stock'          => '',
821
				'_stock_status'          => '',
822
				'_backorders'            => null,
823
				'_tax_class'             => null,
824
				'_variation_description' => ''
825
			);
826
827 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...
828
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
829
			}
830
831
			// Add the variation attributes
832
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
833
834
			// Formatting
835
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
836
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
837
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
838
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
839
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
840
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
841
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
842
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
843
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
844
			$variation_data['menu_order']     = $variation->menu_order;
845
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
846
847
			// Get tax classes
848
			$tax_classes           = WC_Tax::get_tax_classes();
849
			$tax_class_options     = array();
850
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
851
852 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...
853
				foreach ( $tax_classes as $class ) {
854
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
855
				}
856
			}
857
858
			// Set backorder options
859
			$backorder_options = array(
860
				'no'     => __( 'Do not allow', 'woocommerce' ),
861
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
862
				'yes'    => __( 'Allow', 'woocommerce' )
863
			);
864
865
			// set stock status options
866
			$stock_status_options = array(
867
				'instock'    => __( 'In stock', 'woocommerce' ),
868
				'outofstock' => __( 'Out of stock', 'woocommerce' )
869
			);
870
871
			// Get attributes
872
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
873
874
			$parent_data = array(
875
				'id'                   => $post_id,
876
				'attributes'           => $attributes,
877
				'tax_class_options'    => $tax_class_options,
878
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
879
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
880
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
881
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
882
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
883
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
884
				'backorder_options'    => $backorder_options,
885
				'stock_status_options' => $stock_status_options
886
			);
887
888
			if ( ! $parent_data['weight'] ) {
889
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
890
			}
891
892
			if ( ! $parent_data['length'] ) {
893
				$parent_data['length'] = wc_format_localized_decimal( 0 );
894
			}
895
896
			if ( ! $parent_data['width'] ) {
897
				$parent_data['width'] = wc_format_localized_decimal( 0 );
898
			}
899
900
			if ( ! $parent_data['height'] ) {
901
				$parent_data['height'] = wc_format_localized_decimal( 0 );
902
			}
903
904
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
905
		}
906
907
		die();
908
	}
909
910
	/**
911
	 * Link all variations via ajax function.
912
	 */
913
	public static function link_all_variations() {
914
915
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
916
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
917
		}
918
919
		check_ajax_referer( 'link-variations', 'security' );
920
921
		if ( ! current_user_can( 'edit_products' ) ) {
922
			die(-1);
923
		}
924
925
		wc_set_time_limit( 0 );
926
927
		$post_id = intval( $_POST['post_id'] );
928
929
		if ( ! $post_id ) {
930
			die();
931
		}
932
933
		$variations = array();
934
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
935
936
		// Put variation attributes into an array
937
		foreach ( $_product->get_attributes() as $attribute ) {
938
939
			if ( ! $attribute['is_variation'] ) {
940
				continue;
941
			}
942
943
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
944
945
			if ( $attribute['is_taxonomy'] ) {
946
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
947
			} else {
948
				$options = explode( WC_DELIMITER, $attribute['value'] );
949
			}
950
951
			$options = array_map( 'trim', $options );
952
953
			$variations[ $attribute_field_name ] = $options;
954
		}
955
956
		// Quit out if none were found
957
		if ( sizeof( $variations ) == 0 ) {
958
			die();
959
		}
960
961
		// Get existing variations so we don't create duplicates
962
		$available_variations = array();
963
964
		foreach( $_product->get_children() as $child_id ) {
965
			$child = $_product->get_child( $child_id );
966
967
			if ( ! empty( $child->variation_id ) ) {
968
				$available_variations[] = $child->get_variation_attributes();
969
			}
970
		}
971
972
		// Created posts will all have the following data
973
		$variation_post_data = array(
974
			'post_title'   => 'Product #' . $post_id . ' Variation',
975
			'post_content' => '',
976
			'post_status'  => 'publish',
977
			'post_author'  => get_current_user_id(),
978
			'post_parent'  => $post_id,
979
			'post_type'    => 'product_variation'
980
		);
981
982
		$variation_ids       = array();
983
		$added               = 0;
984
		$possible_variations = wc_array_cartesian( $variations );
985
986
		foreach ( $possible_variations as $variation ) {
987
988
			// Check if variation already exists
989
			if ( in_array( $variation, $available_variations ) ) {
990
				continue;
991
			}
992
993
			$variation_id = wp_insert_post( $variation_post_data );
994
995
			$variation_ids[] = $variation_id;
996
997
			foreach ( $variation as $key => $value ) {
998
				update_post_meta( $variation_id, $key, $value );
999
			}
1000
1001
			// Save stock status
1002
			update_post_meta( $variation_id, '_stock_status', 'instock' );
1003
1004
			$added++;
1005
1006
			do_action( 'product_variation_linked', $variation_id );
1007
1008
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
1009
				break;
1010
			}
1011
		}
1012
1013
		delete_transient( 'wc_product_children_' . $post_id );
1014
1015
		echo $added;
1016
1017
		die();
1018
	}
1019
1020
	/**
1021
	 * Delete download permissions via ajax function.
1022
	 */
1023
	public static function revoke_access_to_download() {
1024
1025
		check_ajax_referer( 'revoke-access', 'security' );
1026
1027
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1028
			die(-1);
1029
		}
1030
1031
		global $wpdb;
1032
1033
		$download_id = $_POST['download_id'];
1034
		$product_id  = intval( $_POST['product_id'] );
1035
		$order_id    = intval( $_POST['order_id'] );
1036
1037
		$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 ) );
1038
1039
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
1040
1041
		die();
1042
	}
1043
1044
	/**
1045
	 * Grant download permissions via ajax function.
1046
	 */
1047
	public static function grant_access_to_download() {
1048
1049
		check_ajax_referer( 'grant-access', 'security' );
1050
1051
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1052
			die(-1);
1053
		}
1054
1055
		global $wpdb;
1056
1057
		$wpdb->hide_errors();
1058
1059
		$order_id     = intval( $_POST['order_id'] );
1060
		$product_ids  = $_POST['product_ids'];
1061
		$loop         = intval( $_POST['loop'] );
1062
		$file_counter = 0;
1063
		$order        = wc_get_order( $order_id );
1064
1065
		if ( ! is_array( $product_ids ) ) {
1066
			$product_ids = array( $product_ids );
1067
		}
1068
1069
		foreach ( $product_ids as $product_id ) {
1070
			$product = wc_get_product( $product_id );
1071
			$files   = $product->get_files();
1072
1073
			if ( ! $order->billing_email ) {
1074
				die();
1075
			}
1076
1077
			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...
1078
				foreach ( $files as $download_id => $file ) {
1079
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
1080
1081
						// insert complete - get inserted data
1082
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1083
1084
						$loop ++;
1085
						$file_counter ++;
1086
1087 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...
1088
							$file_count = $file['name'];
1089
						} else {
1090
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1091
						}
1092
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1093
					}
1094
				}
1095
			}
1096
		}
1097
1098
		die();
1099
	}
1100
1101
	/**
1102
	 * Get customer details via ajax.
1103
	 */
1104
	public static function get_customer_details() {
1105
		ob_start();
1106
1107
		check_ajax_referer( 'get-customer-details', 'security' );
1108
1109
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1110
			die(-1);
1111
		}
1112
1113
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1114
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1115
1116
		$customer_data = array(
1117
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1118
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1119
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1120
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1121
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1122
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1123
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1124
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1125
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1126
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1127
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1128
		);
1129
1130
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1131
1132
		wp_send_json( $customer_data );
1133
	}
1134
1135
	/**
1136
	 * Add order item via ajax.
1137
	 */
1138
	public static function add_order_item() {
1139
		check_ajax_referer( 'order-item', 'security' );
1140
1141
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1142
			die(-1);
1143
		}
1144
1145
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1146
		$order_id    = absint( $_POST['order_id'] );
1147
1148
		// Find the item
1149
		if ( ! is_numeric( $item_to_add ) ) {
1150
			die();
1151
		}
1152
1153
		$post = get_post( $item_to_add );
1154
1155
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1156
			die();
1157
		}
1158
1159
		$_product    = wc_get_product( $post->ID );
1160
		$order       = wc_get_order( $order_id );
1161
		$order_taxes = $order->get_taxes();
1162
		$class       = 'new_row';
1163
1164
		// Set values
1165
		$item = array();
1166
1167
		$item['product_id']        = $_product->id;
1168
		$item['variation_id']      = isset( $_product->variation_id ) ? $_product->variation_id : '';
1169
		$item['variation_data']    = $item['variation_id'] ? $_product->get_variation_attributes() : '';
1170
		$item['name']              = $_product->get_title();
1171
		$item['tax_class']         = $_product->get_tax_class();
1172
		$item['qty']               = 1;
1173
		$item['line_subtotal']     = wc_format_decimal( $_product->get_price_excluding_tax() );
1174
		$item['line_subtotal_tax'] = '';
1175
		$item['line_total']        = wc_format_decimal( $_product->get_price_excluding_tax() );
1176
		$item['line_tax']          = '';
1177
		$item['type']              = 'line_item';
1178
1179
		// Add line item
1180
		$item_id = wc_add_order_item( $order_id, array(
1181
			'order_item_name' 		=> $item['name'],
1182
			'order_item_type' 		=> 'line_item'
1183
		) );
1184
1185
		// Add line item meta
1186
		if ( $item_id ) {
1187
			wc_add_order_item_meta( $item_id, '_qty', $item['qty'] );
1188
			wc_add_order_item_meta( $item_id, '_tax_class', $item['tax_class'] );
1189
			wc_add_order_item_meta( $item_id, '_product_id', $item['product_id'] );
1190
			wc_add_order_item_meta( $item_id, '_variation_id', $item['variation_id'] );
1191
			wc_add_order_item_meta( $item_id, '_line_subtotal', $item['line_subtotal'] );
1192
			wc_add_order_item_meta( $item_id, '_line_subtotal_tax', $item['line_subtotal_tax'] );
1193
			wc_add_order_item_meta( $item_id, '_line_total', $item['line_total'] );
1194
			wc_add_order_item_meta( $item_id, '_line_tax', $item['line_tax'] );
1195
1196
			// Since 2.2
1197
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
1198
1199
			// Store variation data in meta
1200 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...
1201
				foreach ( $item['variation_data'] as $key => $value ) {
1202
					wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
1203
				}
1204
			}
1205
1206
			do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1207
		}
1208
1209
		$item['item_meta']       = $order->get_item_meta( $item_id );
1210
		$item['item_meta_array'] = $order->get_item_meta_array( $item_id );
1211
		$item                    = $order->expand_item_meta( $item );
1212
		$item                    = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
1213
1214
		include( 'admin/meta-boxes/views/html-order-item.php' );
1215
1216
		// Quit out
1217
		die();
1218
	}
1219
1220
	/**
1221
	 * Add order fee via ajax.
1222
	 */
1223
	public static function add_order_fee() {
1224
1225
		check_ajax_referer( 'order-item', 'security' );
1226
1227
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1228
			die(-1);
1229
		}
1230
1231
		$order_id      = absint( $_POST['order_id'] );
1232
		$order         = wc_get_order( $order_id );
1233
		$order_taxes   = $order->get_taxes();
1234
		$item          = array();
1235
1236
		// Add new fee
1237
		$fee            = new stdClass();
1238
		$fee->name      = '';
1239
		$fee->tax_class = '';
1240
		$fee->taxable   = $fee->tax_class !== '0';
1241
		$fee->amount    = '';
1242
		$fee->tax       = '';
1243
		$fee->tax_data  = array();
1244
		$item_id        = $order->add_fee( $fee );
1245
1246
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1247
1248
		// Quit out
1249
		die();
1250
	}
1251
1252
	/**
1253
	 * Add order shipping cost via ajax.
1254
	 */
1255
	public static function add_order_shipping() {
1256
1257
		check_ajax_referer( 'order-item', 'security' );
1258
1259
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1260
			die(-1);
1261
		}
1262
1263
		$order_id         = absint( $_POST['order_id'] );
1264
		$order            = wc_get_order( $order_id );
1265
		$order_taxes      = $order->get_taxes();
1266
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1267
		$item             = array();
1268
1269
		// Add new shipping
1270
		$shipping        = new WC_Shipping_Rate();
1271
		$item_id         = $order->add_shipping( $shipping );
1272
1273
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1274
1275
		// Quit out
1276
		die();
1277
	}
1278
1279
	/**
1280
	 * Add order tax column via ajax.
1281
	 */
1282
	public static function add_order_tax() {
1283
		global $wpdb;
1284
1285
		check_ajax_referer( 'order-item', 'security' );
1286
1287
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1288
			die(-1);
1289
		}
1290
1291
		$order_id = absint( $_POST['order_id'] );
1292
		$rate_id  = absint( $_POST['rate_id'] );
1293
		$order    = wc_get_order( $order_id );
1294
		$data     = get_post_meta( $order_id );
1295
1296
		// Add new tax
1297
		$order->add_tax( $rate_id, 0, 0 );
1298
1299
		// Return HTML items
1300
		include( 'admin/meta-boxes/views/html-order-items.php' );
1301
1302
		die();
1303
	}
1304
1305
	/**
1306
	 * Remove an order item.
1307
	 */
1308
	public static function remove_order_item() {
1309
		check_ajax_referer( 'order-item', 'security' );
1310
1311
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1312
			die(-1);
1313
		}
1314
1315
		$order_item_ids = $_POST['order_item_ids'];
1316
1317
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1318
			$order_item_ids = array( $order_item_ids );
1319
		}
1320
1321
		if ( sizeof( $order_item_ids ) > 0 ) {
1322
			foreach( $order_item_ids as $id ) {
1323
				wc_delete_order_item( absint( $id ) );
1324
			}
1325
		}
1326
1327
		die();
1328
	}
1329
1330
	/**
1331
	 * Remove an order tax.
1332
	 */
1333
	public static function remove_order_tax() {
1334
1335
		check_ajax_referer( 'order-item', 'security' );
1336
1337
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1338
			die(-1);
1339
		}
1340
1341
		$order_id = absint( $_POST['order_id'] );
1342
		$rate_id  = absint( $_POST['rate_id'] );
1343
1344
		wc_delete_order_item( $rate_id );
1345
1346
		// Return HTML items
1347
		$order = wc_get_order( $order_id );
1348
		$data  = get_post_meta( $order_id );
1349
		include( 'admin/meta-boxes/views/html-order-items.php' );
1350
1351
		die();
1352
	}
1353
1354
	/**
1355
	 * Reduce order item stock.
1356
	 */
1357
	public static function reduce_order_item_stock() {
1358
		check_ajax_referer( 'order-item', 'security' );
1359
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1360
			die(-1);
1361
		}
1362
		$order_id       = absint( $_POST['order_id'] );
1363
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1364
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1365
		$order          = wc_get_order( $order_id );
1366
		$order_items    = $order->get_items();
1367
		$return         = array();
1368
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1369
			foreach ( $order_items as $item_id => $order_item ) {
1370
				// Only reduce checked items
1371
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1372
					continue;
1373
				}
1374
				$_product = $order->get_product_from_item( $order_item );
1375
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1376
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1377
					$new_stock    = $_product->reduce_stock( $stock_change );
1378
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1379
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1380
					$return[]     = $note;
1381
					$order->add_order_note( $note );
1382
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1383
				}
1384
			}
1385
			do_action( 'woocommerce_reduce_order_stock', $order );
1386
			if ( empty( $return ) ) {
1387
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1388
			}
1389
			echo implode( ', ', $return );
1390
		}
1391
		die();
1392
	}
1393
1394
	/**
1395
	 * Increase order item stock.
1396
	 */
1397
	public static function increase_order_item_stock() {
1398
		check_ajax_referer( 'order-item', 'security' );
1399
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1400
			die(-1);
1401
		}
1402
		$order_id       = absint( $_POST['order_id'] );
1403
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1404
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1405
		$order          = wc_get_order( $order_id );
1406
		$order_items    = $order->get_items();
1407
		$return         = array();
1408
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1409
			foreach ( $order_items as $item_id => $order_item ) {
1410
				// Only reduce checked items
1411
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1412
					continue;
1413
				}
1414
				$_product = $order->get_product_from_item( $order_item );
1415
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1416
					$old_stock    = $_product->get_stock_quantity();
1417
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1418
					$new_quantity = $_product->increase_stock( $stock_change );
1419
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1420
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1421
					$return[]     = $note;
1422
					$order->add_order_note( $note );
1423
				}
1424
			}
1425
			do_action( 'woocommerce_restore_order_stock', $order );
1426
			if ( empty( $return ) ) {
1427
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1428
			}
1429
			echo implode( ', ', $return );
1430
		}
1431
		die();
1432
	}
1433
1434
	/**
1435
	 * Add some meta to a line item.
1436
	 */
1437
	public static function add_order_item_meta() {
1438
		check_ajax_referer( 'order-item', 'security' );
1439
1440
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1441
			die(-1);
1442
		}
1443
1444
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1445
1446
		if ( $meta_id ) {
1447
			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>';
1448
		}
1449
1450
		die();
1451
	}
1452
1453
	/**
1454
	 * Remove meta from a line item.
1455
	 */
1456
	public static function remove_order_item_meta() {
1457
		check_ajax_referer( 'order-item', 'security' );
1458
1459
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1460
			die(-1);
1461
		}
1462
1463
		global $wpdb;
1464
1465
		$wpdb->delete( "{$wpdb->prefix}woocommerce_order_itemmeta", array(
1466
			'meta_id' => absint( $_POST['meta_id'] ),
1467
		) );
1468
1469
		die();
1470
	}
1471
1472
	/**
1473
	 * Calc line tax.
1474
	 */
1475
	public static function calc_line_taxes() {
1476
		global $wpdb;
1477
1478
		check_ajax_referer( 'calc-totals', 'security' );
1479
1480
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1481
			die(-1);
1482
		}
1483
1484
		$tax                    = new WC_Tax();
1485
		$tax_based_on           = get_option( 'woocommerce_tax_based_on' );
1486
		$order_id               = absint( $_POST['order_id'] );
1487
		$items                  = array();
1488
		$country                = strtoupper( esc_attr( $_POST['country'] ) );
1489
		$state                  = strtoupper( esc_attr( $_POST['state'] ) );
1490
		$postcode               = strtoupper( esc_attr( $_POST['postcode'] ) );
1491
		$city                   = wc_clean( esc_attr( $_POST['city'] ) );
1492
		$order                  = wc_get_order( $order_id );
1493
		$taxes                  = array();
1494
		$shipping_taxes         = array();
1495
		$order_item_tax_classes = array();
1496
1497
		// Default to base
1498 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...
1499
			$default  = wc_get_base_location();
1500
			$country  = $default['country'];
1501
			$state    = $default['state'];
1502
			$postcode = '';
1503
			$city     = '';
1504
		}
1505
1506
		// Parse the jQuery serialized items
1507
		parse_str( $_POST['items'], $items );
1508
1509
		// Prevent undefined warnings
1510
		if ( ! isset( $items['line_tax'] ) ) {
1511
			$items['line_tax'] = array();
1512
		}
1513
		if ( ! isset( $items['line_subtotal_tax'] ) ) {
1514
			$items['line_subtotal_tax'] = array();
1515
		}
1516
		$items['order_taxes'] = array();
1517
1518
		// Action
1519
		$items = apply_filters( 'woocommerce_ajax_calc_line_taxes', $items, $order_id, $country, $_POST );
1520
1521
		$is_vat_exempt = get_post_meta( $order_id, '_is_vat_exempt', true );
1522
1523
		// Tax is calculated only if tax is enabled and order is not vat exempted
1524
		if ( wc_tax_enabled() && $is_vat_exempt !== 'yes' ) {
1525
1526
			// Get items and fees taxes
1527
			if ( isset( $items['order_item_id'] ) ) {
1528
				$line_total = $line_subtotal = array();
1529
1530
				foreach ( $items['order_item_id'] as $item_id ) {
1531
					$item_id                            = absint( $item_id );
1532
					$line_total[ $item_id ]             = isset( $items['line_total'][ $item_id ] ) ? wc_format_decimal( $items['line_total'][ $item_id ] ) : 0;
1533
					$line_subtotal[ $item_id ]          = isset( $items['line_subtotal'][ $item_id ] ) ? wc_format_decimal( $items['line_subtotal'][ $item_id ] ) : $line_total[ $item_id ];
1534
					$order_item_tax_classes[ $item_id ] = isset( $items['order_item_tax_class'][ $item_id ] ) ? sanitize_text_field( $items['order_item_tax_class'][ $item_id ] ) : '';
1535
					$product_id                         = $order->get_item_meta( $item_id, '_product_id', true );
1536
1537
					// Get product details
1538
					if ( get_post_type( $product_id ) == 'product' ) {
1539
						$_product        = wc_get_product( $product_id );
1540
						$item_tax_status = $_product->get_tax_status();
1541
					} else {
1542
						$item_tax_status = 'taxable';
1543
					}
1544
1545
					if ( '0' !== $order_item_tax_classes[ $item_id ] && 'taxable' === $item_tax_status ) {
1546
						$tax_rates = WC_Tax::find_rates( array(
1547
							'country'   => $country,
1548
							'state'     => $state,
1549
							'postcode'  => $postcode,
1550
							'city'      => $city,
1551
							'tax_class' => $order_item_tax_classes[ $item_id ]
1552
						) );
1553
1554
						$line_taxes          = WC_Tax::calc_tax( $line_total[ $item_id ], $tax_rates, false );
1555
						$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal[ $item_id ], $tax_rates, false );
1556
1557
						// Set the new line_tax
1558
						foreach ( $line_taxes as $_tax_id => $_tax_value ) {
1559
							$items['line_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1560
						}
1561
1562
						// Set the new line_subtotal_tax
1563
						foreach ( $line_subtotal_taxes as $_tax_id => $_tax_value ) {
1564
							$items['line_subtotal_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1565
						}
1566
1567
						// Sum the item taxes
1568
						foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1569
							$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1570
						}
1571
					}
1572
				}
1573
			}
1574
1575
			// Get shipping taxes
1576
			if ( isset( $items['shipping_method_id'] ) ) {
1577
				$matched_tax_rates      = array();
1578
				$order_item_tax_classes = array_unique( array_values( $order_item_tax_classes ) );
1579
1580
				// If multiple classes are found, use the first one. Don't bother with standard rate, we can get that later.
1581 View Code Duplication
				if ( sizeof( $order_item_tax_classes ) > 1 && ! in_array( '', $order_item_tax_classes ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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