Completed
Push — master ( 08c7be...bf151a )
by
unknown
25:52 queued 10s
created

WC_AJAX::link_all_variations()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
nc 16
nop 0
dl 0
loc 28
ccs 0
cts 16
cp 0
crap 30
rs 9.1608
c 0
b 0
f 0
1
<?php
2
/**
3
 * WooCommerce WC_AJAX. AJAX Event Handlers.
4
 *
5
 * @class   WC_AJAX
6
 * @package WooCommerce/Classes
7
 */
8
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * WC_Ajax class.
13
 */
14
class WC_AJAX {
15
16
	/**
17
	 * Hook in ajax handlers.
18
	 */
19
	public static function init() {
20
		add_action( 'init', array( __CLASS__, 'define_ajax' ), 0 );
21
		add_action( 'template_redirect', array( __CLASS__, 'do_wc_ajax' ), 0 );
22
		self::add_ajax_events();
23
	}
24
25
	/**
26
	 * Get WC Ajax Endpoint.
27
	 *
28
	 * @param string $request Optional.
29
	 *
30
	 * @return string
31
	 */
32
	public static function get_endpoint( $request = '' ) {
33
		return esc_url_raw( apply_filters( 'woocommerce_ajax_get_endpoint', add_query_arg( 'wc-ajax', $request, remove_query_arg( array( 'remove_item', 'add-to-cart', 'added-to-cart', 'order_again', '_wpnonce' ), home_url( '/', 'relative' ) ) ), $request ) );
34
	}
35
36
	/**
37
	 * Set WC AJAX constant and headers.
38
	 */
39
	public static function define_ajax() {
40
		// phpcs:disable
41
		if ( ! empty( $_GET['wc-ajax'] ) ) {
42
			wc_maybe_define_constant( 'DOING_AJAX', true );
43
			wc_maybe_define_constant( 'WC_DOING_AJAX', true );
44
			if ( ! WP_DEBUG || ( WP_DEBUG && ! WP_DEBUG_DISPLAY ) ) {
45
				@ini_set( 'display_errors', 0 ); // Turn off display_errors during AJAX events to prevent malformed JSON.
0 ignored issues
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...
Coding Style introduced by
Silencing errors is discouraged
Loading history...
46
			}
47
			$GLOBALS['wpdb']->hide_errors();
48
		}
49
		// phpcs:enable
50
	}
51
52
	/**
53
	 * Send headers for WC Ajax Requests.
54
	 *
55
	 * @since 2.5.0
56
	 */
57
	private static function wc_ajax_headers() {
58
		if ( ! headers_sent() ) {
59
			send_origin_headers();
60
			send_nosniff_header();
61
			wc_nocache_headers();
62
			header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
63
			header( 'X-Robots-Tag: noindex' );
64
			status_header( 200 );
65 View Code Duplication
		} elseif ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
66
			headers_sent( $file, $line );
67
			trigger_error( "wc_ajax_headers cannot set headers - headers already sent by {$file} on line {$line}", E_USER_NOTICE ); // @codingStandardsIgnoreLine
68
		}
69
	}
70
71
	/**
72
	 * Check for WC Ajax request and fire action.
73
	 */
