Completed
Push — master ( dc5d37...ca5a46 )
by Mike
07:40
created

WC_AJAX::tax_rates_save_changes()   C

Complexity

Conditions 12
Paths 57

Size

Total Lines 66
Code Lines 39

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 66
rs 5.9123
cc 12
eloc 39
nc 57
nop 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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

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

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

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

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

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

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
65
		send_nosniff_header();
66
		nocache_headers();
67
		status_header( 200 );
68
	}
69
70
	/**
71
	 * Check for WC Ajax request and fire action.
72
	 */
73
	public static function do_wc_ajax() {
74
		global $wp_query;
75
76
		if ( ! empty( $_GET['wc-ajax'] ) ) {
77
			$wp_query->set( 'wc-ajax', sanitize_text_field( $_GET['wc-ajax'] ) );
78
		}
79
80
		if ( $action = $wp_query->get( 'wc-ajax' ) ) {
81
			self::wc_ajax_headers();
82
			do_action( 'wc_ajax_' . sanitize_text_field( $action ) );
83
			die();
84
		}
85
	}
86
87
	/**
88
	 * Hook in methods - uses WordPress ajax handlers (admin-ajax).
89
	 */
90
	public static function add_ajax_events() {
91
		// woocommerce_EVENT => nopriv
92
		$ajax_events = array(
93
			'get_refreshed_fragments'                          => true,
94
			'apply_coupon'                                     => true,
95
			'remove_coupon'                                    => true,
96
			'update_shipping_method'                           => true,
97
			'get_cart_totals'                                  => true,
98
			'update_order_review'                              => true,
99
			'add_to_cart'                                      => true,
100
			'checkout'                                         => true,
101
			'get_variation'                                    => true,
102
			'feature_product'                                  => false,
103
			'mark_order_status'                                => false,
104
			'add_attribute'                                    => false,
105
			'add_new_attribute'                                => false,
106
			'remove_variation'                                 => false,
107
			'remove_variations'                                => false,
108
			'save_attributes'                                  => false,
109
			'add_variation'                                    => false,
110
			'link_all_variations'                              => false,
111
			'revoke_access_to_download'                        => false,
112
			'grant_access_to_download'                         => false,
113
			'get_customer_details'                             => false,
114
			'add_order_item'                                   => false,
115
			'add_order_fee'                                    => false,
116
			'add_order_shipping'                               => false,
117
			'add_order_tax'                                    => false,
118
			'remove_order_item'                                => false,
119
			'remove_order_tax'                                 => false,
120
			'reduce_order_item_stock'                          => false,
121
			'increase_order_item_stock'                        => false,
122
			'add_order_item_meta'                              => false,
123
			'remove_order_item_meta'                           => false,
124
			'calc_line_taxes'                                  => false,
125
			'save_order_items'                                 => false,
126
			'load_order_items'                                 => false,
127
			'add_order_note'                                   => false,
128
			'delete_order_note'                                => false,
129
			'json_search_products'                             => false,
130
			'json_search_products_and_variations'              => false,
131
			'json_search_grouped_products'                     => false,
132
			'json_search_downloadable_products_and_variations' => false,
133
			'json_search_customers'                            => false,
134
			'term_ordering'                                    => false,
135
			'product_ordering'                                 => false,
136
			'refund_line_items'                                => false,
137
			'delete_refund'                                    => false,
138
			'rated'                                            => false,
139
			'update_api_key'                                   => false,
140
			'get_customer_location'                            => true,
141
			'load_variations'                                  => false,
142
			'save_variations'                                  => false,
143
			'bulk_edit_variations'                             => false,
144
			'tax_rates_save_changes'                           => false,
145
			'shipping_zones_save_changes'                      => false,
146
			'shipping_zone_add_method'                         => false,
147
			'shipping_zone_methods_save_changes'               => false,
148
			'shipping_classes_save_changes'                    => false,
149
		);
150
151
		foreach ( $ajax_events as $ajax_event => $nopriv ) {
152
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
153
154
			if ( $nopriv ) {
155
				add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
156
157
				// WC AJAX can be used for frontend ajax requests
158
				add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
159
			}
160
		}
161
	}
162
163
	/**
164
	 * Get a refreshed cart fragment.
165
	 */
166
	public static function get_refreshed_fragments() {
167
168
		// Get mini cart
169
		ob_start();
170
171
		woocommerce_mini_cart();
172
173
		$mini_cart = ob_get_clean();
174
175
		// Fragments and mini cart are returned
176
		$data = array(
177
			'fragments' => apply_filters( 'woocommerce_add_to_cart_fragments', array(
178
					'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>'
179
				)
180
			),
181
			'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() )
182
		);
183
184
		wp_send_json( $data );
185
186
	}
187
188
	/**
189
	 * AJAX apply coupon on checkout page.
190
	 */
191
	public static function apply_coupon() {
192
193
		check_ajax_referer( 'apply-coupon', 'security' );
194
195
		if ( ! empty( $_POST['coupon_code'] ) ) {
196
			WC()->cart->add_discount( sanitize_text_field( $_POST['coupon_code'] ) );
197
		} else {
198
			wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
199
		}
200
201
		wc_print_notices();
202
203
		die();
204
	}
205
206
	/**
207
	 * AJAX remove coupon on cart and checkout page.
208
	 */
209
	public static function remove_coupon() {
210
211
		check_ajax_referer( 'remove-coupon', 'security' );
212
213
		$coupon = wc_clean( $_POST['coupon'] );
214
215
		if ( ! isset( $coupon ) || empty( $coupon ) ) {
216
			wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ) );
217
218
		} else {
219
220
			WC()->cart->remove_coupon( $coupon );
1 ignored issue
show
Bug introduced by
It seems like $coupon defined by wc_clean($_POST['coupon']) on line 213 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...
221
222
			wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
223
		}
224
225
		wc_print_notices();
226
227
		die();
228
	}
229
230
	/**
231
	 * AJAX update shipping method on cart page.
232
	 */
233
	public static function update_shipping_method() {
234
235
		check_ajax_referer( 'update-shipping-method', 'security' );
236
237
		if ( ! defined('WOOCOMMERCE_CART') ) {
238
			define( 'WOOCOMMERCE_CART', true );
239
		}
240
241
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
242
243 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...
244
			foreach ( $_POST['shipping_method'] as $i => $value ) {
245
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
246
			}
247
		}
248
249
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
250
251
		WC()->cart->calculate_totals();
252
253
		woocommerce_cart_totals();
254
255
		die();
256
	}
257
258
	/**
259
	 * AJAX receive updated cart_totals div.
260
	 */
261
	public static function get_cart_totals() {
262
263
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
264
			define( 'WOOCOMMERCE_CART', true );
265
		}
266
267
		WC()->cart->calculate_totals();
268
269
		woocommerce_cart_totals();
270
271
		die();
272
	}
