Completed
Pull Request — master (#9826)
by Mike
07:49
created

WC_AJAX::variation_bulk_action_toggle_virtual()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 7
Ratio 100 %
Metric Value
dl 7
loc 7
rs 9.4285
cc 3
eloc 5
nc 3
nop 2
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 18 and the first side effect is on line 4.

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

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

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

Loading history...
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit; // Exit if accessed directly
5
}
6
7
/**
8
 * WooCommerce WC_AJAX.
9
 *
10
 * AJAX Event Handler.
11
 *
12
 * @class    WC_AJAX
13
 * @version  2.4.0
14
 * @package  WooCommerce/Classes
15
 * @category Class
16
 * @author   WooThemes
17
 */
18
class WC_AJAX {
19
20
	/**
21
	 * Hook in ajax handlers.
22
	 */
23
	public static function init() {
24
		add_action( 'init', array( __CLASS__, 'define_ajax' ), 0 );
25
		add_action( 'template_redirect', array( __CLASS__, 'do_wc_ajax' ), 0 );
26
		self::add_ajax_events();
27
	}
28
29
	/**
30
	 * Get WC Ajax Endpoint.
31
	 * @param  string $request Optional
32
	 * @return string
33
	 */
34
	public static function get_endpoint( $request = '' ) {
35
		return esc_url_raw( add_query_arg( 'wc-ajax', $request, remove_query_arg( array( 'remove_item', 'add-to-cart', 'added-to-cart' ) ) ) );
36
	}
37
38
	/**
39
	 * Set WC AJAX constant and headers.
40
	 */
41
	public static function define_ajax() {
42
		if ( ! empty( $_GET['wc-ajax'] ) ) {
43
			if ( ! defined( 'DOING_AJAX' ) ) {
44
				define( 'DOING_AJAX', true );
45
			}
46
			if ( ! defined( 'WC_DOING_AJAX' ) ) {
47
				define( 'WC_DOING_AJAX', true );
48
			}
49
			// Turn off display_errors during AJAX events to prevent malformed JSON
50
			if ( ! WP_DEBUG || ( WP_DEBUG && ! WP_DEBUG_DISPLAY ) ) {
51
				@ini_set( 'display_errors', 0 );
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
52
			}
53
			$GLOBALS['wpdb']->hide_errors();
54
		}
55
	}
56
57
	/**
58
	 * Send headers for WC Ajax Requests
59
	 * @since 2.5.0
60
	 */
61
	private static function wc_ajax_headers() {
62
		send_origin_headers();
63
		@header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
65
		send_nosniff_header();
66
		nocache_headers();
67
		status_header( 200 );
68
	}
69
70
	/**
71
	 * Check for WC Ajax request and fire action.
72
	 */
73
	public static function do_wc_ajax() {
74
		global $wp_query;
75
76
		if ( ! empty( $_GET['wc-ajax'] ) ) {
77
			$wp_query->set( 'wc-ajax', sanitize_text_field( $_GET['wc-ajax'] ) );
78
		}
79
80
		if ( $action = $wp_query->get( 'wc-ajax' ) ) {
81
			self::wc_ajax_headers();
82
			do_action( 'wc_ajax_' . sanitize_text_field( $action ) );
83
			die();
84
		}
85
	}
86
87
	/**
88
	 * Hook in methods - uses WordPress ajax handlers (admin-ajax).
89
	 */
90
	public static function add_ajax_events() {
91
		// woocommerce_EVENT => nopriv
92
		$ajax_events = array(
93
			'get_refreshed_fragments'                          => true,
94
			'apply_coupon'                                     => true,
95
			'remove_coupon'                                    => true,
96
			'update_shipping_method'                           => true,
97
			'update_order_review'                              => true,
98
			'add_to_cart'                                      => true,
99
			'checkout'                                         => true,
100
			'get_variation'                                    => true,
101
			'feature_product'                                  => false,
102
			'mark_order_status'                                => false,
103
			'add_attribute'                                    => false,
104
			'add_new_attribute'                                => false,
105
			'remove_variation'                                 => false,
106
			'remove_variations'                                => false,
107
			'save_attributes'                                  => false,
108
			'add_variation'                                    => false,
109
			'link_all_variations'                              => false,
110
			'revoke_access_to_download'                        => false,
111
			'grant_access_to_download'                         => false,
112
			'get_customer_details'                             => false,
113
			'add_order_item'                                   => false,
114
			'add_order_fee'                                    => false,
115
			'add_order_shipping'                               => false,
116
			'add_order_tax'                                    => false,
117
			'remove_order_item'                                => false,
118
			'remove_order_tax'                                 => false,
119
			'reduce_order_item_stock'                          => false,
120
			'increase_order_item_stock'                        => false,
121
			'add_order_item_meta'                              => false,
122
			'remove_order_item_meta'                           => false,
123
			'calc_line_taxes'                                  => false,
124
			'save_order_items'                                 => false,
125
			'load_order_items'                                 => false,
126
			'add_order_note'                                   => false,
127
			'delete_order_note'                                => false,
128
			'json_search_products'                             => false,
129
			'json_search_products_and_variations'              => false,
130
			'json_search_grouped_products'                     => false,
131
			'json_search_downloadable_products_and_variations' => false,
132
			'json_search_customers'                            => false,
133
			'term_ordering'                                    => false,
134
			'product_ordering'                                 => false,
135
			'refund_line_items'                                => false,
136
			'delete_refund'                                    => false,
137
			'rated'                                            => false,
138
			'update_api_key'                                   => false,
139
			'get_customer_location'                            => true,
140
			'load_variations'                                  => false,
141
			'save_variations'                                  => false,
142
			'bulk_edit_variations'                             => false,
143
			'tax_rates_save_changes'                           => false,
144
			'shipping_zones_save_changes'                      => false,
145
			'shipping_zone_add_method'                         => false,
146
			'shipping_zone_methods_save_changes'               => false,
147
			'shipping_classes_save_changes'                    => false,
148
		);
149
150
		foreach ( $ajax_events as $ajax_event => $nopriv ) {
151
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
152
153
			if ( $nopriv ) {
154
				add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
155
156
				// WC AJAX can be used for frontend ajax requests
157
				add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
158
			}
159
		}
160
	}
161
162
	/**
163
	 * Get a refreshed cart fragment.
164
	 */
165
	public static function get_refreshed_fragments() {
166
167
		// Get mini cart
168
		ob_start();
169
170
		woocommerce_mini_cart();
171
172
		$mini_cart = ob_get_clean();
173
174
		// Fragments and mini cart are returned
175
		$data = array(
176
			'fragments' => apply_filters( 'woocommerce_add_to_cart_fragments', array(
177
					'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>'
178
				)
179
			),
180
			'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() )
181
		);
182
183
		wp_send_json( $data );
184
185
	}
186
187
	/**
188
	 * AJAX apply coupon on checkout page.
189
	 */
190
	public static function apply_coupon() {
191
192
		check_ajax_referer( 'apply-coupon', 'security' );
193
194
		if ( ! empty( $_POST['coupon_code'] ) ) {
195
			WC()->cart->add_discount( sanitize_text_field( $_POST['coupon_code'] ) );
196
		} else {
197
			wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
198
		}
199
200
		wc_print_notices();
201
202
		die();
203
	}
204
205
	/**
206
	 * AJAX remove coupon on cart and checkout page.
207
	 */
208
	public static function remove_coupon() {
209
210
		check_ajax_referer( 'remove-coupon', 'security' );
211
212
		$coupon = wc_clean( $_POST['coupon'] );
213
214
		if ( ! isset( $coupon ) || empty( $coupon ) ) {
215
			wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ) );
216
217
		} else {
218
219
			WC()->cart->remove_coupon( $coupon );
1 ignored issue
show
Bug introduced by
It seems like $coupon defined by wc_clean($_POST['coupon']) on line 212 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...
220
221
			wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
222
		}
223
224
		wc_print_notices();
225
226
		die();
227
	}
228
229
	/**
230
	 * AJAX update shipping method on cart page.
231
	 */
232
	public static function update_shipping_method() {
233
234
		check_ajax_referer( 'update-shipping-method', 'security' );
235
236
		if ( ! defined('WOOCOMMERCE_CART') ) {
237
			define( 'WOOCOMMERCE_CART', true );
238
		}
239
240
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
241
242 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...
243
			foreach ( $_POST['shipping_method'] as $i => $value ) {
244
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
245
			}
246
		}
247
248
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
249
250
		WC()->cart->calculate_totals();
251
252
		woocommerce_cart_totals();
253
254
		die();
255
	}
256
257
	/**
258
	 * AJAX update order review on checkout.
259
	 */
260
	public static function update_order_review() {
261
		ob_start();
262
263
		check_ajax_referer( 'update-order-review', 'security' );
264
265
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
266
			define( 'WOOCOMMERCE_CHECKOUT', true );
267
		}
268
269
		if ( WC()->cart->is_empty() ) {
270
			$data = array(
271
				'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
272
					'form.woocommerce-checkout' => '<div class="woocommerce-error">' . __( 'Sorry, your session has expired.', 'woocommerce' ) . ' <a href="' . home_url() . '" class="wc-backward">' . __( 'Return to homepage', 'woocommerce' ) . '</a></div>'
273
				) )
274
			);
275
276
			wp_send_json( $data );
277
278
			die();
279
		}
280
281
		do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] );
282
283
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
284
285 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...
286
			foreach ( $_POST['shipping_method'] as $i => $value ) {
287
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
288
			}
289
		}
290
291
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
292
		WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
293
294
		if ( isset( $_POST['country'] ) ) {
295
			WC()->customer->set_country( $_POST['country'] );
296
		}
297
298
		if ( isset( $_POST['state'] ) ) {
299
			WC()->customer->set_state( $_POST['state'] );
300
		}
301
302
		if ( isset( $_POST['postcode'] ) ) {
303
			WC()->customer->set_postcode( $_POST['postcode'] );
304
		}