74
	public static function do_wc_ajax() {
75
		global $wp_query;
76
77
		// phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification
78
		if ( ! empty( $_GET['wc-ajax'] ) ) {
79
			$wp_query->set( 'wc-ajax', sanitize_text_field( wp_unslash( $_GET['wc-ajax'] ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
80
		}
81
82
		$action = $wp_query->get( 'wc-ajax' );
83
84
		if ( $action ) {
85
			self::wc_ajax_headers();
86
			$action = sanitize_text_field( $action );
87
			do_action( 'wc_ajax_' . $action );
88
			wp_die();
89
		}
90
		// phpcs:enable
91
	}
92
93
	/**
94
	 * Hook in methods - uses WordPress ajax handlers (admin-ajax).
95
	 */
96
	public static function add_ajax_events() {
97
		$ajax_events_nopriv = array(
98
			'get_refreshed_fragments',
99
			'apply_coupon',
100
			'remove_coupon',
101
			'update_shipping_method',
102
			'get_cart_totals',
103
			'update_order_review',
104
			'add_to_cart',
105
			'remove_from_cart',
106
			'checkout',
107
			'get_variation',
108
			'get_customer_location',
109
		);
110
111
		foreach ( $ajax_events_nopriv as $ajax_event ) {
112
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
113
			add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
114
115
			// WC AJAX can be used for frontend ajax requests.
116
			add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
117
		}
118
119
		$ajax_events = array(
120
			'feature_product',
121
			'mark_order_status',
122
			'get_order_details',
123
			'add_attribute',
124
			'add_new_attribute',
125
			'remove_variation',
126
			'remove_variations',
127
			'save_attributes',
128
			'add_variation',
129
			'link_all_variations',
130
			'revoke_access_to_download',
131
			'grant_access_to_download',
132
			'get_customer_details',
133
			'add_order_item',
134
			'add_order_fee',
135
			'add_order_shipping',
136
			'add_order_tax',
137
			'add_coupon_discount',
138
			'remove_order_coupon',
139
			'remove_order_item',
140
			'remove_order_tax',
141
			'reduce_order_item_stock',
142
			'increase_order_item_stock',
143
			'add_order_item_meta',
144
			'remove_order_item_meta',
145
			'calc_line_taxes',
146
			'save_order_items',
147
			'load_order_items',
148
			'add_order_note',
149
			'delete_order_note',
150
			'json_search_products',
151
			'json_search_products_and_variations',
152
			'json_search_downloadable_products_and_variations',
153
			'json_search_customers',
154
			'json_search_categories',
155
			'term_ordering',
156
			'product_ordering',
157
			'refund_line_items',
158
			'delete_refund',
159
			'rated',
160
			'update_api_key',
161
			'load_variations',
162
			'save_variations',
163
			'bulk_edit_variations',
164
			'tax_rates_save_changes',
165
			'shipping_zones_save_changes',
166
			'shipping_zone_add_method',
167
			'shipping_zone_methods_save_changes',
168
			'shipping_zone_methods_save_settings',
169
			'shipping_classes_save_changes',
170
			'toggle_gateway_enabled',
171
		);
172
173
		foreach ( $ajax_events as $ajax_event ) {
174
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
175
		}
176
	}
177
178
	/**
179
	 * Get a refreshed cart fragment, including the mini cart HTML.
180
	 */
181
	public static function get_refreshed_fragments() {
182
		ob_start();
183
184
		woocommerce_mini_cart();
185
186
		$mini_cart = ob_get_clean();
187
188
		$data = array(
189
			'fragments' => apply_filters(
190
				'woocommerce_add_to_cart_fragments',
191
				array(
192
					'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>',
193
				)
194
			),
195
			'cart_hash' => WC()->cart->get_cart_hash(),
196
		);
197
198
		wp_send_json( $data );
199
	}
200
201
	/**
202
	 * AJAX apply coupon on checkout page.
203
	 */
204
	public static function apply_coupon() {
205
206
		check_ajax_referer( 'apply-coupon', 'security' );
207
208
		if ( ! empty( $_POST['coupon_code'] ) ) {
209
			WC()->cart->add_discount( wc_format_coupon_code( wp_unslash( $_POST['coupon_code'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
210
		} else {
211
			wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
212
		}
213
214
		wc_print_notices();
215
		wp_die();
216
	}
217
218
	/**
219
	 * AJAX remove coupon on cart and checkout page.
220
	 */
221
	public static function remove_coupon() {
222
		check_ajax_referer( 'remove-coupon', 'security' );
223
224
		$coupon = isset( $_POST['coupon'] ) ? wc_format_coupon_code( wp_unslash( $_POST['coupon'] ) ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
225
226
		if ( empty( $coupon ) ) {
227
			wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ), 'error' );
228
		} else {
229
			WC()->cart->remove_coupon( $coupon );
230
			wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
231
		}
232
233
		wc_print_notices();
234
		wp_die();
235
	}
236
237
	/**
238
	 * AJAX update shipping method on cart page.
239
	 */
240
	public static function update_shipping_method() {
241
		check_ajax_referer( 'update-shipping-method', 'security' );
242
243
		wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
244
245
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
246
		$posted_shipping_methods = isset( $_POST['shipping_method'] ) ? wc_clean( wp_unslash( $_POST['shipping_method'] ) ) : array();
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
247
248
		if ( is_array( $posted_shipping_methods ) ) {
249
			foreach ( $posted_shipping_methods as $i => $value ) {
250
				$chosen_shipping_methods[ $i ] = $value;
251
			}
252
		}
253
254
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
255
256
		self::get_cart_totals();
257
	}
258
259
	/**
260
	 * AJAX receive updated cart_totals div.
261
	 */
262
	public static function get_cart_totals() {
263
		wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
264
		WC()->cart->calculate_totals();
265
		woocommerce_cart_totals();
266
		wp_die();
267
	}
268
269
	/**
270
	 * Session has expired.
271
	 */
272
	private static function update_order_review_expired() {
273
		wp_send_json(
274
			array(
275
				'fragments' => apply_filters(
276
					'woocommerce_update_order_review_fragments',
277
					array(
278
						'form.woocommerce-checkout' => '<div class="woocommerce-error">' . __( 'Sorry, your session has expired.', 'woocommerce' ) . ' <a href="' . esc_url( wc_get_page_permalink( 'shop' ) ) . '" class="wc-backward">' . __( 'Return to shop', 'woocommerce' ) . '</a></div>',
279
					)
280
				),
281
			)
282
		);
283
	}
284
285
	/**
286
	 * AJAX update order review on checkout.
287
	 */
288
	public static function update_order_review() {
289
		check_ajax_referer( 'update-order-review', 'security' );
290
291
		wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
292
293 View Code Duplication
		if ( WC()->cart->is_empty() && ! is_customize_preview() && apply_filters( 'woocommerce_checkout_update_order_review_expired', true ) ) {
294
			self::update_order_review_expired();
295
		}
296
297
		do_action( 'woocommerce_checkout_update_order_review', isset( $_POST['post_data'] ) ? wp_unslash( $_POST['post_data'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
298
299
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
300
		$posted_shipping_methods = isset( $_POST['shipping_method'] ) ? wc_clean( wp_unslash( $_POST['shipping_method'] ) ) : array();
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
301
302
		if ( is_array( $posted_shipping_methods ) ) {
303
			foreach ( $posted_shipping_methods as $i => $value ) {
304
				$chosen_shipping_methods[ $i ] = $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'] ) ? '' : wc_clean( wp_unslash( $_POST['payment_method'] ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
310
		WC()->customer->set_props(
311
			array(
312
				'billing_country'   => isset( $_POST['country'] ) ? wc_clean( wp_unslash( $_POST['country'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
313
				'billing_state'     => isset( $_POST['state'] ) ? wc_clean( wp_unslash( $_POST['state'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
314
				'billing_postcode'  => isset( $_POST['postcode'] ) ? wc_clean( wp_unslash( $_POST['postcode'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
315
				'billing_city'      => isset( $_POST['city'] ) ? wc_clean( wp_unslash( $_POST['city'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
316
				'billing_address_1' => isset( $_POST['address'] ) ? wc_clean( wp_unslash( $_POST['address'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
317
				'billing_address_2' => isset( $_POST['address_2'] ) ? wc_clean( wp_unslash( $_POST['address_2'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
318
			)
319
		);
320
321
		if ( wc_ship_to_billing_address_only() ) {
322
			WC()->customer->set_props(
323
				array(
324
					'shipping_country'   => isset( $_POST['country'] ) ? wc_clean( wp_unslash( $_POST['country'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
325
					'shipping_state'     => isset( $_POST['state'] ) ? wc_clean( wp_unslash( $_POST['state'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
326
					'shipping_postcode'  => isset( $_POST['postcode'] ) ? wc_clean( wp_unslash( $_POST['postcode'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
327
					'shipping_city'      => isset( $_POST['city'] ) ? wc_clean( wp_unslash( $_POST['city'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
328
					'shipping_address_1' => isset( $_POST['address'] ) ? wc_clean( wp_unslash( $_POST['address'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
329
					'shipping_address_2' => isset( $_POST['address_2'] ) ? wc_clean( wp_unslash( $_POST['address_2'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
330
				)
331
			);
332
		} else {
333
			WC()->customer->set_props(
334
				array(
335
					'shipping_country'   => isset( $_POST['s_country'] ) ? wc_clean( wp_unslash( $_POST['s_country'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
336
					'shipping_state'     => isset( $_POST['s_state'] ) ? wc_clean( wp_unslash( $_POST['s_state'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
337
					'shipping_postcode'  => isset( $_POST['s_postcode'] ) ? wc_clean( wp_unslash( $_POST['s_postcode'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
338
					'shipping_city'      => isset( $_POST['s_city'] ) ? wc_clean( wp_unslash( $_POST['s_city'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
339
					'shipping_address_1' => isset( $_POST['s_address'] ) ? wc_clean( wp_unslash( $_POST['s_address'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
340
					'shipping_address_2' => isset( $_POST['s_address_2'] ) ? wc_clean( wp_unslash( $_POST['s_address_2'] ) ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
341
				)
342
			);
343
		}
344
345
		if ( isset( $_POST['has_full_address'] ) && wc_string_to_bool( wc_clean( wp_unslash( $_POST['has_full_address'] ) ) ) ) {
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['has_full_address'])) targeting wc_clean() can also be of type array; however, wc_string_to_bool() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
346
			WC()->customer->set_calculated_shipping( true );
347
		} else {
348
			WC()->customer->set_calculated_shipping( false );
349
		}
350
351
		WC()->customer->save();
352
353
		// Calculate shipping before totals. This will ensure any shipping methods that affect things like taxes are chosen prior to final totals being calculated. Ref: #22708.
354
		WC()->cart->calculate_shipping();
355
		WC()->cart->calculate_totals();
356
357
		// Get order review fragment.
358
		ob_start();
359
		woocommerce_order_review();
360
		$woocommerce_order_review = ob_get_clean();
361
362
		// Get checkout payment fragment.
363
		ob_start();
364
		woocommerce_checkout_payment();
365
		$woocommerce_checkout_payment = ob_get_clean();
366
367
		// Get messages if reload checkout is not true.
368
		$reload_checkout = isset( WC()->session->reload_checkout ) ? true : false;
369
		if ( ! $reload_checkout ) {
370
			$messages = wc_print_notices( true );
371
		} else {
372
			$messages = '';
373
		}
374
375
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
376
377
		wp_send_json(
378
			array(
379
				'result'    => empty( $messages ) ? 'success' : 'failure',
380
				'messages'  => $messages,
381
				'reload'    => $reload_checkout ? 'true' : 'false',
382
				'fragments' => apply_filters(
383
					'woocommerce_update_order_review_fragments',
384
					array(
385
						'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
386
						'.woocommerce-checkout-payment' => $woocommerce_checkout_payment,
387
					)
388
				),
389
			)
390
		);
391
	}
392
393
	/**
394
	 * AJAX add to cart.
395
	 */
396
	public static function add_to_cart() {
397
		ob_start();
398
399
		// phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification
400
		if ( ! isset( $_POST['product_id'] ) ) {
401
			return;
402
		}
403
404
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
405
		$product           = wc_get_product( $product_id );
406
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_POST['quantity'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
407
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
408
		$product_status    = get_post_status( $product_id );
409
		$variation_id      = 0;
410
		$variation         = array();
411
412
		if ( $product && 'variation' === $product->get_type() ) {
413
			$variation_id = $product_id;
414
			$product_id   = $product->get_parent_id();
415
			$variation    = $product->get_variation_attributes();
416
		}
417
418
		if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation ) && 'publish' === $product_status ) {
419
420
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
421
422
			if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
423
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
424
			}
425
426
			self::get_refreshed_fragments();
427
428
		} else {
429
430
			// If there was an error adding to the cart, redirect to the product page to show any errors.
431
			$data = array(
432
				'error'       => true,
433
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id ),
434
			);
435
436
			wp_send_json( $data );
437
		}
438
		// phpcs:enable
439
	}
440
441
	/**
442
	 * AJAX remove from cart.
443
	 */
444
	public static function remove_from_cart() {
445
		ob_start();
446
447
		// phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
448
		$cart_item_key = wc_clean( isset( $_POST['cart_item_key'] ) ? wp_unslash( $_POST['cart_item_key'] ) : '' );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
449
450
		if ( $cart_item_key && false !== WC()->cart->remove_cart_item( $cart_item_key ) ) {
451
			self::get_refreshed_fragments();
452
		} else {
453
			wp_send_json_error();
454
		}
455
	}
456
457
	/**
458
	 * Process ajax checkout form.
459
	 */
460
	public static function checkout() {
461
		wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
462
		WC()->checkout()->process_checkout();
463
		wp_die( 0 );
464
	}
465
466
	/**
467
	 * Get a matching variation based on posted attributes.
468
	 */
469
	public static function get_variation() {
470
		ob_start();
471
472
		// phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification
473
		if ( empty( $_POST['product_id'] ) ) {
474
			wp_die();
475
		}
476
477
		$variable_product = wc_get_product( absint( $_POST['product_id'] ) );
478
479
		if ( ! $variable_product ) {
480
			wp_die();
481
		}
482
483
		$data_store   = WC_Data_Store::load( 'product' );
484
		$variation_id = $data_store->find_matching_product_variation( $variable_product, wp_unslash( $_POST ) );
0 ignored issues
show
Documentation Bug introduced by
The method find_matching_product_variation does not exist on object<WC_Data_Store>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
485
		$variation    = $variation_id ? $variable_product->get_available_variation( $variation_id ) : false;
486
		wp_send_json( $variation );
487
		// phpcs:enable
488
	}
489
490
	/**
491
	 * Locate user via AJAX.
492
	 */
493
	public static function get_customer_location() {
494
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
495
		wp_send_json_success( array( 'hash' => $location_hash ) );
496
	}
497
498
	/**
499
	 * Toggle Featured status of a product from admin.
500
	 */
501
	public static function feature_product() {
502
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) && isset( $_GET['product_id'] ) ) {
503
			$product = wc_get_product( absint( $_GET['product_id'] ) );
504
505
			if ( $product ) {
506
				$product->set_featured( ! $product->get_featured() );
507
				$product->save();
508
			}
509
		}
510
511
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=product' ) );
512
		exit;
513
	}
514
515
	/**
516
	 * Mark an order with a status.
517
	 */
518
	public static function mark_order_status() {
519
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) && isset( $_GET['status'], $_GET['order_id'] ) ) {
520
			$status = sanitize_text_field( wp_unslash( $_GET['status'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
521
			$order  = wc_get_order( absint( wp_unslash( $_GET['order_id'] ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
522
523
			if ( wc_is_order_status( 'wc-' . $status ) && $order ) {
524
				// Initialize payment gateways in case order has hooked status transition actions.
525
				WC()->payment_gateways();
526
527
				$order->update_status( $status, '', true );
528
				do_action( 'woocommerce_order_edit_status', $order->get_id(), $status );
529
			}
530
		}
531
532
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
533
		exit;
534
	}
535
536
	/**
537
	 * Get order details.
538
	 */
539
	public static function get_order_details() {
540
		check_admin_referer( 'woocommerce-preview-order', 'security' );
541
542
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_GET['order_id'] ) ) {
543
			wp_die( -1 );
544
		}
545
546
		$order = wc_get_order( absint( $_GET['order_id'] ) ); // WPCS: sanitization ok.
547
548
		if ( $order ) {
549
			include_once 'admin/list-tables/class-wc-admin-list-table-orders.php';
550
551
			wp_send_json_success( WC_Admin_List_Table_Orders::order_preview_get_order_details( $order ) );
552
		}
553
		wp_die();
554
	}
555
556
	/**
557
	 * Add an attribute row.
558
	 */
559
	public static function add_attribute() {
560
		ob_start();
561
562
		check_ajax_referer( 'add-attribute', 'security' );
563
564
		if ( ! current_user_can( 'edit_products' ) || ! isset( $_POST['taxonomy'], $_POST['i'] ) ) {
565
			wp_die( -1 );
566
		}
567
568
		$i             = absint( $_POST['i'] );
569
		$metabox_class = array();
570
		$attribute     = new WC_Product_Attribute();
571
572
		$attribute->set_id( wc_attribute_taxonomy_id_by_name( sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
573
		$attribute->set_name( sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
574
		$attribute->set_visible( apply_filters( 'woocommerce_attribute_default_visibility', 1 ) );
575
		$attribute->set_variation( apply_filters( 'woocommerce_attribute_default_is_variation', 0 ) );
576
577
		if ( $attribute->is_taxonomy() ) {
578
			$metabox_class[] = 'taxonomy';
579
			$metabox_class[] = $attribute->get_name();
580
		}
581
582
		include 'admin/meta-boxes/views/html-product-attribute.php';
583
		wp_die();
584
	}
585
586
	/**
587
	 * Add a new attribute via ajax function.
588
	 */
589
	public static function add_new_attribute() {
590
		check_ajax_referer( 'add-attribute', 'security' );
591
592
		if ( current_user_can( 'manage_product_terms' ) && isset( $_POST['taxonomy'], $_POST['term'] ) ) {
593
			$taxonomy = esc_attr( wp_unslash( $_POST['taxonomy'] ) ); // phpcs:ignore
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
594
			$term     = wc_clean( wp_unslash( $_POST['term'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
595
596
			if ( taxonomy_exists( $taxonomy ) ) {
597
598
				$result = wp_insert_term( $term, $taxonomy );
599
600
				if ( is_wp_error( $result ) ) {
601
					wp_send_json(
602
						array(
603
							'error' => $result->get_error_message(),
604
						)
605
					);
606
				} else {
607
					$term = get_term_by( 'id', $result['term_id'], $taxonomy );
608
					wp_send_json(
609
						array(
610
							'term_id' => $term->term_id,
611
							'name'    => $term->name,
612
							'slug'    => $term->slug,
613
						)
614
					);
615
				}
616
			}
617
		}
618
		wp_die( -1 );
619
	}
620
621
	/**
622
	 * Delete variations via ajax function.
623
	 */
624
	public static function remove_variations() {
625
		check_ajax_referer( 'delete-variations', 'security' );
626
627
		if ( current_user_can( 'edit_products' ) && isset( $_POST['variation_ids'] ) ) {
628
			$variation_ids = array_map( 'absint', (array) wp_unslash( $_POST['variation_ids'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
629
630
			foreach ( $variation_ids as $variation_id ) {
631
				if ( 'product_variation' === get_post_type( $variation_id ) ) {
632
					$variation = wc_get_product( $variation_id );
633
					$variation->delete( true );
634
				}
635
			}
636
		}
637
638
		wp_die( -1 );
639
	}
640
641
	/**
642
	 * Save attributes via ajax.
643
	 */
644
	public static function save_attributes() {
645
		check_ajax_referer( 'save-attributes', 'security' );
646
647 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || ! isset( $_POST['data'], $_POST['post_id'] ) ) {
648
			wp_die( -1 );
649
		}
650
651
		$response = array();
652
653
		try {
654
			parse_str( wp_unslash( $_POST['data'] ), $data ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
655
656
			$attributes   = WC_Meta_Box_Product_Data::prepare_attributes( $data );
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type null; however, WC_Meta_Box_Product_Data::prepare_attributes() does only seem to accept false|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...
657
			$product_id   = absint( wp_unslash( $_POST['post_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
658
			$product_type = ! empty( $_POST['product_type'] ) ? wc_clean( wp_unslash( $_POST['product_type'] ) ) : 'simple';
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
659
			$classname    = WC_Product_Factory::get_product_classname( $product_id, $product_type );
0 ignored issues
show
Bug introduced by
It seems like $product_type defined by !empty($_POST['product_t...uct_type'])) : 'simple' on line 658 can also be of type array; however, WC_Product_Factory::get_product_classname() 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...
660
			$product      = new $classname( $product_id );
661
662
			$product->set_attributes( $attributes );
663
			$product->save();
664
665
			ob_start();
666
			$attributes = $product->get_attributes( 'edit' );
667
			$i          = -1;
668
			if ( ! empty( $data['attribute_names'] ) ) {
669
				foreach ( $data['attribute_names'] as $attribute_name ) {
670
					$attribute = isset( $attributes[ sanitize_title( $attribute_name ) ] ) ? $attributes[ sanitize_title( $attribute_name ) ] : false;
671
					if ( ! $attribute ) {
672
						continue;
673
					}
674
					$i++;
675
					$metabox_class = array();
676
677
					if ( $attribute->is_taxonomy() ) {
678
						$metabox_class[] = 'taxonomy';
679
						$metabox_class[] = $attribute->get_name();
680
					}
681
682
					include 'admin/meta-boxes/views/html-product-attribute.php';
683
				}
684
			}
685
686
			$response['html'] = ob_get_clean();
687
		} catch ( Exception $e ) {
688
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
689
		}
690
691
		// wp_send_json_success must be outside the try block not to break phpunit tests.
692
		wp_send_json_success( $response );
693
	}
694
695
	/**
696
	 * Add variation via ajax function.
697
	 */
698
	public static function add_variation() {
699
		check_ajax_referer( 'add-variation', 'security' );
700
701 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || ! isset( $_POST['post_id'], $_POST['loop'] ) ) {
702
			wp_die( -1 );
703
		}
704
705
		global $post; // Set $post global so its available, like within the admin screens.
706
707
		$product_id       = intval( $_POST['post_id'] );
708
		$post             = get_post( $product_id ); // phpcs:ignore
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
709
		$loop             = intval( $_POST['loop'] );
710
		$product_object   = new WC_Product_Variable( $product_id ); // Forces type to variable in case product is unsaved.
711
		$variation_object = new WC_Product_Variation();
712
		$variation_object->set_parent_id( $product_id );
713
		$variation_object->set_attributes( array_fill_keys( array_map( 'sanitize_title', array_keys( $product_object->get_variation_attributes() ) ), '' ) );
714
		$variation_id   = $variation_object->save();
715
		$variation      = get_post( $variation_id );
716
		$variation_data = array_merge( get_post_custom( $variation_id ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility.
717
		include 'admin/meta-boxes/views/html-variation-admin.php';
718
		wp_die();
719
	}
720
721
	/**
722
	 * Link all variations via ajax function.
723
	 */
724
	public static function link_all_variations() {
725
		check_ajax_referer( 'link-variations', 'security' );
726
727
		if ( ! current_user_can( 'edit_products' ) ) {
728
			wp_die( -1 );
729
		}
730
731
		wc_maybe_define_constant( 'WC_MAX_LINKED_VARIATIONS', 50 );
732
		wc_set_time_limit( 0 );
733
734
		$post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
735
736
		if ( ! $post_id ) {
737
			wp_die();
738
		}
739
740
		$product    = wc_get_product( $post_id );
741
		$data_store = $product->get_data_store();
742
743
		if ( ! is_callable( array( $data_store, 'create_all_product_variations' ) ) ) {
744
			wp_die();
745
		}
746
747
		echo esc_html( $data_store->create_all_product_variations( $product, WC_MAX_LINKED_VARIATIONS ) );
748
749
		$data_store->sort_all_product_variations( $product->get_id() );
750
		wp_die();
751
	}
752
753
	/**
754
	 * Delete download permissions via ajax function.
755
	 */
756
	public static function revoke_access_to_download() {
757
		check_ajax_referer( 'revoke-access', 'security' );
758
759
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['download_id'], $_POST['product_id'], $_POST['order_id'], $_POST['permission_id'] ) ) {
760
			wp_die( -1 );
761
		}
762
		$download_id   = wc_clean( wp_unslash( $_POST['download_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
763
		$product_id    = intval( $_POST['product_id'] );
764
		$order_id      = intval( $_POST['order_id'] );
765
		$permission_id = absint( $_POST['permission_id'] );
766
		$data_store    = WC_Data_Store::load( 'customer-download' );
767
		$data_store->delete_by_id( $permission_id );
0 ignored issues
show
Documentation Bug introduced by
The method delete_by_id does not exist on object<WC_Data_Store>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
768
769
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id, $permission_id );
770
771
		wp_die();
772
	}
773
774
	/**
775
	 * Grant download permissions via ajax function.
776
	 */
777
	public static function grant_access_to_download() {
778
779
		check_ajax_referer( 'grant-access', 'security' );
780
781 View Code Duplication
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['loop'], $_POST['order_id'], $_POST['product_ids'] ) ) {
782
			wp_die( -1 );
783
		}
784
785
		global $wpdb;
786
787
		$wpdb->hide_errors();
788
789
		$order_id     = intval( $_POST['order_id'] );
790
		$product_ids  = array_filter( array_map( 'absint', (array) wp_unslash( $_POST['product_ids'] ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
791
		$loop         = intval( $_POST['loop'] );
792
		$file_counter = 0;
793
		$order        = wc_get_order( $order_id );
794
795
		foreach ( $product_ids as $product_id ) {
796
			$product = wc_get_product( $product_id );
797
			$files   = $product->get_downloads();
798
799
			if ( ! $order->get_billing_email() ) {
800
				wp_die();
801
			}
802
803
			if ( ! empty( $files ) ) {
804
				foreach ( $files as $download_id => $file ) {
805
					$inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order );
0 ignored issues
show
Documentation introduced by
$order is of type false|object, but the function expects a object<WC_Order>.

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...
806
					if ( $inserted_id ) {
807
						$download = new WC_Customer_Download( $inserted_id );
0 ignored issues
show
Bug introduced by
It seems like $inserted_id defined by wc_downloadable_file_per...d, $product_id, $order) on line 805 can also be of type boolean; however, WC_Customer_Download::__construct() does only seem to accept integer, 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...
808
						$loop ++;
809
						$file_counter ++;
810
811
						if ( $file->get_name() ) {
812
							$file_count = $file->get_name();
813
						} else {
814
							/* translators: %d file count */
815
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
816
						}
817
						include 'admin/meta-boxes/views/html-order-download-permission.php';
818
					}
819
				}
820
			}
821
		}
822
		wp_die();
823
	}
824
825
	/**
826
	 * Get customer details via ajax.
827
	 */
828
	public static function get_customer_details() {
829
		check_ajax_referer( 'get-customer-details', 'security' );
830
831
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['user_id'] ) ) {
832
			wp_die( -1 );
833
		}
834
835
		$user_id  = absint( $_POST['user_id'] );
836
		$customer = new WC_Customer( $user_id );
837
838
		if ( has_filter( 'woocommerce_found_customer_details' ) ) {
839
			wc_deprecated_function( 'The woocommerce_found_customer_details filter', '3.0', 'woocommerce_ajax_get_customer_details' );
840
		}
841
842
		$data                  = $customer->get_data();
843
		$data['date_created']  = $data['date_created'] ? $data['date_created']->getTimestamp() : null;
844
		$data['date_modified'] = $data['date_modified'] ? $data['date_modified']->getTimestamp() : null;
845
846
		$customer_data = apply_filters( 'woocommerce_ajax_get_customer_details', $data, $customer, $user_id );
847
		wp_send_json( $customer_data );
848
	}
849
850
	/**
851
	 * Add order item via ajax. Used on the edit order screen in WP Admin.
852
	 *
853
	 * @throws Exception If order is invalid.
854
	 */
855
	public static function add_order_item() {
856
		check_ajax_referer( 'order-item', 'security' );
857
858
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
859
			wp_die( -1 );
860
		}
861
862
		$response = array();
863
864
		try {
865
			if ( ! isset( $_POST['order_id'] ) ) {
866
				throw new Exception( __( 'Invalid order', 'woocommerce' ) );
867
			}
868
869
			$order_id = absint( wp_unslash( $_POST['order_id'] ) ); // WPCS: input var ok.
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
870
			$order    = wc_get_order( $order_id );
871
872
			if ( ! $order ) {
873
				throw new Exception( __( 'Invalid order', 'woocommerce' ) );
874
			}
875
876
			// If we passed through items it means we need to save first before adding a new one.
877
			$items = ( ! empty( $_POST['items'] ) ) ? wp_unslash( $_POST['items'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
878
879 View Code Duplication
			if ( ! empty( $items ) ) {
880
				$save_items = array();
881
				parse_str( $items, $save_items );
882
				wc_save_order_items( $order->get_id(), $save_items );
0 ignored issues
show
Bug introduced by
It seems like $save_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...
883
			}
884
885
			$items_to_add = isset( $_POST['data'] ) ? array_filter( wp_unslash( (array) $_POST['data'] ) ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
886
887
			// Add items to order.
888
			$order_notes = array();
889
890
			foreach ( $items_to_add as $item ) {
891
				if ( ! isset( $item['id'], $item['qty'] ) || empty( $item['id'] ) ) {
892
					continue;
893
				}
894
				$product_id = absint( $item['id'] );
895
				$qty        = wc_stock_amount( $item['qty'] );
896
				$product    = wc_get_product( $product_id );
897
898
				if ( ! $product ) {
899
					throw new Exception( __( 'Invalid product ID', 'woocommerce' ) . ' ' . $product_id );
900
				}
901
902
				$item_id                 = $order->add_product( $product, $qty );
903
				$item                    = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id );
904
				$added_items[ $item_id ] = $item;
905
				$order_notes[ $item_id ] = $product->get_formatted_name();
906
907
				if ( $product->managing_stock() ) {
908
					$new_stock               = wc_update_product_stock( $product, $qty, 'decrease' );
909
					$order_notes[ $item_id ] = $product->get_formatted_name() . ' &ndash; ' . ( $new_stock + $qty ) . '&rarr;' . $new_stock;
910
					$item->add_meta_data( '_reduced_stock', $qty, true );
911
					$item->save();
912
				}
913
914
				do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item, $order );
915
			}
916
917
			/* translators: %s item name. */
918
			$order->add_order_note( sprintf( __( 'Added line items: %s', 'woocommerce' ), implode( ', ', $order_notes ) ), false, true );
919
920
			do_action( 'woocommerce_ajax_order_items_added', $added_items, $order );
921
922
			$data = get_post_meta( $order_id );
923
924
			// Get HTML to return.
925
			ob_start();
926
			include 'admin/meta-boxes/views/html-order-items.php';
927
			$items_html = ob_get_clean();
928
929
			ob_start();
930
			$notes = wc_get_order_notes( array( 'order_id' => $order_id ) );
931
			include 'admin/meta-boxes/views/html-order-notes.php';
932
			$notes_html = ob_get_clean();
933
934
			wp_send_json_success(
935
				array(
936
					'html'       => $items_html,
937
					'notes_html' => $notes_html,
938
				)
939
			);
940
		} catch ( Exception $e ) {
941
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
942
		}
943
944
		// wp_send_json_success must be outside the try block not to break phpunit tests.
945
		wp_send_json_success( $response );
946
	}
947
948
	/**
949
	 * Add order fee via ajax.
950
	 *
951
	 * @throws Exception If order is invalid.
952
	 */
953
	public static function add_order_fee() {
954
		check_ajax_referer( 'order-item', 'security' );
955
956
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
957
			wp_die( -1 );
958
		}
959
960
		$response = array();
961
962
		try {
963
			$order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
964
			$order    = wc_get_order( $order_id );
965
966
			if ( ! $order ) {
967
				throw new Exception( __( 'Invalid order', 'woocommerce' ) );
968
			}
969
970
			$amount = isset( $_POST['amount'] ) ? wc_clean( wp_unslash( $_POST['amount'] ) ) : 0;
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
971
972
			$calculate_tax_args = array(
973
				'country'  => isset( $_POST['country'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['country'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
974
				'state'    => isset( $_POST['state'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['state'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
975
				'postcode' => isset( $_POST['postcode'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['postcode'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
976
				'city'     => isset( $_POST['city'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['city'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
977
			);
978
979
			if ( strstr( $amount, '%' ) ) {
980
				$formatted_amount = $amount;
981
				$percent          = floatval( trim( $amount, '%' ) );
982
				$amount           = $order->get_total() * ( $percent / 100 );
983
			} else {
984
				$amount           = floatval( $amount );
985
				$formatted_amount = wc_price( $amount, array( 'currency' => $order->get_currency() ) );
986
			}
987
988
			$fee = new WC_Order_Item_Fee();
989
			$fee->set_amount( $amount );
990
			$fee->set_total( $amount );
991
			/* translators: %s fee amount */
992
			$fee->set_name( sprintf( __( '%s fee', 'woocommerce' ), wc_clean( $formatted_amount ) ) );
993
994
			$order->add_item( $fee );
995
			$order->calculate_taxes( $calculate_tax_args );
996
			$order->calculate_totals( false );
997
			$order->save();
998
999
			ob_start();
1000
			include 'admin/meta-boxes/views/html-order-items.php';
1001
			$response['html'] = ob_get_clean();
1002
		} catch ( Exception $e ) {
1003
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1004
		}
1005
1006
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1007
		wp_send_json_success( $response );
1008
	}
1009
1010
	/**
1011
	 * Add order shipping cost via ajax.
1012
	 *
1013
	 * @throws Exception If order is invalid.
1014
	 */
1015
	public static function add_order_shipping() {
1016
		check_ajax_referer( 'order-item', 'security' );
1017
1018
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1019
			wp_die( -1 );
1020
		}
1021
1022
		$response = array();
1023
1024
		try {
1025
			$order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
1026
			$order    = wc_get_order( $order_id );
1027
1028
			if ( ! $order ) {
1029
				throw new Exception( __( 'Invalid order', 'woocommerce' ) );
1030
			}
1031
1032
			$order_taxes      = $order->get_taxes();
1033
			$shipping_methods = WC()->shipping() ? WC()->shipping()->load_shipping_methods() : array();
1034
1035
			// Add new shipping.
1036
			$item = new WC_Order_Item_Shipping();
1037
			$item->set_shipping_rate( new WC_Shipping_Rate() );
1038
			$item->set_order_id( $order_id );
1039
			$item_id = $item->save();
1040
1041
			ob_start();
1042
			include 'admin/meta-boxes/views/html-order-shipping.php';
1043
			$response['html'] = ob_get_clean();
1044
		} catch ( Exception $e ) {
1045
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1046
		}
1047
1048
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1049
		wp_send_json_success( $response );
1050
	}
1051
1052
	/**
1053
	 * Add order tax column via ajax.
1054
	 *
1055
	 * @throws Exception If order or tax rate is invalid.
1056
	 */
1057
	public static function add_order_tax() {
1058
		check_ajax_referer( 'order-item', 'security' );
1059
1060
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1061
			wp_die( -1 );
1062
		}
1063
1064
		$response = array();
1065
1066
		try {
1067
			$order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
1068
			$order    = wc_get_order( $order_id );
1069
1070
			if ( ! $order ) {
1071
				throw new Exception( __( 'Invalid order', 'woocommerce' ) );
1072
			}
1073
1074
			$rate_id = isset( $_POST['rate_id'] ) ? absint( $_POST['rate_id'] ) : '';
1075
1076
			if ( ! $rate_id ) {
1077
				throw new Exception( __( 'Invalid rate', 'woocommerce' ) );
1078
			}
1079
1080
			$data = get_post_meta( $order_id );
1081
1082
			// Add new tax.
1083
			$item = new WC_Order_Item_Tax();
1084
			$item->set_rate( $rate_id );
1085
			$item->set_order_id( $order_id );
1086
			$item->save();
1087
1088
			ob_start();
1089
			include 'admin/meta-boxes/views/html-order-items.php';
1090
			$response['html'] = ob_get_clean();
1091
		} catch ( Exception $e ) {
1092
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1093
		}
1094
1095
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1096
		wp_send_json_success( $response );
1097
	}
1098
1099
	/**
1100
	 * Add order discount via ajax.
1101
	 *
1102
	 * @throws Exception If order or coupon is invalid.
1103
	 */
1104
	public static function add_coupon_discount() {
1105
		check_ajax_referer( 'order-item', 'security' );
1106
1107
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1108
			wp_die( -1 );
1109
		}
1110
1111
		$response = array();
1112
1113
		try {
1114
			$order_id           = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
1115
			$order              = wc_get_order( $order_id );
1116
			$calculate_tax_args = array(
1117
				'country'  => isset( $_POST['country'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['country'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1118
				'state'    => isset( $_POST['state'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['state'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1119
				'postcode' => isset( $_POST['postcode'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['postcode'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1120
				'city'     => isset( $_POST['city'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['city'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1121
			);
1122
1123
			if ( ! $order ) {
1124
				throw new Exception( __( 'Invalid order', 'woocommerce' ) );
1125
			}
1126
1127
			if ( empty( $_POST['coupon'] ) ) {
1128
				throw new Exception( __( 'Invalid coupon', 'woocommerce' ) );
1129
			}
1130
1131
			// Add user ID so validation for coupon limits works.
1132
			$user_id_arg = isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : 0;
1133
1134
			if ( $user_id_arg ) {
1135
				$order->set_customer_id( $user_id_arg );
1136
			}
1137
1138
			$result = $order->apply_coupon( wc_format_coupon_code( wp_unslash( $_POST['coupon'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1139
1140
			if ( is_wp_error( $result ) ) {
1141
				throw new Exception( html_entity_decode( wp_strip_all_tags( $result->get_error_message() ) ) );
1142
			}
1143
1144
			$order->calculate_taxes( $calculate_tax_args );
1145
			$order->calculate_totals( false );
1146
1147
			ob_start();
1148
			include 'admin/meta-boxes/views/html-order-items.php';
1149
			$response['html'] = ob_get_clean();
1150
		} catch ( Exception $e ) {
1151
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1152
		}
1153
1154
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1155
		wp_send_json_success( $response );
1156
	}
1157
1158
	/**
1159
	 * Remove coupon from an order via ajax.
1160
	 *
1161
	 * @throws Exception If order or coupon is invalid.
1162
	 */
1163
	public static function remove_order_coupon() {
1164
		check_ajax_referer( 'order-item', 'security' );
1165
1166
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1167
			wp_die( -1 );
1168
		}
1169
1170
		$response = array();
1171
1172
		try {
1173
			$order_id           = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
1174
			$order              = wc_get_order( $order_id );
1175
			$calculate_tax_args = array(
1176
				'country'  => isset( $_POST['country'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['country'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1177
				'state'    => isset( $_POST['state'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['state'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1178
				'postcode' => isset( $_POST['postcode'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['postcode'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1179
				'city'     => isset( $_POST['city'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['city'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1180
			);
1181
1182
			if ( ! $order ) {
1183
				throw new Exception( __( 'Invalid order', 'woocommerce' ) );
1184
			}
1185
1186
			if ( empty( $_POST['coupon'] ) ) {
1187
				throw new Exception( __( 'Invalid coupon', 'woocommerce' ) );
1188
			}
1189
1190
			$order->remove_coupon( wc_format_coupon_code( wp_unslash( $_POST['coupon'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1191
			$order->calculate_taxes( $calculate_tax_args );
1192
			$order->calculate_totals( false );
1193
1194
			ob_start();
1195
			include 'admin/meta-boxes/views/html-order-items.php';
1196
			$response['html'] = ob_get_clean();
1197
		} catch ( Exception $e ) {
1198
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1199
		}
1200
1201
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1202
		wp_send_json_success( $response );
1203
	}
1204
1205
	/**
1206
	 * Remove an order item.
1207
	 *
1208
	 * @throws Exception If order is invalid.
1209
	 */
1210
	public static function remove_order_item() {
1211
		check_ajax_referer( 'order-item', 'security' );
1212
1213
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'], $_POST['order_item_ids'] ) ) {
1214
			wp_die( -1 );
1215
		}
1216
1217
		$response = array();
1218
1219
		try {
1220
			$order_id = absint( $_POST['order_id'] );
1221
			$order    = wc_get_order( $order_id );
1222
1223
			if ( ! $order ) {
1224
				throw new Exception( __( 'Invalid order', 'woocommerce' ) );
1225
			}
1226
1227
			if ( ! isset( $_POST['order_item_ids'] ) ) {
1228
				throw new Exception( __( 'Invalid items', 'woocommerce' ) );
1229
			}
1230
1231
			$order_item_ids     = wp_unslash( $_POST['order_item_ids'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1232
			$items              = ( ! empty( $_POST['items'] ) ) ? wp_unslash( $_POST['items'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1233
			$calculate_tax_args = array(
1234
				'country'  => isset( $_POST['country'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['country'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1235
				'state'    => isset( $_POST['state'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['state'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1236
				'postcode' => isset( $_POST['postcode'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['postcode'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1237
				'city'     => isset( $_POST['city'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['city'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1238
			);
1239
1240
			if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1241
				$order_item_ids = array( $order_item_ids );
1242
			}
1243
1244
			// If we passed through items it means we need to save first before deleting.
1245 View Code Duplication
			if ( ! empty( $items ) ) {
1246
				$save_items = array();
1247
				parse_str( $items, $save_items );
1248
				wc_save_order_items( $order->get_id(), $save_items );
0 ignored issues
show
Bug introduced by
It seems like $save_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...
1249
			}
1250
1251
			if ( ! empty( $order_item_ids ) ) {
1252
				$order_notes = array();
1253
1254
				foreach ( $order_item_ids as $item_id ) {
1255
					$item_id = absint( $item_id );
1256
					$item    = $order->get_item( $item_id );
1257
1258
					// Before deleting the item, adjust any stock values already reduced.
1259
					if ( $item->is_type( 'line_item' ) ) {
1260
						$changed_stock = wc_maybe_adjust_line_item_product_stock( $item, 0 );
1261
1262
						if ( $changed_stock && ! is_wp_error( $changed_stock ) ) {
1263
							/* translators: %1$s: item name %2$s: stock change */
1264
							$order->add_order_note( sprintf( __( 'Deleted %1$s and adjusted stock (%2$s)', 'woocommerce' ), $item->get_name(), $changed_stock['from'] . '&rarr;' . $changed_stock['to'] ), false, true );
1265
						} else {
1266
							/* translators: %s item name. */
1267
							$order->add_order_note( sprintf( __( 'Deleted %s', 'woocommerce' ), $item->get_name() ), false, true );
1268
						}
1269
					}
1270
1271
					wc_delete_order_item( $item_id );
1272
				}
1273
			}
1274
1275
			$order = wc_get_order( $order_id );
1276
			$order->calculate_taxes( $calculate_tax_args );
1277
			$order->calculate_totals( false );
1278
1279
			// Get HTML to return.
1280
			ob_start();
1281
			include 'admin/meta-boxes/views/html-order-items.php';
1282
			$items_html = ob_get_clean();
1283
1284
			ob_start();
1285
			$notes = wc_get_order_notes( array( 'order_id' => $order_id ) );
1286
			include 'admin/meta-boxes/views/html-order-notes.php';
1287
			$notes_html = ob_get_clean();
1288
1289
			wp_send_json_success(
1290
				array(
1291
					'html'       => $items_html,
1292
					'notes_html' => $notes_html,
1293
				)
1294
			);
1295
		} catch ( Exception $e ) {
1296
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1297
		}
1298
1299
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1300
		wp_send_json_success( $response );
1301
	}
1302
1303
	/**
1304
	 * Remove an order tax.
1305
	 *
1306
	 * @throws Exception If there is an error whilst deleting the rate.
1307
	 */
1308
	public static function remove_order_tax() {
1309
		check_ajax_referer( 'order-item', 'security' );
1310
1311
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'], $_POST['rate_id'] ) ) {
1312
			wp_die( -1 );
1313
		}
1314
1315
		$response = array();
1316
1317
		try {
1318
			$order_id = absint( $_POST['order_id'] );
1319
			$rate_id  = absint( $_POST['rate_id'] );
1320
1321
			wc_delete_order_item( $rate_id );
1322
1323
			$order = wc_get_order( $order_id );
1324
			$order->calculate_totals( false );
1325
1326
			ob_start();
1327
			include 'admin/meta-boxes/views/html-order-items.php';
1328
			$response['html'] = ob_get_clean();
1329
		} catch ( Exception $e ) {
1330
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1331
		}
1332
1333
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1334
		wp_send_json_success( $response );
1335
	}
1336
1337
	/**
1338
	 * Calc line tax.
1339
	 */
1340
	public static function calc_line_taxes() {
1341
		check_ajax_referer( 'calc-totals', 'security' );
1342
1343
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'], $_POST['items'] ) ) {
1344
			wp_die( -1 );
1345
		}
1346
1347
		$order_id           = absint( $_POST['order_id'] );
1348
		$calculate_tax_args = array(
1349
			'country'  => isset( $_POST['country'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['country'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1350
			'state'    => isset( $_POST['state'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['state'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1351
			'postcode' => isset( $_POST['postcode'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['postcode'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1352
			'city'     => isset( $_POST['city'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ) : '',
0 ignored issues
show
Bug introduced by
It seems like wc_clean(wp_unslash($_POST['city'])) targeting wc_clean() can also be of type array; however, wc_strtoupper() 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...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1353
		);
1354
1355
		// Parse the jQuery serialized items.
1356
		$items = array();
1357
		parse_str( wp_unslash( $_POST['items'] ), $items ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1358
1359
		// Save order items first.
1360
		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...
1361
1362
		// Grab the order and recalculate taxes.
1363
		$order = wc_get_order( $order_id );
1364
		$order->calculate_taxes( $calculate_tax_args );
1365
		$order->calculate_totals( false );
1366
		include 'admin/meta-boxes/views/html-order-items.php';
1367
		wp_die();
1368
	}
1369
1370
	/**
1371
	 * Save order items via ajax.
1372
	 */
1373
	public static function save_order_items() {
1374
		check_ajax_referer( 'order-item', 'security' );
1375
1376
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'], $_POST['items'] ) ) {
1377
			wp_die( -1 );
1378
		}
1379
1380
		if ( isset( $_POST['order_id'], $_POST['items'] ) ) {
1381
			$order_id = absint( $_POST['order_id'] );
1382
1383
			// Parse the jQuery serialized items.
1384
			$items = array();
1385
			parse_str( wp_unslash( $_POST['items'] ), $items ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1386
1387
			// Save order items.
1388
			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...
1389
1390
			// Return HTML items.
1391
			$order = wc_get_order( $order_id );
1392
1393
			// Get HTML to return.
1394
			ob_start();
1395
			include 'admin/meta-boxes/views/html-order-items.php';
1396
			$items_html = ob_get_clean();
1397
1398
			ob_start();
1399
			$notes = wc_get_order_notes( array( 'order_id' => $order_id ) );
1400
			include 'admin/meta-boxes/views/html-order-notes.php';
1401
			$notes_html = ob_get_clean();
1402
1403
			wp_send_json_success(
1404
				array(
1405
					'html'       => $items_html,
1406
					'notes_html' => $notes_html,
1407
				)
1408
			);
1409
		}
1410
		wp_die();
1411
	}
1412
1413
	/**
1414
	 * Load order items via ajax.
1415
	 */
1416 View Code Duplication
	public static function load_order_items() {
0 ignored issues
show
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...
1417
		check_ajax_referer( 'order-item', 'security' );
1418
1419
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'] ) ) {
1420
			wp_die( -1 );
1421
		}
1422
1423
		// Return HTML items.
1424
		$order_id = absint( $_POST['order_id'] );
1425
		$order    = wc_get_order( $order_id );
1426
		include 'admin/meta-boxes/views/html-order-items.php';
1427
		wp_die();
1428
	}
1429
1430
	/**
1431
	 * Add order note via ajax.
1432
	 */
1433
	public static function add_order_note() {
1434
		check_ajax_referer( 'add-order-note', 'security' );
1435
1436 View Code Duplication
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['post_id'], $_POST['note'], $_POST['note_type'] ) ) {
1437
			wp_die( -1 );
1438
		}
1439
1440
		$post_id   = absint( $_POST['post_id'] );
1441
		$note      = wp_kses_post( trim( wp_unslash( $_POST['note'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1442
		$note_type = wc_clean( wp_unslash( $_POST['note_type'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1443
1444
		$is_customer_note = ( 'customer' === $note_type ) ? 1 : 0;
1445
1446
		if ( $post_id > 0 ) {
1447
			$order      = wc_get_order( $post_id );
1448
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1449
			$note       = wc_get_order_note( $comment_id );
1450
1451
			$note_classes   = array( 'note' );
1452
			$note_classes[] = $is_customer_note ? 'customer-note' : '';
1453
			$note_classes   = apply_filters( 'woocommerce_order_note_class', array_filter( $note_classes ), $note );
1454
			?>
1455
			<li rel="<?php echo absint( $note->id ); ?>" class="<?php echo esc_attr( implode( ' ', $note_classes ) ); ?>">
1456
				<div class="note_content">
1457
					<?php echo wp_kses_post( wpautop( wptexturize( make_clickable( $note->content ) ) ) ); ?>
1458
				</div>
1459
				<p class="meta">
1460
					<abbr class="exact-date" title="<?php echo esc_attr( $note->date_created->date( 'y-m-d h:i:s' ) ); ?>">
1461
						<?php
1462
						/* translators: $1: Date created, $2 Time created */
1463
						printf( esc_html__( 'added on %1$s at %2$s', 'woocommerce' ), esc_html( $note->date_created->date_i18n( wc_date_format() ) ), esc_html( $note->date_created->date_i18n( wc_time_format() ) ) );
1464
						?>
1465
					</abbr>
1466
					<?php
1467
					if ( 'system' !== $note->added_by ) :
1468
						/* translators: %s: note author */
1469
						printf( ' ' . esc_html__( 'by %s', 'woocommerce' ), esc_html( $note->added_by ) );
1470
					endif;
1471
					?>
1472
					<a href="#" class="delete_note" role="button"><?php esc_html_e( 'Delete note', 'woocommerce' ); ?></a>
1473
				</p>
1474
			</li>
1475
			<?php
1476
		}
1477
		wp_die();
1478
	}
1479
1480
	/**
1481
	 * Delete order note via ajax.
1482
	 */
1483 View Code Duplication
	public static function delete_order_note() {
0 ignored issues
show
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...
1484
		check_ajax_referer( 'delete-order-note', 'security' );
1485
1486
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['note_id'] ) ) {
1487
			wp_die( -1 );
1488
		}
1489
1490
		$note_id = (int) $_POST['note_id'];
1491
1492
		if ( $note_id > 0 ) {
1493
			wc_delete_order_note( $note_id );
1494
		}
1495
		wp_die();
1496
	}
1497
1498
	/**
1499
	 * Search for products and echo json.
1500
	 *
1501
	 * @param string $term (default: '') Term to search for.
1502
	 * @param bool   $include_variations in search or not.
1503
	 */
1504
	public static function json_search_products( $term = '', $include_variations = false ) {
1505
		check_ajax_referer( 'search-products', 'security' );
1506
1507 View Code Duplication
		if ( empty( $term ) && isset( $_GET['term'] ) ) {
1508
			$term = (string) wc_clean( wp_unslash( $_GET['term'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1509
		}
1510
1511
		if ( empty( $term ) ) {
1512
			wp_die();
1513
		}
1514
1515 View Code Duplication
		if ( ! empty( $_GET['limit'] ) ) {
1516
			$limit = absint( $_GET['limit'] );
1517
		} else {
1518
			$limit = absint( apply_filters( 'woocommerce_json_search_limit', 30 ) );
1519
		}
1520
1521
		$include_ids = ! empty( $_GET['include'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['include'] ) ) : array();
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1522
		$exclude_ids = ! empty( $_GET['exclude'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['exclude'] ) ) : array();
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1523
1524
		$data_store = WC_Data_Store::load( 'product' );
1525
		$ids        = $data_store->search_products( $term, '', (bool) $include_variations, false, $limit, $include_ids, $exclude_ids );
0 ignored issues
show
Documentation Bug introduced by
The method search_products does not exist on object<WC_Data_Store>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1526
1527
		$product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' );
1528
		$products        = array();
1529
1530
		foreach ( $product_objects as $product_object ) {
1531
			$formatted_name = $product_object->get_formatted_name();
1532
			$managing_stock = $product_object->managing_stock();
1533
1534
			if ( $managing_stock && ! empty( $_GET['display_stock'] ) ) {
1535
				$stock_amount    = $product_object->get_stock_quantity();
1536
				/* Translators: %d stock amount */
1537
				$formatted_name .= ' &ndash; ' . sprintf( __( 'Stock: %d', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_amount, $product_object ) );
1538
			}
1539
1540
			$products[ $product_object->get_id() ] = rawurldecode( $formatted_name );
1541
		}
1542
1543
		wp_send_json( apply_filters( 'woocommerce_json_search_found_products', $products ) );
1544
	}
1545
1546
	/**
1547
	 * Search for product variations and return json.
1548
	 *
1549
	 * @see WC_AJAX::json_search_products()
1550
	 */
1551
	public static function json_search_products_and_variations() {
1552
		self::json_search_products( '', true );
1553
	}
1554
1555
	/**
1556
	 * Search for downloadable product variations and return json.
1557
	 *
1558
	 * @see WC_AJAX::json_search_products()
1559
	 */
1560
	public static function json_search_downloadable_products_and_variations() {
1561
		check_ajax_referer( 'search-products', 'security' );
1562
1563 View Code Duplication
		if ( ! empty( $_GET['limit'] ) ) {
1564
			$limit = absint( $_GET['limit'] );
1565
		} else {
1566
			$limit = absint( apply_filters( 'woocommerce_json_search_limit', 30 ) );
1567
		}
1568
1569
		$include_ids = ! empty( $_GET['include'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['include'] ) ) : array();
0 ignored issues
show
Unused Code introduced by
$include_ids 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...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1570
		$exclude_ids = ! empty( $_GET['exclude'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['exclude'] ) ) : array();
0 ignored issues
show
Unused Code introduced by
$exclude_ids 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...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1571
1572
		$term       = isset( $_GET['term'] ) ? (string) wc_clean( wp_unslash( $_GET['term'] ) ) : '';
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1573
		$data_store = WC_Data_Store::load( 'product' );
1574
		$ids        = $data_store->search_products( $term, 'downloadable', true, false, $limit );
0 ignored issues
show
Documentation Bug introduced by
The method search_products does not exist on object<WC_Data_Store>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1575
1576
		$product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' );
1577
		$products        = array();
1578
1579
		foreach ( $product_objects as $product_object ) {
1580
			$products[ $product_object->get_id() ] = rawurldecode( $product_object->get_formatted_name() );
1581
		}
1582
1583
		wp_send_json( $products );
1584
	}
1585
1586
	/**
1587
	 * Search for customers and return json.
1588
	 */
1589
	public static function json_search_customers() {
1590
		ob_start();
1591
1592
		check_ajax_referer( 'search-customers', 'security' );
1593
1594
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1595
			wp_die( -1 );
1596
		}
1597
1598
		$term  = isset( $_GET['term'] ) ? (string) wc_clean( wp_unslash( $_GET['term'] ) ) : '';
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1599
		$limit = 0;
1600
1601
		if ( empty( $term ) ) {
1602
			wp_die();
1603
		}
1604
1605
		$ids = array();
1606
		// Search by ID.
1607
		if ( is_numeric( $term ) ) {
1608
			$customer = new WC_Customer( intval( $term ) );
1609
1610
			// Customer does not exists.
1611
			if ( 0 !== $customer->get_id() ) {
1612
				$ids = array( $customer->get_id() );
1613
			}
1614
		}
1615
1616
		// Usernames can be numeric so we first check that no users was found by ID before searching for numeric username, this prevents performance issues with ID lookups.
1617
		if ( empty( $ids ) ) {
1618
			$data_store = WC_Data_Store::load( 'customer' );
1619
1620
			// If search is smaller than 3 characters, limit result set to avoid
1621
			// too many rows being returned.
1622
			if ( 3 > strlen( $term ) ) {
1623
				$limit = 20;
1624
			}
1625
			$ids = $data_store->search_customers( $term, $limit );
0 ignored issues
show
Documentation Bug introduced by
The method search_customers does not exist on object<WC_Data_Store>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1626
		}
1627
1628
		$found_customers = array();
1629
1630
		if ( ! empty( $_GET['exclude'] ) ) {
1631
			$ids = array_diff( $ids, array_map( 'absint', (array) wp_unslash( $_GET['exclude'] ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1632
		}
1633
1634
		foreach ( $ids as $id ) {
1635
			$customer = new WC_Customer( $id );
1636
			/* translators: 1: user display name 2: user ID 3: user email */
1637
			$found_customers[ $id ] = sprintf(
1638
				/* translators: $1: customer name, $2 customer id, $3: customer email */
1639
				esc_html__( '%1$s (#%2$s &ndash; %3$s)', 'woocommerce' ),
1640
				$customer->get_first_name() . ' ' . $customer->get_last_name(),
1641
				$customer->get_id(),
1642
				$customer->get_email()
1643
			);
1644
		}
1645
1646
		wp_send_json( apply_filters( 'woocommerce_json_search_found_customers', $found_customers ) );
1647
	}
1648
1649
	/**
1650
	 * Search for categories and return json.
1651
	 */
1652
	public static function json_search_categories() {
1653
		ob_start();
1654
1655
		check_ajax_referer( 'search-categories', 'security' );
1656
1657
		if ( ! current_user_can( 'edit_products' ) ) {
1658
			wp_die( -1 );
1659
		}
1660
1661
		$search_text = isset( $_GET['term'] ) ? wc_clean( wp_unslash( $_GET['term'] ) ) : '';
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1662
1663
		if ( ! $search_text ) {
1664
			wp_die();
1665
		}
1666
1667
		$found_categories = array();
1668
		$args             = array(
1669
			'taxonomy'   => array( 'product_cat' ),
1670
			'orderby'    => 'id',
1671
			'order'      => 'ASC',
1672
			'hide_empty' => true,
1673
			'fields'     => 'all',
1674
			'name__like' => $search_text,
1675
		);
1676
1677
		$terms = get_terms( $args );
1678
1679
		if ( $terms ) {
1680
			foreach ( $terms as $term ) {
1681
				$term->formatted_name = '';
1682
1683 View Code Duplication
				if ( $term->parent ) {
1684
					$ancestors = array_reverse( get_ancestors( $term->term_id, 'product_cat' ) );
1685
					foreach ( $ancestors as $ancestor ) {
1686
						$ancestor_term = get_term( $ancestor, 'product_cat' );
1687
						if ( $ancestor_term ) {
1688
							$term->formatted_name .= $ancestor_term->name . ' > ';
1689
						}
1690
					}
1691
				}
1692
1693
				$term->formatted_name              .= $term->name . ' (' . $term->count . ')';
1694
				$found_categories[ $term->term_id ] = $term;
1695
			}
1696
		}
1697
1698
		wp_send_json( apply_filters( 'woocommerce_json_search_found_categories', $found_categories ) );
1699
	}
1700
1701
	/**
1702
	 * Ajax request handling for categories ordering.
1703
	 */
1704
	public static function term_ordering() {
1705
		// phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification
1706
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
1707
			wp_die( -1 );
1708
		}
1709
1710
		$id       = (int) $_POST['id'];
1711
		$next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
1712
		$taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( wp_unslash( $_POST['thetaxonomy'] ) ) : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1713
		$term     = get_term_by( 'id', $id, $taxonomy );
1714
1715
		if ( ! $id || ! $term || ! $taxonomy ) {
1716
			wp_die( 0 );
1717
		}
1718
1719
		wc_reorder_terms( $term, $next_id, $taxonomy );
1720
1721
		$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
1722
1723
		if ( $term && count( $children ) ) {
1724
			echo 'children';
1725
			wp_die();
1726
		}
1727
		// phpcs:enable
1728
	}
1729
1730
	/**
1731
	 * Ajax request handling for product ordering.
1732
	 *
1733
	 * Based on Simple Page Ordering by 10up (https://wordpress.org/plugins/simple-page-ordering/).
1734
	 */
1735
	public static function product_ordering() {
1736
		global $wpdb;
1737
1738
		// phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification
1739
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
1740
			wp_die( -1 );
1741
		}
1742
1743
		$sorting_id  = absint( $_POST['id'] );
1744
		$previd      = absint( isset( $_POST['previd'] ) ? $_POST['previd'] : 0 );
1745
		$nextid      = absint( isset( $_POST['nextid'] ) ? $_POST['nextid'] : 0 );
1746
		$menu_orders = wp_list_pluck( $wpdb->get_results( "SELECT ID, menu_order FROM {$wpdb->posts} WHERE post_type = 'product' ORDER BY menu_order ASC, post_title ASC" ), 'menu_order', 'ID' );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1747
		$index       = 0;
1748
1749
		foreach ( $menu_orders as $id => $menu_order ) {
1750
			$id = absint( $id );
1751
1752
			if ( $sorting_id === $id ) {
1753
				continue;
1754
			}
1755
			if ( $nextid === $id ) {
1756
				$index ++;
1757
			}
1758
			$index ++;
1759
			$menu_orders[ $id ] = $index;
1760
			$wpdb->update( $wpdb->posts, array( 'menu_order' => $index ), array( 'ID' => $id ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
1761
1762
			/**
1763
			 * When a single product has gotten it's ordering updated.
1764
			 * $id The product ID
1765
			 * $index The new menu order
1766
			*/
1767
			do_action( 'woocommerce_after_single_product_ordering', $id, $index );
1768
		}
1769
1770
		if ( isset( $menu_orders[ $previd ] ) ) {
1771
			$menu_orders[ $sorting_id ] = $menu_orders[ $previd ] + 1;
1772
		} elseif ( isset( $menu_orders[ $nextid ] ) ) {
1773
			$menu_orders[ $sorting_id ] = $menu_orders[ $nextid ] - 1;
1774
		} else {
1775
			$menu_orders[ $sorting_id ] = 0;
1776
		}
1777
1778
		$wpdb->update( $wpdb->posts, array( 'menu_order' => $menu_orders[ $sorting_id ] ), array( 'ID' => $sorting_id ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
1779
1780
		do_action( 'woocommerce_after_product_ordering', $sorting_id, $menu_orders );
1781
		wp_send_json( $menu_orders );
1782
		// phpcs:enable
1783
	}
1784
1785
	/**
1786
	 * Handle a refund via the edit order screen.
1787
	 *
1788
	 * @throws Exception To return errors.
1789
	 */
1790
	public static function refund_line_items() {
1791
		ob_start();
1792
1793
		check_ajax_referer( 'order-item', 'security' );
1794
1795
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1796
			wp_die( -1 );
1797
		}
1798
1799
		$order_id               = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
1800
		$refund_amount          = isset( $_POST['refund_amount'] ) ? wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['refund_amount'] ) ), wc_get_price_decimals() ) : 0;
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1801
		$refunded_amount        = isset( $_POST['refunded_amount'] ) ? wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['refunded_amount'] ) ), wc_get_price_decimals() ) : 0;
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1802
		$refund_reason          = isset( $_POST['refund_reason'] ) ? sanitize_text_field( wp_unslash( $_POST['refund_reason'] ) ) : '';
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1803
		$line_item_qtys         = isset( $_POST['line_item_qtys'] ) ? json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_qtys'] ) ), true ) : array();
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1804
		$line_item_totals       = isset( $_POST['line_item_totals'] ) ? json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_totals'] ) ), true ) : array();
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1805
		$line_item_tax_totals   = isset( $_POST['line_item_tax_totals'] ) ? json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_tax_totals'] ) ), true ) : array();
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1806
		$api_refund             = isset( $_POST['api_refund'] ) && 'true' === $_POST['api_refund'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1807
		$restock_refunded_items = isset( $_POST['restock_refunded_items'] ) && 'true' === $_POST['restock_refunded_items'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1808
		$refund                 = false;
1809
		$response               = array();
1810
1811
		try {
1812
			$order       = wc_get_order( $order_id );
1813
			$order_items = $order->get_items();
0 ignored issues
show
Unused Code introduced by
$order_items 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...
1814
			$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded(), wc_get_price_decimals() );
1815
1816
			if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) {
1817
				throw new Exception( __( 'Invalid refund amount', 'woocommerce' ) );
1818
			}
1819
1820
			if ( wc_format_decimal( $order->get_total_refunded(), wc_get_price_decimals() ) !== $refunded_amount ) {
1821
				throw new Exception( __( 'Error processing refund. Please try again.', 'woocommerce' ) );
1822
			}
1823
1824
			// Prepare line items which we are refunding.
1825
			$line_items = array();
1826
			$item_ids   = array_unique( array_merge( array_keys( $line_item_qtys ), array_keys( $line_item_totals ) ) );
1827
1828
			foreach ( $item_ids as $item_id ) {
1829
				$line_items[ $item_id ] = array(
1830
					'qty'          => 0,
1831
					'refund_total' => 0,
1832
					'refund_tax'   => array(),
1833
				);
1834
			}
1835
			foreach ( $line_item_qtys as $item_id => $qty ) {
1836
				$line_items[ $item_id ]['qty'] = max( $qty, 0 );
1837
			}
1838
			foreach ( $line_item_totals as $item_id => $total ) {
1839
				$line_items[ $item_id ]['refund_total'] = wc_format_decimal( $total );
1840
			}
1841
			foreach ( $line_item_tax_totals as $item_id => $tax_totals ) {
1842
				$line_items[ $item_id ]['refund_tax'] = array_filter( array_map( 'wc_format_decimal', $tax_totals ) );
1843
			}
1844
1845
			// Create the refund object.
1846
			$refund = wc_create_refund(
1847
				array(
1848
					'amount'         => $refund_amount,
1849
					'reason'         => $refund_reason,
1850
					'order_id'       => $order_id,
1851
					'line_items'     => $line_items,
1852
					'refund_payment' => $api_refund,
1853
					'restock_items'  => $restock_refunded_items,
1854
				)
1855
			);
1856
1857
			if ( is_wp_error( $refund ) ) {
1858
				throw new Exception( $refund->get_error_message() );
1859
			}
1860
1861
			if ( did_action( 'woocommerce_order_fully_refunded' ) ) {
1862
				$response['status'] = 'fully_refunded';
1863
			}
1864
		} catch ( Exception $e ) {
1865
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1866
		}
1867
1868
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1869
		wp_send_json_success( $response );
1870
	}
1871
1872
	/**
1873
	 * Delete a refund.
1874
	 */
1875
	public static function delete_refund() {
1876
		check_ajax_referer( 'order-item', 'security' );
1877
1878
		if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['refund_id'] ) ) {
1879
			wp_die( -1 );
1880
		}
1881
1882
		$refund_ids = array_map( 'absint', is_array( $_POST['refund_id'] ) ? wp_unslash( $_POST['refund_id'] ) : array( wp_unslash( $_POST['refund_id'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1883
		foreach ( $refund_ids as $refund_id ) {
1884
			if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
1885
				$refund   = wc_get_order( $refund_id );
1886
				$order_id = $refund->get_parent_id();
1887
				$refund->delete( true );
1888
				do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
1889
			}
1890
		}
1891
		wp_die();
1892
	}
1893
1894
	/**
1895
	 * Triggered when clicking the rating footer.
1896
	 */
1897
	public static function rated() {
1898
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
1899
			wp_die( -1 );
1900
		}
1901
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
1902
		wp_die();
1903
	}
1904
1905
	/**
1906
	 * Create/Update API key.
1907
	 *
1908
	 * @throws Exception On invalid or empty description, user, or permissions.
1909
	 */
1910
	public static function update_api_key() {
1911
		ob_start();
1912
1913
		global $wpdb;
1914
1915
		check_ajax_referer( 'update-api-key', 'security' );
1916
1917
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
1918
			wp_die( -1 );
1919
		}
1920
1921
		$response = array();
1922
1923
		try {
1924
			if ( empty( $_POST['description'] ) ) {
1925
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
1926
			}
1927
			if ( empty( $_POST['user'] ) ) {
1928
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
1929
			}
1930
			if ( empty( $_POST['permissions'] ) ) {
1931
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
1932
			}
1933
1934
			$key_id      = isset( $_POST['key_id'] ) ? absint( $_POST['key_id'] ) : 0;
1935
			$description = sanitize_text_field( wp_unslash( $_POST['description'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1936
			$permissions = ( in_array( wp_unslash( $_POST['permissions'] ), array( 'read', 'write', 'read_write' ), true ) ) ? sanitize_text_field( wp_unslash( $_POST['permissions'] ) ) : 'read';
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1937
			$user_id     = absint( $_POST['user'] );
1938
1939
			// Check if current user can edit other users.
1940
			if ( $user_id && ! current_user_can( 'edit_user', $user_id ) ) {
1941
				if ( get_current_user_id() !== $user_id ) {
1942
					throw new Exception( __( 'You do not have permission to assign API Keys to the selected user.', 'woocommerce' ) );
1943
				}
1944
			}
1945
1946
			if ( 0 < $key_id ) {
1947
				$data = array(
1948
					'user_id'     => $user_id,
1949
					'description' => $description,
1950
					'permissions' => $permissions,
1951
				);
1952
1953
				$wpdb->update(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
1954
					$wpdb->prefix . 'woocommerce_api_keys',
1955
					$data,
1956
					array( 'key_id' => $key_id ),
1957
					array(
1958
						'%d',
1959
						'%s',
1960
						'%s',
1961
					),
1962
					array( '%d' )
1963
				);
1964
1965
				$response                    = $data;
1966
				$response['consumer_key']    = '';
1967
				$response['consumer_secret'] = '';
1968
				$response['message']         = __( 'API Key updated successfully.', 'woocommerce' );
1969
			} else {
1970
				$consumer_key    = 'ck_' . wc_rand_hash();
1971
				$consumer_secret = 'cs_' . wc_rand_hash();
1972
1973
				$data = array(
1974
					'user_id'         => $user_id,
1975
					'description'     => $description,
1976
					'permissions'     => $permissions,
1977
					'consumer_key'    => wc_api_hash( $consumer_key ),
1978
					'consumer_secret' => $consumer_secret,
1979
					'truncated_key'   => substr( $consumer_key, -7 ),
1980
				);
1981
1982
				$wpdb->insert(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
1983
					$wpdb->prefix . 'woocommerce_api_keys',
1984
					$data,
1985
					array(
1986
						'%d',
1987
						'%s',
1988
						'%s',
1989
						'%s',
1990
						'%s',
1991
						'%s',
1992
					)
1993
				);
1994
1995
				$key_id                      = $wpdb->insert_id;
1996
				$response                    = $data;
1997
				$response['consumer_key']    = $consumer_key;
1998
				$response['consumer_secret'] = $consumer_secret;
1999
				$response['message']         = __( 'API Key generated successfully. Make sure to copy your new keys now as the secret key will be hidden once you leave this page.', 'woocommerce' );
2000
				$response['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=advanced&section=keys' ) ), 'revoke' ) ) . '">' . __( 'Revoke key', 'woocommerce' ) . '</a>';
2001
			}
2002
		} catch ( Exception $e ) {
2003
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
2004
		}
2005
2006
		// wp_send_json_success must be outside the try block not to break phpunit tests.
2007
		wp_send_json_success( $response );
2008
	}
2009
2010
	/**
2011
	 * Load variations via AJAX.
2012
	 */
2013
	public static function load_variations() {
2014
		ob_start();
2015
2016
		check_ajax_referer( 'load-variations', 'security' );
2017
2018
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) ) {
2019
			wp_die( -1 );
2020
		}
2021
2022
		// Set $post global so its available, like within the admin screens.
2023
		global $post;
2024
2025
		$loop           = 0;
2026
		$product_id     = absint( $_POST['product_id'] );
2027
		$post           = get_post( $product_id ); // phpcs:ignore
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
2028
		$product_object = wc_get_product( $product_id );
2029
		$per_page       = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
2030
		$page           = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
2031
		$variations     = wc_get_products(
2032
			array(
2033
				'status'  => array( 'private', 'publish' ),
2034
				'type'    => 'variation',
2035
				'parent'  => $product_id,
2036
				'limit'   => $per_page,
2037
				'page'    => $page,
2038
				'orderby' => array(
2039
					'menu_order' => 'ASC',
2040
					'ID'         => 'DESC',
2041
				),
2042
				'return'  => 'objects',
2043
			)
2044
		);
2045
2046
		if ( $variations ) {
2047
			foreach ( $variations as $variation_object ) {
2048
				$variation_id   = $variation_object->get_id();
2049
				$variation      = get_post( $variation_id );
2050
				$variation_data = array_merge( get_post_custom( $variation_id ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility.
2051
				include 'admin/meta-boxes/views/html-variation-admin.php';
2052
				$loop++;
2053
			}
2054
		}
2055
		wp_die();
2056
	}
2057
2058
	/**
2059
	 * Save variations via AJAX.
2060
	 */
2061
	public static function save_variations() {
2062
		ob_start();
2063
2064
		check_ajax_referer( 'save-variations', 'security' );
2065
2066
		// Check permissions again and make sure we have what we need.
2067 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST ) || empty( $_POST['product_id'] ) ) {
2068
			wp_die( -1 );
2069
		}
2070
2071
		$product_id                           = absint( $_POST['product_id'] );
2072
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
2073
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
2074
2075
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
2076
2077
		$errors = WC_Admin_Meta_Boxes::$meta_box_errors;
2078
2079
		if ( $errors ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors 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...
2080
			echo '<div class="error notice is-dismissible">';
2081
2082
			foreach ( $errors as $error ) {
2083
				echo '<p>' . wp_kses_post( $error ) . '</p>';
2084
			}
2085
2086
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
2087
			echo '</div>';
2088
2089
			delete_option( 'woocommerce_meta_box_errors' );
2090
		}
2091
2092
		wp_die();
2093
	}
2094
2095
	/**
2096
	 * Bulk action - Toggle Enabled.
2097
	 *
2098
	 * @param array $variations List of variations.
2099
	 * @param array $data Data to set.
2100
	 *
2101
	 * @used-by bulk_edit_variations
2102
	 */
2103
	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...
2104
		foreach ( $variations as $variation_id ) {
2105
			$variation = wc_get_product( $variation_id );
2106
			$variation->set_status( 'private' === $variation->get_status( 'edit' ) ? 'publish' : 'private' );
2107
			$variation->save();
2108
		}
2109
	}
2110
2111
	/**
2112
	 * Bulk action - Toggle Downloadable Checkbox.
2113
	 *
2114
	 * @param array $variations List of variations.
2115
	 * @param array $data Data to set.
2116
	 *
2117
	 * @used-by bulk_edit_variations
2118
	 */
2119
	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...
2120
		self::variation_bulk_toggle( $variations, 'downloadable' );
2121
	}
2122
2123
	/**
2124
	 * Bulk action - Toggle Virtual Checkbox.
2125
	 *
2126
	 * @param array $variations List of variations.
2127
	 * @param array $data Data to set.
2128
	 *
2129
	 * @used-by bulk_edit_variations
2130
	 */
2131
	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...
2132
		self::variation_bulk_toggle( $variations, 'virtual' );
2133
	}
2134
2135
	/**
2136
	 * Bulk action - Toggle Manage Stock Checkbox.
2137
	 *
2138
	 * @param array $variations List of variations.
2139
	 * @param array $data Data to set.
2140
	 *
2141
	 * @used-by bulk_edit_variations
2142
	 */
2143
	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...
2144
		self::variation_bulk_toggle( $variations, 'manage_stock' );
2145
	}
2146
2147
	/**
2148
	 * Bulk action - Set Regular Prices.
2149
	 *
2150
	 * @param array $variations List of variations.
2151
	 * @param array $data Data to set.
2152
	 *
2153
	 * @used-by bulk_edit_variations
2154
	 */
2155
	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...
2156
		self::variation_bulk_set( $variations, 'regular_price', $data['value'] );
2157
	}
2158
2159
	/**
2160
	 * Bulk action - Set Sale Prices.
2161
	 *
2162
	 * @param array $variations List of variations.
2163
	 * @param array $data Data to set.
2164
	 *
2165
	 * @used-by bulk_edit_variations
2166
	 */
2167
	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...
2168
		self::variation_bulk_set( $variations, 'sale_price', $data['value'] );
2169
	}
2170
2171
	/**
2172
	 * Bulk action - Set Stock Status as In Stock.
2173
	 *
2174
	 * @param array $variations List of variations.
2175
	 * @param array $data Data to set.
2176
	 *
2177
	 * @used-by bulk_edit_variations
2178
	 */
2179
	private static function variation_bulk_action_variable_stock_status_instock( $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...
2180
		self::variation_bulk_set( $variations, 'stock_status', 'instock' );
2181
	}
2182
2183
	/**
2184
	 * Bulk action - Set Stock Status as Out of Stock.
2185
	 *
2186
	 * @param array $variations List of variations.
2187
	 * @param array $data Data to set.
2188
	 *
2189
	 * @used-by bulk_edit_variations
2190
	 */
2191
	private static function variation_bulk_action_variable_stock_status_outofstock( $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...
2192
		self::variation_bulk_set( $variations, 'stock_status', 'outofstock' );
2193
	}
2194
2195
	/**
2196
	 * Bulk action - Set Stock Status as On Backorder.
2197
	 *
2198
	 * @param array $variations List of variations.
2199
	 * @param array $data Data to set.
2200
	 *
2201
	 * @used-by bulk_edit_variations
2202
	 */
2203
	private static function variation_bulk_action_variable_stock_status_onbackorder( $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...
2204
		self::variation_bulk_set( $variations, 'stock_status', 'onbackorder' );
2205
	}
2206
2207
	/**
2208
	 * Bulk action - Set Stock.
2209
	 *
2210
	 * @param array $variations List of variations.
2211
	 * @param array $data Data to set.
2212
	 *
2213
	 * @used-by bulk_edit_variations
2214
	 */
2215
	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...
2216
		if ( ! isset( $data['value'] ) ) {
2217
			return;
2218
		}
2219
2220
		$quantity = wc_stock_amount( wc_clean( $data['value'] ) );
0 ignored issues
show
Documentation introduced by
wc_clean($data['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...
2221
2222
		foreach ( $variations as $variation_id ) {
2223
			$variation = wc_get_product( $variation_id );
2224
			if ( $variation->managing_stock() ) {
2225
				$variation->set_stock_quantity( $quantity );
2226
			} else {
2227
				$variation->set_stock_quantity( null );
2228
			}
2229
			$variation->save();
2230
		}
2231
	}
2232
2233
	/**
2234
	 * Bulk action - Set Weight.
2235
	 *
2236
	 * @param array $variations List of variations.
2237
	 * @param array $data Data to set.
2238
	 *
2239
	 * @used-by bulk_edit_variations
2240
	 */
2241
	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...
2242
		self::variation_bulk_set( $variations, 'weight', $data['value'] );
2243
	}
2244
2245
	/**
2246
	 * Bulk action - Set Length.
2247
	 *
2248
	 * @param array $variations List of variations.
2249
	 * @param array $data Data to set.
2250
	 *
2251
	 * @used-by bulk_edit_variations
2252
	 */
2253
	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...
2254
		self::variation_bulk_set( $variations, 'length', $data['value'] );
2255
	}
2256
2257
	/**
2258
	 * Bulk action - Set Width.
2259
	 *
2260
	 * @param array $variations List of variations.
2261
	 * @param array $data Data to set.
2262
	 *
2263
	 * @used-by bulk_edit_variations
2264
	 */
2265
	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...
2266
		self::variation_bulk_set( $variations, 'width', $data['value'] );
2267
	}
2268
2269
	/**
2270
	 * Bulk action - Set Height.
2271
	 *
2272
	 * @param array $variations List of variations.
2273
	 * @param array $data Data to set.
2274
	 *
2275
	 * @used-by bulk_edit_variations
2276
	 */
2277
	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...
2278
		self::variation_bulk_set( $variations, 'height', $data['value'] );
2279
	}
2280
2281
	/**
2282
	 * Bulk action - Set Download Limit.
2283
	 *
2284
	 * @param array $variations List of variations.
2285
	 * @param array $data Data to set.
2286
	 *
2287
	 * @used-by bulk_edit_variations
2288
	 */
2289
	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...
2290
		self::variation_bulk_set( $variations, 'download_limit', $data['value'] );
2291
	}
2292
2293
	/**
2294
	 * Bulk action - Set Download Expiry.
2295
	 *
2296
	 * @param array $variations List of variations.
2297
	 * @param array $data Data to set.
2298
	 *
2299
	 * @used-by bulk_edit_variations
2300
	 */
2301
	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...
2302
		self::variation_bulk_set( $variations, 'download_expiry', $data['value'] );
2303
	}
2304
2305
	/**
2306
	 * Bulk action - Delete all.
2307
	 *
2308
	 * @param array $variations List of variations.
2309
	 * @param array $data Data to set.
2310
	 *
2311
	 * @used-by bulk_edit_variations
2312
	 */
2313
	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...
2314
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2315
			foreach ( $variations as $variation_id ) {
2316
				$variation = wc_get_product( $variation_id );
2317
				$variation->delete( true );
2318
			}
2319
		}
2320
	}
2321
2322
	/**
2323
	 * Bulk action - Sale Schedule.
2324
	 *
2325
	 * @param array $variations List of variations.
2326
	 * @param array $data Data to set.
2327
	 *
2328
	 * @used-by bulk_edit_variations
2329
	 */
2330
	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...
2331
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2332
			return;
2333
		}
2334
2335
		foreach ( $variations as $variation_id ) {
2336
			$variation = wc_get_product( $variation_id );
2337
2338
			if ( 'false' !== $data['date_from'] ) {
2339
				$variation->set_date_on_sale_from( wc_clean( $data['date_from'] ) );
2340
			}
2341
2342
			if ( 'false' !== $data['date_to'] ) {
2343
				$variation->set_date_on_sale_to( wc_clean( $data['date_to'] ) );
2344
			}
2345
2346
			$variation->save();
2347
		}
2348
	}
2349
2350
	/**
2351
	 * Bulk action - Increase Regular Prices.
2352
	 *
2353
	 * @param array $variations List of variations.
2354
	 * @param array $data Data to set.
2355
	 *
2356
	 * @used-by bulk_edit_variations
2357
	 */
2358
	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...
2359
		self::variation_bulk_adjust_price( $variations, 'regular_price', '+', wc_clean( $data['value'] ) );
0 ignored issues
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...
2360
	}
2361
2362
	/**
2363
	 * Bulk action - Decrease Regular Prices.
2364
	 *
2365
	 * @param array $variations List of variations.
2366
	 * @param array $data Data to set.
2367
	 *
2368
	 * @used-by bulk_edit_variations
2369
	 */
2370
	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...
2371
		self::variation_bulk_adjust_price( $variations, 'regular_price', '-', wc_clean( $data['value'] ) );
0 ignored issues
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...
2372
	}
2373
2374
	/**
2375
	 * Bulk action - Increase Sale Prices.
2376
	 *
2377
	 * @param array $variations List of variations.
2378
	 * @param array $data Data to set.
2379
	 *
2380
	 * @used-by bulk_edit_variations
2381
	 */
2382
	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...
2383
		self::variation_bulk_adjust_price( $variations, 'sale_price', '+', wc_clean( $data['value'] ) );
0 ignored issues
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...
2384
	}
2385
2386
	/**
2387
	 * Bulk action - Decrease Sale Prices.
2388
	 *
2389
	 * @param array $variations List of variations.
2390
	 * @param array $data Data to set.
2391
	 *
2392
	 * @used-by bulk_edit_variations
2393
	 */
2394
	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...
2395
		self::variation_bulk_adjust_price( $variations, 'sale_price', '-', wc_clean( $data['value'] ) );
0 ignored issues
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...
2396
	}
2397
2398
	/**
2399
	 * Bulk action - Set Price.
2400
	 *
2401
	 * @param array  $variations List of variations.
2402
	 * @param string $field price being adjusted _regular_price or _sale_price.
2403
	 * @param string $operator + or -.
2404
	 * @param string $value Price or Percent.
2405
	 *
2406
	 * @used-by bulk_edit_variations
2407
	 */
2408
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2409
		foreach ( $variations as $variation_id ) {
2410
			$variation   = wc_get_product( $variation_id );
2411
			$field_value = $variation->{"get_$field"}( 'edit' );
2412
2413
			if ( '%' === substr( $value, -1 ) ) {
2414
				$percent      = wc_format_decimal( substr( $value, 0, -1 ) );
2415
				$field_value += round( ( $field_value / 100 ) * $percent, wc_get_price_decimals() ) * "{$operator}1";
2416
			} else {
2417
				$field_value += $value * "{$operator}1";
2418
			}
2419
2420
			$variation->{"set_$field"}( $field_value );
2421
			$variation->save();
2422
		}
2423
	}
2424
2425
	/**
2426
	 * Bulk set convenience function.
2427
	 *
2428
	 * @param array  $variations List of variations.
2429
	 * @param string $field Field to set.
2430
	 * @param string $value to set.
2431
	 */
2432
	private static function variation_bulk_set( $variations, $field, $value ) {
2433
		foreach ( $variations as $variation_id ) {
2434
			$variation = wc_get_product( $variation_id );
2435
			$variation->{ "set_$field" }( wc_clean( $value ) );
2436
			$variation->save();
2437
		}
2438
	}
2439
2440
	/**
2441
	 * Bulk toggle convenience function.
2442
	 *
2443
	 * @param array  $variations List of variations.
2444
	 * @param string $field Field to toggle.
2445
	 */
2446
	private static function variation_bulk_toggle( $variations, $field ) {
2447
		foreach ( $variations as $variation_id ) {
2448
			$variation  = wc_get_product( $variation_id );
2449
			$prev_value = $variation->{ "get_$field" }( 'edit' );
2450
			$variation->{ "set_$field" }( ! $prev_value );
2451
			$variation->save();
2452
		}
2453
	}
2454
2455
	/**
2456
	 * Bulk edit variations via AJAX.
2457
	 *
2458
	 * @uses WC_AJAX::variation_bulk_set()
2459
	 * @uses WC_AJAX::variation_bulk_adjust_price()
2460
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price_decrease()
2461
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price_increase()
2462
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price_decrease()
2463
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price_increase()
2464
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_schedule()
2465
	 * @uses WC_AJAX::variation_bulk_action_delete_all()
2466
	 * @uses WC_AJAX::variation_bulk_action_variable_download_expiry()
2467
	 * @uses WC_AJAX::variation_bulk_action_variable_download_limit()
2468
	 * @uses WC_AJAX::variation_bulk_action_variable_height()
2469
	 * @uses WC_AJAX::variation_bulk_action_variable_width()
2470
	 * @uses WC_AJAX::variation_bulk_action_variable_length()
2471
	 * @uses WC_AJAX::variation_bulk_action_variable_weight()
2472
	 * @uses WC_AJAX::variation_bulk_action_variable_stock()
2473
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price()
2474
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price()
2475
	 * @uses WC_AJAX::variation_bulk_action_toggle_manage_stock()
2476
	 * @uses WC_AJAX::variation_bulk_action_toggle_virtual()
2477
	 * @uses WC_AJAX::variation_bulk_action_toggle_downloadable()
2478
	 * @uses WC_AJAX::variation_bulk_action_toggle_enabled
2479
	 */
2480
	public static function bulk_edit_variations() {
2481
		ob_start();
2482
2483
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2484
2485
		// Check permissions again and make sure we have what we need.
2486 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) || empty( $_POST['bulk_action'] ) ) {
2487
			wp_die( -1 );
2488
		}
2489
2490
		$product_id  = absint( $_POST['product_id'] );
2491
		$bulk_action = wc_clean( wp_unslash( $_POST['bulk_action'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2492
		$data        = ! empty( $_POST['data'] ) ? wc_clean( wp_unslash( $_POST['data'] ) ) : array();
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2493
		$variations  = array();
2494
2495
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2496
			$variations = get_posts(
2497
				array(
2498
					'post_parent'    => $product_id,
2499
					'posts_per_page' => -1,
0 ignored issues
show
introduced by
Disabling pagination is prohibited in VIP context, do not set posts_per_page to -1 ever.
Loading history...
2500
					'post_type'      => 'product_variation',
2501
					'fields'         => 'ids',
2502
					'post_status'    => array( 'publish', 'private' ),
2503
				)
2504
			);
2505
		}
2506
2507
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2508
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2509
		} else {
2510
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2511
		}
2512
2513
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2514
		WC_Product_Variable::sync( $product_id );
2515
		wc_delete_product_transients( $product_id );
2516
		wp_die();
2517
	}
2518
2519
	/**
2520
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2521
	 */
2522
	public static function tax_rates_save_changes() {
2523
		// phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification
2524
		if ( ! isset( $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2525
			wp_send_json_error( 'missing_fields' );
2526
			wp_die();
2527
		}
2528
2529
		$current_class = ! empty( $_POST['current_class'] ) ? wp_unslash( $_POST['current_class'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2530
2531
		if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_tax_nonce'] ), 'wc_tax_nonce-class:' . $current_class ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2532
			wp_send_json_error( 'bad_nonce' );
2533
			wp_die();
2534
		}
2535
2536
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
2537
2538
		// Check User Caps.
2539
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2540
			wp_send_json_error( 'missing_capabilities' );
2541
			wp_die();
2542
		}
2543
2544
		$changes = wp_unslash( $_POST['changes'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2545
		foreach ( $changes as $tax_rate_id => $data ) {
2546
			if ( isset( $data['deleted'] ) ) {
2547
				if ( isset( $data['newRow'] ) ) {
2548
					// So the user added and deleted a new row.
2549
					// That's fine, it's not in the database anyways. NEXT!
2550
					continue;
2551
				}
2552
				WC_Tax::_delete_tax_rate( $tax_rate_id );
2553
			}
2554
2555
			$tax_rate = array_intersect_key(
2556
				$data,
2557
				array(
2558
					'tax_rate_country'  => 1,
2559
					'tax_rate_state'    => 1,
2560
					'tax_rate'          => 1,
2561
					'tax_rate_name'     => 1,
2562
					'tax_rate_priority' => 1,
2563
					'tax_rate_compound' => 1,
2564
					'tax_rate_shipping' => 1,
2565
					'tax_rate_order'    => 1,
2566
				)
2567
			);
2568
2569
			if ( isset( $tax_rate['tax_rate'] ) ) {
2570
				$tax_rate['tax_rate'] = wc_format_decimal( $tax_rate['tax_rate'] );
2571
			}
2572
2573
			if ( isset( $data['newRow'] ) ) {
2574
				$tax_rate['tax_rate_class'] = $current_class;
2575
				$tax_rate_id                = WC_Tax::_insert_tax_rate( $tax_rate );
2576
			} elseif ( ! empty( $tax_rate ) ) {
2577
				WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
2578
			}
2579
2580
			if ( isset( $data['postcode'] ) ) {
2581
				$postcode = array_map( 'wc_clean', $data['postcode'] );
2582
				$postcode = array_map( 'wc_normalize_postcode', $postcode );
2583
				WC_Tax::_update_tax_rate_postcodes( $tax_rate_id, $postcode );
0 ignored issues
show
Documentation introduced by
$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...
2584
			}
2585
			if ( isset( $data['city'] ) ) {
2586
				WC_Tax::_update_tax_rate_cities( $tax_rate_id, array_map( 'wc_clean', array_map( 'wp_unslash', $data['city'] ) ) );
0 ignored issues
show
Documentation introduced by
array_map('wc_clean', ar...slash', $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...
2587
			}
2588
		}
2589
2590
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
2591
		WC_Cache_Helper::get_transient_version( 'shipping', true );
2592
2593
		wp_send_json_success(
2594
			array(
2595
				'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
2596
			)
2597
		);
2598
		// phpcs:enable
2599
	}
2600
2601
	/**
2602
	 * Handle submissions from assets/js/wc-shipping-zones.js Backbone model.
2603
	 */
2604
	public static function shipping_zones_save_changes() {
2605
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['changes'] ) ) {
2606
			wp_send_json_error( 'missing_fields' );
2607
			wp_die();
2608
		}
2609
2610
		if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_shipping_zones_nonce'] ), 'wc_shipping_zones_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2611
			wp_send_json_error( 'bad_nonce' );
2612
			wp_die();
2613
		}
2614
2615
		// Check User Caps.
2616
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2617
			wp_send_json_error( 'missing_capabilities' );
2618
			wp_die();
2619
		}
2620
2621
		$changes = wp_unslash( $_POST['changes'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2622
		foreach ( $changes as $zone_id => $data ) {
2623
			if ( isset( $data['deleted'] ) ) {
2624
				if ( isset( $data['newRow'] ) ) {
2625
					// So the user added and deleted a new row.
2626
					// That's fine, it's not in the database anyways. NEXT!
2627
					continue;
2628
				}
2629
				WC_Shipping_Zones::delete_zone( $zone_id );
2630
				continue;
2631
			}
2632
2633
			$zone_data = array_intersect_key(
2634
				$data,
2635
				array(
2636
					'zone_id'    => 1,
2637
					'zone_order' => 1,
2638
				)
2639
			);
2640
2641
			if ( isset( $zone_data['zone_id'] ) ) {
2642
				$zone = new WC_Shipping_Zone( $zone_data['zone_id'] );
2643
2644
				if ( isset( $zone_data['zone_order'] ) ) {
2645
					$zone->set_zone_order( $zone_data['zone_order'] );
2646
				}
2647
2648
				$zone->save();
2649
			}
2650
		}
2651
2652
		wp_send_json_success(
2653
			array(
2654
				'zones' => WC_Shipping_Zones::get_zones( 'json' ),
2655
			)
2656
		);
2657
	}
2658
2659
	/**
2660
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
2661
	 */
2662
	public static function shipping_zone_add_method() {
2663 View Code Duplication
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['method_id'] ) ) {
2664
			wp_send_json_error( 'missing_fields' );
2665
			wp_die();
2666
		}
2667
2668
		if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_shipping_zones_nonce'] ), 'wc_shipping_zones_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2669
			wp_send_json_error( 'bad_nonce' );
2670
			wp_die();
2671
		}
2672
2673
		// Check User Caps.
2674
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2675
			wp_send_json_error( 'missing_capabilities' );
2676
			wp_die();
2677
		}
2678
2679
		$zone_id     = wc_clean( wp_unslash( $_POST['zone_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2680
		$zone        = new WC_Shipping_Zone( $zone_id );
2681
		$instance_id = $zone->add_shipping_method( wc_clean( wp_unslash( $_POST['method_id'] ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2682
2683
		wp_send_json_success(
2684
			array(
2685
				'instance_id' => $instance_id,
2686
				'zone_id'     => $zone->get_id(),
2687
				'zone_name'   => $zone->get_zone_name(),
2688
				'methods'     => $zone->get_shipping_methods( false, 'json' ),
2689
			)
2690
		);
2691
	}
2692
2693
	/**
2694
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
2695
	 */
2696
	public static function shipping_zone_methods_save_changes() {
2697 View Code Duplication
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['changes'] ) ) {
2698
			wp_send_json_error( 'missing_fields' );
2699
			wp_die();
2700
		}
2701
2702
		if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_shipping_zones_nonce'] ), 'wc_shipping_zones_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2703
			wp_send_json_error( 'bad_nonce' );
2704
			wp_die();
2705
		}
2706
2707
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2708
			wp_send_json_error( 'missing_capabilities' );
2709
			wp_die();
2710
		}
2711
2712
		global $wpdb;
2713
2714
		$zone_id = wc_clean( wp_unslash( $_POST['zone_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2715
		$zone    = new WC_Shipping_Zone( $zone_id );
2716
		$changes = wp_unslash( $_POST['changes'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2717
2718
		if ( isset( $changes['zone_name'] ) ) {
2719
			$zone->set_zone_name( wc_clean( $changes['zone_name'] ) );
2720
		}
2721
2722
		if ( isset( $changes['zone_locations'] ) ) {
2723
			$zone->clear_locations( array( 'state', 'country', 'continent' ) );
2724
			$locations = array_filter( array_map( 'wc_clean', (array) $changes['zone_locations'] ) );
2725
			foreach ( $locations as $location ) {
2726
				// Each posted location will be in the format type:code.
2727
				$location_parts = explode( ':', $location );
2728
				switch ( $location_parts[0] ) {
2729
					case 'state':
2730
						$zone->add_location( $location_parts[1] . ':' . $location_parts[2], 'state' );
2731
						break;
2732
					case 'country':
2733
						$zone->add_location( $location_parts[1], 'country' );
2734
						break;
2735
					case 'continent':
2736
						$zone->add_location( $location_parts[1], 'continent' );
2737
						break;
2738
				}
2739
			}
2740
		}
2741
2742
		if ( isset( $changes['zone_postcodes'] ) ) {
2743
			$zone->clear_locations( 'postcode' );
2744
			$postcodes = array_filter( array_map( 'strtoupper', array_map( 'wc_clean', explode( "\n", $changes['zone_postcodes'] ) ) ) );
2745
			foreach ( $postcodes as $postcode ) {
2746
				$zone->add_location( $postcode, 'postcode' );
2747
			}
2748
		}
2749
2750
		if ( isset( $changes['methods'] ) ) {
2751
			foreach ( $changes['methods'] as $instance_id => $data ) {
2752
				$method_id = $wpdb->get_var( $wpdb->prepare( "SELECT method_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE instance_id = %d", $instance_id ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
2753
2754
				if ( isset( $data['deleted'] ) ) {
2755
					$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
2756
					$option_key      = $shipping_method->get_instance_option_key();
2757
					if ( $wpdb->delete( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'instance_id' => $instance_id ) ) ) {
2758
						delete_option( $option_key );
2759
						do_action( 'woocommerce_shipping_zone_method_deleted', $instance_id, $method_id, $zone_id );
2760
					}
2761
					continue;
2762
				}
2763
2764
				$method_data = array_intersect_key(
2765
					$data,
2766
					array(
2767
						'method_order' => 1,
2768
						'enabled'      => 1,
2769
					)
2770
				);
2771
2772
				if ( isset( $method_data['method_order'] ) ) {
2773
					$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'method_order' => absint( $method_data['method_order'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
2774
				}
2775
2776
				if ( isset( $method_data['enabled'] ) ) {
2777
					$is_enabled = absint( 'yes' === $method_data['enabled'] );
2778
					if ( $wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'is_enabled' => $is_enabled ), array( 'instance_id' => absint( $instance_id ) ) ) ) {
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
2779
						do_action( 'woocommerce_shipping_zone_method_status_toggled', $instance_id, $method_id, $zone_id, $is_enabled );
2780
					}
2781
				}
2782
			}
2783
		}
2784
2785
		$zone->save();
2786
2787
		wp_send_json_success(
2788
			array(
2789
				'zone_id'   => $zone->get_id(),
2790
				'zone_name' => $zone->get_zone_name(),
2791
				'methods'   => $zone->get_shipping_methods( false, 'json' ),
2792
			)
2793
		);
2794
	}
2795
2796
	/**
2797
	 * Save method settings
2798
	 */
2799
	public static function shipping_zone_methods_save_settings() {
2800
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['instance_id'], $_POST['data'] ) ) {
2801
			wp_send_json_error( 'missing_fields' );
2802
			wp_die();
2803
		}
2804
2805
		if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_shipping_zones_nonce'] ), 'wc_shipping_zones_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2806
			wp_send_json_error( 'bad_nonce' );
2807
			wp_die();
2808
		}
2809
2810
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2811
			wp_send_json_error( 'missing_capabilities' );
2812
			wp_die();
2813
		}
2814
2815
		$instance_id     = absint( $_POST['instance_id'] );
2816
		$zone            = WC_Shipping_Zones::get_zone_by( 'instance_id', $instance_id );
2817
		$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
2818
		$shipping_method->set_post_data( wp_unslash( $_POST['data'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2819
		$shipping_method->process_admin_options();
2820
2821
		WC_Cache_Helper::get_transient_version( 'shipping', true );
2822
2823
		wp_send_json_success(
2824
			array(
2825
				'zone_id'   => $zone->get_id(),
2826
				'zone_name' => $zone->get_zone_name(),
2827
				'methods'   => $zone->get_shipping_methods( false, 'json' ),
2828
				'errors'    => $shipping_method->get_errors(),
2829
			)
2830
		);
2831
	}
2832
2833
	/**
2834
	 * Handle submissions from assets/js/wc-shipping-classes.js Backbone model.
2835
	 */
2836
	public static function shipping_classes_save_changes() {
2837
		if ( ! isset( $_POST['wc_shipping_classes_nonce'], $_POST['changes'] ) ) {
2838
			wp_send_json_error( 'missing_fields' );
2839
			wp_die();
2840
		}
2841
2842
		if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_shipping_classes_nonce'] ), 'wc_shipping_classes_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2843
			wp_send_json_error( 'bad_nonce' );
2844
			wp_die();
2845
		}
2846
2847
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2848
			wp_send_json_error( 'missing_capabilities' );
2849
			wp_die();
2850
		}
2851
2852
		$changes = wp_unslash( $_POST['changes'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2853
2854
		foreach ( $changes as $term_id => $data ) {
2855
			$term_id = absint( $term_id );
2856
2857
			if ( isset( $data['deleted'] ) ) {
2858
				if ( isset( $data['newRow'] ) ) {
2859
					// So the user added and deleted a new row.
2860
					// That's fine, it's not in the database anyways. NEXT!
2861
					continue;
2862
				}
2863
				wp_delete_term( $term_id, 'product_shipping_class' );
2864
				continue;
2865
			}
2866
2867
			$update_args = array();
2868
2869
			if ( isset( $data['name'] ) ) {
2870
				$update_args['name'] = wc_clean( $data['name'] );
2871
			}
2872
2873
			if ( isset( $data['slug'] ) ) {
2874
				$update_args['slug'] = wc_clean( $data['slug'] );
2875
			}
2876
2877
			if ( isset( $data['description'] ) ) {
2878
				$update_args['description'] = wc_clean( $data['description'] );
2879
			}
2880
2881
			if ( isset( $data['newRow'] ) ) {
2882
				$update_args = array_filter( $update_args );
2883
				if ( empty( $update_args['name'] ) ) {
2884
					continue;
2885
				}
2886
				$inserted_term = wp_insert_term( $update_args['name'], 'product_shipping_class', $update_args );
2887
				$term_id       = is_wp_error( $inserted_term ) ? 0 : $inserted_term['term_id'];
2888
			} else {
2889
				wp_update_term( $term_id, 'product_shipping_class', $update_args );
2890
			}
2891
2892
			do_action( 'woocommerce_shipping_classes_save_class', $term_id, $data );
2893
		}
2894
2895
		$wc_shipping = WC_Shipping::instance();
2896
2897
		wp_send_json_success(
2898
			array(
2899
				'shipping_classes' => $wc_shipping->get_shipping_classes(),
2900
			)
2901
		);
2902
	}
2903
2904
	/**
2905
	 * Toggle payment gateway on or off via AJAX.
2906
	 *
2907
	 * @since 3.4.0
2908
	 */
2909
	public static function toggle_gateway_enabled() {
2910
		if ( current_user_can( 'manage_woocommerce' ) && check_ajax_referer( 'woocommerce-toggle-payment-gateway-enabled', 'security' ) && isset( $_POST['gateway_id'] ) ) {
2911
			// Load gateways.
2912
			$payment_gateways = WC()->payment_gateways->payment_gateways();
2913
2914
			// Get posted gateway.
2915
			$gateway_id = wc_clean( wp_unslash( $_POST['gateway_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2916
2917
			foreach ( $payment_gateways as $gateway ) {
2918
				if ( ! in_array( $gateway_id, array( $gateway->id, sanitize_title( get_class( $gateway ) ) ), true ) ) {
2919
					continue;
2920
				}
2921
				$enabled = $gateway->get_option( 'enabled', 'no' );
2922
2923
				if ( ! wc_string_to_bool( $enabled ) ) {
2924
					if ( $gateway->needs_setup() ) {
2925
						wp_send_json_error( 'needs_setup' );
2926
						wp_die();
2927
					} else {
2928
						$gateway->update_option( 'enabled', 'yes' );
2929
					}
2930
				} else {
2931
					// Disable the gateway.
2932
					$gateway->update_option( 'enabled', 'no' );
2933
				}
2934
2935
				wp_send_json_success( ! wc_string_to_bool( $enabled ) );
2936
				wp_die();
2937
			}
2938
		}
2939
2940
		wp_send_json_error( 'invalid_gateway_id' );
2941
		wp_die();
2942
	}
2943
}
2944
2945
WC_AJAX::init();
2946