273
274
	/**
275
	 * AJAX update order review on checkout.
276
	 */
277
	public static function update_order_review() {
278
		ob_start();
279
280
		check_ajax_referer( 'update-order-review', 'security' );
281
282
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
283
			define( 'WOOCOMMERCE_CHECKOUT', true );
284
		}
285
286
		if ( WC()->cart->is_empty() ) {
287
			$data = array(
288
				'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
289
					'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>'
290
				) )
291
			);
292
293
			wp_send_json( $data );
294
295
			die();
296
		}
297
298
		do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] );
299
300
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
301
302 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...
303
			foreach ( $_POST['shipping_method'] as $i => $value ) {
304
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
305
			}
306
		}
307
308
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
309
		WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
310
311
		if ( isset( $_POST['country'] ) ) {
312
			WC()->customer->set_country( $_POST['country'] );
313
		}
314
315
		if ( isset( $_POST['state'] ) ) {
316
			WC()->customer->set_state( $_POST['state'] );
317
		}
318
319
		if ( isset( $_POST['postcode'] ) ) {
320
			WC()->customer->set_postcode( $_POST['postcode'] );
321
		}
322
323
		if ( isset( $_POST['city'] ) ) {
324
			WC()->customer->set_city( $_POST['city'] );
325
		}
326
327
		if ( isset( $_POST['address'] ) ) {
328
			WC()->customer->set_address( $_POST['address'] );
329
		}
330
331
		if ( isset( $_POST['address_2'] ) ) {
332
			WC()->customer->set_address_2( $_POST['address_2'] );
333
		}
334
335
		if ( wc_ship_to_billing_address_only() ) {
336
337 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...
338
				WC()->customer->set_shipping_country( $_POST['country'] );
339
				WC()->customer->calculated_shipping( true );
340
			}
341
342
			if ( isset( $_POST['state'] ) ) {
343
				WC()->customer->set_shipping_state( $_POST['state'] );
344
			}
345
346
			if ( isset( $_POST['postcode'] ) ) {
347
				WC()->customer->set_shipping_postcode( $_POST['postcode'] );
348
			}
349
350
			if ( isset( $_POST['city'] ) ) {
351
				WC()->customer->set_shipping_city( $_POST['city'] );
352
			}
353
354
			if ( isset( $_POST['address'] ) ) {
355
				WC()->customer->set_shipping_address( $_POST['address'] );
356
			}
357
358
			if ( isset( $_POST['address_2'] ) ) {
359
				WC()->customer->set_shipping_address_2( $_POST['address_2'] );
360
			}
361
		} else {
362
363 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...
364
				WC()->customer->set_shipping_country( $_POST['s_country'] );
365
				WC()->customer->calculated_shipping( true );
366
			}
367
368
			if ( isset( $_POST['s_state'] ) ) {
369
				WC()->customer->set_shipping_state( $_POST['s_state'] );
370
			}
371
372
			if ( isset( $_POST['s_postcode'] ) ) {
373
				WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
374
			}
375
376
			if ( isset( $_POST['s_city'] ) ) {
377
				WC()->customer->set_shipping_city( $_POST['s_city'] );
378
			}
379
380
			if ( isset( $_POST['s_address'] ) ) {
381
				WC()->customer->set_shipping_address( $_POST['s_address'] );
382
			}
383
384
			if ( isset( $_POST['s_address_2'] ) ) {
385
				WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
386
			}
387
		}
388
389
		WC()->cart->calculate_totals();
390
391
		// Get order review fragment
392
		ob_start();
393
		woocommerce_order_review();
394
		$woocommerce_order_review = ob_get_clean();
395
396
		// Get checkout payment fragment
397
		ob_start();
398
		woocommerce_checkout_payment();
399
		$woocommerce_checkout_payment = ob_get_clean();
400
401
		// Get messages if reload checkout is not true
402
		$messages = '';
403
		if ( ! isset( WC()->session->reload_checkout ) ) {
404
			ob_start();
405
			wc_print_notices();
406
			$messages = ob_get_clean();
407
		}
408
409
		$data = array(
410
			'result'    => empty( $messages ) ? 'success' : 'failure',
411
			'messages'  => $messages,
412
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
413
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
414
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
415
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
416
			) )
417
		);
418
419
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
420
421
		wp_send_json( $data );
422
423
		die();
424
	}
425
426
	/**
427
	 * AJAX add to cart.
428
	 */
429
	public static function add_to_cart() {
430
		ob_start();
431
432
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
433
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
434
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
435
		$product_status    = get_post_status( $product_id );
436
437
		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...
438
439
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
440
441
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
442
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
443
			}
444
445
			// Return fragments
446
			self::get_refreshed_fragments();
447
448
		} else {
449
450
			// If there was an error adding to the cart, redirect to the product page to show any errors
451
			$data = array(
452
				'error'       => true,
453
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
454
			);
455
456
			wp_send_json( $data );
457
458
		}
459
460
		die();
461
	}
462
463
	/**
464
	 * Process ajax checkout form.
465
	 */
466
	public static function checkout() {
467
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
468
			define( 'WOOCOMMERCE_CHECKOUT', true );
469
		}
470
471
		WC()->checkout()->process_checkout();
472
473
		die(0);
474
	}
475
476
	/**
477
	 * Get a matching variation based on posted attributes.
478
	 */
479
	public static function get_variation() {
480
		ob_start();
481
482
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
483
			die();
484
		}
485
486
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
487
488
		if ( $variation_id ) {
489
			$variation = $variable_product->get_available_variation( $variation_id );
490
		} else {
491
			$variation = false;
492
		}
493
494
		wp_send_json( $variation );
495
496
		die();
497
	}
498
499
	/**
500
	 * Feature a product from admin.
501
	 */
502
	public static function feature_product() {
503
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
504
			$product_id = absint( $_GET['product_id'] );
505
506
			if ( 'product' === get_post_type( $product_id ) ) {
507
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
508
509
				delete_transient( 'wc_featured_products' );
510
			}
511
		}
512
513
		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' ) );
514
		die();
515
	}
516
517
	/**
518
	 * Mark an order with a status.
519
	 */
520
	public static function mark_order_status() {
521
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
522
			$status   = sanitize_text_field( $_GET['status'] );
523
			$order_id = absint( $_GET['order_id'] );
524
525
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
526
				$order = wc_get_order( $order_id );
527
				$order->update_status( $status, '', true );
528
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
529
			}
530
		}
531
532
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
533
		die();
534
	}
535
536
	/**
537
	 * Add an attribute row.
538
	 */