305
306
		if ( isset( $_POST['city'] ) ) {
307
			WC()->customer->set_city( $_POST['city'] );
308
		}
309
310
		if ( isset( $_POST['address'] ) ) {
311
			WC()->customer->set_address( $_POST['address'] );
312
		}
313
314
		if ( isset( $_POST['address_2'] ) ) {
315
			WC()->customer->set_address_2( $_POST['address_2'] );
316
		}
317
318
		if ( wc_ship_to_billing_address_only() ) {
319
320 View Code Duplication
			if ( isset( $_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...
321
				WC()->customer->set_shipping_country( $_POST['country'] );
322
				WC()->customer->calculated_shipping( true );
323
			}
324
325
			if ( isset( $_POST['state'] ) ) {
326
				WC()->customer->set_shipping_state( $_POST['state'] );
327
			}
328
329
			if ( isset( $_POST['postcode'] ) ) {
330
				WC()->customer->set_shipping_postcode( $_POST['postcode'] );
331
			}
332
333
			if ( isset( $_POST['city'] ) ) {
334
				WC()->customer->set_shipping_city( $_POST['city'] );
335
			}
336
337
			if ( isset( $_POST['address'] ) ) {
338
				WC()->customer->set_shipping_address( $_POST['address'] );
339
			}
340
341
			if ( isset( $_POST['address_2'] ) ) {
342
				WC()->customer->set_shipping_address_2( $_POST['address_2'] );
343
			}
344
		} else {
345
346 View Code Duplication
			if ( isset( $_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...
347
				WC()->customer->set_shipping_country( $_POST['s_country'] );
348
				WC()->customer->calculated_shipping( true );
349
			}
350
351
			if ( isset( $_POST['s_state'] ) ) {
352
				WC()->customer->set_shipping_state( $_POST['s_state'] );
353
			}
354
355
			if ( isset( $_POST['s_postcode'] ) ) {
356
				WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
357
			}
358
359
			if ( isset( $_POST['s_city'] ) ) {
360
				WC()->customer->set_shipping_city( $_POST['s_city'] );
361
			}
362
363
			if ( isset( $_POST['s_address'] ) ) {
364
				WC()->customer->set_shipping_address( $_POST['s_address'] );
365
			}
366
367
			if ( isset( $_POST['s_address_2'] ) ) {
368
				WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
369
			}
370
		}
371
372
		WC()->cart->calculate_totals();
373
374
		// Get order review fragment
375
		ob_start();
376
		woocommerce_order_review();
377
		$woocommerce_order_review = ob_get_clean();
378
379
		// Get checkout payment fragment
380
		ob_start();
381
		woocommerce_checkout_payment();
382
		$woocommerce_checkout_payment = ob_get_clean();
383
384
		// Get messages if reload checkout is not true
385
		$messages = '';
386
		if ( ! isset( WC()->session->reload_checkout ) ) {
387
			ob_start();
388
			wc_print_notices();
389
			$messages = ob_get_clean();
390
		}
391
392
		$data = array(
393
			'result'    => empty( $messages ) ? 'success' : 'failure',
394
			'messages'  => $messages,
395
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
396
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
397
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
398
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
399
			) )
400
		);
401
402
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
403
404
		wp_send_json( $data );
405
406
		die();
407
	}
408
409
	/**
410
	 * AJAX add to cart.
411
	 */
412
	public static function add_to_cart() {
413
		ob_start();
414
415
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
416
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
417
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
418
		$product_status    = get_post_status( $product_id );
419
420
		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...
421
422
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
423
424
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
425
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
426
			}
427
428
			// Return fragments
429
			self::get_refreshed_fragments();
430
431
		} else {
432
433
			// If there was an error adding to the cart, redirect to the product page to show any errors
434
			$data = array(
435
				'error'       => true,
436
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
437
			);
438
439
			wp_send_json( $data );
440
441
		}
442
443
		die();
444
	}
445
446
	/**
447
	 * Process ajax checkout form.
448
	 */
449
	public static function checkout() {
450
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
451
			define( 'WOOCOMMERCE_CHECKOUT', true );
452
		}
453
454
		WC()->checkout()->process_checkout();
455
456
		die(0);
457
	}
458
459
	/**
460
	 * Get a matching variation based on posted attributes.
461
	 */
462
	public static function get_variation() {
463
		ob_start();
464
465
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
466
			die();
467
		}
468
469
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
470
471
		if ( $variation_id ) {
472
			$variation = $variable_product->get_available_variation( $variation_id );
473
		} else {
474
			$variation = false;
475
		}
476
477
		wp_send_json( $variation );
478
479
		die();
480
	}
481
482
	/**
483
	 * Feature a product from admin.
484
	 */
485
	public static function feature_product() {
486
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
487
			$product_id = absint( $_GET['product_id'] );
488
489
			if ( 'product' === get_post_type( $product_id ) ) {
490
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
491
492
				delete_transient( 'wc_featured_products' );
493
			}
494
		}
495
496
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=shop_order' ) );
497
		die();
498
	}
499
500
	/**
501
	 * Mark an order with a status.
502
	 */
503
	public static function mark_order_status() {
504
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
505
			$status   = sanitize_text_field( $_GET['status'] );
506
			$order_id = absint( $_GET['order_id'] );
507
508
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
509
				$order = wc_get_order( $order_id );
510
				$order->update_status( $status, '', true );
511
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
512
			}
513
		}
514
515
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
516
		die();
517
	}
518
519
	/**
520
	 * Add an attribute row.
521
	 */
522
	public static function add_attribute() {
523
		ob_start();
524
525
		check_ajax_referer( 'add-attribute', 'security' );
526
527
		if ( ! current_user_can( 'edit_products' ) ) {
528
			die(-1);
529
		}
530
531
		global $wc_product_attributes;
532
533
		$thepostid     = 0;
534
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
535
		$i             = absint( $_POST['i'] );
536
		$position      = 0;
537
		$metabox_class = array();
538
		$attribute     = array(
539
			'name'         => $taxonomy,
540
			'value'        => '',
541
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
542
			'is_variation' => 0,
543
			'is_taxonomy'  => $taxonomy ? 1 : 0
544
		);
545
546
		if ( $taxonomy ) {
547
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
548
			$metabox_class[]    = 'taxonomy';
549
			$metabox_class[]    = $taxonomy;
550
			$attribute_label    = wc_attribute_label( $taxonomy );
551
		} else {
552
			$attribute_label = '';
553
		}
554
555
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
556
		die();
557
	}
558
559
	/**
560
	 * Add a new attribute via ajax function.
561
	 */
562
	public static function add_new_attribute() {
563
		ob_start();
564
565
		check_ajax_referer( 'add-attribute', 'security' );
566
567
		if ( ! current_user_can( 'manage_product_terms' ) ) {
568
			die(-1);
569
		}
570
571
		$taxonomy = esc_attr( $_POST['taxonomy'] );
572
		$term     = wc_clean( $_POST['term'] );
573
574
		if ( taxonomy_exists( $taxonomy ) ) {
575
576
			$result = wp_insert_term( $term, $taxonomy );
577
578
			if ( is_wp_error( $result ) ) {
579
				wp_send_json( array(
580
					'error' => $result->get_error_message()
581
				) );
582
			} else {
583
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
584
				wp_send_json( array(
585
					'term_id' => $term->term_id,
586
					'name'    => $term->name,
587
					'slug'    => $term->slug
588
				) );
589
			}
590
		}
591
592
		die();
593
	}
594
595
	/**
596
	 * Delete variations via ajax function.
597
	 */
598
	public static function remove_variations() {
599
		check_ajax_referer( 'delete-variations', 'security' );
600
601
		if ( ! current_user_can( 'edit_products' ) ) {
602
			die(-1);
603
		}
604
605
		$variation_ids = (array) $_POST['variation_ids'];
606
607
		foreach ( $variation_ids as $variation_id ) {
608
			$variation = get_post( $variation_id );
609
610
			if ( $variation && 'product_variation' == $variation->post_type ) {
611
				wp_delete_post( $variation_id );
612
			}
613
		}
614
615
		die();
616
	}
617
618
	/**
619
	 * Save attributes via ajax.
620
	 */
621
	public static function save_attributes() {
622
623
		check_ajax_referer( 'save-attributes', 'security' );
624
625
		if ( ! current_user_can( 'edit_products' ) ) {
626
			die(-1);
627
		}
628
629
		// Get post data
630
		parse_str( $_POST['data'], $data );
631
		$post_id = absint( $_POST['post_id'] );
632
633
		// Save Attributes
634
		$attributes = array();
635
636
		if ( isset( $data['attribute_names'] ) ) {
637
638
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
639
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
640
641
			if ( isset( $data['attribute_visibility'] ) ) {
642
				$attribute_visibility = $data['attribute_visibility'];
643
			}
644
645
			if ( isset( $data['attribute_variation'] ) ) {
646
				$attribute_variation = $data['attribute_variation'];
647
			}
648
649
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
650
			$attribute_position      = $data['attribute_position'];
651
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
652
653
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
654
				if ( empty( $attribute_names[ $i ] ) ) {
655
					continue;
656
				}
657
658
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
659
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
660
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
661
662
				if ( $is_taxonomy ) {
663
664
					if ( isset( $attribute_values[ $i ] ) ) {
665
666
						// Select based attributes - Format values (posted values are slugs)
667
						if ( is_array( $attribute_values[ $i ] ) ) {
668
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
669
670
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
671
						} else {
672
							$values     = array();
673
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
674
675
							foreach ( $raw_values as $value ) {
676
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
677
								if ( ! $term ) {
678
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
679
680
									if ( $term && ! is_wp_error( $term ) ) {
681
										$values[] = $term['term_id'];
682
									}
683
								} else {
684
									$values[] = $term->term_id;
685
								}
686
							}
687
						}
688
689
						// Remove empty items in the array
690
						$values = array_filter( $values, 'strlen' );
691
692
					} else {
693
						$values = array();
694
					}
695
696
					// Update post terms
697
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
698
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
699
					}
700
701 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...
702
						// Add attribute to array, but don't set values
703
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
704
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
705
							'value' 		=> '',
706
							'position' 		=> $attribute_position[ $i ],
707
							'is_visible' 	=> $is_visible,
708
							'is_variation' 	=> $is_variation,
709
							'is_taxonomy' 	=> $is_taxonomy
710
						);
711
					}
