Completed
Push — master ( 527840...a5d62b )
by Mike
53:46 queued 43:33
created

WC_AJAX::update_order_review()   F

Complexity

Conditions 32
Paths 64

Size

Total Lines 100

Duplication

Lines 8
Ratio 8 %

Code Coverage

Tests 0
CRAP Score 1056

Importance

Changes 0
Metric Value
cc 32
nc 64
nop 0
dl 8
loc 100
ccs 0
cts 56
cp 0
crap 1056
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
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
		if ( ! empty( $_GET['wc-ajax'] ) ) {
41
			wc_maybe_define_constant( 'DOING_AJAX', true );
42
			wc_maybe_define_constant( 'WC_DOING_AJAX', true );
43
			if ( ! WP_DEBUG || ( WP_DEBUG && ! WP_DEBUG_DISPLAY ) ) {
44
				@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...
45
			}
46
			$GLOBALS['wpdb']->hide_errors();
47
		}
48
	}
49
50
	/**
51
	 * Send headers for WC Ajax Requests.
52
	 *
53
	 * @since 2.5.0
54
	 */
55
	private static function wc_ajax_headers() {
56
		send_origin_headers();
57
		@header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
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...
58
		@header( 'X-Robots-Tag: noindex' );
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...
59
		send_nosniff_header();
60
		wc_nocache_headers();
61
		status_header( 200 );
62
	}
63
64
	/**
65
	 * Check for WC Ajax request and fire action.
66
	 */
67
	public static function do_wc_ajax() {
68
		global $wp_query;
69
70
		if ( ! empty( $_GET['wc-ajax'] ) ) {
71
			$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...
72
		}
73
74
		$action = $wp_query->get( 'wc-ajax' );
75
76
		if ( $action ) {
77
			self::wc_ajax_headers();
78
			$action = sanitize_text_field( $action );
79
			do_action( 'wc_ajax_' . $action );
80
			wp_die();
81
		}
82
	}
83
84
	/**
85
	 * Hook in methods - uses WordPress ajax handlers (admin-ajax).
86
	 */
87
	public static function add_ajax_events() {
88
		// woocommerce_EVENT => nopriv.
89
		$ajax_events = array(
90
			'get_refreshed_fragments'                          => true,
91
			'apply_coupon'                                     => true,
92
			'remove_coupon'                                    => true,
93
			'update_shipping_method'                           => true,
94
			'get_cart_totals'                                  => true,
95
			'update_order_review'                              => true,
96
			'add_to_cart'                                      => true,
97
			'remove_from_cart'                                 => true,
98
			'checkout'                                         => true,
99
			'get_variation'                                    => true,
100
			'get_customer_location'                            => true,
101
			'feature_product'                                  => false,
102
			'mark_order_status'                                => false,
103
			'get_order_details'                                => false,
104
			'add_attribute'                                    => false,
105
			'add_new_attribute'                                => false,
106
			'remove_variation'                                 => false,
107
			'remove_variations'                                => false,
108
			'save_attributes'                                  => false,
109
			'add_variation'                                    => false,
110
			'link_all_variations'                              => false,
111
			'revoke_access_to_download'                        => false,
112
			'grant_access_to_download'                         => false,
113
			'get_customer_details'                             => false,
114
			'add_order_item'                                   => false,
115
			'add_order_fee'                                    => false,
116
			'add_order_shipping'                               => false,
117
			'add_order_tax'                                    => false,
118
			'add_coupon_discount'                              => false,
119
			'remove_order_coupon'                              => false,
120
			'remove_order_item'                                => false,
121
			'remove_order_tax'                                 => false,
122
			'reduce_order_item_stock'                          => false,
123
			'increase_order_item_stock'                        => false,
124
			'add_order_item_meta'                              => false,
125
			'remove_order_item_meta'                           => false,
126
			'calc_line_taxes'                                  => false,
127
			'save_order_items'                                 => false,
128
			'load_order_items'                                 => false,
129
			'add_order_note'                                   => false,
130
			'delete_order_note'                                => false,
131
			'json_search_products'                             => false,
132
			'json_search_products_and_variations'              => false,
133
			'json_search_downloadable_products_and_variations' => false,
134
			'json_search_customers'                            => false,
135
			'json_search_categories'                           => false,
136
			'term_ordering'                                    => false,
137
			'product_ordering'                                 => false,
138
			'refund_line_items'                                => false,
139
			'delete_refund'                                    => false,
140
			'rated'                                            => false,
141
			'update_api_key'                                   => false,
142
			'load_variations'                                  => false,
143
			'save_variations'                                  => false,
144
			'bulk_edit_variations'                             => false,
145
			'tax_rates_save_changes'                           => false,
146
			'shipping_zones_save_changes'                      => false,
147
			'shipping_zone_add_method'                         => false,
148
			'shipping_zone_methods_save_changes'               => false,
149
			'shipping_zone_methods_save_settings'              => false,
150
			'shipping_classes_save_changes'                    => false,
151
			'toggle_gateway_enabled'                           => false,
152
		);
153
154
		foreach ( $ajax_events as $ajax_event => $nopriv ) {
155
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
156
157
			if ( $nopriv ) {
158
				add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
159
160
				// WC AJAX can be used for frontend ajax requests.
161
				add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
162
			}
163
		}
164
	}
165
166
	/**
167
	 * Get a refreshed cart fragment, including the mini cart HTML.
168
	 */
169
	public static function get_refreshed_fragments() {
170
		ob_start();
171
172
		woocommerce_mini_cart();
173
174
		$mini_cart = ob_get_clean();
175
176
		$data = array(
177
			'fragments' => apply_filters(
178
				'woocommerce_add_to_cart_fragments', array(
179
					'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>',
180
				)
181
			),
182
			'cart_hash' => WC()->cart->get_cart_hash(),
183
		);
184
185
		wp_send_json( $data );
186
	}
187
188
	/**
189
	 * AJAX apply coupon on checkout page.
190
	 */
191
	public static function apply_coupon() {
192
193
		check_ajax_referer( 'apply-coupon', 'security' );
194
195
		if ( ! empty( $_POST['coupon_code'] ) ) {
196
			WC()->cart->add_discount( sanitize_text_field( wp_unslash( $_POST['coupon_code'] ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
197
		} else {
198
			wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
199
		}
200
201
		wc_print_notices();
202
		wp_die();
203
	}
204
205
	/**
206
	 * AJAX remove coupon on cart and checkout page.
207
	 */
208
	public static function remove_coupon() {
209
		check_ajax_referer( 'remove-coupon', 'security' );
210
211
		$coupon = isset( $_POST['coupon'] ) ? wc_clean( $_POST['coupon'] ) : false;
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
212
213
		if ( empty( $coupon ) ) {
214
			wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ), 'error' );
215
		} else {
216
			WC()->cart->remove_coupon( $coupon );
217
			wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
218
		}
219
220
		wc_print_notices();
221
		wp_die();
222
	}
223
224
	/**
225
	 * AJAX update shipping method on cart page.
226
	 */
227
	public static function update_shipping_method() {
228
		check_ajax_referer( 'update-shipping-method', 'security' );
229
230
		wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
231
232
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
233
234 View Code Duplication
		if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) {
235
			foreach ( $_POST['shipping_method'] as $i => $value ) {
236
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
237
			}
238
		}
239
240
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
241
242
		self::get_cart_totals();
243
	}
244
245
	/**
246
	 * AJAX receive updated cart_totals div.
247
	 */
248
	public static function get_cart_totals() {
249
		wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
250
		WC()->cart->calculate_totals();
251
		woocommerce_cart_totals();
252
		wp_die();
253
	}
254
255
	/**
256
	 * Session has expired.
257
	 */
258
	private static function update_order_review_expired() {
259
		wp_send_json(
260
			array(
261
				'fragments' => apply_filters(
262
					'woocommerce_update_order_review_fragments', array(
263
						'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>',
264
					)
265
				),
266
			)
267
		);
268
	}
269
270
	/**
271
	 * AJAX update order review on checkout.
272
	 */
273
	public static function update_order_review() {
274
		check_ajax_referer( 'update-order-review', 'security' );
275
276
		wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
277
278 View Code Duplication
		if ( WC()->cart->is_empty() && ! is_customize_preview() && apply_filters( 'woocommerce_checkout_update_order_review_expired', true ) ) {
279
			self::update_order_review_expired();
280
		}
281
282
		do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
283
284
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
285
286 View Code Duplication
		if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) {
287
			foreach ( $_POST['shipping_method'] as $i => $value ) {
288
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
289
			}
290
		}
291
292
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
293
		WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