539
	public static function add_attribute() {
540
		ob_start();
541
542
		check_ajax_referer( 'add-attribute', 'security' );
543
544
		if ( ! current_user_can( 'edit_products' ) ) {
545
			die(-1);
546
		}
547
548
		global $wc_product_attributes;
549
550
		$thepostid     = 0;
551
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
552
		$i             = absint( $_POST['i'] );
553
		$position      = 0;
554
		$metabox_class = array();
555
		$attribute     = array(
556
			'name'         => $taxonomy,
557
			'value'        => '',
558
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
559
			'is_variation' => 0,
560
			'is_taxonomy'  => $taxonomy ? 1 : 0
561
		);
562
563
		if ( $taxonomy ) {
564
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
565
			$metabox_class[]    = 'taxonomy';
566
			$metabox_class[]    = $taxonomy;
567
			$attribute_label    = wc_attribute_label( $taxonomy );
568
		} else {
569
			$attribute_label = '';
570
		}
571
572
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
573
		die();
574
	}
575
576
	/**
577
	 * Add a new attribute via ajax function.
578
	 */
579
	public static function add_new_attribute() {
580
		ob_start();
581
582
		check_ajax_referer( 'add-attribute', 'security' );
583
584
		if ( ! current_user_can( 'manage_product_terms' ) ) {
585
			die(-1);
586
		}
587
588
		$taxonomy = esc_attr( $_POST['taxonomy'] );
589
		$term     = wc_clean( $_POST['term'] );
590
591
		if ( taxonomy_exists( $taxonomy ) ) {
592
593
			$result = wp_insert_term( $term, $taxonomy );
594
595
			if ( is_wp_error( $result ) ) {
596
				wp_send_json( array(
597
					'error' => $result->get_error_message()
598
				) );
599
			} else {
600
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
601
				wp_send_json( array(
602
					'term_id' => $term->term_id,
603
					'name'    => $term->name,
604
					'slug'    => $term->slug
605
				) );
606
			}
607
		}
608
609
		die();
610
	}
611
612
	/**
613
	 * Delete variations via ajax function.
614
	 */
615
	public static function remove_variations() {
616
		check_ajax_referer( 'delete-variations', 'security' );
617
618
		if ( ! current_user_can( 'edit_products' ) ) {
619
			die(-1);
620
		}
621
622
		$variation_ids = (array) $_POST['variation_ids'];
623
624
		foreach ( $variation_ids as $variation_id ) {
625
			$variation = get_post( $variation_id );
626
627
			if ( $variation && 'product_variation' == $variation->post_type ) {
628
				wp_delete_post( $variation_id );
629
			}
630
		}
631
632
		die();
633
	}
634
635
	/**
636
	 * Save attributes via ajax.
637
	 */
638
	public static function save_attributes() {
639
640
		check_ajax_referer( 'save-attributes', 'security' );
641
642
		if ( ! current_user_can( 'edit_products' ) ) {
643
			die(-1);
644
		}
645
646
		// Get post data
647
		parse_str( $_POST['data'], $data );
648
		$post_id = absint( $_POST['post_id'] );
649
650
		// Save Attributes
651
		$attributes = array();
652
653
		if ( isset( $data['attribute_names'] ) ) {
654
655
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
656
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
657
658
			if ( isset( $data['attribute_visibility'] ) ) {
659
				$attribute_visibility = $data['attribute_visibility'];
660
			}
661
662
			if ( isset( $data['attribute_variation'] ) ) {
663
				$attribute_variation = $data['attribute_variation'];
664
			}
665
666
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
667
			$attribute_position      = $data['attribute_position'];
668
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
669
670
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
671
				if ( empty( $attribute_names[ $i ] ) ) {
672
					continue;
673
				}
674
675
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
676
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
677
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
678
679
				if ( $is_taxonomy ) {
680
681
					if ( isset( $attribute_values[ $i ] ) ) {
682
683
						// Select based attributes - Format values (posted values are slugs)
684
						if ( is_array( $attribute_values[ $i ] ) ) {
685
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
686
687
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
688
						} else {
689
							$values     = array();
690
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
691
692
							foreach ( $raw_values as $value ) {
693
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
694
								if ( ! $term ) {
695
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
696
697
									if ( $term && ! is_wp_error( $term ) ) {
698
										$values[] = $term['term_id'];
699
									}
700
								} else {
701
									$values[] = $term->term_id;
702
								}
703
							}
704
						}
705
706
						// Remove empty items in the array
707
						$values = array_filter( $values, 'strlen' );
708
709
					} else {
710
						$values = array();
711
					}
712
713
					// Update post terms
714
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
715
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
716
					}
717
718 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...
719
						// Add attribute to array, but don't set values
720
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
721
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
722
							'value' 		=> '',
723
							'position' 		=> $attribute_position[ $i ],
724
							'is_visible' 	=> $is_visible,
725
							'is_variation' 	=> $is_variation,
726
							'is_taxonomy' 	=> $is_taxonomy
727
						);
728
					}
729
730 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...
731
732
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
733
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
734
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
735
736
					// Custom attribute - Add attribute to array and set the values
737
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
738
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
739
						'value' 		=> $values,
740
						'position' 		=> $attribute_position[ $i ],
741
						'is_visible' 	=> $is_visible,
742
						'is_variation' 	=> $is_variation,
743
						'is_taxonomy' 	=> $is_taxonomy
744
					);
745
				}
746
747
			 }
748
		}
749
750
		if ( ! function_exists( 'attributes_cmp' ) ) {
751
			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 (L964-970) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
752
				if ( $a['position'] == $b['position'] ) {
753
					return 0;
754
				}
755
756
				return ( $a['position'] < $b['position'] ) ? -1 : 1;
757
			}
758
		}
759
		uasort( $attributes, 'attributes_cmp' );
760
761
		update_post_meta( $post_id, '_product_attributes', $attributes );
762
763
		die();
764
	}
765
766
	/**
767
	 * Add variation via ajax function.
768
	 */
769
	public static function add_variation() {
770
771
		check_ajax_referer( 'add-variation', 'security' );
772
773
		if ( ! current_user_can( 'edit_products' ) ) {
774
			die(-1);
775
		}
776
777
		global $post;
778
779
		$post_id = intval( $_POST['post_id'] );
780
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
781
		$loop    = intval( $_POST['loop'] );
782
783
		$variation = array(
784
			'post_title'   => 'Product #' . $post_id . ' Variation',
785
			'post_content' => '',
786
			'post_status'  => 'publish',
787
			'post_author'  => get_current_user_id(),
788
			'post_parent'  => $post_id,
789
			'post_type'    => 'product_variation',
790
			'menu_order'   => -1
791
		);
792
793
		$variation_id = wp_insert_post( $variation );
794
795
		do_action( 'woocommerce_create_product_variation', $variation_id );
796
797
		if ( $variation_id ) {
798
			$variation        = get_post( $variation_id );
799
			$variation_meta   = get_post_meta( $variation_id );
800
			$variation_data   = array();
801
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
802
			$variation_fields = array(
803
				'_sku'                   => '',
804
				'_stock'                 => '',
805
				'_regular_price'         => '',
806
				'_sale_price'            => '',
807
				'_weight'                => '',
808
				'_length'                => '',
809
				'_width'                 => '',
810
				'_height'                => '',
811
				'_download_limit'        => '',
812
				'_download_expiry'       => '',
813
				'_downloadable_files'    => '',
814
				'_downloadable'          => '',
815
				'_virtual'               => '',
816
				'_thumbnail_id'          => '',
817
				'_sale_price_dates_from' => '',
818
				'_sale_price_dates_to'   => '',
819
				'_manage_stock'          => '',
820
				'_stock_status'          => '',
821
				'_backorders'            => null,
822
				'_tax_class'             => null,
823
				'_variation_description' => ''
824
			);
825
826 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...
827
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
828
			}
829
830
			// Add the variation attributes
831
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
832
833
			// Formatting
834
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
835
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
836
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
837
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
838
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
839
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
840
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
841
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
842
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
843
			$variation_data['menu_order']     = $variation->menu_order;
844
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
845
846
			// Get tax classes
847
			$tax_classes           = WC_Tax::get_tax_classes();
848
			$tax_class_options     = array();
849
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
850
851 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...
852
				foreach ( $tax_classes as $class ) {
853
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
854
				}
855
			}