712
713 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...
714
715
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
716
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
717
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
718
719
					// Custom attribute - Add attribute to array and set the values
720
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
721
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
722
						'value' 		=> $values,
723
						'position' 		=> $attribute_position[ $i ],
724
						'is_visible' 	=> $is_visible,
725
						'is_variation' 	=> $is_variation,
726
						'is_taxonomy' 	=> $is_taxonomy
727
					);
728
				}
729
730
			 }
731
		}
732
733
		if ( ! function_exists( 'attributes_cmp' ) ) {
734
			function attributes_cmp( $a, $b ) {
0 ignored issues
show
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...
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 (L965-971) 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...
735
				if ( $a['position'] == $b['position'] ) {
736
					return 0;
737
				}
738
739
				return ( $a['position'] < $b['position'] ) ? -1 : 1;
740
			}
741
		}
742
		uasort( $attributes, 'attributes_cmp' );
743
744
		update_post_meta( $post_id, '_product_attributes', $attributes );
745
746
		die();
747
	}
748
749
	/**
750
	 * Add variation via ajax function.
751
	 */
752
	public static function add_variation() {
753
754
		check_ajax_referer( 'add-variation', 'security' );
755
756
		if ( ! current_user_can( 'edit_products' ) ) {
757
			die(-1);
758
		}
759
760
		global $post;
761
762
		$post_id = intval( $_POST['post_id'] );
763
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
764
		$loop    = intval( $_POST['loop'] );
765
766
		$variation = array(
767
			'post_title'   => 'Product #' . $post_id . ' Variation',
768
			'post_content' => '',
769
			'post_status'  => 'publish',
770
			'post_author'  => get_current_user_id(),
771
			'post_parent'  => $post_id,
772
			'post_type'    => 'product_variation',
773
			'menu_order'   => -1
774
		);
775
776
		$variation_id = wp_insert_post( $variation );
777
778
		do_action( 'woocommerce_create_product_variation', $variation_id );
779
780
		if ( $variation_id ) {
781
			$variation        = get_post( $variation_id );
782
			$variation_meta   = get_post_meta( $variation_id );
783
			$variation_data   = array();
784
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
785
			$variation_fields = array(
786
				'_sku'                   => '',
787
				'_stock'                 => '',
788
				'_regular_price'         => '',
789
				'_sale_price'            => '',
790
				'_weight'                => '',
791
				'_length'                => '',
792
				'_width'                 => '',
793
				'_height'                => '',
794
				'_download_limit'        => '',
795
				'_download_expiry'       => '',
796
				'_downloadable_files'    => '',
797
				'_downloadable'          => '',
798
				'_virtual'               => '',
799
				'_thumbnail_id'          => '',
800
				'_sale_price_dates_from' => '',
801
				'_sale_price_dates_to'   => '',
802
				'_manage_stock'          => '',
803
				'_stock_status'          => '',
804
				'_backorders'            => null,
805
				'_tax_class'             => null,
806
				'_variation_description' => ''
807
			);
808
809 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...
810
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
811
			}
812
813
			// Add the variation attributes
814
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
815
816
			// Formatting
817
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
818
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
819
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
820
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
821
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
822
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
823
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
824
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
825
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
826
			$variation_data['menu_order']     = $variation->menu_order;
827
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
828
829
			// Get tax classes
830
			$tax_classes           = WC_Tax::get_tax_classes();
831
			$tax_class_options     = array();
832
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
833
834 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...
835
				foreach ( $tax_classes as $class ) {
836
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
837
				}
838
			}
839
840
			// Set backorder options
841
			$backorder_options = array(
842
				'no'     => __( 'Do not allow', 'woocommerce' ),
843
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
844
				'yes'    => __( 'Allow', 'woocommerce' )
845
			);
846
847
			// set stock status options
848
			$stock_status_options = array(
849
				'instock'    => __( 'In stock', 'woocommerce' ),
850
				'outofstock' => __( 'Out of stock', 'woocommerce' )
851
			);
852
853
			// Get attributes
854
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
855
856
			$parent_data = array(
857
				'id'                   => $post_id,
858
				'attributes'           => $attributes,
859
				'tax_class_options'    => $tax_class_options,
860
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
861
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
862
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
863
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
864
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
865
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
866
				'backorder_options'    => $backorder_options,
867
				'stock_status_options' => $stock_status_options
868
			);
869
870
			if ( ! $parent_data['weight'] ) {
871
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
872
			}
873
874
			if ( ! $parent_data['length'] ) {
875
				$parent_data['length'] = wc_format_localized_decimal( 0 );
876
			}
877
878
			if ( ! $parent_data['width'] ) {
879
				$parent_data['width'] = wc_format_localized_decimal( 0 );
880
			}
881
882
			if ( ! $parent_data['height'] ) {
883
				$parent_data['height'] = wc_format_localized_decimal( 0 );
884
			}
885
886
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
887
		}
888
889
		die();
890
	}
891
892
	/**
893
	 * Link all variations via ajax function.
894
	 */
895
	public static function link_all_variations() {
896
897
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
898
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
899
		}
900
901
		check_ajax_referer( 'link-variations', 'security' );
902
903
		if ( ! current_user_can( 'edit_products' ) ) {
904
			die(-1);
905
		}
906
907 View Code Duplication
		if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
908
			@set_time_limit( 0 );
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
909
		}
910
911
		$post_id = intval( $_POST['post_id'] );
912
913
		if ( ! $post_id ) {
914
			die();
915
		}
916
917
		$variations = array();
918
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
919
920
		// Put variation attributes into an array
921
		foreach ( $_product->get_attributes() as $attribute ) {
922
923
			if ( ! $attribute['is_variation'] ) {
924
				continue;
925
			}
926
927
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
928
929
			if ( $attribute['is_taxonomy'] ) {
930
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
931
			} else {
932
				$options = explode( WC_DELIMITER, $attribute['value'] );
933
			}
934
935
			$options = array_map( 'trim', $options );
936
937
			$variations[ $attribute_field_name ] = $options;
938
		}
939
940
		// Quit out if none were found
941
		if ( sizeof( $variations ) == 0 ) {
942
			die();
943
		}
944
945
		// Get existing variations so we don't create duplicates
946
		$available_variations = array();
947
948
		foreach( $_product->get_children() as $child_id ) {
949
			$child = $_product->get_child( $child_id );
950
951
			if ( ! empty( $child->variation_id ) ) {
952
				$available_variations[] = $child->get_variation_attributes();
953
			}
954
		}
955
956
		// Created posts will all have the following data
957
		$variation_post_data = array(
958
			'post_title'   => 'Product #' . $post_id . ' Variation',
959
			'post_content' => '',
960
			'post_status'  => 'publish',
961
			'post_author'  => get_current_user_id(),
962
			'post_parent'  => $post_id,
963
			'post_type'    => 'product_variation'
964
		);
965
966
		$variation_ids       = array();
967
		$added               = 0;
968
		$possible_variations = wc_array_cartesian( $variations );
969
970
		foreach ( $possible_variations as $variation ) {
971
972
			// Check if variation already exists
973
			if ( in_array( $variation, $available_variations ) ) {
974
				continue;
975
			}
976
977
			$variation_id = wp_insert_post( $variation_post_data );
978
979
			$variation_ids[] = $variation_id;
980
981
			foreach ( $variation as $key => $value ) {
982
				update_post_meta( $variation_id, $key, $value );
983
			}
984
985
			// Save stock status
986
			update_post_meta( $variation_id, '_stock_status', 'instock' );
987
988
			$added++;
989
990
			do_action( 'product_variation_linked', $variation_id );
991
992
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
993
				break;
994
			}
995
		}
996
997
		delete_transient( 'wc_product_children_' . $post_id );
998
999
		echo $added;
1000
1001
		die();
1002
	}
1003
1004
	/**
1005
	 * Delete download permissions via ajax function.
1006
	 */
1007
	public static function revoke_access_to_download() {
1008
1009
		check_ajax_referer( 'revoke-access', 'security' );
1010
1011
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1012
			die(-1);
1013
		}
1014
1015
		global $wpdb;
1016
1017
		$download_id = $_POST['download_id'];
1018
		$product_id  = intval( $_POST['product_id'] );
1019
		$order_id    = intval( $_POST['order_id'] );
1020
1021
		$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 ) );
1022
1023
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
1024
1025
		die();
1026
	}
1027
1028
	/**
1029
	 * Grant download permissions via ajax function.
1030
	 */
1031
	public static function grant_access_to_download() {
1032
1033
		check_ajax_referer( 'grant-access', 'security' );
1034
1035
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1036
			die(-1);
1037
		}
1038
1039
		global $wpdb;
1040
1041
		$wpdb->hide_errors();
1042
1043
		$order_id     = intval( $_POST['order_id'] );
1044
		$product_ids  = $_POST['product_ids'];
1045
		$loop         = intval( $_POST['loop'] );
1046
		$file_counter = 0;
1047
		$order        = wc_get_order( $order_id );
1048
1049
		if ( ! is_array( $product_ids ) ) {
1050
			$product_ids = array( $product_ids );
1051
		}
1052
1053
		foreach ( $product_ids as $product_id ) {
1054
			$product = wc_get_product( $product_id );
1055
			$files   = $product->get_files();
1056
1057
			if ( ! $order->billing_email ) {
1058
				die();
1059
			}
1060
1061
			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...
1062
				foreach ( $files as $download_id => $file ) {
1063
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
1064
1065
						// insert complete - get inserted data
1066
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1067
1068
						$loop ++;
1069
						$file_counter ++;
1070
1071 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...
1072
							$file_count = $file['name'];
1073
						} else {
1074
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1075
						}
1076
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1077
					}