294
		WC()->customer->set_props(
295
			array(
296
				'billing_country'   => isset( $_POST['country'] ) ? wp_unslash( $_POST['country'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
297
				'billing_state'     => isset( $_POST['state'] ) ? wp_unslash( $_POST['state'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
298
				'billing_postcode'  => isset( $_POST['postcode'] ) ? wp_unslash( $_POST['postcode'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
299
				'billing_city'      => isset( $_POST['city'] ) ? wp_unslash( $_POST['city'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
300
				'billing_address_1' => isset( $_POST['address'] ) ? wp_unslash( $_POST['address'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
301
				'billing_address_2' => isset( $_POST['address_2'] ) ? wp_unslash( $_POST['address_2'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
302
			)
303
		);
304
305
		if ( wc_ship_to_billing_address_only() ) {
306
			WC()->customer->set_props(
307
				array(
308
					'shipping_country'   => isset( $_POST['country'] ) ? wp_unslash( $_POST['country'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
309
					'shipping_state'     => isset( $_POST['state'] ) ? wp_unslash( $_POST['state'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
310
					'shipping_postcode'  => isset( $_POST['postcode'] ) ? wp_unslash( $_POST['postcode'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
311
					'shipping_city'      => isset( $_POST['city'] ) ? wp_unslash( $_POST['city'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
312
					'shipping_address_1' => isset( $_POST['address'] ) ? wp_unslash( $_POST['address'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
313
					'shipping_address_2' => isset( $_POST['address_2'] ) ? wp_unslash( $_POST['address_2'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
314
				)
315
			);
316
		} else {
317
			WC()->customer->set_props(
318
				array(
319
					'shipping_country'   => isset( $_POST['s_country'] ) ? wp_unslash( $_POST['s_country'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
320
					'shipping_state'     => isset( $_POST['s_state'] ) ? wp_unslash( $_POST['s_state'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
321
					'shipping_postcode'  => isset( $_POST['s_postcode'] ) ? wp_unslash( $_POST['s_postcode'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
322
					'shipping_city'      => isset( $_POST['s_city'] ) ? wp_unslash( $_POST['s_city'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
323
					'shipping_address_1' => isset( $_POST['s_address'] ) ? wp_unslash( $_POST['s_address'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
324
					'shipping_address_2' => isset( $_POST['s_address_2'] ) ? wp_unslash( $_POST['s_address_2'] ) : null,
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
325
				)
326
			);
327
		}
328
329
		if ( wc_string_to_bool( $_POST['has_full_address'] ) ) {
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
330
			WC()->customer->set_calculated_shipping( true );
331
		} else {
332
			WC()->customer->set_calculated_shipping( false );
333
		}
334
335
		WC()->customer->save();
336
		WC()->cart->calculate_totals();
337
338
		// Get order review fragment.
339
		ob_start();
340
		woocommerce_order_review();
341
		$woocommerce_order_review = ob_get_clean();
342
343
		// Get checkout payment fragment.
344
		ob_start();
345
		woocommerce_checkout_payment();
346
		$woocommerce_checkout_payment = ob_get_clean();
347
348
		// Get messages if reload checkout is not true.
349
		$reload_checkout = isset( WC()->session->reload_checkout ) ? true : false;
350
		if ( ! $reload_checkout ) {
351
			$messages = wc_print_notices( true );
352
		} else {
353
			$messages = '';
354
		}
355
356
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
357
358
		wp_send_json(
359
			array(
360
				'result'    => empty( $messages ) ? 'success' : 'failure',
361
				'messages'  => $messages,
362
				'reload'    => $reload_checkout ? 'true' : 'false',
363
				'fragments' => apply_filters(
364
					'woocommerce_update_order_review_fragments',
365
					array(
366
						'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
367
						'.woocommerce-checkout-payment' => $woocommerce_checkout_payment,
368
					)
369
				),
370
			)
371
		);
372
	}
373
374
	/**
375
	 * AJAX add to cart.
376
	 */
377
	public static function add_to_cart() {
378
		ob_start();
379
380
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
381
		$product           = wc_get_product( $product_id );
382
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
383
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
384
		$product_status    = get_post_status( $product_id );
385
		$variation_id      = 0;
386
		$variation         = array();
387
388
		if ( $product && 'variation' === $product->get_type() ) {
389
			$variation_id = $product_id;
390
			$product_id   = $product->get_parent_id();
391
			$variation    = $product->get_variation_attributes();
392
		}
393
394
		if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation ) && 'publish' === $product_status ) {
395
396
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
397
398
			if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
399
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
400
			}
401
402
			// Return fragments
403
			self::get_refreshed_fragments();
404
405
		} else {
406
407
			// If there was an error adding to the cart, redirect to the product page to show any errors
408
			$data = array(
409
				'error'       => true,
410
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id ),
411
			);
412
413
			wp_send_json( $data );
414
		}
415
	}
416
417
	/**
418
	 * AJAX remove from cart.
419
	 */
420
	public static function remove_from_cart() {
421
		ob_start();
422
423
		$cart_item_key = wc_clean( $_POST['cart_item_key'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
424
425
		if ( $cart_item_key && false !== WC()->cart->remove_cart_item( $cart_item_key ) ) {
426
			self::get_refreshed_fragments();
427
		} else {
428
			wp_send_json_error();
429
		}
430
	}
431
432
	/**
433
	 * Process ajax checkout form.
434
	 */
435
	public static function checkout() {
436
		wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
437
		WC()->checkout()->process_checkout();
438
		wp_die( 0 );
439
	}
440
441
	/**
442
	 * Get a matching variation based on posted attributes.
443
	 */
444
	public static function get_variation() {
445
		ob_start();
446
447
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ) ) ) ) {
448
			wp_die();
449
		}
450
451
		$data_store   = WC_Data_Store::load( 'product' );
452
		$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...
453
		$variation    = $variation_id ? $variable_product->get_available_variation( $variation_id ) : false;
454
		wp_send_json( $variation );
455
	}
456
457
	/**
458
	 * Locate user via AJAX.
459
	 */
460
	public static function get_customer_location() {
461
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
462
		wp_send_json_success( array( 'hash' => $location_hash ) );
463
	}
464
465
	/**
466
	 * Toggle Featured status of a product from admin.
467
	 */
468
	public static function feature_product() {
469
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
470
			$product = wc_get_product( absint( $_GET['product_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_GET
Loading history...
471
472
			if ( $product ) {
473
				$product->set_featured( ! $product->get_featured() );
474
				$product->save();
475
			}
476
		}
477
478
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=product' ) );
479
		exit;
480
	}
481
482
	/**
483
	 * Mark an order with a status.
484
	 */
485
	public static function mark_order_status() {
486
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
487
			$status = sanitize_text_field( $_GET['status'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_GET
Loading history...
488
			$order  = wc_get_order( absint( $_GET['order_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_GET
Loading history...
489
490
			if ( wc_is_order_status( 'wc-' . $status ) && $order ) {
491
				// Initialize payment gateways in case order has hooked status transition actions.
492
				WC()->payment_gateways();
493
494
				$order->update_status( $status, '', true );
495
				do_action( 'woocommerce_order_edit_status', $order->get_id(), $status );
496
			}
497
		}
498
499
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
500
		exit;
501
	}
502
503
	/**
504
	 * Get order details.
505
	 */
506 View Code Duplication
	public static function get_order_details() {
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...
507
		check_admin_referer( 'woocommerce-preview-order', 'security' );
508
509
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
510
			wp_die( -1 );
511
		}
512
513
		$order = wc_get_order( absint( $_GET['order_id'] ) ); // WPCS: sanitization ok.
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_GET
Loading history...
514
515
		if ( $order ) {
516
			include_once 'admin/list-tables/class-wc-admin-list-table-orders.php';
517
518
			wp_send_json_success( WC_Admin_List_Table_Orders::order_preview_get_order_details( $order ) );
519
		}
520
		wp_die();
521
	}
522
523
	/**
524
	 * Add an attribute row.
525
	 */
526
	public static function add_attribute() {
527
		ob_start();
528
529
		check_ajax_referer( 'add-attribute', 'security' );
530
531
		if ( ! current_user_can( 'edit_products' ) ) {
532
			wp_die( -1 );
533
		}
534
535
		$i             = absint( $_POST['i'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
536
		$metabox_class = array();
537
		$attribute     = new WC_Product_Attribute();
538
539
		$attribute->set_id( wc_attribute_taxonomy_id_by_name( sanitize_text_field( $_POST['taxonomy'] ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
540
		$attribute->set_name( sanitize_text_field( $_POST['taxonomy'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
541
		$attribute->set_visible( apply_filters( 'woocommerce_attribute_default_visibility', 1 ) );
542
		$attribute->set_variation( apply_filters( 'woocommerce_attribute_default_is_variation', 0 ) );
543
544
		if ( $attribute->is_taxonomy() ) {
545
			$metabox_class[] = 'taxonomy';
546
			$metabox_class[] = $attribute->get_name();
547
		}
548
549
		include 'admin/meta-boxes/views/html-product-attribute.php';
550
		wp_die();
551
	}
552
553
	/**
554
	 * Add a new attribute via ajax function.
555
	 */
556
	public static function add_new_attribute() {
557
		check_ajax_referer( 'add-attribute', 'security' );
558
559
		if ( current_user_can( 'manage_product_terms' ) ) {
560
			$taxonomy = esc_attr( $_POST['taxonomy'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
561
			$term     = wc_clean( $_POST['term'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
562
563
			if ( taxonomy_exists( $taxonomy ) ) {
564
565
				$result = wp_insert_term( $term, $taxonomy );
566
567
				if ( is_wp_error( $result ) ) {
568
					wp_send_json(
569
						array(
570
							'error' => $result->get_error_message(),
571
						)
572
					);
573
				} else {
574
					$term = get_term_by( 'id', $result['term_id'], $taxonomy );
575
					wp_send_json(
576
						array(
577
							'term_id' => $term->term_id,
578
							'name'    => $term->name,
579
							'slug'    => $term->slug,
580
						)
581
					);
582
				}
583
			}
584
		}
585
		wp_die( -1 );
586
	}
587
588
	/**
589
	 * Delete variations via ajax function.
590
	 */
591
	public static function remove_variations() {
592
		check_ajax_referer( 'delete-variations', 'security' );
593
594
		if ( current_user_can( 'edit_products' ) ) {
595
			$variation_ids = (array) $_POST['variation_ids'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
596
597
			foreach ( $variation_ids as $variation_id ) {
598
				if ( 'product_variation' === get_post_type( $variation_id ) ) {
599
					$variation = wc_get_product( $variation_id );
600
					$variation->delete( true );
601
				}
602
			}
603
		}
604
605
		wp_die( -1 );
606
	}
607
608
	/**
609
	 * Save attributes via ajax.
610
	 */
611
	public static function save_attributes() {
612
		check_ajax_referer( 'save-attributes', 'security' );
613
614
		if ( ! current_user_can( 'edit_products' ) ) {
615
			wp_die( -1 );
616
		}
617
618
		$response = array();
619
620
		try {
621
			parse_str( $_POST['data'], $data );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
622
623
			$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...
624
			$product_id   = absint( $_POST['post_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
625
			$product_type = ! empty( $_POST['product_type'] ) ? wc_clean( $_POST['product_type'] ) : 'simple';
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
626
			$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...duct_type']) : 'simple' on line 625 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...
627
			$product      = new $classname( $product_id );
628
629
			$product->set_attributes( $attributes );
630
			$product->save();
631
632
			ob_start();
633
			$attributes = $product->get_attributes( 'edit' );
634
			$i          = -1;
635
636
			foreach ( $data['attribute_names'] as $attribute_name ) {
637
				$attribute = isset( $attributes[ sanitize_title( $attribute_name ) ] ) ? $attributes[ sanitize_title( $attribute_name ) ] : false;
638
				if ( ! $attribute ) {
639
					continue;
640
				}
641
				$i++;
642
				$metabox_class = array();
643
644
				if ( $attribute->is_taxonomy() ) {
645
					$metabox_class[] = 'taxonomy';
646
					$metabox_class[] = $attribute->get_name();
647
				}
648
649
				include( 'admin/meta-boxes/views/html-product-attribute.php' );
650
			}
651
652
			$response['html'] = ob_get_clean();
653
		} catch ( Exception $e ) {
654
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
655
		}
656
657
		// wp_send_json_success must be outside the try block not to break phpunit tests.
658
		wp_send_json_success( $response );
659
	}
660
661
	/**
662
	 * Add variation via ajax function.
663
	 */
664
	public static function add_variation() {
665
		check_ajax_referer( 'add-variation', 'security' );
666
667
		if ( ! current_user_can( 'edit_products' ) ) {
668
			wp_die( -1 );
669
		}
670
671
		global $post; // Set $post global so its available, like within the admin screens.
672
673
		$product_id       = intval( $_POST['post_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
674
		$post             = get_post( $product_id );
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
675
		$loop             = intval( $_POST['loop'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
676
		$product_object   = wc_get_product( $product_id );
677
		$variation_object = new WC_Product_Variation();
678
		$variation_object->set_parent_id( $product_id );
679
		$variation_object->set_attributes( array_fill_keys( array_map( 'sanitize_title', array_keys( $product_object->get_variation_attributes() ) ), '' ) );
680
		$variation_id   = $variation_object->save();
681
		$variation      = get_post( $variation_id );
682
		$variation_data = array_merge( get_post_custom( $variation_id ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility.
683
		include 'admin/meta-boxes/views/html-variation-admin.php';
684
		wp_die();
685
	}
686
687
	/**
688
	 * Link all variations via ajax function.
689
	 */
690
	public static function link_all_variations() {
691
		check_ajax_referer( 'link-variations', 'security' );
692
693
		if ( ! current_user_can( 'edit_products' ) ) {
694
			wp_die( -1 );
695
		}
696
697
		wc_maybe_define_constant( 'WC_MAX_LINKED_VARIATIONS', 49 );
698
		wc_set_time_limit( 0 );
699
700
		$post_id = intval( $_POST['post_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
701
702
		if ( ! $post_id ) {
703
			wp_die();
704
		}
705
706
		$product    = wc_get_product( $post_id );
707
		$attributes = wc_list_pluck( array_filter( $product->get_attributes(), 'wc_attributes_array_filter_variation' ), 'get_slugs' );
708
709
		if ( ! empty( $attributes ) ) {
710
			// Get existing variations so we don't create duplicates.
711
			$existing_variations = array_map( 'wc_get_product', $product->get_children() );
712
			$existing_attributes = array();
713
714
			foreach ( $existing_variations as $existing_variation ) {
715
				$existing_attributes[] = $existing_variation->get_attributes();
716
			}
717
718
			$added               = 0;
719
			$possible_attributes = array_reverse( wc_array_cartesian( $attributes ) );
720
721
			foreach ( $possible_attributes as $possible_attribute ) {
722
				if ( in_array( $possible_attribute, $existing_attributes ) ) {
723
					continue;
724
				}
725
				$variation = new WC_Product_Variation();
726
				$variation->set_parent_id( $post_id );
727
				$variation->set_attributes( $possible_attribute );
728
729
				do_action( 'product_variation_linked', $variation->save() );
730
731
				if ( ( $added ++ ) > WC_MAX_LINKED_VARIATIONS ) {
732
					break;
733
				}
734
			}
735
736
			echo $added;
737
		}
738
739
		$data_store = $product->get_data_store();
740
		$data_store->sort_all_product_variations( $product->get_id() );
741
		wp_die();
742
	}
743
744
	/**
745
	 * Delete download permissions via ajax function.
746
	 */
747
	public static function revoke_access_to_download() {
748
		check_ajax_referer( 'revoke-access', 'security' );
749
750
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
751
			wp_die( -1 );
752
		}
753
		$download_id   = $_POST['download_id'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
754
		$product_id    = intval( $_POST['product_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
755
		$order_id      = intval( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
756
		$permission_id = absint( $_POST['permission_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
757
		$data_store    = WC_Data_Store::load( 'customer-download' );
758
		$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...
759
760
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id, $permission_id );
761
762
		wp_die();
763
	}
764
765
	/**
766
	 * Grant download permissions via ajax function.
767
	 */
768
	public static function grant_access_to_download() {
769
770
		check_ajax_referer( 'grant-access', 'security' );
771
772
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
773
			wp_die( -1 );
774
		}
775
776
		global $wpdb;
777
778
		$wpdb->hide_errors();
779
780
		$order_id     = intval( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
781
		$product_ids  = $_POST['product_ids'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
782
		$loop         = intval( $_POST['loop'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
783
		$file_counter = 0;
784
		$order        = wc_get_order( $order_id );
785
786
		if ( ! is_array( $product_ids ) ) {
787
			$product_ids = array( $product_ids );
788
		}
789
790
		foreach ( $product_ids as $product_id ) {
791
			$product = wc_get_product( $product_id );
792
			$files   = $product->get_downloads();
793
794
			if ( ! $order->get_billing_email() ) {
795
				wp_die();
796
			}
797
798
			if ( ! empty( $files ) ) {
799
				foreach ( $files as $download_id => $file ) {
800
					if ( $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...
801
						$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 800 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...
802
						$loop ++;
803
						$file_counter ++;
804
805
						if ( $file->get_name() ) {
806
							$file_count = $file->get_name();
807
						} else {
808
							/* translators: %d file count */
809
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
810
						}
811
						include 'admin/meta-boxes/views/html-order-download-permission.php';
812
					}
813
				}
814
			}
815
		}
816
		wp_die();
817
	}
818
819
	/**
820
	 * Get customer details via ajax.
821
	 */
822
	public static function get_customer_details() {
823
		check_ajax_referer( 'get-customer-details', 'security' );
824
825
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
826
			wp_die( -1 );
827
		}
828
829
		$user_id  = absint( $_POST['user_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
830
		$customer = new WC_Customer( $user_id );
831
832
		if ( has_filter( 'woocommerce_found_customer_details' ) ) {
833
			wc_deprecated_function( 'The woocommerce_found_customer_details filter', '3.0', 'woocommerce_ajax_get_customer_details' );
834
		}
835
836
		$data                  = $customer->get_data();
837
		$data['date_created']  = $data['date_created'] ? $data['date_created']->getTimestamp() : null;
838
		$data['date_modified'] = $data['date_modified'] ? $data['date_modified']->getTimestamp() : null;
839
840
		$customer_data = apply_filters( 'woocommerce_ajax_get_customer_details', $data, $customer, $user_id );
841
		wp_send_json( $customer_data );
842
	}
843
844
	/**
845
	 * Add order item via ajax.
846
	 */
847
	public static function add_order_item() {
848
		check_ajax_referer( 'order-item', 'security' );
849
850
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
851
			wp_die( -1 );
852
		}
853
854
		$response = array();
855
856
		try {
857
			if ( ! isset( $_POST['order_id'] ) ) {
858
				throw new Exception( __( 'Invalid order', 'woocommerce' ) );
859
			}
860
861
			$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...
862
			$order        = wc_get_order( $order_id );
863
864
			if ( ! $order ) {
865
				throw new Exception( __( 'Invalid order', 'woocommerce' ) );
866
			}
867
868
			// If we passed through items it means we need to save first before adding a new one.
869
			$items = ( ! empty( $_POST['items'] ) ) ? $_POST['items'] : '';
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
870
871
			if ( ! empty( $items ) ) {
872
				$save_items = array();
873
				parse_str( $items, $save_items );
874
				// Save order items.
875
				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...
876
			}
877
878
			$items_to_add = array_filter( wp_unslash( (array) $_POST['data'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
879
880
			// Add items to order.
881
			foreach ( $items_to_add as $item ) {
882
				if ( ! isset( $item['id'], $item['qty'] ) || empty( $item['id'] ) ) {
883
					continue;
884
				}
885
				$product_id = absint( $item['id'] );
886
				$qty        = wc_stock_amount( $item['qty'] );
887
				$product    = wc_get_product( $product_id );
888
889
				if ( ! $product ) {
890
					throw new Exception( __( 'Invalid product ID', 'woocommerce' ) . ' ' . $product_id );
891
				}
892
893
				$item_id                 = $order->add_product( $product, $qty );
894
				$item                    = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id );
895
				$added_items[ $item_id ] = $item;
896
897
				do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item, $order );
898
			}
899
900
			do_action( 'woocommerce_ajax_order_items_added', $added_items, $order );
901
902
			$data = get_post_meta( $order_id );
903
904
			ob_start();
905
			include 'admin/meta-boxes/views/html-order-items.php';
906
			$response['html'] = ob_get_clean();
907
		} catch ( Exception $e ) {
908
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
909
		}
910
911
		// wp_send_json_success must be outside the try block not to break phpunit tests.
912
		wp_send_json_success( $response );
913
	}
914
915
	/**
916
	 * Add order fee via ajax.
917
	 */
918
	public static function add_order_fee() {
919
		check_ajax_referer( 'order-item', 'security' );
920
921
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
922
			wp_die( -1 );
923
		}
924
925
		$response = array();
926
927
		try {
928
			$order_id           = absint( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
929
			$amount             = wc_clean( $_POST['amount'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
930
			$order              = wc_get_order( $order_id );
931
			$calculate_tax_args = array(
932
				'country'  => strtoupper( wc_clean( $_POST['country'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
933
				'state'    => strtoupper( wc_clean( $_POST['state'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
934
				'postcode' => strtoupper( wc_clean( $_POST['postcode'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
935
				'city'     => strtoupper( wc_clean( $_POST['city'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
936
			);
937
938
			if ( ! $order ) {
939
				throw new exception( __( 'Invalid order', 'woocommerce' ) );
940
			}
941
942
			if ( strstr( $amount, '%' ) ) {
943
				$formatted_amount = $amount;
944
				$percent          = floatval( trim( $amount, '%' ) );
945
				$amount           = $order->get_total() * ( $percent / 100 );
946
			} else {
947
				$amount           = floatval( $amount );
948
				$formatted_amount = wc_price( $amount, array( 'currency' => $order->get_currency() ) );
949
			}
950
951
			$fee = new WC_Order_Item_Fee();
952
			$fee->set_amount( $amount );
953
			$fee->set_total( $amount );
954
			/* translators: %s fee amount */
955
			$fee->set_name( sprintf( __( '%s fee', 'woocommerce' ), wc_clean( $formatted_amount ) ) );
956
957
			$order->add_item( $fee );
958
			$order->calculate_taxes( $calculate_tax_args );
959
			$order->calculate_totals( false );
960
			$order->save();
961
962
			ob_start();
963
			include 'admin/meta-boxes/views/html-order-items.php';
964
			$response['html'] = ob_get_clean();
965
		} catch ( Exception $e ) {
966
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
967
		}
968
969
		// wp_send_json_success must be outside the try block not to break phpunit tests.
970
		wp_send_json_success( $response );
971
	}
972
973
	/**
974
	 * Add order shipping cost via ajax.
975
	 */
976
	public static function add_order_shipping() {
977
		check_ajax_referer( 'order-item', 'security' );
978
979
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
980
			wp_die( -1 );
981
		}
982
983
		$response = array();
984
985
		try {
986
			$order_id         = absint( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
987
			$order            = wc_get_order( $order_id );
988
			$order_taxes      = $order->get_taxes();
989
			$shipping_methods = WC()->shipping() ? WC()->shipping()->load_shipping_methods() : array();
990
991
			// Add new shipping
992
			$item = new WC_Order_Item_Shipping();
993
			$item->set_shipping_rate( new WC_Shipping_Rate() );
994
			$item->set_order_id( $order_id );
995
			$item_id = $item->save();
996
997
			ob_start();
998
			include 'admin/meta-boxes/views/html-order-shipping.php';
999
			$response['html'] = ob_get_clean();
1000
		} catch ( Exception $e ) {
1001
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1002
		}
1003
1004
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1005
		wp_send_json_success( $response );
1006
	}
1007
1008
	/**
1009
	 * Add order tax column via ajax.
1010
	 */
1011
	public static function add_order_tax() {
1012
		check_ajax_referer( 'order-item', 'security' );
1013
1014
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1015
			wp_die( -1 );
1016
		}
1017
1018
		$response = array();
1019
1020
		try {
1021
			$order_id = absint( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1022
			$rate_id  = absint( $_POST['rate_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1023
			$order    = wc_get_order( $order_id );
1024
			$data     = get_post_meta( $order_id );
1025
1026
			// Add new tax
1027
			$item = new WC_Order_Item_Tax();
1028
			$item->set_rate( $rate_id );
1029
			$item->set_order_id( $order_id );
1030
			$item->save();
1031
1032
			ob_start();
1033
			include 'admin/meta-boxes/views/html-order-items.php';
1034
			$response['html'] = ob_get_clean();
1035
		} catch ( Exception $e ) {
1036
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1037
		}
1038
1039
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1040
		wp_send_json_success( $response );
1041
	}
1042
1043
	/**
1044
	 * Add order discount via ajax.
1045
	 */
1046
	public static function add_coupon_discount() {
1047
		check_ajax_referer( 'order-item', 'security' );
1048
1049
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1050
			wp_die( -1 );
1051
		}
1052
1053
		$response = array();
1054
1055
		try {
1056
			$order_id = absint( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1057
			$order    = wc_get_order( $order_id );
1058
			$result   = $order->apply_coupon( wc_clean( $_POST['coupon'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1059
1060
			if ( is_wp_error( $result ) ) {
1061
				throw new Exception( html_entity_decode( wp_strip_all_tags( $result->get_error_message() ) ) );
1062
			}
1063
1064
			ob_start();
1065
			include 'admin/meta-boxes/views/html-order-items.php';
1066
			$response['html'] = ob_get_clean();
1067
		} catch ( Exception $e ) {
1068
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1069
		}
1070
1071
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1072
		wp_send_json_success( $response );
1073
	}
1074
1075
	/**
1076
	 * Remove coupon from an order via ajax.
1077
	 */
1078
	public static function remove_order_coupon() {
1079
		check_ajax_referer( 'order-item', 'security' );
1080
1081
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1082
			wp_die( -1 );
1083
		}
1084
1085
		$response = array();
1086
1087
		try {
1088
			$order_id = absint( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1089
			$order    = wc_get_order( $order_id );
1090
1091
			$order->remove_coupon( wc_clean( $_POST['coupon'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1092
1093
			ob_start();
1094
			include 'admin/meta-boxes/views/html-order-items.php';
1095
			$response['html'] = ob_get_clean();
1096
		} catch ( Exception $e ) {
1097
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1098
		}
1099
1100
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1101
		wp_send_json_success( $response );
1102
	}
1103
1104
	/**
1105
	 * Remove an order item.
1106
	 */
1107
	public static function remove_order_item() {
1108
		check_ajax_referer( 'order-item', 'security' );
1109
1110
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1111
			wp_die( -1 );
1112
		}
1113
1114
		$response = array();
1115
1116
		try {
1117
			$order_id           = absint( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1118
			$order_item_ids     = $_POST['order_item_ids'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1119
			$items              = ( ! empty( $_POST['items'] ) ) ? $_POST['items'] : '';
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1120
			$calculate_tax_args = array(
1121
				'country'  => strtoupper( wc_clean( $_POST['country'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1122
				'state'    => strtoupper( wc_clean( $_POST['state'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1123
				'postcode' => strtoupper( wc_clean( $_POST['postcode'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1124
				'city'     => strtoupper( wc_clean( $_POST['city'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1125
			);
1126
1127
			if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1128
				$order_item_ids = array( $order_item_ids );
1129
			}
1130
1131
			// If we passed through items it means we need to save first before deleting.
1132
			if ( ! empty( $items ) ) {
1133
				$save_items = array();
1134
				parse_str( $items, $save_items );
1135
				// Save order items
1136
				wc_save_order_items( $order_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...
1137
			}
1138
1139
			if ( sizeof( $order_item_ids ) > 0 ) {
1140
				foreach ( $order_item_ids as $id ) {
1141
					wc_delete_order_item( absint( $id ) );
1142
				}
1143
			}
1144
1145
			$order = wc_get_order( $order_id );
1146
			$order->calculate_taxes( $calculate_tax_args );
1147
			$order->calculate_totals( false );
1148
1149
			ob_start();
1150
			include 'admin/meta-boxes/views/html-order-items.php';
1151
			$response['html'] = ob_get_clean();
1152
		} catch ( Exception $e ) {
1153
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1154
		}
1155
1156
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1157
		wp_send_json_success( $response );
1158
	}
1159
1160
	/**
1161
	 * Remove an order tax.
1162
	 */
1163
	public static function remove_order_tax() {
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 = absint( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1174
			$rate_id  = absint( $_POST['rate_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1175
1176
			wc_delete_order_item( $rate_id );
1177
1178
			$order = wc_get_order( $order_id );
1179
			$order->calculate_totals( false );
1180
1181
			ob_start();
1182
			include 'admin/meta-boxes/views/html-order-items.php';
1183
			$response['html'] = ob_get_clean();
1184
		} catch ( Exception $e ) {
1185
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1186
		}
1187
1188
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1189
		wp_send_json_success( $response );
1190
	}
1191
1192
	/**
1193
	 * Calc line tax.
1194
	 */
1195
	public static function calc_line_taxes() {
1196
		check_ajax_referer( 'calc-totals', 'security' );
1197
1198
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1199
			wp_die( -1 );
1200
		}
1201
1202
		$order_id           = absint( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1203
		$calculate_tax_args = array(
1204
			'country'  => strtoupper( wc_clean( $_POST['country'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1205
			'state'    => strtoupper( wc_clean( $_POST['state'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1206
			'postcode' => strtoupper( wc_clean( $_POST['postcode'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1207
			'city'     => strtoupper( wc_clean( $_POST['city'] ) ),
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1208
		);
1209
1210
		// Parse the jQuery serialized items
1211
		$items = array();
1212
		parse_str( $_POST['items'], $items );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1213
1214
		// Save order items first
1215
		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...
1216
1217
		// Grab the order and recalculate taxes
1218
		$order = wc_get_order( $order_id );
1219
		$order->calculate_taxes( $calculate_tax_args );
1220
		$order->calculate_totals( false );
1221
		include 'admin/meta-boxes/views/html-order-items.php';
1222
		wp_die();
1223
	}
1224
1225
	/**
1226
	 * Save order items via ajax.
1227
	 */
1228
	public static function save_order_items() {
1229
		check_ajax_referer( 'order-item', 'security' );
1230
1231
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1232
			wp_die( -1 );
1233
		}
1234
1235
		if ( isset( $_POST['order_id'], $_POST['items'] ) ) {
1236
			$order_id = absint( $_POST['order_id'] );
1237
1238
			// Parse the jQuery serialized items
1239
			$items = array();
1240
			parse_str( $_POST['items'], $items );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1241
1242
			// Save order items
1243
			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...
1244
1245
			// Return HTML items
1246
			$order = wc_get_order( $order_id );
1247
			include 'admin/meta-boxes/views/html-order-items.php';
1248
		}
1249
		wp_die();
1250
	}
1251
1252
	/**
1253
	 * Load order items via ajax.
1254
	 */
1255 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...
1256
		check_ajax_referer( 'order-item', 'security' );
1257
1258
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1259
			wp_die( -1 );
1260
		}
1261
1262
		// Return HTML items
1263
		$order_id = absint( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1264
		$order    = wc_get_order( $order_id );
1265
		include 'admin/meta-boxes/views/html-order-items.php';
1266
		wp_die();
1267
	}
1268
1269
	/**
1270
	 * Add order note via ajax.
1271
	 */
1272
	public static function add_order_note() {
1273
		check_ajax_referer( 'add-order-note', 'security' );
1274
1275
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1276
			wp_die( -1 );
1277
		}
1278
1279
		$post_id   = absint( $_POST['post_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1280
		$note      = wp_kses_post( trim( wp_unslash( $_POST['note'] ) ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1281
		$note_type = $_POST['note_type'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1282
1283
		$is_customer_note = ( 'customer' === $note_type ) ? 1 : 0;
1284
1285
		if ( $post_id > 0 ) {
1286
			$order      = wc_get_order( $post_id );
1287
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1288
			$note       = wc_get_order_note( $comment_id );
1289
1290
			$note_classes   = array( 'note' );
1291
			$note_classes[] = $is_customer_note ? 'customer-note' : '';
1292
			$note_classes   = apply_filters( 'woocommerce_order_note_class', array_filter( $note_classes ), $note );
1293
			?>
1294
			<li rel="<?php echo absint( $note->id ); ?>" class="<?php echo esc_attr( implode( ' ', $note_classes ) ); ?>">
1295
				<div class="note_content">
1296
					<?php echo wpautop( wptexturize( wp_kses_post( make_clickable( $note->content ) ) ) ); ?>
1297
				</div>
1298
				<p class="meta">
1299
					<abbr class="exact-date" title="<?php echo $note->date_created->date( 'y-m-d h:i:s' ); ?>">
1300
						<?php
1301
						/* translators: $1: Date created, $2 Time created */
1302
						printf( __( 'added on %1$s at %2$s', 'woocommerce' ), $note->date_created->date_i18n( wc_date_format() ), $note->date_created->date_i18n( wc_time_format() ) );
1303
						?>
1304
					</abbr>
1305
					<?php
1306
					if ( 'system' !== $note->added_by ) :
1307
						/* translators: %s: note author */
1308
						printf( ' ' . __( 'by %s', 'woocommerce' ), $note->added_by );
1309
					endif;
1310
					?>
1311
					<a href="#" class="delete_note" role="button"><?php _e( 'Delete note', 'woocommerce' ); ?></a>
1312
				</p>
1313
			</li>
1314
			<?php
1315
		}
1316
		wp_die();
1317
	}
1318
1319
	/**
1320
	 * Delete order note via ajax.
1321
	 */
1322
	public static function delete_order_note() {
1323
		check_ajax_referer( 'delete-order-note', 'security' );
1324
1325
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1326
			wp_die( -1 );
1327
		}
1328
1329
		$note_id = (int) $_POST['note_id'];
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1330
1331
		if ( $note_id > 0 ) {
1332
			wc_delete_order_note( $note_id );
1333
		}
1334
		wp_die();
1335
	}
1336
1337
	/**
1338
	 * Search for products and echo json.
1339
	 *
1340
	 * @param string $term (default: '')
1341
	 * @param bool   $include_variations in search or not
1342
	 */
1343
	public static function json_search_products( $term = '', $include_variations = false ) {
1344
		check_ajax_referer( 'search-products', 'security' );
1345
1346
		$term = wc_clean( empty( $term ) ? wp_unslash( $_GET['term'] ) : $term );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_GET
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1347
1348
		if ( empty( $term ) ) {
1349
			wp_die();
1350
		}
1351
1352
		if ( ! empty( $_GET['limit'] ) ) {
1353
			$limit = absint( $_GET['limit'] );
1354
		} else {
1355
			$limit = absint( apply_filters( 'woocommerce_json_search_limit', 30 ) );
1356
		}
1357
1358
		$data_store = WC_Data_Store::load( 'product' );
1359
		$ids        = $data_store->search_products( $term, '', (bool) $include_variations, 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...
1360
1361
		if ( ! empty( $_GET['exclude'] ) ) {
1362
			$ids = array_diff( $ids, (array) $_GET['exclude'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1363
		}
1364
1365
		if ( ! empty( $_GET['include'] ) ) {
1366
			$ids = array_intersect( $ids, (array) $_GET['include'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1367
		}
1368
1369
		$product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' );
1370
		$products        = array();
1371
1372
		foreach ( $product_objects as $product_object ) {
1373
			$formatted_name = $product_object->get_formatted_name();
1374
			$managing_stock = $product_object->managing_stock();
1375
1376
			if ( $managing_stock && ! empty( $_GET['display_stock'] ) ) {
1377
				$formatted_name .= ' &ndash; ' . wc_format_stock_for_display( $product_object );
1378
			}
1379
1380
			$products[ $product_object->get_id() ] = rawurldecode( $formatted_name );
1381
		}
1382
1383
		wp_send_json( apply_filters( 'woocommerce_json_search_found_products', $products ) );
1384
	}
1385
1386
	/**
1387
	 * Search for product variations and return json.
1388
	 *
1389
	 * @see WC_AJAX::json_search_products()
1390
	 */
1391
	public static function json_search_products_and_variations() {
1392
		self::json_search_products( '', true );
1393
	}
1394
1395
	/**
1396
	 * Search for downloadable product variations and return json.
1397
	 *
1398
	 * @see WC_AJAX::json_search_products()
1399
	 */
1400
	public static function json_search_downloadable_products_and_variations() {
1401
		check_ajax_referer( 'search-products', 'security' );
1402
1403
		$term       = (string) wc_clean( wp_unslash( $_GET['term'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_GET
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1404
		$data_store = WC_Data_Store::load( 'product' );
1405
		$ids        = $data_store->search_products( $term, 'downloadable', true );
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...
1406
1407
		if ( ! empty( $_GET['exclude'] ) ) {
1408
			$ids = array_diff( $ids, (array) $_GET['exclude'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1409
		}
1410
1411
		if ( ! empty( $_GET['include'] ) ) {
1412
			$ids = array_intersect( $ids, (array) $_GET['include'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1413
		}
1414
1415
		if ( ! empty( $_GET['limit'] ) ) {
1416
			$ids = array_slice( $ids, 0, absint( $_GET['limit'] ) );
1417
		}
1418
1419
		$product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' );
1420
		$products        = array();
1421
1422
		foreach ( $product_objects as $product_object ) {
1423
			$products[ $product_object->get_id() ] = rawurldecode( $product_object->get_formatted_name() );
1424
		}
1425
1426
		wp_send_json( $products );
1427
	}
1428
1429
	/**
1430
	 * Search for customers and return json.
1431
	 */
1432
	public static function json_search_customers() {
1433
		ob_start();
1434
1435
		check_ajax_referer( 'search-customers', 'security' );
1436
1437
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1438
			wp_die( -1 );
1439
		}
1440
1441
		$term  = wc_clean( wp_unslash( $_GET['term'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_GET
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1442
		$limit = 0;
1443
1444
		if ( empty( $term ) ) {
1445
			wp_die();
1446
		}
1447
1448
		$ids = array();
1449
		// Search by ID.
1450
		if ( is_numeric( $term ) ) {
1451
			$customer = new WC_Customer( intval( $term ) );
1452
1453
			// Customer does not exists.
1454
			if ( 0 !== $customer->get_id() ) {
1455
				$ids = array( $customer->get_id() );
1456
			}
1457
		}
1458
1459
		// 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.
1460
		if ( empty( $ids ) ) {
1461
			$data_store = WC_Data_Store::load( 'customer' );
1462
1463
			// If search is smaller than 3 characters, limit result set to avoid
1464
			// too many rows being returned.
1465
			if ( 3 > strlen( $term ) ) {
1466
				$limit = 20;
1467
			}
1468
			$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...
1469
		}
1470
1471
		$found_customers = array();
1472
1473
		if ( ! empty( $_GET['exclude'] ) ) {
1474
			$ids = array_diff( $ids, (array) $_GET['exclude'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1475
		}
1476
1477
		foreach ( $ids as $id ) {
1478
			$customer = new WC_Customer( $id );
1479
			/* translators: 1: user display name 2: user ID 3: user email */
1480
			$found_customers[ $id ] = sprintf(
1481
				/* translators: $1: customer name, $2 customer id, $3: customer email */
1482
				esc_html__( '%1$s (#%2$s &ndash; %3$s)', 'woocommerce' ),
1483
				$customer->get_first_name() . ' ' . $customer->get_last_name(),
1484
				$customer->get_id(),
1485
				$customer->get_email()
1486
			);
1487
		}
1488
1489
		wp_send_json( apply_filters( 'woocommerce_json_search_found_customers', $found_customers ) );
1490
	}
1491
1492
	/**
1493
	 * Search for categories and return json.
1494
	 */
1495
	public static function json_search_categories() {
1496
		ob_start();
1497
1498
		check_ajax_referer( 'search-categories', 'security' );
1499
1500
		if ( ! current_user_can( 'edit_products' ) ) {
1501
			wp_die( -1 );
1502
		}
1503
1504
		if ( ! $search_text = wc_clean( wp_unslash( $_GET['term'] ) ) ) {
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_GET
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
1505
			wp_die();
1506
		}
1507
1508
		$found_categories = array();
1509
		$args             = array(
1510
			'taxonomy'   => array( 'product_cat' ),
1511
			'orderby'    => 'id',
1512
			'order'      => 'ASC',
1513
			'hide_empty' => true,
1514
			'fields'     => 'all',
1515
			'name__like' => $search_text,
1516
		);
1517
1518
		if ( $terms = get_terms( $args ) ) {
1519
			foreach ( $terms as $term ) {
1520
				$term->formatted_name = '';
1521
1522 View Code Duplication
				if ( $term->parent ) {
1523
					$ancestors = array_reverse( get_ancestors( $term->term_id, 'product_cat' ) );
1524
					foreach ( $ancestors as $ancestor ) {
1525
						if ( $ancestor_term = get_term( $ancestor, 'product_cat' ) ) {
1526
							$term->formatted_name .= $ancestor_term->name . ' > ';
1527
						}
1528
					}
1529
				}
1530
1531
				$term->formatted_name              .= $term->name . ' (' . $term->count . ')';
1532
				$found_categories[ $term->term_id ] = $term;
1533
			}
1534
		}
1535
1536
		wp_send_json( apply_filters( 'woocommerce_json_search_found_categories', $found_categories ) );
1537
	}
1538
1539
	/**
1540
	 * Ajax request handling for categories ordering.
1541
	 */
1542
	public static function term_ordering() {
1543
1544
		// check permissions again and make sure we have what we need
1545
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
1546
			wp_die( -1 );
1547
		}
1548
1549
		$id       = (int) $_POST['id'];
1550
		$next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
1551
		$taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null;
1552
		$term     = get_term_by( 'id', $id, $taxonomy );
1553
1554
		if ( ! $id || ! $term || ! $taxonomy ) {
1555
			wp_die( 0 );
1556
		}
1557
1558
		wc_reorder_terms( $term, $next_id, $taxonomy );
1559
1560
		$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
1561
1562
		if ( $term && sizeof( $children ) ) {
1563
			echo 'children';
1564
			wp_die();
1565
		}
1566
	}
1567
1568
	/**
1569
	 * Ajax request handling for product ordering.
1570
	 *
1571
	 * Based on Simple Page Ordering by 10up (https://wordpress.org/plugins/simple-page-ordering/).
1572
	 */
1573
	public static function product_ordering() {
1574
		global $wpdb;
1575
1576
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
1577
			wp_die( -1 );
1578
		}
1579
1580
		$sorting_id  = absint( $_POST['id'] );
1581
		$previd      = absint( isset( $_POST['previd'] ) ? $_POST['previd'] : 0 );
1582
		$nextid      = absint( isset( $_POST['nextid'] ) ? $_POST['nextid'] : 0 );
1583
		$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...
1584
		$index       = 0;
1585
1586
		foreach ( $menu_orders as $id => $menu_order ) {
1587
			$id = absint( $id );
1588
1589
			if ( $sorting_id === $id ) {
1590
				continue;
1591
			}
1592
			if ( $nextid === $id ) {
1593
				$index ++;
1594
			}
1595
			$index ++;
1596
			$menu_orders[ $id ] = $index;
1597
			$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...
1598
1599
			/**
1600
			 * When a single product has gotten it's ordering updated.
1601
			 * $id The product ID
1602
			 * $index The new menu order
1603
			*/
1604
			do_action( 'woocommerce_after_single_product_ordering', $id, $index );
1605
		}
1606
1607
		if ( isset( $menu_orders[ $previd ] ) ) {
1608
			$menu_orders[ $sorting_id ] = $menu_orders[ $previd ] + 1;
1609
		} elseif ( isset( $menu_orders[ $nextid ] ) ) {
1610
			$menu_orders[ $sorting_id ] = $menu_orders[ $nextid ] - 1;
1611
		} else {
1612
			$menu_orders[ $sorting_id ] = 0;
1613
		}
1614
1615
		$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...
1616
1617
		do_action( 'woocommerce_after_product_ordering', $sorting_id, $menu_orders );
1618
		wp_send_json( $menu_orders );
1619
	}
1620
1621
	/**
1622
	 * Handle a refund via the edit order screen.
1623
	 *
1624
	 * @throws Exception To return errors.
1625
	 */
1626
	public static function refund_line_items() {
1627
		ob_start();
1628
1629
		check_ajax_referer( 'order-item', 'security' );
1630
1631
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1632
			wp_die( -1 );
1633
		}
1634
1635
		$order_id               = absint( $_POST['order_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1636
		$refund_amount          = wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['refund_amount'] ) ), wc_get_price_decimals() );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1637
		$refunded_amount        = wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['refunded_amount'] ) ), wc_get_price_decimals() );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1638
		$refund_reason          = sanitize_text_field( $_POST['refund_reason'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1639
		$line_item_qtys         = json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_qtys'] ) ), true );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1640
		$line_item_totals       = json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_totals'] ) ), true );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1641
		$line_item_tax_totals   = json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_tax_totals'] ) ), true );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1642
		$api_refund             = 'true' === $_POST['api_refund'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1643
		$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...
1644
		$refund                 = false;
1645
		$response               = array();
1646
1647
		try {
1648
			$order       = wc_get_order( $order_id );
1649
			$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...
1650
			$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded(), wc_get_price_decimals() );
1651
1652
			if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) {
1653
				throw new exception( __( 'Invalid refund amount', 'woocommerce' ) );
1654
			}
1655
1656
			if ( $refunded_amount !== wc_format_decimal( $order->get_total_refunded(), wc_get_price_decimals() ) ) {
1657
				throw new exception( __( 'Error processing refund. Please try again.', 'woocommerce' ) );
1658
			}
1659
1660
			// Prepare line items which we are refunding.
1661
			$line_items = array();
1662
			$item_ids   = array_unique( array_merge( array_keys( $line_item_qtys, $line_item_totals ) ) );
1663
1664
			foreach ( $item_ids as $item_id ) {
1665
				$line_items[ $item_id ] = array(
1666
					'qty'          => 0,
1667
					'refund_total' => 0,
1668
					'refund_tax'   => array(),
1669
				);
1670
			}
1671
			foreach ( $line_item_qtys as $item_id => $qty ) {
1672
				$line_items[ $item_id ]['qty'] = max( $qty, 0 );
1673
			}
1674
			foreach ( $line_item_totals as $item_id => $total ) {
1675
				$line_items[ $item_id ]['refund_total'] = wc_format_decimal( $total );
1676
			}
1677
			foreach ( $line_item_tax_totals as $item_id => $tax_totals ) {
1678
				$line_items[ $item_id ]['refund_tax'] = array_filter( array_map( 'wc_format_decimal', $tax_totals ) );
1679
			}
1680
1681
			// Create the refund object.
1682
			$refund = wc_create_refund(
1683
				array(
1684
					'amount'         => $refund_amount,
1685
					'reason'         => $refund_reason,
1686
					'order_id'       => $order_id,
1687
					'line_items'     => $line_items,
1688
					'refund_payment' => $api_refund,
1689
					'restock_items'  => $restock_refunded_items,
1690
				)
1691
			);
1692
1693
			if ( is_wp_error( $refund ) ) {
1694
				throw new Exception( $refund->get_error_message() );
1695
			}
1696
1697
			if ( did_action( 'woocommerce_order_fully_refunded' ) ) {
1698
				$response['status'] = 'fully_refunded';
1699
			}
1700
		} catch ( Exception $e ) {
1701
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
1702
		}
1703
1704
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1705
		wp_send_json_success( $response );
1706
	}
1707
1708
	/**
1709
	 * Delete a refund.
1710
	 */
1711
	public static function delete_refund() {
1712
		check_ajax_referer( 'order-item', 'security' );
1713
1714
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1715
			wp_die( -1 );
1716
		}
1717
1718
		$refund_ids = array_map( 'absint', is_array( $_POST['refund_id'] ) ? $_POST['refund_id'] : array( $_POST['refund_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1719
		foreach ( $refund_ids as $refund_id ) {
1720
			if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
1721
				$refund   = wc_get_order( $refund_id );
1722
				$order_id = $refund->get_parent_id();
1723
				$refund->delete( true );
1724
				do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
1725
			}
1726
		}
1727
		wp_die();
1728
	}
1729
1730
	/**
1731
	 * Triggered when clicking the rating footer.
1732
	 */
1733
	public static function rated() {
1734
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
1735
			wp_die( -1 );
1736
		}
1737
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
1738
		wp_die();
1739
	}
1740
1741
	/**
1742
	 * Create/Update API key.
1743
	 */
1744
	public static function update_api_key() {
1745
		ob_start();
1746
1747
		global $wpdb;
1748
1749
		check_ajax_referer( 'update-api-key', 'security' );
1750
1751
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
1752
			wp_die( -1 );
1753
		}
1754
1755
		$response = array();
1756
1757
		try {
1758
			if ( empty( $_POST['description'] ) ) {
1759
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
1760
			}
1761
			if ( empty( $_POST['user'] ) ) {
1762
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
1763
			}
1764
			if ( empty( $_POST['permissions'] ) ) {
1765
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
1766
			}
1767
1768
			$key_id      = absint( $_POST['key_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
1769
			$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...
1770
			$permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read';
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
1771
			$user_id     = absint( $_POST['user'] );
1772
1773
			// Check if current user can edit other users.
1774
			if ( $user_id && ! current_user_can( 'edit_user', $user_id ) ) {
1775
				if ( get_current_user_id() !== $user_id ) {
1776
					throw new Exception( __( 'You do not have permission to assign API Keys to the selected user.', 'woocommerce' ) );
1777
				}
1778
			}
1779
1780
			if ( 0 < $key_id ) {
1781
				$data = array(
1782
					'user_id'     => $user_id,
1783
					'description' => $description,
1784
					'permissions' => $permissions,
1785
				);
1786
1787
				$wpdb->update(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
1788
					$wpdb->prefix . 'woocommerce_api_keys',
1789
					$data,
1790
					array( 'key_id' => $key_id ),
1791
					array(
1792
						'%d',
1793
						'%s',
1794
						'%s',
1795
					),
1796
					array( '%d' )
1797
				);
1798
1799
				$response                    = $data;
1800
				$response['consumer_key']    = '';
1801
				$response['consumer_secret'] = '';
1802
				$response['message']         = __( 'API Key updated successfully.', 'woocommerce' );
1803
			} else {
1804
				$consumer_key    = 'ck_' . wc_rand_hash();
1805
				$consumer_secret = 'cs_' . wc_rand_hash();
1806
1807
				$data = array(
1808
					'user_id'         => $user_id,
1809
					'description'     => $description,
1810
					'permissions'     => $permissions,
1811
					'consumer_key'    => wc_api_hash( $consumer_key ),
1812
					'consumer_secret' => $consumer_secret,
1813
					'truncated_key'   => substr( $consumer_key, -7 ),
1814
				);
1815
1816
				$wpdb->insert(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
1817
					$wpdb->prefix . 'woocommerce_api_keys',
1818
					$data,
1819
					array(
1820
						'%d',
1821
						'%s',
1822
						'%s',
1823
						'%s',
1824
						'%s',
1825
						'%s',
1826
					)
1827
				);
1828
1829
				$key_id                      = $wpdb->insert_id;
1830
				$response                    = $data;
1831
				$response['consumer_key']    = $consumer_key;
1832
				$response['consumer_secret'] = $consumer_secret;
1833
				$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' );
1834
				$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>';
1835
			}
1836
		} catch ( Exception $e ) {
1837
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
1838
		}
1839
1840
		// wp_send_json_success must be outside the try block not to break phpunit tests.
1841
		wp_send_json_success( $response );
1842
	}
1843
1844
	/**
1845
	 * Load variations via AJAX.
1846
	 */
1847
	public static function load_variations() {
1848
		ob_start();
1849
1850
		check_ajax_referer( 'load-variations', 'security' );
1851
1852
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) ) {
1853
			wp_die( -1 );
1854
		}
1855
1856
		// Set $post global so its available, like within the admin screens
1857
		global $post;
1858
1859
		$loop           = 0;
1860
		$product_id     = absint( $_POST['product_id'] );
1861
		$post           = get_post( $product_id );
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
1862
		$product_object = wc_get_product( $product_id );
1863
		$per_page       = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
1864
		$page           = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
1865
		$variations     = wc_get_products(
1866
			array(
1867
				'status'  => array( 'private', 'publish' ),
1868
				'type'    => 'variation',
1869
				'parent'  => $product_id,
1870
				'limit'   => $per_page,
1871
				'page'    => $page,
1872
				'orderby' => array(
1873
					'menu_order' => 'ASC',
1874
					'ID'         => 'DESC',
1875
				),
1876
				'return'  => 'objects',
1877
			)
1878
		);
1879
1880
		if ( $variations ) {
1881
			foreach ( $variations as $variation_object ) {
1882
				$variation_id   = $variation_object->get_id();
1883
				$variation      = get_post( $variation_id );
1884
				$variation_data = array_merge( get_post_custom( $variation_id ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility.
1885
				include 'admin/meta-boxes/views/html-variation-admin.php';
1886
				$loop++;
1887
			}
1888
		}
1889
		wp_die();
1890
	}
1891
1892
	/**
1893
	 * Save variations via AJAX.
1894
	 */
1895
	public static function save_variations() {
1896
		ob_start();
1897
1898
		check_ajax_referer( 'save-variations', 'security' );
1899
1900
		// Check permissions again and make sure we have what we need
1901 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST ) || empty( $_POST['product_id'] ) ) {
1902
			wp_die( -1 );
1903
		}
1904
1905
		$product_id                           = absint( $_POST['product_id'] );
1906
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
1907
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
1908
1909
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
1910
1911
		if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
1912
			echo '<div class="error notice is-dismissible">';
1913
1914
			foreach ( $errors as $error ) {
1915
				echo '<p>' . wp_kses_post( $error ) . '</p>';
1916
			}
1917
1918
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
1919
			echo '</div>';
1920
1921
			delete_option( 'woocommerce_meta_box_errors' );
1922
		}
1923
1924
		wp_die();
1925
	}
1926
1927
	/**
1928
	 * Bulk action - Toggle Enabled.
1929
	 *
1930
	 * @param array $variations
1931
	 * @param array $data
1932
	 *
1933
	 * @used-by bulk_edit_variations
1934
	 */
1935
	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...
1936
		foreach ( $variations as $variation_id ) {
1937
			$variation = wc_get_product( $variation_id );
1938
			$variation->set_status( 'private' === $variation->get_status( 'edit' ) ? 'publish' : 'private' );
1939
			$variation->save();
1940
		}
1941
	}
1942
1943
	/**
1944
	 * Bulk action - Toggle Downloadable Checkbox.
1945
	 *
1946
	 * @param array $variations
1947
	 * @param array $data
1948
	 *
1949
	 * @used-by bulk_edit_variations
1950
	 */
1951
	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...
1952
		self::variation_bulk_toggle( $variations, 'downloadable' );
1953
	}
1954
1955
	/**
1956
	 * Bulk action - Toggle Virtual Checkbox.
1957
	 *
1958
	 * @param array $variations
1959
	 * @param array $data
1960
	 *
1961
	 * @used-by bulk_edit_variations
1962
	 */
1963
	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...
1964
		self::variation_bulk_toggle( $variations, 'virtual' );
1965
	}
1966
1967
	/**
1968
	 * Bulk action - Toggle Manage Stock Checkbox.
1969
	 *
1970
	 * @param array $variations
1971
	 * @param array $data
1972
	 *
1973
	 * @used-by bulk_edit_variations
1974
	 */
1975
	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...
1976
		self::variation_bulk_toggle( $variations, 'manage_stock' );
1977
	}
1978
1979
	/**
1980
	 * Bulk action - Set Regular Prices.
1981
	 *
1982
	 * @param array $variations
1983
	 * @param array $data
1984
	 *
1985
	 * @used-by bulk_edit_variations
1986
	 */
1987
	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...
1988
		self::variation_bulk_set( $variations, 'regular_price', $data['value'] );
1989
	}
1990
1991
	/**
1992
	 * Bulk action - Set Sale Prices.
1993
	 *
1994
	 * @param array $variations
1995
	 * @param array $data
1996
	 *
1997
	 * @used-by bulk_edit_variations
1998
	 */
1999
	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...
2000
		self::variation_bulk_set( $variations, 'sale_price', $data['value'] );
2001
	}
2002
2003
	/**
2004
	 * Bulk action - Set Stock Status as In Stock.
2005
	 *
2006
	 * @param array $variations
2007
	 * @param array $data
2008
	 *
2009
	 * @used-by bulk_edit_variations
2010
	 */
2011
	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...
2012
		self::variation_bulk_set( $variations, 'stock_status', 'instock' );
2013
	}
2014
2015
	/**
2016
	 * Bulk action - Set Stock Status as Out of Stock.
2017
	 *
2018
	 * @param array $variations
2019
	 * @param array $data
2020
	 *
2021
	 * @used-by bulk_edit_variations
2022
	 */
2023
	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...
2024
		self::variation_bulk_set( $variations, 'stock_status', 'outofstock' );
2025
	}
2026
2027
	/**
2028
	 * Bulk action - Set Stock Status as On Backorder.
2029
	 *
2030
	 * @param array $variations
2031
	 * @param array $data
2032
	 *
2033
	 * @used-by bulk_edit_variations
2034
	 */
2035
	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...
2036
		self::variation_bulk_set( $variations, 'stock_status', 'onbackorder' );
2037
	}
2038
2039
	/**
2040
	 * Bulk action - Set Stock.
2041
	 *
2042
	 * @param array $variations
2043
	 * @param array $data
2044
	 *
2045
	 * @used-by bulk_edit_variations
2046
	 */
2047
	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...
2048
		if ( ! isset( $data['value'] ) ) {
2049
			return;
2050
		}
2051
2052
		$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...
2053
2054
		foreach ( $variations as $variation_id ) {
2055
			$variation = wc_get_product( $variation_id );
2056
			if ( $variation->managing_stock() ) {
2057
				$variation->set_stock_quantity( $quantity );
2058
			} else {
2059
				$variation->set_stock_quantity( null );
2060
			}
2061
			$variation->save();
2062
		}
2063
	}
2064
2065
	/**
2066
	 * Bulk action - Set Weight.
2067
	 *
2068
	 * @param array $variations
2069
	 * @param array $data
2070
	 *
2071
	 * @used-by bulk_edit_variations
2072
	 */
2073
	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...
2074
		self::variation_bulk_set( $variations, 'weight', $data['value'] );
2075
	}
2076
2077
	/**
2078
	 * Bulk action - Set Length.
2079
	 *
2080
	 * @param array $variations
2081
	 * @param array $data
2082
	 *
2083
	 * @used-by bulk_edit_variations
2084
	 */
2085
	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...
2086
		self::variation_bulk_set( $variations, 'length', $data['value'] );
2087
	}
2088
2089
	/**
2090
	 * Bulk action - Set Width.
2091
	 *
2092
	 * @param array $variations
2093
	 * @param array $data
2094
	 *
2095
	 * @used-by bulk_edit_variations
2096
	 */
2097
	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...
2098
		self::variation_bulk_set( $variations, 'width', $data['value'] );
2099
	}
2100
2101
	/**
2102
	 * Bulk action - Set Height.
2103
	 *
2104
	 * @param array $variations
2105
	 * @param array $data
2106
	 *
2107
	 * @used-by bulk_edit_variations
2108
	 */
2109
	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...
2110
		self::variation_bulk_set( $variations, 'height', $data['value'] );
2111
	}
2112
2113
	/**
2114
	 * Bulk action - Set Download Limit.
2115
	 *
2116
	 * @param array $variations
2117
	 * @param array $data
2118
	 *
2119
	 * @used-by bulk_edit_variations
2120
	 */
2121
	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...
2122
		self::variation_bulk_set( $variations, 'download_limit', $data['value'] );
2123
	}
2124
2125
	/**
2126
	 * Bulk action - Set Download Expiry.
2127
	 *
2128
	 * @param array $variations
2129
	 * @param array $data
2130
	 *
2131
	 * @used-by bulk_edit_variations
2132
	 */
2133
	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...
2134
		self::variation_bulk_set( $variations, 'download_expiry', $data['value'] );
2135
	}
2136
2137
	/**
2138
	 * Bulk action - Delete all.
2139
	 *
2140
	 * @param array $variations
2141
	 * @param array $data
2142
	 *
2143
	 * @used-by bulk_edit_variations
2144
	 */
2145
	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...
2146
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2147
			foreach ( $variations as $variation_id ) {
2148
				$variation = wc_get_product( $variation_id );
2149
				$variation->delete( true );
2150
			}
2151
		}
2152
	}
2153
2154
	/**
2155
	 * Bulk action - Sale Schedule.
2156
	 *
2157
	 * @param array $variations
2158
	 * @param array $data
2159
	 *
2160
	 * @used-by bulk_edit_variations
2161
	 */
2162
	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...
2163
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2164
			return;
2165
		}
2166
2167
		foreach ( $variations as $variation_id ) {
2168
			$variation = wc_get_product( $variation_id );
2169
2170
			if ( 'false' !== $data['date_from'] ) {
2171
				$variation->set_date_on_sale_from( wc_clean( $data['date_from'] ) );
2172
			}
2173
2174
			if ( 'false' !== $data['date_to'] ) {
2175
				$variation->set_date_on_sale_to( wc_clean( $data['date_to'] ) );
2176
			}
2177
2178
			$variation->save();
2179
		}
2180
	}
2181
2182
	/**
2183
	 * Bulk action - Increase Regular Prices.
2184
	 *
2185
	 * @param array $variations
2186
	 * @param array $data
2187
	 *
2188
	 * @used-by bulk_edit_variations
2189
	 */
2190
	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...
2191
		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...
2192
	}
2193
2194
	/**
2195
	 * Bulk action - Decrease Regular Prices.
2196
	 *
2197
	 * @param array $variations
2198
	 * @param array $data
2199
	 *
2200
	 * @used-by bulk_edit_variations
2201
	 */
2202
	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...
2203
		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...
2204
	}
2205
2206
	/**
2207
	 * Bulk action - Increase Sale Prices.
2208
	 *
2209
	 * @param array $variations
2210
	 * @param array $data
2211
	 *
2212
	 * @used-by bulk_edit_variations
2213
	 */
2214
	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...
2215
		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...
2216
	}
2217
2218
	/**
2219
	 * Bulk action - Decrease Sale Prices.
2220
	 *
2221
	 * @param array $variations
2222
	 * @param array $data
2223
	 *
2224
	 * @used-by bulk_edit_variations
2225
	 */
2226
	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...
2227
		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...
2228
	}
2229
2230
	/**
2231
	 * Bulk action - Set Price.
2232
	 *
2233
	 * @param array  $variations
2234
	 * @param string $operator + or -
2235
	 * @param string $field price being adjusted _regular_price or _sale_price
2236
	 * @param string $value Price or Percent
2237
	 *
2238
	 * @used-by bulk_edit_variations
2239
	 */
2240
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2241
		foreach ( $variations as $variation_id ) {
2242
			$variation   = wc_get_product( $variation_id );
2243
			$field_value = $variation->{"get_$field"}( 'edit' );
2244
2245
			if ( '%' === substr( $value, -1 ) ) {
2246
				$percent      = wc_format_decimal( substr( $value, 0, -1 ) );
2247
				$field_value += round( ( $field_value / 100 ) * $percent, wc_get_price_decimals() ) * "{$operator}1";
2248
			} else {
2249
				$field_value += $value * "{$operator}1";
2250
			}
2251
2252
			$variation->{"set_$field"}( $field_value );
2253
			$variation->save();
2254
		}
2255
	}
2256
2257
	/**
2258
	 * Bulk set convenience function.
2259
	 *
2260
	 * @param array  $variations
2261
	 * @param string $field
2262
	 * @param string $value
2263
	 */
2264
	private static function variation_bulk_set( $variations, $field, $value ) {
2265
		foreach ( $variations as $variation_id ) {
2266
			$variation = wc_get_product( $variation_id );
2267
			$variation->{ "set_$field" }( wc_clean( $value ) );
2268
			$variation->save();
2269
		}
2270
	}
2271
2272
	/**
2273
	 * Bulk toggle convenience function.
2274
	 *
2275
	 * @param array  $variations
2276
	 * @param string $field
2277
	 */
2278
	private static function variation_bulk_toggle( $variations, $field ) {
2279
		foreach ( $variations as $variation_id ) {
2280
			$variation  = wc_get_product( $variation_id );
2281
			$prev_value = $variation->{ "get_$field" }( 'edit' );
2282
			$variation->{ "set_$field" }( ! $prev_value );
2283
			$variation->save();
2284
		}
2285
	}
2286
2287
	/**
2288
	 * Bulk edit variations via AJAX.
2289
	 *
2290
	 * @uses WC_AJAX::variation_bulk_set()
2291
	 * @uses WC_AJAX::variation_bulk_adjust_price()
2292
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price_decrease()
2293
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price_increase()
2294
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price_decrease()
2295
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price_increase()
2296
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_schedule()
2297
	 * @uses WC_AJAX::variation_bulk_action_delete_all()
2298
	 * @uses WC_AJAX::variation_bulk_action_variable_download_expiry()
2299
	 * @uses WC_AJAX::variation_bulk_action_variable_download_limit()
2300
	 * @uses WC_AJAX::variation_bulk_action_variable_height()
2301
	 * @uses WC_AJAX::variation_bulk_action_variable_width()
2302
	 * @uses WC_AJAX::variation_bulk_action_variable_length()
2303
	 * @uses WC_AJAX::variation_bulk_action_variable_weight()
2304
	 * @uses WC_AJAX::variation_bulk_action_variable_stock()
2305
	 * @uses WC_AJAX::variation_bulk_action_variable_sale_price()
2306
	 * @uses WC_AJAX::variation_bulk_action_variable_regular_price()
2307
	 * @uses WC_AJAX::variation_bulk_action_toggle_manage_stock()
2308
	 * @uses WC_AJAX::variation_bulk_action_toggle_virtual()
2309
	 * @uses WC_AJAX::variation_bulk_action_toggle_downloadable()
2310
	 * @uses WC_AJAX::variation_bulk_action_toggle_enabled
2311
	 */
2312
	public static function bulk_edit_variations() {
2313
		ob_start();
2314
2315
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2316
2317
		// Check permissions again and make sure we have what we need
2318 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) || empty( $_POST['bulk_action'] ) ) {
2319
			wp_die( -1 );
2320
		}
2321
2322
		$product_id  = absint( $_POST['product_id'] );
2323
		$bulk_action = wc_clean( $_POST['bulk_action'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2324
		$data        = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array();
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2325
		$variations  = array();
2326
2327
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2328
			$variations = get_posts(
2329
				array(
2330
					'post_parent'    => $product_id,
2331
					'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...
2332
					'post_type'      => 'product_variation',
2333
					'fields'         => 'ids',
2334
					'post_status'    => array( 'publish', 'private' ),
2335
				)
2336
			);
2337
		}
2338
2339
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2340
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2341
		} else {
2342
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2343
		}
2344
2345
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2346
		WC_Product_Variable::sync( $product_id );
2347
		wc_delete_product_transients( $product_id );
2348
		wp_die();
2349
	}
2350
2351
	/**
2352
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2353
	 */
2354
	public static function tax_rates_save_changes() {
2355
		if ( ! isset( $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2356
			wp_send_json_error( 'missing_fields' );
2357
			wp_die();
2358
		}
2359
2360
		$current_class = ! empty( $_POST['current_class'] ) ? $_POST['current_class'] : ''; // This is sanitized seven lines later.
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2361
2362
		if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) {
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2363
			wp_send_json_error( 'bad_nonce' );
2364
			wp_die();
2365
		}
2366
2367
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
2368
2369
		// Check User Caps
2370
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2371
			wp_send_json_error( 'missing_capabilities' );
2372
			wp_die();
2373
		}
2374
2375
		$changes = stripslashes_deep( $_POST['changes'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2376
		foreach ( $changes as $tax_rate_id => $data ) {
2377
			if ( isset( $data['deleted'] ) ) {
2378
				if ( isset( $data['newRow'] ) ) {
2379
					// So the user added and deleted a new row.
2380
					// That's fine, it's not in the database anyways. NEXT!
2381
					continue;
2382
				}
2383
				WC_Tax::_delete_tax_rate( $tax_rate_id );
2384
			}
2385
2386
			$tax_rate = array_intersect_key(
2387
				$data, array(
2388
					'tax_rate_country'  => 1,
2389
					'tax_rate_state'    => 1,
2390
					'tax_rate'          => 1,
2391
					'tax_rate_name'     => 1,
2392
					'tax_rate_priority' => 1,
2393
					'tax_rate_compound' => 1,
2394
					'tax_rate_shipping' => 1,
2395
					'tax_rate_order'    => 1,
2396
				)
2397
			);
2398
2399
			if ( isset( $tax_rate['tax_rate'] ) ) {
2400
				$tax_rate['tax_rate'] = wc_format_decimal( $tax_rate['tax_rate'] );
2401
			}
2402
2403
			if ( isset( $data['newRow'] ) ) {
2404
				$tax_rate['tax_rate_class'] = $current_class;
2405
				$tax_rate_id                = WC_Tax::_insert_tax_rate( $tax_rate );
2406
			} elseif ( ! empty( $tax_rate ) ) {
2407
				WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
2408
			}
2409
2410
			if ( isset( $data['postcode'] ) ) {
2411
				$postcode = array_map( 'wc_clean', $data['postcode'] );
2412
				$postcode = array_map( 'wc_normalize_postcode', $postcode );
2413
				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...
2414
			}
2415
			if ( isset( $data['city'] ) ) {
2416
				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...
2417
			}
2418
		}
2419
2420
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
2421
		WC_Cache_Helper::get_transient_version( 'shipping', true );
2422
2423
		wp_send_json_success(
2424
			array(
2425
				'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
2426
			)
2427
		);
2428
	}
2429
2430
	/**
2431
	 * Handle submissions from assets/js/wc-shipping-zones.js Backbone model.
2432
	 */
2433
	public static function shipping_zones_save_changes() {
2434
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['changes'] ) ) {
2435
			wp_send_json_error( 'missing_fields' );
2436
			wp_die();
2437
		}
2438
2439
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2440
			wp_send_json_error( 'bad_nonce' );
2441
			wp_die();
2442
		}
2443
2444
		// Check User Caps
2445
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2446
			wp_send_json_error( 'missing_capabilities' );
2447
			wp_die();
2448
		}
2449
2450
		$changes = $_POST['changes'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2451
		foreach ( $changes as $zone_id => $data ) {
2452
			if ( isset( $data['deleted'] ) ) {
2453
				if ( isset( $data['newRow'] ) ) {
2454
					// So the user added and deleted a new row.
2455
					// That's fine, it's not in the database anyways. NEXT!
2456
					continue;
2457
				}
2458
				WC_Shipping_Zones::delete_zone( $zone_id );
2459
				continue;
2460
			}
2461
2462
			$zone_data = array_intersect_key(
2463
				$data, array(
2464
					'zone_id'    => 1,
2465
					'zone_order' => 1,
2466
				)
2467
			);
2468
2469
			if ( isset( $zone_data['zone_id'] ) ) {
2470
				$zone = new WC_Shipping_Zone( $zone_data['zone_id'] );
2471
2472
				if ( isset( $zone_data['zone_order'] ) ) {
2473
					$zone->set_zone_order( $zone_data['zone_order'] );
2474
				}
2475
2476
				$zone->save();
2477
			}
2478
		}
2479
2480
		wp_send_json_success(
2481
			array(
2482
				'zones' => WC_Shipping_Zones::get_zones( 'json' ),
2483
			)
2484
		);
2485
	}
2486
2487
	/**
2488
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
2489
	 */
2490
	public static function shipping_zone_add_method() {
2491 View Code Duplication
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['method_id'] ) ) {
2492
			wp_send_json_error( 'missing_fields' );
2493
			wp_die();
2494
		}
2495
2496
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2497
			wp_send_json_error( 'bad_nonce' );
2498
			wp_die();
2499
		}
2500
2501
		// Check User Caps
2502
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2503
			wp_send_json_error( 'missing_capabilities' );
2504
			wp_die();
2505
		}
2506
2507
		$zone_id     = wc_clean( $_POST['zone_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2508
		$zone        = new WC_Shipping_Zone( $zone_id );
2509
		$instance_id = $zone->add_shipping_method( wc_clean( $_POST['method_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2510
2511
		wp_send_json_success(
2512
			array(
2513
				'instance_id' => $instance_id,
2514
				'zone_id'     => $zone->get_id(),
2515
				'zone_name'   => $zone->get_zone_name(),
2516
				'methods'     => $zone->get_shipping_methods( false, 'json' ),
2517
			)
2518
		);
2519
	}
2520
2521
	/**
2522
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
2523
	 */
2524
	public static function shipping_zone_methods_save_changes() {
2525 View Code Duplication
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['changes'] ) ) {
2526
			wp_send_json_error( 'missing_fields' );
2527
			wp_die();
2528
		}
2529
2530
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2531
			wp_send_json_error( 'bad_nonce' );
2532
			wp_die();
2533
		}
2534
2535
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2536
			wp_send_json_error( 'missing_capabilities' );
2537
			wp_die();
2538
		}
2539
2540
		global $wpdb;
2541
2542
		$zone_id = wc_clean( $_POST['zone_id'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2543
		$zone    = new WC_Shipping_Zone( $zone_id );
2544
		$changes = $_POST['changes'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2545
2546
		if ( isset( $changes['zone_name'] ) ) {
2547
			$zone->set_zone_name( wc_clean( $changes['zone_name'] ) );
2548
		}
2549
2550
		if ( isset( $changes['zone_locations'] ) ) {
2551
			$zone->clear_locations( array( 'state', 'country', 'continent' ) );
2552
			$locations = array_filter( array_map( 'wc_clean', (array) $changes['zone_locations'] ) );
2553
			foreach ( $locations as $location ) {
2554
				// Each posted location will be in the format type:code
2555
				$location_parts = explode( ':', $location );
2556
				switch ( $location_parts[0] ) {
2557
					case 'state':
2558
						$zone->add_location( $location_parts[1] . ':' . $location_parts[2], 'state' );
2559
						break;
2560
					case 'country':
2561
						$zone->add_location( $location_parts[1], 'country' );
2562
						break;
2563
					case 'continent':
2564
						$zone->add_location( $location_parts[1], 'continent' );
2565
						break;
2566
				}
2567
			}
2568
		}
2569
2570
		if ( isset( $changes['zone_postcodes'] ) ) {
2571
			$zone->clear_locations( 'postcode' );
2572
			$postcodes = array_filter( array_map( 'strtoupper', array_map( 'wc_clean', explode( "\n", $changes['zone_postcodes'] ) ) ) );
2573
			foreach ( $postcodes as $postcode ) {
2574
				$zone->add_location( $postcode, 'postcode' );
2575
			}
2576
		}
2577
2578
		if ( isset( $changes['methods'] ) ) {
2579
			foreach ( $changes['methods'] as $instance_id => $data ) {
2580
				$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...
2581
2582
				if ( isset( $data['deleted'] ) ) {
2583
					$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
2584
					$option_key      = $shipping_method->get_instance_option_key();
2585
					if ( $wpdb->delete( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'instance_id' => $instance_id ) ) ) {
2586
						delete_option( $option_key );
2587
						do_action( 'woocommerce_shipping_zone_method_deleted', $instance_id, $method_id, $zone_id );
2588
					}
2589
					continue;
2590
				}
2591
2592
				$method_data = array_intersect_key(
2593
					$data, array(
2594
						'method_order' => 1,
2595
						'enabled'      => 1,
2596
					)
2597
				);
2598
2599
				if ( isset( $method_data['method_order'] ) ) {
2600
					$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...
2601
				}
2602
2603
				if ( isset( $method_data['enabled'] ) ) {
2604
					$is_enabled = absint( 'yes' === $method_data['enabled'] );
2605
					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...
2606
						do_action( 'woocommerce_shipping_zone_method_status_toggled', $instance_id, $method_id, $zone_id, $is_enabled );
2607
					}
2608
				}
2609
			}
2610
		}
2611
2612
		$zone->save();
2613
2614
		wp_send_json_success(
2615
			array(
2616
				'zone_id'   => $zone->get_id(),
2617
				'zone_name' => $zone->get_zone_name(),
2618
				'methods'   => $zone->get_shipping_methods( false, 'json' ),
2619
			)
2620
		);
2621
	}
2622
2623
	/**
2624
	 * Save method settings
2625
	 */
2626
	public static function shipping_zone_methods_save_settings() {
2627
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['instance_id'], $_POST['data'] ) ) {
2628
			wp_send_json_error( 'missing_fields' );
2629
			wp_die();
2630
		}
2631
2632
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2633
			wp_send_json_error( 'bad_nonce' );
2634
			wp_die();
2635
		}
2636
2637
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2638
			wp_send_json_error( 'missing_capabilities' );
2639
			wp_die();
2640
		}
2641
2642
		$instance_id     = absint( $_POST['instance_id'] );
2643
		$zone            = WC_Shipping_Zones::get_zone_by( 'instance_id', $instance_id );
2644
		$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
2645
		$shipping_method->set_post_data( $_POST['data'] );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2646
		$shipping_method->process_admin_options();
2647
2648
		WC_Cache_Helper::get_transient_version( 'shipping', true );
2649
2650
		wp_send_json_success(
2651
			array(
2652
				'zone_id'   => $zone->get_id(),
2653
				'zone_name' => $zone->get_zone_name(),
2654
				'methods'   => $zone->get_shipping_methods( false, 'json' ),
2655
				'errors'    => $shipping_method->get_errors(),
2656
			)
2657
		);
2658
	}
2659
2660
	/**
2661
	 * Handle submissions from assets/js/wc-shipping-classes.js Backbone model.
2662
	 */
2663
	public static function shipping_classes_save_changes() {
2664
		if ( ! isset( $_POST['wc_shipping_classes_nonce'], $_POST['changes'] ) ) {
2665
			wp_send_json_error( 'missing_fields' );
2666
			wp_die();
2667
		}
2668
2669
		if ( ! wp_verify_nonce( $_POST['wc_shipping_classes_nonce'], 'wc_shipping_classes_nonce' ) ) {
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2670
			wp_send_json_error( 'bad_nonce' );
2671
			wp_die();
2672
		}
2673
2674
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2675
			wp_send_json_error( 'missing_capabilities' );
2676
			wp_die();
2677
		}
2678
2679
		$changes = $_POST['changes'];
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2680
2681
		foreach ( $changes as $term_id => $data ) {
2682
			$term_id = absint( $term_id );
2683
2684
			if ( isset( $data['deleted'] ) ) {
2685
				if ( isset( $data['newRow'] ) ) {
2686
					// So the user added and deleted a new row.
2687
					// That's fine, it's not in the database anyways. NEXT!
2688
					continue;
2689
				}
2690
				wp_delete_term( $term_id, 'product_shipping_class' );
2691
				continue;
2692
			}
2693
2694
			$update_args = array();
2695
2696
			if ( isset( $data['name'] ) ) {
2697
				$update_args['name'] = wc_clean( $data['name'] );
2698
			}
2699
2700
			if ( isset( $data['slug'] ) ) {
2701
				$update_args['slug'] = wc_clean( $data['slug'] );
2702
			}
2703
2704
			if ( isset( $data['description'] ) ) {
2705
				$update_args['description'] = wc_clean( $data['description'] );
2706
			}
2707
2708
			if ( isset( $data['newRow'] ) ) {
2709
				$update_args = array_filter( $update_args );
2710
				if ( empty( $update_args['name'] ) ) {
2711
					continue;
2712
				}
2713
				$inserted_term = wp_insert_term( $update_args['name'], 'product_shipping_class', $update_args );
2714
				$term_id       = is_wp_error( $inserted_term ) ? 0 : $inserted_term['term_id'];
2715
			} else {
2716
				wp_update_term( $term_id, 'product_shipping_class', $update_args );
2717
			}
2718
2719
			do_action( 'woocommerce_shipping_classes_save_class', $term_id, $data );
2720
		}
2721
2722
		$wc_shipping = WC_Shipping::instance();
2723
2724
		wp_send_json_success(
2725
			array(
2726
				'shipping_classes' => $wc_shipping->get_shipping_classes(),
2727
			)
2728
		);
2729
	}
2730
2731
	/**
2732
	 * Toggle payment gateway on or off via AJAX.
2733
	 *
2734
	 * @since 3.4.0
2735
	 */
2736
	public static function toggle_gateway_enabled() {
2737
		if ( current_user_can( 'manage_woocommerce' ) && check_ajax_referer( 'woocommerce-toggle-payment-gateway-enabled', 'security' ) ) {
2738
			// Load gateways.
2739
			$payment_gateways = WC()->payment_gateways->payment_gateways();
2740
2741
			// Get posted gateway.
2742
			$gateway_id = wc_clean( wp_unslash( $_POST['gateway_id'] ) );
0 ignored issues
show
introduced by
Detected usage of a non-validated input variable: $_POST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
2743
2744
			foreach ( $payment_gateways as $gateway ) {
2745
				if ( ! in_array( $gateway_id, array( $gateway->id, sanitize_title( get_class( $gateway ) ) ), true ) ) {
2746
					continue;
2747
				}
2748
				$enabled = $gateway->get_option( 'enabled', 'no' );
2749
2750
				if ( ! wc_string_to_bool( $enabled ) ) {
2751
					if ( $gateway->needs_setup() ) {
2752
						wp_send_json_error( 'needs_setup' );
2753
						wp_die();
2754
					} else {
2755
						$gateway->update_option( 'enabled', 'yes' );
2756
					}
2757
				} else {
2758
					// Disable the gateway.
2759
					$gateway->update_option( 'enabled', 'no' );
2760
				}
2761
2762
				wp_send_json_success( ! wc_string_to_bool( $enabled ) );
2763
				wp_die();
2764
			}
2765
		}
2766
2767
		wp_send_json_error( 'invalid_gateway_id' );
2768
		wp_die();
2769
	}
2770
}
2771
2772
WC_AJAX::init();
2773