856
857
			// Set backorder options
858
			$backorder_options = array(
859
				'no'     => __( 'Do not allow', 'woocommerce' ),
860
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
861
				'yes'    => __( 'Allow', 'woocommerce' )
862
			);
863
864
			// set stock status options
865
			$stock_status_options = array(
866
				'instock'    => __( 'In stock', 'woocommerce' ),
867
				'outofstock' => __( 'Out of stock', 'woocommerce' )
868
			);
869
870
			// Get attributes
871
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
872
873
			$parent_data = array(
874
				'id'                   => $post_id,
875
				'attributes'           => $attributes,
876
				'tax_class_options'    => $tax_class_options,
877
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
878
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
879
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
880
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
881
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
882
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
883
				'backorder_options'    => $backorder_options,
884
				'stock_status_options' => $stock_status_options
885
			);
886
887
			if ( ! $parent_data['weight'] ) {
888
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
889
			}
890
891
			if ( ! $parent_data['length'] ) {
892
				$parent_data['length'] = wc_format_localized_decimal( 0 );
893
			}
894
895
			if ( ! $parent_data['width'] ) {
896
				$parent_data['width'] = wc_format_localized_decimal( 0 );
897
			}
898
899
			if ( ! $parent_data['height'] ) {
900
				$parent_data['height'] = wc_format_localized_decimal( 0 );
901
			}
902
903
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
904
		}
905
906
		die();
907
	}
908
909
	/**
910
	 * Link all variations via ajax function.
911
	 */
912
	public static function link_all_variations() {
913
914
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
915
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
916
		}
917
918
		check_ajax_referer( 'link-variations', 'security' );
919
920
		if ( ! current_user_can( 'edit_products' ) ) {
921
			die(-1);
922
		}
923
924 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...
925
			@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...
926
		}
927
928
		$post_id = intval( $_POST['post_id'] );
929
930
		if ( ! $post_id ) {
931
			die();
932
		}
933
934
		$variations = array();
935
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
936
937
		// Put variation attributes into an array
938
		foreach ( $_product->get_attributes() as $attribute ) {
939
940
			if ( ! $attribute['is_variation'] ) {
941
				continue;
942
			}
943
944
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
945
946
			if ( $attribute['is_taxonomy'] ) {
947
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
948
			} else {
949
				$options = explode( WC_DELIMITER, $attribute['value'] );
950
			}
951
952
			$options = array_map( 'trim', $options );
953
954
			$variations[ $attribute_field_name ] = $options;
955
		}
956
957
		// Quit out if none were found
958
		if ( sizeof( $variations ) == 0 ) {
959
			die();
960
		}
961
962
		// Get existing variations so we don't create duplicates
963
		$available_variations = array();
964
965
		foreach( $_product->get_children() as $child_id ) {
966
			$child = $_product->get_child( $child_id );
967
968
			if ( ! empty( $child->variation_id ) ) {
969
				$available_variations[] = $child->get_variation_attributes();
970
			}
971
		}
972
973
		// Created posts will all have the following data
974
		$variation_post_data = array(
975
			'post_title'   => 'Product #' . $post_id . ' Variation',
976
			'post_content' => '',
977
			'post_status'  => 'publish',
978
			'post_author'  => get_current_user_id(),
979
			'post_parent'  => $post_id,
980
			'post_type'    => 'product_variation'
981
		);
982
983
		$variation_ids       = array();
984
		$added               = 0;
985
		$possible_variations = wc_array_cartesian( $variations );
986
987
		foreach ( $possible_variations as $variation ) {
988
989
			// Check if variation already exists
990
			if ( in_array( $variation, $available_variations ) ) {
991
				continue;
992
			}
993
994
			$variation_id = wp_insert_post( $variation_post_data );
995
996
			$variation_ids[] = $variation_id;
997
998
			foreach ( $variation as $key => $value ) {
999
				update_post_meta( $variation_id, $key, $value );
1000
			}
1001
1002
			// Save stock status
1003
			update_post_meta( $variation_id, '_stock_status', 'instock' );
1004
1005
			$added++;
1006
1007
			do_action( 'product_variation_linked', $variation_id );
1008
1009
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
1010
				break;
1011
			}
1012
		}
1013
1014
		delete_transient( 'wc_product_children_' . $post_id );
1015
1016
		echo $added;
1017
1018
		die();
1019
	}
1020
1021
	/**
1022
	 * Delete download permissions via ajax function.
1023
	 */
1024
	public static function revoke_access_to_download() {
1025
1026
		check_ajax_referer( 'revoke-access', 'security' );
1027
1028
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1029
			die(-1);
1030
		}
1031
1032
		global $wpdb;
1033
1034
		$download_id = $_POST['download_id'];
1035
		$product_id  = intval( $_POST['product_id'] );
1036
		$order_id    = intval( $_POST['order_id'] );
1037
1038
		$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 ) );
1039
1040
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
1041
1042
		die();
1043
	}
1044
1045
	/**
1046
	 * Grant download permissions via ajax function.
1047
	 */
1048
	public static function grant_access_to_download() {
1049
1050
		check_ajax_referer( 'grant-access', 'security' );
1051
1052
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1053
			die(-1);
1054
		}
1055
1056
		global $wpdb;
1057
1058
		$wpdb->hide_errors();
1059
1060
		$order_id     = intval( $_POST['order_id'] );
1061
		$product_ids  = $_POST['product_ids'];
1062
		$loop         = intval( $_POST['loop'] );
1063
		$file_counter = 0;
1064
		$order        = wc_get_order( $order_id );
1065
1066
		if ( ! is_array( $product_ids ) ) {
1067
			$product_ids = array( $product_ids );
1068
		}