1078
				}
1079
			}
1080
		}
1081
1082
		die();
1083
	}
1084
1085
	/**
1086
	 * Get customer details via ajax.
1087
	 */
1088
	public static function get_customer_details() {
1089
		ob_start();
1090
1091
		check_ajax_referer( 'get-customer-details', 'security' );
1092
1093
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1094
			die(-1);
1095
		}
1096
1097
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1098
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1099
1100
		$customer_data = array(
1101
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1102
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1103
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1104
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1105
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1106
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1107
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1108
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1109
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1110
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1111
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1112
		);
1113
1114
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1115
1116
		wp_send_json( $customer_data );
1117
	}
1118
1119
	/**
1120
	 * Add order item via ajax.
1121
	 */
1122
	public static function add_order_item() {
1123
		check_ajax_referer( 'order-item', 'security' );
1124
1125
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1126
			die(-1);
1127
		}
1128
1129
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1130
		$order_id    = absint( $_POST['order_id'] );
1131
1132
		// Find the item
1133
		if ( ! is_numeric( $item_to_add ) ) {
1134
			die();
1135
		}
1136
1137
		$post = get_post( $item_to_add );
1138
1139
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1140
			die();
1141
		}
1142
1143
		$_product    = wc_get_product( $post->ID );
1144
		$order       = wc_get_order( $order_id );
1145
		$order_taxes = $order->get_taxes();
1146
		$class       = 'new_row';
1147
1148
		// Set values
1149
		$item = array();
1150
1151
		$item['product_id']        = $_product->id;
1152
		$item['variation_id']      = isset( $_product->variation_id ) ? $_product->variation_id : '';
1153
		$item['variation_data']    = $item['variation_id'] ? $_product->get_variation_attributes() : '';
1154
		$item['name']              = $_product->get_title();
1155
		$item['tax_class']         = $_product->get_tax_class();
1156
		$item['qty']               = 1;
1157
		$item['line_subtotal']     = wc_format_decimal( $_product->get_price_excluding_tax() );
1158
		$item['line_subtotal_tax'] = '';
1159
		$item['line_total']        = wc_format_decimal( $_product->get_price_excluding_tax() );
1160
		$item['line_tax']          = '';
1161
		$item['type']              = 'line_item';
1162
1163
		// Add line item
1164
		$item_id = wc_add_order_item( $order_id, array(
1165
			'order_item_name' 		=> $item['name'],
1166
			'order_item_type' 		=> 'line_item'
1167
		) );
1168
1169
		// Add line item meta
1170
		if ( $item_id ) {
1171
			wc_add_order_item_meta( $item_id, '_qty', $item['qty'] );
1172
			wc_add_order_item_meta( $item_id, '_tax_class', $item['tax_class'] );
1173
			wc_add_order_item_meta( $item_id, '_product_id', $item['product_id'] );
1174
			wc_add_order_item_meta( $item_id, '_variation_id', $item['variation_id'] );
1175
			wc_add_order_item_meta( $item_id, '_line_subtotal', $item['line_subtotal'] );
1176
			wc_add_order_item_meta( $item_id, '_line_subtotal_tax', $item['line_subtotal_tax'] );
1177
			wc_add_order_item_meta( $item_id, '_line_total', $item['line_total'] );
1178
			wc_add_order_item_meta( $item_id, '_line_tax', $item['line_tax'] );
1179
1180
			// Since 2.2
1181
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
1182
1183
			// Store variation data in meta
1184 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...
1185
				foreach ( $item['variation_data'] as $key => $value ) {
1186
					wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
1187
				}
1188
			}
1189
1190
			do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1191
		}
1192
1193
		$item['item_meta']       = $order->get_item_meta( $item_id );
1194
		$item['item_meta_array'] = $order->get_item_meta_array( $item_id );
1195
		$item                    = $order->expand_item_meta( $item );
1196
		$item                    = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
1197
1198
		include( 'admin/meta-boxes/views/html-order-item.php' );
1199
1200
		// Quit out
1201
		die();
1202
	}
1203
1204
	/**
1205
	 * Add order fee via ajax.
1206
	 */
1207
	public static function add_order_fee() {
1208
1209
		check_ajax_referer( 'order-item', 'security' );
1210
1211
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1212
			die(-1);
1213
		}
1214
1215
		$order_id      = absint( $_POST['order_id'] );
1216
		$order         = wc_get_order( $order_id );
1217
		$order_taxes   = $order->get_taxes();
1218
		$item          = array();
1219
1220
		// Add new fee
1221
		$fee            = new stdClass();
1222
		$fee->name      = '';
1223
		$fee->tax_class = '';
1224
		$fee->taxable   = $fee->tax_class !== '0';
1225
		$fee->amount    = '';
1226
		$fee->tax       = '';
1227
		$fee->tax_data  = array();
1228
		$item_id        = $order->add_fee( $fee );
1229
1230
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1231
1232
		// Quit out
1233
		die();
1234
	}
1235
1236
	/**
1237
	 * Add order shipping cost via ajax.
1238
	 */
1239
	public static function add_order_shipping() {
1240
1241
		check_ajax_referer( 'order-item', 'security' );
1242
1243
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1244
			die(-1);
1245
		}
1246
1247
		$order_id         = absint( $_POST['order_id'] );
1248
		$order            = wc_get_order( $order_id );
1249
		$order_taxes      = $order->get_taxes();
1250
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1251
		$item             = array();
1252
1253
		// Add new shipping
1254
		$shipping        = new stdClass();
1255
		$shipping->label = '';
1256
		$shipping->id    = '';
1257
		$shipping->cost  = '';
1258
		$shipping->taxes = array();
1259
		$item_id         = $order->add_shipping( $shipping );
1260
1261
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1262
1263
		// Quit out
1264
		die();
1265
	}
1266
1267
	/**
1268
	 * Add order tax column via ajax.
1269
	 */
1270
	public static function add_order_tax() {
1271
		global $wpdb;
1272
1273
		check_ajax_referer( 'order-item', 'security' );
1274
1275
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1276
			die(-1);
1277
		}
1278
1279
		$order_id = absint( $_POST['order_id'] );
1280
		$rate_id  = absint( $_POST['rate_id'] );
1281
		$order    = wc_get_order( $order_id );
1282
		$data     = get_post_meta( $order_id );
1283
1284
		// Add new tax
1285
		$order->add_tax( $rate_id, 0, 0 );
1286
1287
		// Return HTML items
1288
		include( 'admin/meta-boxes/views/html-order-items.php' );
1289
1290
		die();
1291
	}
1292
1293
	/**
1294
	 * Remove an order item.
1295
	 */
1296
	public static function remove_order_item() {
1297
		check_ajax_referer( 'order-item', 'security' );
1298
1299
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1300
			die(-1);
1301
		}
1302
1303
		$order_item_ids = $_POST['order_item_ids'];
1304
1305
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1306
			$order_item_ids = array( $order_item_ids );
1307
		}
1308
1309
		if ( sizeof( $order_item_ids ) > 0 ) {
1310
			foreach( $order_item_ids as $id ) {
1311
				wc_delete_order_item( absint( $id ) );
1312
			}
1313
		}
1314
1315
		die();
1316
	}
1317
1318
	/**
1319
	 * Remove an order tax.
1320
	 */
1321
	public static function remove_order_tax() {
1322
1323
		check_ajax_referer( 'order-item', 'security' );
1324
1325
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1326
			die(-1);
1327
		}
1328
1329
		$order_id = absint( $_POST['order_id'] );
1330
		$rate_id  = absint( $_POST['rate_id'] );
1331
1332
		wc_delete_order_item( $rate_id );
1333
1334
		// Return HTML items
1335
		$order = wc_get_order( $order_id );
1336
		$data  = get_post_meta( $order_id );
1337
		include( 'admin/meta-boxes/views/html-order-items.php' );
1338
1339
		die();
1340
	}
1341
1342
	/**
1343
	 * Reduce order item stock.
1344
	 */
1345
	public static function reduce_order_item_stock() {
1346
		check_ajax_referer( 'order-item', 'security' );
1347
1348
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1349
			die(-1);
1350
		}
1351
1352
		$order_id       = absint( $_POST['order_id'] );
1353
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1354
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1355
		$order          = wc_get_order( $order_id );
1356
		$order_items    = $order->get_items();
1357
		$return         = array();
1358
1359
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1360
1361
			foreach ( $order_items as $item_id => $order_item ) {
1362
1363
				// Only reduce checked items
1364
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1365
					continue;
1366
				}
1367
1368
				$_product = $order->get_product_from_item( $order_item );
1369
1370
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1371
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1372
					$new_stock    = $_product->reduce_stock( $stock_change );
1373
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1374
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1375
					$return[]     = $note;
1376
1377
					$order->add_order_note( $note );
1378
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1379
				}
1380
			}
1381
1382
			do_action( 'woocommerce_reduce_order_stock', $order );
1383
1384
			if ( empty( $return ) ) {
1385
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1386
			}
1387
1388
			echo implode( ', ', $return );
1389
		}
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
1400
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1401
			die(-1);
1402
		}
1403
1404
		$order_id       = absint( $_POST['order_id'] );
1405
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1406
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1407
		$order          = wc_get_order( $order_id );
1408
		$order_items    = $order->get_items();
1409
		$return         = array();
1410
1411
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1412
1413
			foreach ( $order_items as $item_id => $order_item ) {
1414
1415
				// Only reduce checked items
1416
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1417
					continue;
1418
				}
1419
1420
				$_product = $order->get_product_from_item( $order_item );
1421
1422
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1423
					$old_stock    = $_product->get_stock_quantity();
1424
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1425
					$new_quantity = $_product->increase_stock( $stock_change );
1426
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1427
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1428
					$return[]     = $note;
1429
1430
					$order->add_order_note( $note );
1431
				}
1432
			}
1433
1434
			do_action( 'woocommerce_restore_order_stock', $order );
