Completed
Pull Request — master (#10361)
by Shiva
13:33
created

WC_AJAX::update_api_key()   C

Complexity

Conditions 8
Paths 44

Size

Total Lines 88
Code Lines 63

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 88
rs 5.6862
cc 8
eloc 63
nc 44
nop 0

How to fix   Long Method   

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
The function attributes_cmp() has been defined more than once; this definition is ignored, only the first definition in includes/admin/meta-boxe...ta-box-product-data.php (L965-971) is considered.

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

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

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

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

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