1069
1070
		foreach ( $product_ids as $product_id ) {
1071
			$product = wc_get_product( $product_id );
1072
			$files   = $product->get_files();
1073
1074
			if ( ! $order->billing_email ) {
1075
				die();
1076
			}
1077
1078
			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...
1079
				foreach ( $files as $download_id => $file ) {
1080
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
1081
1082
						// insert complete - get inserted data
1083
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1084
1085
						$loop ++;
1086
						$file_counter ++;
1087
1088 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...
1089
							$file_count = $file['name'];
1090
						} else {
1091
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1092
						}
1093
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1094
					}
1095
				}
1096
			}
1097
		}
1098
1099
		die();
1100
	}
1101
1102
	/**
1103
	 * Get customer details via ajax.
1104
	 */
1105
	public static function get_customer_details() {
1106
		ob_start();
1107
1108
		check_ajax_referer( 'get-customer-details', 'security' );
1109
1110
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1111
			die(-1);
1112
		}
1113
1114
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1115
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1116
1117
		$customer_data = array(
1118
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1119
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1120
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1121
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1122
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1123
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1124
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1125
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1126
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1127
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1128
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1129
		);
1130
1131
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1132
1133
		wp_send_json( $customer_data );
1134
	}
1135
1136
	/**
1137
	 * Add order item via ajax.
1138
	 */
1139
	public static function add_order_item() {
1140
		check_ajax_referer( 'order-item', 'security' );
1141
1142
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1143
			die(-1);
1144
		}
1145
1146
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1147
		$order_id    = absint( $_POST['order_id'] );
1148
1149
		// Find the item
1150
		if ( ! is_numeric( $item_to_add ) ) {
1151
			die();
1152
		}
1153
1154
		$post = get_post( $item_to_add );
1155
1156
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1157
			die();
1158
		}
1159
1160
		$_product    = wc_get_product( $post->ID );
1161
		$order       = wc_get_order( $order_id );
1162
		$order_taxes = $order->get_taxes();
1163
		$class       = 'new_row';
1164
1165
		// Set values
1166
		$item = array();
1167
1168
		$item['product_id']        = $_product->id;
1169
		$item['variation_id']      = isset( $_product->variation_id ) ? $_product->variation_id : '';
1170
		$item['variation_data']    = $item['variation_id'] ? $_product->get_variation_attributes() : '';
1171
		$item['name']              = $_product->get_title();
1172
		$item['tax_class']         = $_product->get_tax_class();
1173
		$item['qty']               = 1;
1174
		$item['line_subtotal']     = wc_format_decimal( $_product->get_price_excluding_tax() );
1175
		$item['line_subtotal_tax'] = '';
1176
		$item['line_total']        = wc_format_decimal( $_product->get_price_excluding_tax() );
1177
		$item['line_tax']          = '';
1178
		$item['type']              = 'line_item';
1179
1180
		// Add line item
1181
		$item_id = wc_add_order_item( $order_id, array(
1182
			'order_item_name' 		=> $item['name'],
1183
			'order_item_type' 		=> 'line_item'
1184
		) );
1185
1186
		// Add line item meta
1187
		if ( $item_id ) {
1188
			wc_add_order_item_meta( $item_id, '_qty', $item['qty'] );
1189
			wc_add_order_item_meta( $item_id, '_tax_class', $item['tax_class'] );
1190
			wc_add_order_item_meta( $item_id, '_product_id', $item['product_id'] );
1191
			wc_add_order_item_meta( $item_id, '_variation_id', $item['variation_id'] );
1192
			wc_add_order_item_meta( $item_id, '_line_subtotal', $item['line_subtotal'] );
1193
			wc_add_order_item_meta( $item_id, '_line_subtotal_tax', $item['line_subtotal_tax'] );
1194
			wc_add_order_item_meta( $item_id, '_line_total', $item['line_total'] );
1195
			wc_add_order_item_meta( $item_id, '_line_tax', $item['line_tax'] );
1196
1197
			// Since 2.2
1198
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
1199
1200
			// Store variation data in meta
1201 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...
1202
				foreach ( $item['variation_data'] as $key => $value ) {
1203
					wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
1204
				}
1205
			}
1206
1207
			do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1208
		}
1209
1210
		$item['item_meta']       = $order->get_item_meta( $item_id );
1211
		$item['item_meta_array'] = $order->get_item_meta_array( $item_id );
1212
		$item                    = $order->expand_item_meta( $item );
1213
		$item                    = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
1214
1215
		include( 'admin/meta-boxes/views/html-order-item.php' );
1216
1217
		// Quit out
1218
		die();
1219
	}
1220
1221
	/**
1222
	 * Add order fee via ajax.
1223
	 */
1224
	public static function add_order_fee() {
1225
1226
		check_ajax_referer( 'order-item', 'security' );
1227
1228
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1229
			die(-1);
1230
		}
1231
1232
		$order_id      = absint( $_POST['order_id'] );
1233
		$order         = wc_get_order( $order_id );
1234
		$order_taxes   = $order->get_taxes();
1235
		$item          = array();
1236
1237
		// Add new fee
1238
		$fee            = new stdClass();
1239
		$fee->name      = '';
1240
		$fee->tax_class = '';
1241
		$fee->taxable   = $fee->tax_class !== '0';
1242
		$fee->amount    = '';
1243
		$fee->tax       = '';
1244
		$fee->tax_data  = array();
1245
		$item_id        = $order->add_fee( $fee );
1246
1247
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1248
1249
		// Quit out
1250
		die();
1251
	}
1252
1253
	/**
1254
	 * Add order shipping cost via ajax.
1255
	 */
1256
	public static function add_order_shipping() {
1257
1258
		check_ajax_referer( 'order-item', 'security' );
1259
1260
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1261
			die(-1);
1262
		}
1263
1264
		$order_id         = absint( $_POST['order_id'] );
1265
		$order            = wc_get_order( $order_id );
1266
		$order_taxes      = $order->get_taxes();
1267
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1268
		$item             = array();
1269
1270
		// Add new shipping
1271
		$shipping        = new WC_Shipping_Rate();
1272
		$item_id         = $order->add_shipping( $shipping );
1273
1274
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1275
1276
		// Quit out
1277
		die();
1278
	}
1279
1280
	/**
1281
	 * Add order tax column via ajax.
1282
	 */
1283
	public static function add_order_tax() {
1284
		global $wpdb;
1285
1286
		check_ajax_referer( 'order-item', 'security' );
1287
1288
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1289
			die(-1);
1290
		}
1291
1292
		$order_id = absint( $_POST['order_id'] );
1293
		$rate_id  = absint( $_POST['rate_id'] );
1294
		$order    = wc_get_order( $order_id );
1295
		$data     = get_post_meta( $order_id );
1296
1297
		// Add new tax
1298
		$order->add_tax( $rate_id, 0, 0 );
1299
1300
		// Return HTML items