1435
1436
			if ( empty( $return ) ) {
1437
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1438
			}
1439
1440
			echo implode( ', ', $return );
1441
		}
1442
1443
		die();
1444
	}
1445
1446
	/**
1447
	 * Add some meta to a line item.
1448
	 */
1449
	public static function add_order_item_meta() {
1450
		check_ajax_referer( 'order-item', 'security' );
1451
1452
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1453
			die(-1);
1454
		}
1455
1456
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1457
1458
		if ( $meta_id ) {
1459
			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>';
1460
		}
1461
1462
		die();
1463
	}
1464
1465
	/**
1466
	 * Remove meta from a line item.
1467
	 */
1468
	public static function remove_order_item_meta() {
1469
		global $wpdb;
1470
1471
		check_ajax_referer( 'order-item', 'security' );
1472
1473
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1474
			die(-1);
1475
		}
1476
1477
		$meta_id = absint( $_POST['meta_id'] );
1478
1479
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_id = %d", $meta_id ) );
1480
1481
		die();
1482
	}
1483
1484
	/**
1485
	 * Calc line tax.
1486
	 */
1487
	public static function calc_line_taxes() {
1488
		global $wpdb;
1489
1490
		check_ajax_referer( 'calc-totals', 'security' );
1491
1492
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1493
			die(-1);
1494
		}
1495
1496
		$tax            = new WC_Tax();
1497
		$tax_based_on   = get_option( 'woocommerce_tax_based_on' );
1498
		$order_id       = absint( $_POST['order_id'] );
1499
		$items          = array();
1500
		$country        = strtoupper( esc_attr( $_POST['country'] ) );
1501
		$state          = strtoupper( esc_attr( $_POST['state'] ) );
1502
		$postcode       = strtoupper( esc_attr( $_POST['postcode'] ) );
1503
		$city           = wc_clean( esc_attr( $_POST['city'] ) );
1504
		$order          = wc_get_order( $order_id );
1505
		$taxes          = array();
1506
		$shipping_taxes = array();
1507
1508
		// Default to base
1509 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...
1510
			$default  = wc_get_base_location();
1511
			$country  = $default['country'];
1512
			$state    = $default['state'];
1513
			$postcode = '';
1514
			$city     = '';
1515
		}
1516
1517
		// Parse the jQuery serialized items
1518
		parse_str( $_POST['items'], $items );
1519
1520
		// Prevent undefined warnings
1521
		if ( ! isset( $items['line_tax'] ) ) {
1522
			$items['line_tax'] = array();
1523
		}
1524
		if ( ! isset( $items['line_subtotal_tax'] ) ) {
1525
			$items['line_subtotal_tax'] = array();
1526
		}
1527
		$items['order_taxes'] = array();
1528
1529
		// Action
1530
		$items = apply_filters( 'woocommerce_ajax_calc_line_taxes', $items, $order_id, $country, $_POST );
1531
1532
		// Get items and fees taxes
1533
		if ( isset( $items['order_item_id'] ) ) {
1534
			$line_total = $line_subtotal = $order_item_tax_class = array();
1535
1536
			foreach ( $items['order_item_id'] as $item_id ) {
1537
				$item_id                          = absint( $item_id );
1538
				$line_total[ $item_id ]           = isset( $items['line_total'][ $item_id ] ) ? wc_format_decimal( $items['line_total'][ $item_id ] ) : 0;
1539
				$line_subtotal[ $item_id ]        = isset( $items['line_subtotal'][ $item_id ] ) ? wc_format_decimal( $items['line_subtotal'][ $item_id ] ) : $line_total[ $item_id ];
1540
				$order_item_tax_class[ $item_id ] = isset( $items['order_item_tax_class'][ $item_id ] ) ? sanitize_text_field( $items['order_item_tax_class'][ $item_id ] ) : '';
1541
				$product_id                       = $order->get_item_meta( $item_id, '_product_id', true );
1542
1543
				// Get product details
1544
				if ( get_post_type( $product_id ) == 'product' ) {
1545
					$_product        = wc_get_product( $product_id );
1546
					$item_tax_status = $_product->get_tax_status();
1547
				} else {
1548
					$item_tax_status = 'taxable';
1549
				}
1550
1551
				if ( '0' !== $order_item_tax_class[ $item_id ] && 'taxable' === $item_tax_status ) {
1552
					$tax_rates = WC_Tax::find_rates( array(
1553
						'country'   => $country,
1554
						'state'     => $state,
1555
						'postcode'  => $postcode,
1556
						'city'      => $city,
1557
						'tax_class' => $order_item_tax_class[ $item_id ]
1558
					) );
1559
1560
					$line_taxes          = WC_Tax::calc_tax( $line_total[ $item_id ], $tax_rates, false );
1561
					$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal[ $item_id ], $tax_rates, false );
1562
1563
					// Set the new line_tax
1564
					foreach ( $line_taxes as $_tax_id => $_tax_value ) {
1565
						$items['line_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1566
					}
1567
1568
					// Set the new line_subtotal_tax
1569
					foreach ( $line_subtotal_taxes as $_tax_id => $_tax_value ) {
1570
						$items['line_subtotal_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1571
					}
1572
1573
					// Sum the item taxes
1574
					foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1575
						$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1576
					}
1577
				}
1578
			}
1579
		}
1580
1581
		// Get shipping taxes
1582
		if ( isset( $items['shipping_method_id'] ) ) {
1583
			$matched_tax_rates = array();
1584
1585
			$tax_rates = WC_Tax::find_rates( array(
1586
				'country'   => $country,
1587
				'state'     => $state,
1588
				'postcode'  => $postcode,
1589
				'city'      => $city,
1590
				'tax_class' => ''
1591
			) );
1592
1593 View Code Duplication
			if ( $tax_rates ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tax_rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

Loading history...
2199
				if ( WC()->payment_gateways() ) {
2200
					$payment_gateways = WC()->payment_gateways->payment_gateways();
2201
				}
2202
				if ( isset( $payment_gateways[ $order->payment_method ] ) && $payment_gateways[ $order->payment_method ]->supports( 'refunds' ) ) {
2203
					$result = $payment_gateways[ $order->payment_method ]->process_refund( $order_id, $refund_amount, $refund_reason );
2204
2205
					do_action( 'woocommerce_refund_processed', $refund, $result );
2206
2207
					if ( is_wp_error( $result ) ) {
2208
						throw new Exception( $result->get_error_message() );
2209
					} elseif ( ! $result ) {
2210
						throw new Exception( __( 'Refund failed', 'woocommerce' ) );
2211
					}
2212
				}
2213
			}
2214
2215
			// restock items
2216
			foreach ( $line_item_qtys as $item_id => $qty ) {
2217
				if ( $restock_refunded_items && $qty && isset( $order_items[ $item_id ] ) ) {
2218
					$order_item = $order_items[ $item_id ];
2219
					$_product   = $order->get_product_from_item( $order_item );
2220
2221
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2222
						$old_stock    = wc_stock_amount( $_product->stock );
2223
						$new_quantity = $_product->increase_stock( $qty );
2224
2225
						$order->add_order_note( sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
2226
2227
						do_action( 'woocommerce_restock_refunded_item', $_product->id, $old_stock, $new_quantity, $order );
2228
					}
2229
				}
2230
			}
2231
2232
			// Trigger notifications and status changes
2233
			if ( $order->get_remaining_refund_amount() > 0 || ( $order->has_free_item() && $order->get_remaining_refund_items() > 0 ) ) {
2234
				/**
2235
				 * woocommerce_order_partially_refunded.
2236
				 *
2237
				 * @since 2.4.0
2238
				 * Note: 3rd arg was added in err. Kept for bw compat. 2.4.3.
2239
				 */
2240
				do_action( 'woocommerce_order_partially_refunded', $order_id, $refund->id, $refund->id );
2241
			} else {
2242
				do_action( 'woocommerce_order_fully_refunded', $order_id, $refund->id );
2243
2244
				$order->update_status( apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order_id, $refund->id ) );
2245
				$response_data['status'] = 'fully_refunded';
2246
			}
2247
2248
			do_action( 'woocommerce_order_refunded', $order_id, $refund->id );
2249
2250
			// Clear transients
2251
			wc_delete_shop_order_transients( $order_id );
2252
			wp_send_json_success( $response_data );
2253
2254
		} catch ( Exception $e ) {
2255
			if ( $refund && is_a( $refund, 'WC_Order_Refund' ) ) {
2256
				wp_delete_post( $refund->id, true );
2257
			}
2258
2259
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
2260
		}
2261
	}
2262
2263
	/**
2264
	 * Delete a refund.
2265
	 */
2266
	public static function delete_refund() {
2267
		check_ajax_referer( 'order-item', 'security' );
2268
2269
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2270
			die(-1);
2271
		}
2272
2273
		$refund_id = absint( $_POST['refund_id'] );
2274
2275
		if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
2276
			$order_id = wp_get_post_parent_id( $refund_id );
2277
			wc_delete_shop_order_transients( $order_id );
2278
			wp_delete_post( $refund_id );
2279
2280
			do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
2281
		}
2282
2283
		die();
2284
	}
2285
2286
	/**
2287
	 * Triggered when clicking the rating footer.
2288
	 */
2289
	public static function rated() {
2290
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2291
			die(-1);
2292
		}
2293
2294
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
2295
		die();
2296
	}
2297
2298
	/**
2299
	 * Create/Update API key.
2300
	 */
2301
	public static function update_api_key() {
2302
		ob_start();
2303
2304
		global $wpdb;
2305
2306
		check_ajax_referer( 'update-api-key', 'security' );
2307
2308
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2309
			die(-1);
2310
		}