1301
		include( 'admin/meta-boxes/views/html-order-items.php' );
1302
1303
		die();
1304
	}
1305
1306
	/**
1307
	 * Remove an order item.
1308
	 */
1309
	public static function remove_order_item() {
1310
		check_ajax_referer( 'order-item', 'security' );
1311
1312
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1313
			die(-1);
1314
		}
1315
1316
		$order_item_ids = $_POST['order_item_ids'];
1317
1318
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1319
			$order_item_ids = array( $order_item_ids );
1320
		}
1321
1322
		if ( sizeof( $order_item_ids ) > 0 ) {
1323
			foreach( $order_item_ids as $id ) {
1324
				wc_delete_order_item( absint( $id ) );
1325
			}
1326
		}
1327
1328
		die();
1329
	}
1330
1331
	/**
1332
	 * Remove an order tax.
1333
	 */
1334
	public static function remove_order_tax() {
1335
1336
		check_ajax_referer( 'order-item', 'security' );
1337
1338
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1339
			die(-1);
1340
		}
1341
1342
		$order_id = absint( $_POST['order_id'] );
1343
		$rate_id  = absint( $_POST['rate_id'] );
1344
1345
		wc_delete_order_item( $rate_id );
1346
1347
		// Return HTML items
1348
		$order = wc_get_order( $order_id );
1349
		$data  = get_post_meta( $order_id );
1350
		include( 'admin/meta-boxes/views/html-order-items.php' );
1351
1352
		die();
1353
	}
1354
1355
	/**
1356
	 * Reduce order item stock.
1357
	 */
1358
	public static function reduce_order_item_stock() {
1359
		check_ajax_referer( 'order-item', 'security' );
1360
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1361
			die(-1);
1362
		}
1363
		$order_id       = absint( $_POST['order_id'] );
1364
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1365
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1366
		$order          = wc_get_order( $order_id );
1367
		$order_items    = $order->get_items();
1368
		$return         = array();
1369
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1370
			foreach ( $order_items as $item_id => $order_item ) {
1371
				// Only reduce checked items
1372
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1373
					continue;
1374
				}
1375
				$_product = $order->get_product_from_item( $order_item );
1376
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1377
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1378
					$new_stock    = $_product->reduce_stock( $stock_change );
1379
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1380
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1381
					$return[]     = $note;
1382
					$order->add_order_note( $note );
1383
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1384
				}
1385
			}
1386
			do_action( 'woocommerce_reduce_order_stock', $order );
1387
			if ( empty( $return ) ) {
1388
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1389
			}
1390
			echo implode( ', ', $return );
1391
		}
1392
		die();
1393
	}
1394
1395
	/**
1396
	 * Increase order item stock.
1397
	 */
1398
	public static function increase_order_item_stock() {
1399
		check_ajax_referer( 'order-item', 'security' );
1400
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1401
			die(-1);
1402
		}
1403
		$order_id       = absint( $_POST['order_id'] );
1404
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1405
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1406
		$order          = wc_get_order( $order_id );
1407
		$order_items    = $order->get_items();
1408
		$return         = array();
1409
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1410
			foreach ( $order_items as $item_id => $order_item ) {
1411
				// Only reduce checked items
1412
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1413
					continue;
1414
				}
1415
				$_product = $order->get_product_from_item( $order_item );
1416
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1417
					$old_stock    = $_product->get_stock_quantity();
1418
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1419
					$new_quantity = $_product->increase_stock( $stock_change );
1420
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1421
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1422
					$return[]     = $note;
1423
					$order->add_order_note( $note );
1424
				}
1425
			}
1426
			do_action( 'woocommerce_restore_order_stock', $order );
1427
			if ( empty( $return ) ) {
1428
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1429
			}
1430
			echo implode( ', ', $return );
1431
		}
1432
		die();
1433
	}
1434
1435
	/**
1436
	 * Add some meta to a line item.
1437
	 */
1438
	public static function add_order_item_meta() {
1439
		check_ajax_referer( 'order-item', 'security' );
1440
1441
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1442
			die(-1);
1443
		}
1444
1445
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1446
1447
		if ( $meta_id ) {
1448
			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>';
1449
		}
1450
1451
		die();
1452
	}
1453
1454
	/**
1455
	 * Remove meta from a line item.
1456
	 */
1457
	public static function remove_order_item_meta() {
1458
		global $wpdb;
1459
1460
		check_ajax_referer( 'order-item', 'security' );
1461
1462
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1463
			die(-1);
1464
		}
1465
1466
		$meta_id = absint( $_POST['meta_id'] );
1467
1468
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_id = %d", $meta_id ) );
1469
1470
		die();
1471
	}
1472
1473
	/**
1474
	 * Calc line tax.
1475
	 */
1476
	public static function calc_line_taxes() {
1477
		global $wpdb;
1478
1479
		check_ajax_referer( 'calc-totals', 'security' );
1480
1481
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1482
			die(-1);
1483
		}
1484
1485
		$tax            = new WC_Tax();
1486
		$tax_based_on   = get_option( 'woocommerce_tax_based_on' );
1487
		$order_id       = absint( $_POST['order_id'] );
1488
		$items          = array();
1489
		$country        = strtoupper( esc_attr( $_POST['country'] ) );
1490
		$state          = strtoupper( esc_attr( $_POST['state'] ) );
1491
		$postcode       = strtoupper( esc_attr( $_POST['postcode'] ) );
1492
		$city           = wc_clean( esc_attr( $_POST['city'] ) );
1493
		$order          = wc_get_order( $order_id );
1494
		$taxes          = array();
1495
		$shipping_taxes = array();
1496
1497
		// Default to base
1498 View Code Duplication
		if ( 'base' === $tax_based_on || empty( $country ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1499
			$default  = wc_get_base_location();
1500
			$country  = $default['country'];
1501
			$state    = $default['state'];
1502
			$postcode = '';
1503
			$city     = '';
1504
		}
1505
1506
		// Parse the jQuery serialized items
1507
		parse_str( $_POST['items'], $items );
1508
1509
		// Prevent undefined warnings
1510
		if ( ! isset( $items['line_tax'] ) ) {
1511
			$items['line_tax'] = array();
1512
		}
1513
		if ( ! isset( $items['line_subtotal_tax'] ) ) {
1514
			$items['line_subtotal_tax'] = array();
1515
		}
1516
		$items['order_taxes'] = array();
1517
1518
		// Action
1519
		$items = apply_filters( 'woocommerce_ajax_calc_line_taxes', $items, $order_id, $country, $_POST );
1520
1521
		$is_vat_exempt = get_post_meta( $order_id, '_is_vat_exempt', true );
1522
1523
		// Tax is calculated only if tax is enabled and order is not vat exempted
1524
		if ( wc_tax_enabled() && $is_vat_exempt !== 'yes' ) {
1525
1526
			// Get items and fees taxes
1527
			if ( isset( $items['order_item_id'] ) ) {
1528
				$line_total = $line_subtotal = $order_item_tax_class = array();
1529
1530
				foreach ( $items['order_item_id'] as $item_id ) {
1531
					$item_id                          = absint( $item_id );
1532
					$line_total[ $item_id ]           = isset( $items['line_total'][ $item_id ] ) ? wc_format_decimal( $items['line_total'][ $item_id ] ) : 0;
1533
					$line_subtotal[ $item_id ]        = isset( $items['line_subtotal'][ $item_id ] ) ? wc_format_decimal( $items['line_subtotal'][ $item_id ] ) : $line_total[ $item_id ];
1534
					$order_item_tax_class[ $item_id ] = isset( $items['order_item_tax_class'][ $item_id ] ) ? sanitize_text_field( $items['order_item_tax_class'][ $item_id ] ) : '';
1535
					$product_id                       = $order->get_item_meta( $item_id, '_product_id', true );
1536
1537
					// Get product details
1538
					if ( get_post_type( $product_id ) == 'product' ) {
1539
						$_product        = wc_get_product( $product_id );
1540
						$item_tax_status = $_product->get_tax_status();
1541
					} else {
1542
						$item_tax_status = 'taxable';
1543
					}
1544
1545
					if ( '0' !== $order_item_tax_class[ $item_id ] && 'taxable' === $item_tax_status ) {
1546
						$tax_rates = WC_Tax::find_rates( array(
1547
							'country'   => $country,
1548
							'state'     => $state,
1549
							'postcode'  => $postcode,
1550
							'city'      => $city,
1551
							'tax_class' => $order_item_tax_class[ $item_id ]
1552
						) );
1553
1554
						$line_taxes          = WC_Tax::calc_tax( $line_total[ $item_id ], $tax_rates, false );
1555
						$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal[ $item_id ], $tax_rates, false );
1556
1557
						// Set the new line_tax
1558
						foreach ( $line_taxes as $_tax_id => $_tax_value ) {
1559
							$items['line_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1560
						}
1561
1562
						// Set the new line_subtotal_tax
1563
						foreach ( $line_subtotal_taxes as $_tax_id => $_tax_value ) {
1564
							$items['line_subtotal_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1565
						}
1566
1567
						// Sum the item taxes
1568
						foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1569
							$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1570
						}
1571
					}
1572
				}
1573
			}
1574
1575
			// Get shipping taxes
1576
			if ( isset( $items['shipping_method_id'] ) ) {
1577
				$matched_tax_rates = array();
1578
1579
				$tax_rates = WC_Tax::find_rates( array(
1580
					'country'   => $country,
1581
					'state'     => $state,
1582
					'postcode'  => $postcode,
1583
					'city'      => $city,
1584
					'tax_class' => ''
1585
				) );
1586
1587 View Code Duplication
				if ( $tax_rates ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tax_rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

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

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

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

Loading history...
1588
					foreach ( $tax_rates as $key => $rate ) {
1589
						if ( isset( $rate['shipping'] ) && 'yes' == $rate['shipping'] ) {
1590
							$matched_tax_rates[ $key ] = $rate;
1591
						}
1592
					}
1593
				}
1594
1595
				$shipping_cost = $shipping_taxes = array();
1596
1597
				foreach ( $items['shipping_method_id'] as $item_id ) {
1598
					$item_id                   = absint( $item_id );
1599
					$shipping_cost[ $item_id ] = isset( $items['shipping_cost'][ $item_id ] ) ? wc_format_decimal( $items['shipping_cost'][ $item_id ] ) : 0;
1600
					$_shipping_taxes           = WC_Tax::calc_shipping_tax( $shipping_cost[ $item_id ], $matched_tax_rates );
1601
1602
					// Set the new shipping_taxes
1603
					foreach ( $_shipping_taxes as $_tax_id => $_tax_value ) {
1604
						$items['shipping_taxes'][ $item_id ][ $_tax_id ] = $_tax_value;
1605
1606
						$shipping_taxes[ $_tax_id ] = isset( $shipping_taxes[ $_tax_id ] ) ? $shipping_taxes[ $_tax_id ] + $_tax_value : $_tax_value;
1607
					}
1608
				}
1609
			}
1610
		}
1611
1612
		// Remove old tax rows
1613
		$order->remove_order_items( 'tax' );
1614
1615
		// Add tax rows
1616 View Code Duplication
		foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_id ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1617
			$order->add_tax( $tax_rate_id, isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0, isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
1618
		}
1619
1620
		// Create the new order_taxes
1621
		foreach ( $order->get_taxes() as $tax_id => $tax_item ) {
1622
			$items['order_taxes'][ $tax_id ] = absint( $tax_item['rate_id'] );
1623
		}
1624
1625
		$items = apply_filters( 'woocommerce_ajax_after_calc_line_taxes', $items, $order_id, $country, $_POST );
1626
1627
		// Save order items
1628
		wc_save_order_items( $order_id, $items );
1629
1630
		// Return HTML items
1631
		$order = wc_get_order( $order_id );
1632
		$data  = get_post_meta( $order_id );
1633
		include( 'admin/meta-boxes/views/html-order-items.php' );
1634
1635
		die();
1636
	}
1637
1638
	/**
1639
	 * Save order items via ajax.
1640
	 */
1641
	public static function save_order_items() {
1642
		check_ajax_referer( 'order-item', 'security' );
1643
1644
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1645
			die(-1);
1646
		}
1647
1648
		if ( isset( $_POST['order_id'] ) && isset( $_POST['items'] ) ) {
1649
			$order_id = absint( $_POST['order_id'] );
1650
1651
			// Parse the jQuery serialized items
1652
			$items = array();
1653
			parse_str( $_POST['items'], $items );
1654
1655
			// Save order items
1656
			wc_save_order_items( $order_id, $items );
0 ignored issues
show
Bug introduced by
It seems like $items can also be of type null; however, wc_save_order_items() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1657
1658
			// Return HTML items
1659
			$order = wc_get_order( $order_id );
1660
			$data  = get_post_meta( $order_id );
1661
			include( 'admin/meta-boxes/views/html-order-items.php' );
1662
		}
1663
1664
		die();
1665
	}
1666
1667
	/**
1668
	 * Load order items via ajax.
1669
	 */
1670
	public static function load_order_items() {
1671
		check_ajax_referer( 'order-item', 'security' );
1672
1673
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1674
			die(-1);
1675
		}
1676
1677
		// Return HTML items
1678
		$order_id = absint( $_POST['order_id'] );
1679
		$order    = wc_get_order( $order_id );
1680
		$data     = get_post_meta( $order_id );
1681
		include( 'admin/meta-boxes/views/html-order-items.php' );
1682
1683
		die();
1684
	}
1685
1686
	/**
1687
	 * Add order note via ajax.
1688
	 */