2311
2312
		try {
2313
			if ( empty( $_POST['description'] ) ) {
2314
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
2315
			}
2316
			if ( empty( $_POST['user'] ) ) {
2317
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
2318
			}
2319
			if ( empty( $_POST['permissions'] ) ) {
2320
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
2321
			}
2322
2323
			$key_id      = absint( $_POST['key_id'] );
2324
			$description = sanitize_text_field( wp_unslash( $_POST['description'] ) );
2325
			$permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read';
2326
			$user_id     = absint( $_POST['user'] );
2327
2328
			if ( 0 < $key_id ) {
2329
				$data = array(
2330
					'user_id'     => $user_id,
2331
					'description' => $description,
2332
					'permissions' => $permissions
2333
				);
2334
2335
				$wpdb->update(
2336
					$wpdb->prefix . 'woocommerce_api_keys',
2337
					$data,
2338
					array( 'key_id' => $key_id ),
2339
					array(
2340
						'%d',
2341
						'%s',
2342
						'%s'
2343
					),
2344
					array( '%d' )
2345
				);
2346
2347
				$data['consumer_key']    = '';
2348
				$data['consumer_secret'] = '';
2349
				$data['message']         = __( 'API Key updated successfully.', 'woocommerce' );
2350
			} else {
2351
				$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...
2352
				$consumer_key    = 'ck_' . wc_rand_hash();
2353
				$consumer_secret = 'cs_' . wc_rand_hash();
2354
2355
				$data = array(
2356
					'user_id'         => $user_id,
2357
					'description'     => $description,
2358
					'permissions'     => $permissions,
2359
					'consumer_key'    => wc_api_hash( $consumer_key ),
2360
					'consumer_secret' => $consumer_secret,
2361
					'truncated_key'   => substr( $consumer_key, -7 )
2362
				);
2363
2364
				$wpdb->insert(
2365
					$wpdb->prefix . 'woocommerce_api_keys',
2366
					$data,
2367
					array(
2368
						'%d',
2369
						'%s',
2370
						'%s',
2371
						'%s',
2372
						'%s',
2373
						'%s'
2374
					)
2375
				);
2376
2377
				$key_id                  = $wpdb->insert_id;
2378
				$data['consumer_key']    = $consumer_key;
2379
				$data['consumer_secret'] = $consumer_secret;
2380
				$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' );
2381
				$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>';
2382
			}
2383
2384
			wp_send_json_success( $data );
2385
		} catch ( Exception $e ) {
2386
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
2387
		}
2388
	}
2389
2390
	/**
2391
	 * Locate user via AJAX.
2392
	 */
2393
	public static function get_customer_location() {
2394
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
2395
		wp_send_json_success( array( 'hash' => $location_hash ) );
2396
	}
2397
2398
	/**
2399
	 * Load variations via AJAX.
2400
	 */
2401
	public static function load_variations() {
2402
		ob_start();
2403
2404
		check_ajax_referer( 'load-variations', 'security' );
2405
2406
		// Check permissions again and make sure we have what we need
2407 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...
2408
			die( -1 );
2409
		}
2410
2411
		global $post;
2412
2413
		$product_id = absint( $_POST['product_id'] );
2414
		$post       = get_post( $product_id ); // Set $post global so its available like within the admin screens
2415
		$per_page   = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
2416
		$page       = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
2417
2418
		// Get attributes
2419
		$attributes        = array();
2420
		$posted_attributes = wp_unslash( $_POST['attributes'] );
2421
2422
		foreach ( $posted_attributes as $key => $value ) {
2423
			$attributes[ $key ] = array_map( 'wc_clean', $value );
2424
		}
2425
2426
		// Get tax classes
2427
		$tax_classes           = WC_Tax::get_tax_classes();
2428
		$tax_class_options     = array();
2429
		$tax_class_options[''] = __( 'Standard', 'woocommerce' );
2430
2431 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...
2432
			foreach ( $tax_classes as $class ) {
2433
				$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
2434
			}
2435
		}
2436
2437
		// Set backorder options
2438
		$backorder_options = array(
2439
			'no'     => __( 'Do not allow', 'woocommerce' ),
2440
			'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
2441
			'yes'    => __( 'Allow', 'woocommerce' )
2442
		);
2443
2444
		// set stock status options
2445
		$stock_status_options = array(
2446
			'instock'    => __( 'In stock', 'woocommerce' ),
2447
			'outofstock' => __( 'Out of stock', 'woocommerce' )
2448
		);
2449
2450
		$parent_data = array(
2451
			'id'                   => $product_id,
2452
			'attributes'           => $attributes,
2453
			'tax_class_options'    => $tax_class_options,
2454
			'sku'                  => get_post_meta( $product_id, '_sku', true ),
2455
			'weight'               => wc_format_localized_decimal( get_post_meta( $product_id, '_weight', true ) ),
2456
			'length'               => wc_format_localized_decimal( get_post_meta( $product_id, '_length', true ) ),
2457
			'width'                => wc_format_localized_decimal( get_post_meta( $product_id, '_width', true ) ),
2458
			'height'               => wc_format_localized_decimal( get_post_meta( $product_id, '_height', true ) ),
2459
			'tax_class'            => get_post_meta( $product_id, '_tax_class', true ),
2460
			'backorder_options'    => $backorder_options,
2461
			'stock_status_options' => $stock_status_options
2462
		);
2463
2464
		if ( ! $parent_data['weight'] ) {
2465
			$parent_data['weight'] = wc_format_localized_decimal( 0 );
2466
		}
2467
2468
		if ( ! $parent_data['length'] ) {
2469
			$parent_data['length'] = wc_format_localized_decimal( 0 );
2470
		}
2471
2472
		if ( ! $parent_data['width'] ) {
2473
			$parent_data['width'] = wc_format_localized_decimal( 0 );
2474
		}
2475
2476
		if ( ! $parent_data['height'] ) {
2477
			$parent_data['height'] = wc_format_localized_decimal( 0 );
2478
		}
2479
2480
		// Get variations
2481
		$args = apply_filters( 'woocommerce_ajax_admin_get_variations_args', array(
2482
			'post_type'      => 'product_variation',
2483
			'post_status'    => array( 'private', 'publish' ),
2484
			'posts_per_page' => $per_page,
2485
			'paged'          => $page,
2486
			'orderby'        => array( 'menu_order' => 'ASC', 'ID' => 'DESC' ),
2487
			'post_parent'    => $product_id
2488
		), $product_id );
2489
2490
		$variations = get_posts( $args );
2491
		$loop = 0;
2492
2493
		if ( $variations ) {
2494
2495
			foreach ( $variations as $variation ) {
2496
				$variation_id     = absint( $variation->ID );
2497
				$variation_meta   = get_post_meta( $variation_id );
2498
				$variation_data   = array();
2499
				$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
2500
				$variation_fields = array(
2501
					'_sku'                   => '',
2502
					'_stock'                 => '',
2503
					'_regular_price'         => '',
2504
					'_sale_price'            => '',
2505
					'_weight'                => '',
2506
					'_length'                => '',
2507
					'_width'                 => '',
2508
					'_height'                => '',
2509
					'_download_limit'        => '',
2510
					'_download_expiry'       => '',
2511
					'_downloadable_files'    => '',
2512
					'_downloadable'          => '',
2513
					'_virtual'               => '',
2514
					'_thumbnail_id'          => '',
2515
					'_sale_price_dates_from' => '',
2516
					'_sale_price_dates_to'   => '',
2517
					'_manage_stock'          => '',
2518
					'_stock_status'          => '',
2519
					'_backorders'            => null,
2520
					'_tax_class'             => null,
2521
					'_variation_description' => ''
2522
				);
2523
2524 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...
2525
					$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
2526
				}
2527
2528
				// Add the variation attributes
2529
				$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
2530
2531
				// Formatting
2532
				$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
2533
				$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
2534
				$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
2535
				$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
2536
				$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
2537
				$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
2538
				$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
2539
				$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
2540
				$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
2541
				$variation_data['menu_order']     = $variation->menu_order;
2542
				$variation_data['_stock']         = '' === $variation_data['_stock'] ? '' : wc_stock_amount( $variation_data['_stock'] );
2543
2544
				include( 'admin/meta-boxes/views/html-variation-admin.php' );
2545
2546
				$loop++;
2547
			}
2548
		}
2549
2550
		die();
2551
	}
2552
2553
	/**
2554
	 * Save variations via AJAX.
2555
	 */
2556
	public static function save_variations() {
2557
		ob_start();
2558
2559
		check_ajax_referer( 'save-variations', 'security' );
2560
2561
		// Check permissions again and make sure we have what we need
2562 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...
2563
			die( -1 );
2564
		}
2565
2566
		// Remove previous meta box errors
2567
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
2568
2569
		$product_id   = absint( $_POST['product_id'] );
2570
		$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
2571
2572
		$product_type_terms = wp_get_object_terms( $product_id, 'product_type' );
2573
2574
		// If the product type hasn't been set or it has changed, update it before saving variations
2575
		if ( empty( $product_type_terms ) || $product_type !== sanitize_title( current( $product_type_terms )->name ) ) {
2576
			wp_set_object_terms( $product_id, $product_type, 'product_type' );
2577
		}
2578
2579
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
2580
2581
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
2582
2583
		// Clear cache/transients
2584
		wc_delete_product_transients( $product_id );
2585
2586
		if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
2587
			echo '<div class="error notice is-dismissible">';
2588
2589
			foreach ( $errors as $error ) {
2590
				echo '<p>' . wp_kses_post( $error ) . '</p>';
2591
			}
2592
2593
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
2594
			echo '</div>';
2595
2596
			delete_option( 'woocommerce_meta_box_errors' );
2597
		}
2598
2599
		die();
2600
	}
2601
2602
	/**
2603
	 * Bulk action - Toggle Enabled.
2604
	 * @access private
2605
	 * @param  array $variations
2606
	 * @param  array $data
2607
	 */
2608
	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...
2609
		global $wpdb;
2610
2611
		foreach ( $variations as $variation_id ) {
2612
			$post_status = get_post_status( $variation_id );
2613
			$new_status  = 'private' === $post_status ? 'publish' : 'private';
2614
			$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $variation_id ) );
2615
		}
2616
	}
2617
2618
	/**
2619
	 * Bulk action - Toggle Downloadable Checkbox.
2620
	 * @access private
2621
	 * @param  array $variations
2622
	 * @param  array $data
2623
	 */
2624 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...
2625
		foreach ( $variations as $variation_id ) {
2626
			$_downloadable   = get_post_meta( $variation_id, '_downloadable', true );
2627
			$is_downloadable = 'no' === $_downloadable ? 'yes' : 'no';
2628
			update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
2629
		}
2630
	}
2631
2632
	/**
2633
	 * Bulk action - Toggle Virtual Checkbox.
2634
	 * @access private
2635
	 * @param  array $variations
2636
	 * @param  array $data
2637
	 */
2638 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...
2639
		foreach ( $variations as $variation_id ) {
2640
			$_virtual   = get_post_meta( $variation_id, '_virtual', true );
2641
			$is_virtual = 'no' === $_virtual ? 'yes' : 'no';
2642
			update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
2643
		}
2644
	}
2645
2646
	/**
2647
	 * Bulk action - Toggle Manage Stock Checkbox.
2648
	 * @access private
2649
	 * @param  array $variations
2650
	 * @param  array $data
2651
	 */
2652
	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...
2653
		foreach ( $variations as $variation_id ) {
2654
			$_manage_stock   = get_post_meta( $variation_id, '_manage_stock', true );
2655
			$is_manage_stock = 'no' === $_manage_stock || '' === $_manage_stock ? 'yes' : 'no';
2656
			update_post_meta( $variation_id, '_manage_stock', $is_manage_stock );
2657
		}
2658
	}
2659
2660
	/**
2661
	 * Bulk action - Set Regular Prices.
2662
	 * @access private
2663
	 * @param  array $variations
2664
	 * @param  array $data
2665
	 */
2666 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...
2667
		if ( ! isset( $data['value'] ) ) {
2668
			return;
2669
		}
2670
2671
		foreach ( $variations as $variation_id ) {
2672
			// Price fields
2673
			$regular_price = wc_clean( $data['value'] );
2674
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2675
2676
			// Date fields
2677
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2678
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2679
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2680
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2681
2682
			_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...
2683
		}
2684
	}
2685
2686
	/**
2687
	 * Bulk action - Set Sale Prices.
2688
	 * @access private
2689
	 * @param  array $variations
2690
	 * @param  array $data
2691
	 */
2692 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...
2693
		if ( ! isset( $data['value'] ) ) {
2694
			return;
2695
		}
2696
2697
		foreach ( $variations as $variation_id ) {
2698
			// Price fields
2699
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2700
			$sale_price    = wc_clean( $data['value'] );
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
Bug introduced by
It seems like $sale_price defined by wc_clean($data['value']) on line 2700 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...
2709
		}
2710
	}
2711
2712
	/**
2713
	 * Bulk action - Set Stock.
2714
	 * @access private
2715
	 * @param  array $variations
2716
	 * @param  array $data
2717
	 */
2718
	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...
2719
		if ( ! isset( $data['value'] ) ) {
2720
			return;
2721
		}
2722
2723
		$value = wc_clean( $data['value'] );
2724
2725
		foreach ( $variations as $variation_id ) {
2726
			if ( 'yes' === get_post_meta( $variation_id, '_manage_stock', true ) ) {
2727
				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...
2728
			} else {
2729
				delete_post_meta( $variation_id, '_stock' );
2730
			}
2731
		}
2732
	}
2733
2734
	/**
2735
	 * Bulk action - Set Weight.
2736
	 * @access private
2737
	 * @param  array $variations
2738
	 * @param  array $data
2739
	 */
2740
	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...
2741
		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...
2742
	}
2743
2744
	/**
2745
	 * Bulk action - Set Length.
2746
	 * @access private
2747
	 * @param  array $variations
2748
	 * @param  array $data
2749
	 */
2750
	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...
2751
		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...
2752
	}
2753
2754
	/**
2755
	 * Bulk action - Set Width.
2756
	 * @access private
2757
	 * @param  array $variations
2758
	 * @param  array $data
2759
	 */
2760
	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...
2761
		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...
2762
	}
2763
2764
	/**
2765
	 * Bulk action - Set Height.
2766
	 * @access private
2767
	 * @param  array $variations
2768
	 * @param  array $data
2769
	 */
2770
	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...
2771
		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...
2772
	}
2773
2774
	/**
2775
	 * Bulk action - Set Download Limit.
2776
	 * @access private
2777
	 * @param  array $variations
2778
	 * @param  array $data
2779
	 */
2780
	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...
2781
		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...
2782
	}
2783
2784
	/**
2785
	 * Bulk action - Set Download Expiry.
2786
	 * @access private
2787
	 * @param  array $variations
2788
	 * @param  array $data
2789
	 */
2790
	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...
2791
		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...
2792
	}
2793
2794
	/**
2795
	 * Bulk action - Delete all.
2796
	 * @access private
2797
	 * @param  array $variations
2798
	 * @param  array $data
2799
	 */
2800
	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...
2801
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2802
			foreach ( $variations as $variation_id ) {
2803
				wp_delete_post( $variation_id );
2804
			}
2805
		}
2806
	}
2807
2808
	/**
2809
	 * Bulk action - Sale Schedule.
2810
	 * @access private
2811
	 * @param  array $variations
2812
	 * @param  array $data
2813
	 */
2814
	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...
2815
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2816
			return;
2817
		}
2818
2819
		foreach ( $variations as $variation_id ) {
2820
			// Price fields
2821
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2822
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2823
2824
			// Date fields
2825
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2826
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2827
2828 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...
2829
				$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2830
			} else {
2831
				$date_from = $data['date_from'];
2832
			}
2833
2834 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...
2835
				$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2836
			} else {
2837
				$date_to = $data['date_to'];
2838
			}
2839
2840
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
2841
		}
2842
	}
2843
2844
	/**
2845
	 * Bulk action - Increase Regular Prices.
2846
	 * @access private
2847
	 * @param  array $variations
2848
	 * @param  array $data
2849
	 */
2850
	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...
2851
		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...
2852
	}
2853
2854
	/**
2855
	 * Bulk action - Decrease Regular Prices.
2856
	 * @access private
2857
	 * @param  array $variations
2858
	 * @param  array $data
2859
	 */
2860
	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...
2861
		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...
2862
	}
2863
2864
	/**
2865
	 * Bulk action - Increase Sale Prices.
2866
	 * @access private
2867
	 * @param  array $variations
2868
	 * @param  array $data
2869
	 */
2870
	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...
2871
		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...
2872
	}
2873
2874
	/**
2875
	 * Bulk action - Decrease Sale Prices.
2876
	 * @access private
2877
	 * @param  array $variations
2878
	 * @param  array $data
2879
	 */
2880
	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...
2881
		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...
2882
	}
2883
2884
	/**
2885
	 * Bulk action - Set Price.
2886
	 * @access private
2887
	 * @param  array $variations
2888
	 * @param string $operator + or -
2889
	 * @param string $field price being adjusted
2890
	 * @param string $value Price or Percent
2891
	 */
2892
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2893
		foreach ( $variations as $variation_id ) {
2894
			// Get existing data
2895
			$_regular_price = get_post_meta( $variation_id, '_regular_price', true );
2896
			$_sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2897
			$date_from      = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2898
			$date_to        = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2899
			$date_from      = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2900
			$date_to        = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2901
2902
			if ( '%' === substr( $value, -1 ) ) {
2903
				$percent = wc_format_decimal( substr( $value, 0, -1 ) );
2904
				$$field  += ( ( $$field / 100 ) * $percent ) * "{$operator}1";
2905
			} else {
2906
				$$field  += $value * "{$operator}1";
2907
			}
2908
			_wc_save_product_price( $variation_id, $_regular_price, $_sale_price, $date_from, $date_to );
2909
		}
2910
	}
2911
2912
	/**
2913
	 * Bulk action - Set Meta.
2914
	 * @access private
2915
	 * @param array $variations
2916
	 * @param string $field
2917
	 * @param string $value
2918
	 */
2919
	private static function variation_bulk_set_meta( $variations, $field, $value ) {
2920
		foreach ( $variations as $variation_id ) {
2921
			update_post_meta( $variation_id, $field, $value );
2922
		}
2923
	}
2924
2925
2926
	/**
2927
	 * Bulk edit variations via AJAX.
2928
	 */
2929
	public static function bulk_edit_variations() {
2930
		ob_start();
2931
2932
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2933
2934
		// Check permissions again and make sure we have what we need
2935 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...
2936
			die( -1 );
2937
		}
2938
2939
		$product_id  = absint( $_POST['product_id'] );
2940
		$bulk_action = wc_clean( $_POST['bulk_action'] );
2941
		$data        = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array();
2942
		$variations  = array();
2943
2944
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2945
			$variations = get_posts( array(
2946
				'post_parent'    => $product_id,
2947
				'posts_per_page' => -1,
2948
				'post_type'      => 'product_variation',
2949
				'fields'         => 'ids',
2950
				'post_status'    => array( 'publish', 'private' )
2951
			) );
2952
		}
2953
2954
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2955
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2956
		} else {
2957
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2958
		}
2959
2960
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2961
2962
		// Sync and update transients
2963
		WC_Product_Variable::sync( $product_id );
2964
		wc_delete_product_transients( $product_id );
2965
		die();
2966
	}
2967
2968
	/**
2969
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2970
	 */
2971
	public static function tax_rates_save_changes() {
2972
		if ( ! isset( $_POST['current_class'], $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2973
			wp_send_json_error( 'missing_fields' );
2974
			exit;
2975
		}
2976
2977
		$current_class = $_POST['current_class']; // This is sanitized seven lines later.
2978
2979
		if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) {
2980
			wp_send_json_error( 'bad_nonce' );
2981
			exit;
2982
		}
2983
2984
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
2985
2986
		// Check User Caps
2987
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2988
			wp_send_json_error( 'missing_capabilities' );
2989
			exit;
2990
		}