1689
	public static function add_order_note() {
1690
1691
		check_ajax_referer( 'add-order-note', 'security' );
1692
1693
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1694
			die(-1);
1695
		}
1696
1697
		$post_id   = absint( $_POST['post_id'] );
1698
		$note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
1699
		$note_type = $_POST['note_type'];
1700
1701
		$is_customer_note = $note_type == 'customer' ? 1 : 0;
1702
1703
		if ( $post_id > 0 ) {
1704
			$order      = wc_get_order( $post_id );
1705
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1706
1707
			echo '<li rel="' . esc_attr( $comment_id ) . '" class="note ';
1708
			if ( $is_customer_note ) {
1709
				echo 'customer-note';
1710
			}
1711
			echo '"><div class="note_content">';
1712
			echo wpautop( wptexturize( $note ) );
1713
			echo '</div><p class="meta"><a href="#" class="delete_note">'.__( 'Delete note', 'woocommerce' ).'</a></p>';
1714
			echo '</li>';
1715
		}
1716
1717
		// Quit out
1718
		die();
1719
	}
1720
1721
	/**
1722
	 * Delete order note via ajax.
1723
	 */
1724
	public static function delete_order_note() {
1725
1726
		check_ajax_referer( 'delete-order-note', 'security' );
1727
1728
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1729
			die(-1);
1730
		}
1731
1732
		$note_id = (int) $_POST['note_id'];
1733
1734
		if ( $note_id > 0 ) {
1735
			wp_delete_comment( $note_id );
1736
		}
1737
1738
		// Quit out
1739
		die();
1740
	}
1741
1742
	/**
1743
	 * Search for products and echo json.
1744
	 *
1745
	 * @param string $x (default: '')
1746
	 * @param string $post_types (default: array('product'))
1747
	 */
1748
	public static function json_search_products( $x = '', $post_types = array( 'product' ) ) {
0 ignored issues
show
Unused Code introduced by
The parameter $x is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1749
		global $wpdb;
1750
1751
		ob_start();
1752
1753
		check_ajax_referer( 'search-products', 'security' );
1754
1755
		$term = (string) wc_clean( stripslashes( $_GET['term'] ) );
1756
1757
		if ( empty( $term ) ) {
1758
			die();
1759
		}
1760
1761
		$like_term = '%' . $wpdb->esc_like( $term ) . '%';
1762
1763
		if ( is_numeric( $term ) ) {
1764
			$query = $wpdb->prepare( "
1765
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1766
				WHERE posts.post_status = 'publish'
1767
				AND (
1768
					posts.post_parent = %s
1769
					OR posts.ID = %s
1770
					OR posts.post_title LIKE %s
1771
					OR (
1772
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1773
					)
1774
				)
1775
			", $term, $term, $term, $like_term );
1776
		} else {
1777
			$query = $wpdb->prepare( "
1778
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1779
				WHERE posts.post_status = 'publish'
1780
				AND (
1781
					posts.post_title LIKE %s
1782
					or posts.post_content LIKE %s
1783
					OR (
1784
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1785
					)
1786
				)
1787
			", $like_term, $like_term, $like_term );
1788
		}
1789
1790
		$query .= " AND posts.post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "')";
1791
1792 View Code Duplication
		if ( ! empty( $_GET['exclude'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1793
			$query .= " AND posts.ID NOT IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['exclude'] ) ) ) . ")";
1794
		}
1795
1796 View Code Duplication
		if ( ! empty( $_GET['include'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1797
			$query .= " AND posts.ID IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['include'] ) ) ) . ")";
1798
		}
1799
1800
		if ( ! empty( $_GET['limit'] ) ) {
1801
			$query .= " LIMIT " . intval( $_GET['limit'] );
1802
		}
1803
1804
		$posts          = array_unique( $wpdb->get_col( $query ) );
1805
		$found_products = array();
1806
1807
		if ( ! empty( $posts ) ) {
1808
			foreach ( $posts as $post ) {
1809
				$product = wc_get_product( $post );
1810
1811
				if ( ! current_user_can( 'read_product', $post ) ) {
1812
					continue;
1813
				}
1814
1815
				if ( ! $product || ( $product->is_type( 'variation' ) && empty( $product->parent ) ) ) {
1816
					continue;
1817
				}
1818
1819
				$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1820
			}
1821
		}
1822
1823
		$found_products = apply_filters( 'woocommerce_json_search_found_products', $found_products );
1824
1825
		wp_send_json( $found_products );
1826
	}
1827
1828
	/**
1829
	 * Search for product variations and return json.
1830
	 *
1831
	 * @see WC_AJAX::json_search_products()
1832
	 */
1833
	public static function json_search_products_and_variations() {
1834
		self::json_search_products( '', array( 'product', 'product_variation' ) );
1835
	}
1836
1837
	/**
1838
	 * Search for grouped products and return json.
1839
	 */
1840
	public static function json_search_grouped_products() {
1841
		ob_start();
1842
1843
		check_ajax_referer( 'search-products', 'security' );
1844
1845
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1846
		$exclude = array();
1847
1848
		if ( empty( $term ) ) {
1849
			die();
1850
		}
1851
1852 View Code Duplication
		if ( ! empty( $_GET['exclude'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1853
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1854
		}
1855
1856
		$found_products = array();
1857
1858
		if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
1859
1860
			$posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
1861
1862
			if ( sizeof( $posts_in ) > 0 ) {
1863
1864
				$args = array(
1865
					'post_type'        => 'product',
1866
					'post_status'      => 'any',
1867
					'numberposts'      => -1,
1868
					'orderby'          => 'title',
1869
					'order'            => 'asc',
1870
					'post_parent'      => 0,
1871
					'suppress_filters' => 0,
1872
					'include'          => $posts_in,
1873
					's'                => $term,
1874
					'fields'           => 'ids',
1875
					'exclude'          => $exclude
1876
				);
1877
1878
				$posts = get_posts( $args );
1879
1880
				if ( ! empty( $posts ) ) {
1881
					foreach ( $posts as $post ) {
1882
						$product = wc_get_product( $post );
1883
1884
						if ( ! current_user_can( 'read_product', $post ) ) {
1885
							continue;
1886
						}
1887
1888
						$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1889
					}
1890
				}
1891
			}
1892
		}
1893
1894
		$found_products = apply_filters( 'woocommerce_json_search_found_grouped_products', $found_products );
1895
1896
		wp_send_json( $found_products );
1897
	}
1898
1899
	/**
1900
	 * Search for downloadable product variations and return json.
1901
	 *
1902
	 * @see WC_AJAX::json_search_products()
1903
	 */
1904
	public static function json_search_downloadable_products_and_variations() {
1905
		ob_start();
1906
1907
		check_ajax_referer( 'search-products', 'security' );
1908
1909
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1910
		$exclude = array();
1911
1912 View Code Duplication
		if ( ! empty( $_GET['exclude'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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