2991
2992
		$changes = $_POST['changes'];
2993
		foreach ( $changes as $tax_rate_id => $data ) {
2994
			if ( isset( $data['deleted'] ) ) {
2995
				if ( isset( $data['newRow'] ) ) {
2996
					// So the user added and deleted a new row.
2997
					// That's fine, it's not in the database anyways. NEXT!
2998
					continue;
2999
				}
3000
				WC_Tax::_delete_tax_rate( $tax_rate_id );
3001
			}
3002
3003
			$tax_rate = array_intersect_key( $data, array(
3004
				'tax_rate_country'  => 1,
3005
				'tax_rate_state'    => 1,
3006
				'tax_rate'          => 1,
3007
				'tax_rate_name'     => 1,
3008
				'tax_rate_priority' => 1,
3009
				'tax_rate_compound' => 1,
3010
				'tax_rate_shipping' => 1,
3011
				'tax_rate_order'    => 1,
3012
			) );
3013
3014
			if ( isset( $data['newRow'] ) ) {
3015
				// 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...
3016
				$tax_rate['tax_rate_class'] = $current_class;
3017
				$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
3018
			} else {
3019
				// Updating an existing rate ...
3020
				if ( ! empty( $tax_rate ) ) {
3021
					WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
3022
				}
3023
			}
3024
3025
			if ( isset( $data['postcode'] ) ) {
3026
				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...
3027
			}
3028
			if ( isset( $data['city'] ) ) {
3029
				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...
3030
			}
3031
		}
3032
3033
		wp_send_json_success( array(
3034
			'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
3035
		) );
3036
	}
3037
3038
	/**
3039
	 * Handle submissions from assets/js/wc-shipping-zones.js Backbone model.
3040
	 */
3041
	public static function shipping_zones_save_changes() {
3042
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['changes'] ) ) {
3043
			wp_send_json_error( 'missing_fields' );
3044
			exit;
3045
		}
3046
3047
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3048
			wp_send_json_error( 'bad_nonce' );
3049
			exit;
3050
		}
3051
3052
		// Check User Caps
3053
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3054
			wp_send_json_error( 'missing_capabilities' );
3055
			exit;
3056
		}
3057
3058
		$changes = $_POST['changes'];
3059
		foreach ( $changes as $zone_id => $data ) {
3060
			if ( isset( $data['deleted'] ) ) {
3061
				if ( isset( $data['newRow'] ) ) {
3062
					// So the user added and deleted a new row.
3063
					// That's fine, it's not in the database anyways. NEXT!
3064
					continue;
3065
				}
3066
				WC_Shipping_Zones::delete_zone( $zone_id );
3067
				continue;
3068
			}
3069
3070
			$zone_data = array_intersect_key( $data, array(
3071
				'zone_id'        => 1,
3072
				'zone_name'      => 1,
3073
				'zone_order'     => 1,
3074
				'zone_locations' => 1,
3075
				'zone_postcodes' => 1
3076
			) );
3077
3078
			if ( isset( $zone_data['zone_id'] ) ) {
3079
				$zone = new WC_Shipping_Zone( $zone_data['zone_id'] );
3080
3081
				if ( isset( $zone_data['zone_name'] ) ) {
3082
					$zone->set_zone_name( $zone_data['zone_name'] );
3083
				}
3084
3085
				if ( isset( $zone_data['zone_order'] ) ) {
3086
					$zone->set_zone_order( $zone_data['zone_order'] );
3087
				}
3088
3089
				if ( isset( $zone_data['zone_locations'] ) ) {
3090
					$zone->clear_locations( array( 'state', 'country', 'continent' ) );
3091
					$locations = array_filter( array_map( 'wc_clean', (array) $zone_data['zone_locations'] ) );
3092
					foreach ( $locations as $location ) {
3093
						// Each posted location will be in the format type:code
3094
						$location_parts = explode( ':', $location );
3095
						switch ( $location_parts[0] ) {
3096
							case 'state' :
3097
								$zone->add_location( $location_parts[1] . ':' . $location_parts[2], 'state' );
3098
							break;
3099
							case 'country' :
3100
								$zone->add_location( $location_parts[1], 'country' );
3101
							break;
3102
							case 'continent' :
3103
								$zone->add_location( $location_parts[1], 'continent' );
3104
							break;
3105
						}
3106
					}
3107
				}
3108
3109
				if ( isset( $zone_data['zone_postcodes'] ) ) {
3110
					$zone->clear_locations( 'postcode' );
3111
					$postcodes = array_filter( array_map( 'strtoupper', array_map( 'wc_clean', explode( "\n", $zone_data['zone_postcodes'] ) ) ) );
3112
					foreach ( $postcodes as $postcode ) {
3113
						$zone->add_location( $postcode, 'postcode' );
3114
					}
3115
				}
3116
3117
				$zone->save();
3118
			}
3119
		}
3120
3121
		wp_send_json_success( array(
3122
			'zones' => WC_Shipping_Zones::get_zones()
3123
		) );
3124
	}
3125
3126
	/**
3127
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
3128
	 */
3129
	public static function shipping_zone_add_method() {
3130 View Code Duplication
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['method_id'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3131
			wp_send_json_error( 'missing_fields' );
3132
			exit;
3133
		}
3134
3135
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3136
			wp_send_json_error( 'bad_nonce' );
3137
			exit;
3138
		}
3139
3140
		// Check User Caps
3141
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3142
			wp_send_json_error( 'missing_capabilities' );
3143
			exit;
3144
		}
3145
3146
		$zone_id     = absint( $_POST['zone_id'] );
3147
		$zone        = WC_Shipping_Zones::get_zone( $zone_id );
3148
		$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...
3149
3150
		wp_send_json_success( array(
3151
			'instance_id' => $instance_id,
3152
			'zone_id'     => $zone_id,
3153
			'methods'     => $zone->get_shipping_methods()
3154
		) );
3155
	}
3156
3157
	/**
3158
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
3159
	 */
3160
	public static function shipping_zone_methods_save_changes() {
3161 View Code Duplication
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['changes'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3162
			wp_send_json_error( 'missing_fields' );
3163
			exit;
3164
		}
3165
3166
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3167
			wp_send_json_error( 'bad_nonce' );
3168
			exit;
3169
		}
3170
3171
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3172
			wp_send_json_error( 'missing_capabilities' );
3173
			exit;
3174
		}
3175
3176
		global $wpdb;
3177
3178
		$zone_id = absint( $_POST['zone_id'] );
3179
		$zone    = new WC_Shipping_Zone( $zone_id );
3180
		$changes = $_POST['changes'];
3181
3182
		foreach ( $changes as $instance_id => $data ) {
3183
			if ( isset( $data['deleted'] ) ) {
3184
				$wpdb->delete( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'instance_id' => $instance_id ) );
3185
				continue;
3186
			}
3187
3188
			$method_data = array_intersect_key( $data, array(
3189
				'method_order' => 1
3190
			) );
3191
3192
			if ( isset( $method_data['method_order'] ) ) {
3193
				$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'method_order' => absint( $method_data['method_order'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
3194
			}
3195
		}
3196
3197
		wp_send_json_success( array(
3198
			'methods' => $zone->get_shipping_methods()
3199
		) );
3200
	}
3201
3202
	/**
3203
	 * Handle submissions from assets/js/wc-shipping-classes.js Backbone model.
3204
	 */
3205
	public static function shipping_classes_save_changes() {
3206
		if ( ! isset( $_POST['wc_shipping_classes_nonce'], $_POST['changes'] ) ) {
3207
			wp_send_json_error( 'missing_fields' );
3208
			exit;
3209
		}
3210
3211
		if ( ! wp_verify_nonce( $_POST['wc_shipping_classes_nonce'], 'wc_shipping_classes_nonce' ) ) {
3212
			wp_send_json_error( 'bad_nonce' );
3213
			exit;
3214
		}
3215
3216
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3217
			wp_send_json_error( 'missing_capabilities' );
3218
			exit;
3219
		}
3220
3221
		$changes = $_POST['changes'];
3222
3223
		foreach ( $changes as $term_id => $data ) {
3224
			$term_id = absint( $term_id );
3225
3226
			if ( isset( $data['deleted'] ) ) {
3227
				if ( isset( $data['newRow'] ) ) {
3228
					// So the user added and deleted a new row.
3229
					// That's fine, it's not in the database anyways. NEXT!
3230
					continue;
3231
				}
3232
				wp_delete_term( $term_id, 'product_shipping_class' );
3233
				continue;
3234
			}
3235
3236
			$update_args = array();
3237
3238
			if ( isset( $data['name'] ) ) {
3239
				$update_args['name'] = wc_clean( $data['name'] );
3240
			}
3241
3242
			if ( isset( $data['slug'] ) ) {
3243
				$update_args['slug'] = wc_clean( $data['slug'] );
3244
			}
3245
3246
			if ( isset( $data['description'] ) ) {
3247
				$update_args['description'] = wc_clean( $data['description'] );
3248
			}
3249
3250
			if ( isset( $data['newRow'] ) ) {
3251
				$update_args = array_filter( $update_args );
3252
				if ( empty( $update_args['name'] ) ) {
3253
					wp_send_json_error( __( 'Shipping Class name is required', 'woocommerce' ) );
3254
					exit;
3255
				}
3256
				$result      = wp_insert_term( $update_args['name'], 'product_shipping_class', $update_args );
3257
			} else {
3258
				$result = wp_update_term( $term_id, 'product_shipping_class', $update_args );
3259
			}
3260
3261
			if ( is_wp_error( $result ) ) {
3262
				wp_send_json_error( $result->get_error_message() );
3263
				exit;
3264
			}
3265
		}
3266
3267
		$wc_shipping = WC_Shipping::instance();
3268
3269
		wp_send_json_success( array(
3270
			'shipping_classes' => $wc_shipping->get_shipping_classes()
3271
		) );
3272
	}
3273
}
3274
3275
WC_AJAX::init();
3276