Completed
Pull Request — master (#10259)
by Mike
08:16
created

WC_AJAX::add_order_item()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
65
		send_nosniff_header();
66
		nocache_headers();
67
		status_header( 200 );
68
	}
69
70
	/**
71
	 * Check for WC Ajax request and fire action.
72
	 */
73
	public static function do_wc_ajax() {
74
		global $wp_query;
75
76
		if ( ! empty( $_GET['wc-ajax'] ) ) {
77
			$wp_query->set( 'wc-ajax', sanitize_text_field( $_GET['wc-ajax'] ) );
78
		}
79
80
		if ( $action = $wp_query->get( 'wc-ajax' ) ) {
81
			self::wc_ajax_headers();
82
			do_action( 'wc_ajax_' . sanitize_text_field( $action ) );
83
			die();
84
		}
85
	}
86
87
	/**
88
	 * Hook in methods - uses WordPress ajax handlers (admin-ajax).
89
	 */
90
	public static function add_ajax_events() {
91
		// woocommerce_EVENT => nopriv
92
		$ajax_events = array(
93
			'get_refreshed_fragments'                          => true,
94
			'apply_coupon'                                     => true,
95
			'remove_coupon'                                    => true,
96
			'update_shipping_method'                           => true,
97
			'get_cart_totals'                                  => true,
98
			'update_order_review'                              => true,
99
			'add_to_cart'                                      => true,
100
			'checkout'                                         => true,
101
			'get_variation'                                    => true,
102
			'feature_product'                                  => false,
103
			'mark_order_status'                                => false,
104
			'add_attribute'                                    => false,
105
			'add_new_attribute'                                => false,
106
			'remove_variation'                                 => false,
107
			'remove_variations'                                => false,
108
			'save_attributes'                                  => false,
109
			'add_variation'                                    => false,
110
			'link_all_variations'                              => false,
111
			'revoke_access_to_download'                        => false,
112
			'grant_access_to_download'                         => false,
113
			'get_customer_details'                             => false,
114
			'add_order_item'                                   => false,
115
			'add_order_fee'                                    => false,
116
			'add_order_shipping'                               => false,
117
			'add_order_tax'                                    => false,
118
			'remove_order_item'                                => false,
119
			'remove_order_tax'                                 => false,
120
			'reduce_order_item_stock'                          => false,
121
			'increase_order_item_stock'                        => false,
122
			'add_order_item_meta'                              => false,
123
			'remove_order_item_meta'                           => false,
124
			'calc_line_taxes'                                  => false,
125
			'save_order_items'                                 => false,
126
			'load_order_items'                                 => false,
127
			'add_order_note'                                   => false,
128
			'delete_order_note'                                => false,
129
			'json_search_products'                             => false,
130
			'json_search_products_and_variations'              => false,
131
			'json_search_grouped_products'                     => false,
132
			'json_search_downloadable_products_and_variations' => false,
133
			'json_search_customers'                            => false,
134
			'term_ordering'                                    => false,
135
			'product_ordering'                                 => false,
136
			'refund_line_items'                                => false,
137
			'delete_refund'                                    => false,
138
			'rated'                                            => false,
139
			'update_api_key'                                   => false,
140
			'get_customer_location'                            => true,
141
			'load_variations'                                  => false,
142
			'save_variations'                                  => false,
143
			'bulk_edit_variations'                             => false,
144
			'tax_rates_save_changes'                           => false,
145
			'shipping_zones_save_changes'                      => false,
146
			'shipping_zone_add_method'                         => false,
147
			'shipping_zone_methods_save_changes'               => false,
148
			'shipping_classes_save_changes'                    => false,
149
		);
150
151
		foreach ( $ajax_events as $ajax_event => $nopriv ) {
152
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
153
154
			if ( $nopriv ) {
155
				add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
156
157
				// WC AJAX can be used for frontend ajax requests
158
				add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
159
			}
160
		}
161
	}
162
163
	/**
164
	 * Get a refreshed cart fragment.
165
	 */
166
	public static function get_refreshed_fragments() {
167
168
		// Get mini cart
169
		ob_start();
170
171
		woocommerce_mini_cart();
172
173
		$mini_cart = ob_get_clean();
174
175
		// Fragments and mini cart are returned
176
		$data = array(
177
			'fragments' => apply_filters( 'woocommerce_add_to_cart_fragments', array(
178
					'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>'
179
				)
180
			),
181
			'cart_hash' => apply_filters( 'woocommerce_add_to_cart_hash', WC()->cart->get_cart_for_session() ? md5( json_encode( WC()->cart->get_cart_for_session() ) ) : '', WC()->cart->get_cart_for_session() )
182
		);
183
184
		wp_send_json( $data );
185
186
	}
187
188
	/**
189
	 * AJAX apply coupon on checkout page.
190
	 */
191
	public static function apply_coupon() {
192
193
		check_ajax_referer( 'apply-coupon', 'security' );
194
195
		if ( ! empty( $_POST['coupon_code'] ) ) {
196
			WC()->cart->add_discount( sanitize_text_field( $_POST['coupon_code'] ) );
197
		} else {
198
			wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
199
		}
200
201
		wc_print_notices();
202
203
		die();
204
	}
205
206
	/**
207
	 * AJAX remove coupon on cart and checkout page.
208
	 */
209
	public static function remove_coupon() {
210
211
		check_ajax_referer( 'remove-coupon', 'security' );
212
213
		$coupon = wc_clean( $_POST['coupon'] );
214
215
		if ( ! isset( $coupon ) || empty( $coupon ) ) {
216
			wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ) );
217
218
		} else {
219
220
			WC()->cart->remove_coupon( $coupon );
1 ignored issue
show
Bug introduced by
It seems like $coupon defined by wc_clean($_POST['coupon']) on line 213 can also be of type array; however, WC_Cart::remove_coupon() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
221
222
			wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
223
		}
224
225
		wc_print_notices();
226
227
		die();
228
	}
229
230
	/**
231
	 * AJAX update shipping method on cart page.
232
	 */
233
	public static function update_shipping_method() {
234
235
		check_ajax_referer( 'update-shipping-method', 'security' );
236
237
		if ( ! defined('WOOCOMMERCE_CART') ) {
238
			define( 'WOOCOMMERCE_CART', true );
239
		}
240
241
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
242
243 View Code Duplication
		if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
244
			foreach ( $_POST['shipping_method'] as $i => $value ) {
245
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
246
			}
247
		}
248
249
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
250
251
		WC()->cart->calculate_totals();
252
253
		woocommerce_cart_totals();
254
255
		die();
256
	}
257
258
	/**
259
	 * AJAX receive updated cart_totals div.
260
	 */
261
	public static function get_cart_totals() {
262
263
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
264
			define( 'WOOCOMMERCE_CART', true );
265
		}
266
267
		WC()->cart->calculate_totals();
268
269
		woocommerce_cart_totals();
270
271
		die();
272
	}
273
274
	/**
275
	 * AJAX update order review on checkout.
276
	 */
277
	public static function update_order_review() {
278
		ob_start();
279
280
		check_ajax_referer( 'update-order-review', 'security' );
281
282
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
283
			define( 'WOOCOMMERCE_CHECKOUT', true );
284
		}
285
286
		if ( WC()->cart->is_empty() ) {
287
			$data = array(
288
				'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
289
					'form.woocommerce-checkout' => '<div class="woocommerce-error">' . __( 'Sorry, your session has expired.', 'woocommerce' ) . ' <a href="' . home_url() . '" class="wc-backward">' . __( 'Return to homepage', 'woocommerce' ) . '</a></div>'
290
				) )
291
			);
292
293
			wp_send_json( $data );
294
295
			die();
296
		}
297
298
		do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] );
299
300
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
301
302 View Code Duplication
		if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
303
			foreach ( $_POST['shipping_method'] as $i => $value ) {
304
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
305
			}
306
		}
307
308
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
309
		WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
310
311
		if ( isset( $_POST['country'] ) ) {
312
			WC()->customer->set_country( $_POST['country'] );
313
		}
314
315
		if ( isset( $_POST['state'] ) ) {
316
			WC()->customer->set_state( $_POST['state'] );
317
		}
318
319
		if ( isset( $_POST['postcode'] ) ) {
320
			WC()->customer->set_postcode( $_POST['postcode'] );
321
		}
322
323
		if ( isset( $_POST['city'] ) ) {
324
			WC()->customer->set_city( $_POST['city'] );
325
		}
326
327
		if ( isset( $_POST['address'] ) ) {
328
			WC()->customer->set_address( $_POST['address'] );
329
		}
330
331
		if ( isset( $_POST['address_2'] ) ) {
332
			WC()->customer->set_address_2( $_POST['address_2'] );
333
		}
334
335
		if ( wc_ship_to_billing_address_only() ) {
336
337 View Code Duplication
			if ( isset( $_POST['country'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
338
				WC()->customer->set_shipping_country( $_POST['country'] );
339
				WC()->customer->calculated_shipping( true );
340
			}
341
342
			if ( isset( $_POST['state'] ) ) {
343
				WC()->customer->set_shipping_state( $_POST['state'] );
344
			}
345
346
			if ( isset( $_POST['postcode'] ) ) {
347
				WC()->customer->set_shipping_postcode( $_POST['postcode'] );
348
			}
349
350
			if ( isset( $_POST['city'] ) ) {
351
				WC()->customer->set_shipping_city( $_POST['city'] );
352
			}
353
354
			if ( isset( $_POST['address'] ) ) {
355
				WC()->customer->set_shipping_address( $_POST['address'] );
356
			}
357
358
			if ( isset( $_POST['address_2'] ) ) {
359
				WC()->customer->set_shipping_address_2( $_POST['address_2'] );
360
			}
361
		} else {
362
363 View Code Duplication
			if ( isset( $_POST['s_country'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
364
				WC()->customer->set_shipping_country( $_POST['s_country'] );
365
				WC()->customer->calculated_shipping( true );
366
			}
367
368
			if ( isset( $_POST['s_state'] ) ) {
369
				WC()->customer->set_shipping_state( $_POST['s_state'] );
370
			}
371
372
			if ( isset( $_POST['s_postcode'] ) ) {
373
				WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
374
			}
375
376
			if ( isset( $_POST['s_city'] ) ) {
377
				WC()->customer->set_shipping_city( $_POST['s_city'] );
378
			}
379
380
			if ( isset( $_POST['s_address'] ) ) {
381
				WC()->customer->set_shipping_address( $_POST['s_address'] );
382
			}
383
384
			if ( isset( $_POST['s_address_2'] ) ) {
385
				WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
386
			}
387
		}
388
389
		WC()->cart->calculate_totals();
390
391
		// Get order review fragment
392
		ob_start();
393
		woocommerce_order_review();
394
		$woocommerce_order_review = ob_get_clean();
395
396
		// Get checkout payment fragment
397
		ob_start();
398
		woocommerce_checkout_payment();
399
		$woocommerce_checkout_payment = ob_get_clean();
400
401
		// Get messages if reload checkout is not true
402
		$messages = '';
403
		if ( ! isset( WC()->session->reload_checkout ) ) {
404
			ob_start();
405
			wc_print_notices();
406
			$messages = ob_get_clean();
407
		}
408
409
		$data = array(
410
			'result'    => empty( $messages ) ? 'success' : 'failure',
411
			'messages'  => $messages,
412
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
413
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
414
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
415
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
416
			) )
417
		);
418
419
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
420
421
		wp_send_json( $data );
422
423
		die();
424
	}
425
426
	/**
427
	 * AJAX add to cart.
428
	 */
429
	public static function add_to_cart() {
430
		ob_start();
431
432
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
433
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
434
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
435
		$product_status    = get_post_status( $product_id );
436
437
		if ( $passed_validation && WC()->cart->add_to_cart( $product_id, $quantity ) && 'publish' === $product_status ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression WC()->cart->add_to_cart($product_id, $quantity) of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
438
439
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
440
441
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
442
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
443
			}
444
445
			// Return fragments
446
			self::get_refreshed_fragments();
447
448
		} else {
449
450
			// If there was an error adding to the cart, redirect to the product page to show any errors
451
			$data = array(
452
				'error'       => true,
453
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
454
			);
455
456
			wp_send_json( $data );
457
458
		}
459
460
		die();
461
	}
462
463
	/**
464
	 * Process ajax checkout form.
465
	 */
466
	public static function checkout() {
467
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
468
			define( 'WOOCOMMERCE_CHECKOUT', true );
469
		}
470
471
		WC()->checkout()->process_checkout();
472
473
		die(0);
474
	}
475
476
	/**
477
	 * Get a matching variation based on posted attributes.
478
	 */
479
	public static function get_variation() {
480
		ob_start();
481
482
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
483
			die();
484
		}
485
486
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
487
488
		if ( $variation_id ) {
489
			$variation = $variable_product->get_available_variation( $variation_id );
490
		} else {
491
			$variation = false;
492
		}
493
494
		wp_send_json( $variation );
495
496
		die();
497
	}
498
499
	/**
500
	 * Feature a product from admin.
501
	 */
502
	public static function feature_product() {
503
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
504
			$product_id = absint( $_GET['product_id'] );
505
506
			if ( 'product' === get_post_type( $product_id ) ) {
507
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
508
509
				delete_transient( 'wc_featured_products' );
510
			}
511
		}
512
513
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=shop_order' ) );
514
		die();
515
	}
516
517
	/**
518
	 * Mark an order with a status.
519
	 */
520
	public static function mark_order_status() {
521
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
522
			$status   = sanitize_text_field( $_GET['status'] );
523
			$order_id = absint( $_GET['order_id'] );
524
525
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
526
				$order = wc_get_order( $order_id );
527
				$order->update_status( $status, '', true );
528
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
529
			}
530
		}
531
532
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
533
		die();
534
	}
535
536
	/**
537
	 * Add an attribute row.
538
	 */
539
	public static function add_attribute() {
540
		ob_start();
541
542
		check_ajax_referer( 'add-attribute', 'security' );
543
544
		if ( ! current_user_can( 'edit_products' ) ) {
545
			die( -1 );
546
		}
547
548
		global $wc_product_attributes;
549
550
		$thepostid     = 0;
551
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
552
		$i             = absint( $_POST['i'] );
553
		$position      = 0;
554
		$metabox_class = array();
555
		$attribute     = array(
556
			'name'         => $taxonomy,
557
			'value'        => '',
558
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
559
			'is_variation' => 0,
560
			'is_taxonomy'  => $taxonomy ? 1 : 0
561
		);
562
563
		if ( $taxonomy ) {
564
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
565
			$metabox_class[]    = 'taxonomy';
566
			$metabox_class[]    = $taxonomy;
567
			$attribute_label    = wc_attribute_label( $taxonomy );
568
		} else {
569
			$attribute_label = '';
570
		}
571
572
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
573
		die();
574
	}
575
576
	/**
577
	 * Add a new attribute via ajax function.
578
	 */
579
	public static function add_new_attribute() {
580
		ob_start();
581
582
		check_ajax_referer( 'add-attribute', 'security' );
583
584
		if ( ! current_user_can( 'manage_product_terms' ) ) {
585
			die( -1 );
586
		}
587
588
		$taxonomy = esc_attr( $_POST['taxonomy'] );
589
		$term     = wc_clean( $_POST['term'] );
590
591
		if ( taxonomy_exists( $taxonomy ) ) {
592
593
			$result = wp_insert_term( $term, $taxonomy );
594
595
			if ( is_wp_error( $result ) ) {
596
				wp_send_json( array(
597
					'error' => $result->get_error_message()
598
				) );
599
			} else {
600
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
601
				wp_send_json( array(
602
					'term_id' => $term->term_id,
603
					'name'    => $term->name,
604
					'slug'    => $term->slug
605
				) );
606
			}
607
		}
608
609
		die();
610
	}
611
612
	/**
613
	 * Delete variations via ajax function.
614
	 */
615
	public static function remove_variations() {
616
		check_ajax_referer( 'delete-variations', 'security' );
617
618
		if ( ! current_user_can( 'edit_products' ) ) {
619
			die( -1 );
620
		}
621
622
		$variation_ids = (array) $_POST['variation_ids'];
623
624
		foreach ( $variation_ids as $variation_id ) {
625
			$variation = get_post( $variation_id );
626
627
			if ( $variation && 'product_variation' == $variation->post_type ) {
628
				wp_delete_post( $variation_id );
629
			}
630
		}
631
632
		die();
633
	}
634
635
	/**
636
	 * Save attributes via ajax.
637
	 */
638
	public static function save_attributes() {
639
640
		check_ajax_referer( 'save-attributes', 'security' );
641
642
		if ( ! current_user_can( 'edit_products' ) ) {
643
			die( -1 );
644
		}
645
646
		// Get post data
647
		parse_str( $_POST['data'], $data );
648
		$post_id = absint( $_POST['post_id'] );
649
650
		// Save Attributes
651
		$attributes = array();
652
653
		if ( isset( $data['attribute_names'] ) ) {
654
655
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
656
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
657
658
			if ( isset( $data['attribute_visibility'] ) ) {
659
				$attribute_visibility = $data['attribute_visibility'];
660
			}
661
662
			if ( isset( $data['attribute_variation'] ) ) {
663
				$attribute_variation = $data['attribute_variation'];
664
			}
665
666
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
667
			$attribute_position      = $data['attribute_position'];
668
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
669
670
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
671
				if ( empty( $attribute_names[ $i ] ) ) {
672
					continue;
673
				}
674
675
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
676
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
677
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
678
679
				if ( $is_taxonomy ) {
680
681
					if ( isset( $attribute_values[ $i ] ) ) {
682
683
						// Select based attributes - Format values (posted values are slugs)
684
						if ( is_array( $attribute_values[ $i ] ) ) {
685
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
686
687
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
688
						} else {
689
							$values     = array();
690
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
691
692
							foreach ( $raw_values as $value ) {
693
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
694
								if ( ! $term ) {
695
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
696
697
									if ( $term && ! is_wp_error( $term ) ) {
698
										$values[] = $term['term_id'];
699
									}
700
								} else {
701
									$values[] = $term->term_id;
702
								}
703
							}
704
						}
705
706
						// Remove empty items in the array
707
						$values = array_filter( $values, 'strlen' );
708
709
					} else {
710
						$values = array();
711
					}
712
713
					// Update post terms
714
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
715
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
716
					}
717
718 View Code Duplication
					if ( $values ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

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

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

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

Loading history...
719
						// Add attribute to array, but don't set values
720
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
721
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
722
							'value' 		=> '',
723
							'position' 		=> $attribute_position[ $i ],
724
							'is_visible' 	=> $is_visible,
725
							'is_variation' 	=> $is_variation,
726
							'is_taxonomy' 	=> $is_taxonomy
727
						);
728
					}
729
730 View Code Duplication
				} elseif ( isset( $attribute_values[ $i ] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
731
732
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
733
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
734
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
735
736
					// Custom attribute - Add attribute to array and set the values
737
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
738
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
739
						'value' 		=> $values,
740
						'position' 		=> $attribute_position[ $i ],
741
						'is_visible' 	=> $is_visible,
742
						'is_variation' 	=> $is_variation,
743
						'is_taxonomy' 	=> $is_taxonomy
744
					);
745
				}
746
747
			 }
748
		}
749
750
		if ( ! function_exists( 'attributes_cmp' ) ) {
751
			function attributes_cmp( $a, $b ) {
0 ignored issues
show
Best Practice introduced by
The function attributes_cmp() has been defined more than once; this definition is ignored, only the first definition in includes/admin/meta-boxe...ta-box-product-data.php (L965-971) is considered.

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

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

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

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
752
				if ( $a['position'] == $b['position'] ) {
753
					return 0;
754
				}
755
756
				return ( $a['position'] < $b['position'] ) ? -1 : 1;
757
			}
758
		}
759
		uasort( $attributes, 'attributes_cmp' );
760
761
		update_post_meta( $post_id, '_product_attributes', $attributes );
762
763
		die();
764
	}
765
766
	/**
767
	 * Add variation via ajax function.
768
	 */
769
	public static function add_variation() {
770
771
		check_ajax_referer( 'add-variation', 'security' );
772
773
		if ( ! current_user_can( 'edit_products' ) ) {
774
			die( -1 );
775
		}
776
777
		global $post;
778
779
		$post_id = intval( $_POST['post_id'] );
780
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
781
		$loop    = intval( $_POST['loop'] );
782
783
		$variation = array(
784
			'post_title'   => 'Product #' . $post_id . ' Variation',
785
			'post_content' => '',
786
			'post_status'  => 'publish',
787
			'post_author'  => get_current_user_id(),
788
			'post_parent'  => $post_id,
789
			'post_type'    => 'product_variation',
790
			'menu_order'   => -1
791
		);
792
793
		$variation_id = wp_insert_post( $variation );
794
795
		do_action( 'woocommerce_create_product_variation', $variation_id );
796
797
		if ( $variation_id ) {
798
			$variation        = get_post( $variation_id );
799
			$variation_meta   = get_post_meta( $variation_id );
800
			$variation_data   = array();
801
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
802
			$variation_fields = array(
803
				'_sku'                   => '',
804
				'_stock'                 => '',
805
				'_regular_price'         => '',
806
				'_sale_price'            => '',
807
				'_weight'                => '',
808
				'_length'                => '',
809
				'_width'                 => '',
810
				'_height'                => '',
811
				'_download_limit'        => '',
812
				'_download_expiry'       => '',
813
				'_downloadable_files'    => '',
814
				'_downloadable'          => '',
815
				'_virtual'               => '',
816
				'_thumbnail_id'          => '',
817
				'_sale_price_dates_from' => '',
818
				'_sale_price_dates_to'   => '',
819
				'_manage_stock'          => '',
820
				'_stock_status'          => '',
821
				'_backorders'            => null,
822
				'_tax_class'             => null,
823
				'_variation_description' => ''
824
			);
825
826 View Code Duplication
			foreach ( $variation_fields as $field => $value ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
827
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
828
			}
829
830
			// Add the variation attributes
831
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
832
833
			// Formatting
834
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
835
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
836
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
837
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
838
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
839
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
840
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
841
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
842
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
843
			$variation_data['menu_order']     = $variation->menu_order;
844
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
845
846
			// Get tax classes
847
			$tax_classes           = WC_Tax::get_tax_classes();
848
			$tax_class_options     = array();
849
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
850
851 View Code Duplication
			if ( ! empty( $tax_classes ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
852
				foreach ( $tax_classes as $class ) {
853
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
854
				}
855
			}
856
857
			// Set backorder options
858
			$backorder_options = array(
859
				'no'     => __( 'Do not allow', 'woocommerce' ),
860
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
861
				'yes'    => __( 'Allow', 'woocommerce' )
862
			);
863
864
			// set stock status options
865
			$stock_status_options = array(
866
				'instock'    => __( 'In stock', 'woocommerce' ),
867
				'outofstock' => __( 'Out of stock', 'woocommerce' )
868
			);
869
870
			// Get attributes
871
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
872
873
			$parent_data = array(
874
				'id'                   => $post_id,
875
				'attributes'           => $attributes,
876
				'tax_class_options'    => $tax_class_options,
877
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
878
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
879
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
880
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
881
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
882
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
883
				'backorder_options'    => $backorder_options,
884
				'stock_status_options' => $stock_status_options
885
			);
886
887
			if ( ! $parent_data['weight'] ) {
888
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
889
			}
890
891
			if ( ! $parent_data['length'] ) {
892
				$parent_data['length'] = wc_format_localized_decimal( 0 );
893
			}
894
895
			if ( ! $parent_data['width'] ) {
896
				$parent_data['width'] = wc_format_localized_decimal( 0 );
897
			}
898
899
			if ( ! $parent_data['height'] ) {
900
				$parent_data['height'] = wc_format_localized_decimal( 0 );
901
			}
902
903
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
904
		}
905
906
		die();
907
	}
908
909
	/**
910
	 * Link all variations via ajax function.
911
	 */
912
	public static function link_all_variations() {
913
914
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
915
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
916
		}
917
918
		check_ajax_referer( 'link-variations', 'security' );
919
920
		if ( ! current_user_can( 'edit_products' ) ) {
921
			die( -1 );
922
		}
923
924 View Code Duplication
		if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
926
		}
927
928
		$post_id = intval( $_POST['post_id'] );
929
930
		if ( ! $post_id ) {
931
			die();
932
		}
933
934
		$variations = array();
935
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
936
937
		// Put variation attributes into an array
938
		foreach ( $_product->get_attributes() as $attribute ) {
939
940
			if ( ! $attribute['is_variation'] ) {
941
				continue;
942
			}
943
944
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
945
946
			if ( $attribute['is_taxonomy'] ) {
947
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
948
			} else {
949
				$options = explode( WC_DELIMITER, $attribute['value'] );
950
			}
951
952
			$options = array_map( 'trim', $options );
953
954
			$variations[ $attribute_field_name ] = $options;
955
		}
956
957
		// Quit out if none were found
958
		if ( sizeof( $variations ) == 0 ) {
959
			die();
960
		}
961
962
		// Get existing variations so we don't create duplicates
963
		$available_variations = array();
964
965
		foreach( $_product->get_children() as $child_id ) {
966
			$child = $_product->get_child( $child_id );
967
968
			if ( ! empty( $child->variation_id ) ) {
969
				$available_variations[] = $child->get_variation_attributes();
970
			}
971
		}
972
973
		// Created posts will all have the following data
974
		$variation_post_data = array(
975
			'post_title'   => 'Product #' . $post_id . ' Variation',
976
			'post_content' => '',
977
			'post_status'  => 'publish',
978
			'post_author'  => get_current_user_id(),
979
			'post_parent'  => $post_id,
980
			'post_type'    => 'product_variation'
981
		);
982
983
		$variation_ids       = array();
984
		$added               = 0;
985
		$possible_variations = wc_array_cartesian( $variations );
986
987
		foreach ( $possible_variations as $variation ) {
988
989
			// Check if variation already exists
990
			if ( in_array( $variation, $available_variations ) ) {
991
				continue;
992
			}
993
994
			$variation_id = wp_insert_post( $variation_post_data );
995
996
			$variation_ids[] = $variation_id;
997
998
			foreach ( $variation as $key => $value ) {
999
				update_post_meta( $variation_id, $key, $value );
1000
			}
1001
1002
			// Save stock status
1003
			update_post_meta( $variation_id, '_stock_status', 'instock' );
1004
1005
			$added++;
1006
1007
			do_action( 'product_variation_linked', $variation_id );
1008
1009
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
1010
				break;
1011
			}
1012
		}
1013
1014
		delete_transient( 'wc_product_children_' . $post_id );
1015
1016
		echo $added;
1017
1018
		die();
1019
	}
1020
1021
	/**
1022
	 * Delete download permissions via ajax function.
1023
	 */
1024
	public static function revoke_access_to_download() {
1025
1026
		check_ajax_referer( 'revoke-access', 'security' );
1027
1028
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1029
			die( -1 );
1030
		}
1031
1032
		global $wpdb;
1033
1034
		$download_id = $_POST['download_id'];
1035
		$product_id  = intval( $_POST['product_id'] );
1036
		$order_id    = intval( $_POST['order_id'] );
1037
1038
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE order_id = %d AND product_id = %d AND download_id = %s;", $order_id, $product_id, $download_id ) );
1039
1040
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
1041
1042
		die();
1043
	}
1044
1045
	/**
1046
	 * Grant download permissions via ajax function.
1047
	 */
1048
	public static function grant_access_to_download() {
1049
1050
		check_ajax_referer( 'grant-access', 'security' );
1051
1052
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1053
			die( -1 );
1054
		}
1055
1056
		global $wpdb;
1057
1058
		$wpdb->hide_errors();
1059
1060
		$order_id     = intval( $_POST['order_id'] );
1061
		$product_ids  = $_POST['product_ids'];
1062
		$loop         = intval( $_POST['loop'] );
1063
		$file_counter = 0;
1064
		$order        = wc_get_order( $order_id );
1065
1066
		if ( ! is_array( $product_ids ) ) {
1067
			$product_ids = array( $product_ids );
1068
		}
1069
1070
		foreach ( $product_ids as $product_id ) {
1071
			$product = wc_get_product( $product_id );
1072
			$files   = $product->get_files();
1073
1074
			if ( ! $order->get_billing_email() ) {
1075
				die();
1076
			}
1077
1078
			if ( $files ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1079
				foreach ( $files as $download_id => $file ) {
1080
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
1081
1082
						// insert complete - get inserted data
1083
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1084
1085
						$loop ++;
1086
						$file_counter ++;
1087
1088 View Code Duplication
						if ( isset( $file['name'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1089
							$file_count = $file['name'];
1090
						} else {
1091
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1092
						}
1093
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1094
					}
1095
				}
1096
			}
1097
		}
1098
1099
		die();
1100
	}
1101
1102
	/**
1103
	 * Get customer details via ajax.
1104
	 */
1105
	public static function get_customer_details() {
1106
		ob_start();
1107
1108
		check_ajax_referer( 'get-customer-details', 'security' );
1109
1110
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1111
			die( -1 );
1112
		}
1113
1114
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1115
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1116
1117
		$customer_data = array(
1118
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1119
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1120
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1121
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1122
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1123
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1124
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1125
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1126
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1127
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1128
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1129
		);
1130
1131
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1132
1133
		wp_send_json( $customer_data );
1134
	}
1135
1136
	/**
1137
	 * Add order item via ajax.
1138
	 */
1139
	public static function add_order_item() {
1140
		check_ajax_referer( 'order-item', 'security' );
1141
1142
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1143
			die( -1 );
1144
		}
1145
1146
		$item_to_add = absint( $_POST['item_to_add'] );
1147
		$order_id    = absint( $_POST['order_id'] );
1148
		$order       = wc_get_order( $order_id );
1149
		$post_type   = get_post_type( $item_to_add );
1150
1151
		if ( ! in_array( $post_type, array( 'product', 'product_variation' ) ) ) {
1152
			die();
1153
		}
1154
1155
		$product     = wc_get_product( $item_to_add );
1156
		$order_taxes = $order->get_taxes();
1157
		$item        = new WC_Order_Item_Product();
1158
1159
		$item->set_order_id( $order->get_id() );
1160
		$item->set_product( $product );
1161
		$item->set_qty( 1 );
1162
		$item->set_subtotal( wc_format_decimal( $product->get_price_excluding_tax() ) );
0 ignored issues
show
Bug introduced by
It seems like wc_format_decimal($produ..._price_excluding_tax()) targeting wc_format_decimal() can also be of type array; however, WC_Order_Item_Product::set_subtotal() 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...
1163
		$item->set_total( wc_format_decimal( $product->get_price_excluding_tax() ) );
0 ignored issues
show
Bug introduced by
It seems like wc_format_decimal($produ..._price_excluding_tax()) targeting wc_format_decimal() can also be of type array; however, WC_Order_Item_Product::set_total() 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...
1164
		$item->save();
1165
1166
		include( 'admin/meta-boxes/views/html-order-item.php' );
1167
		die();
1168
	}
1169
1170
	/**
1171
	 * Add order fee via ajax.
1172
	 */
1173 View Code Duplication
	public static function add_order_fee() {
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...
1174
		check_ajax_referer( 'order-item', 'security' );
1175
1176
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1177
			die( -1 );
1178
		}
1179
1180
		$order_id = absint( $_POST['order_id'] );
1181
		$order    = wc_get_order( $order_id );
1182
1183
		if ( $order ) {
1184
			$order_taxes = $order->get_taxes();
1185
			$item        = new WC_Order_Item_Fee();
1186
1187
			$item->set_order_id( $order->get_id() );
1188
			$item->save();
1189
1190
			include( 'admin/meta-boxes/views/html-order-fee.php' );
1191
		}
1192
1193
		die();
1194
	}
1195
1196
	/**
1197
	 * Add order shipping cost via ajax.
1198
	 */
1199
	public static function add_order_shipping() {
1200
		check_ajax_referer( 'order-item', 'security' );
1201
1202
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1203
			die( -1 );
1204
		}
1205
1206
		$order_id         = absint( $_POST['order_id'] );
1207
		$order            = wc_get_order( $order_id );
1208
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1209
1210
		if ( $order ) {
1211
			$order_taxes = $order->get_taxes();
1212
			$item        = new WC_Order_Item_Shipping();
1213
1214
			$item->set_order_id( $order->get_id() );
1215
			$item->save();
1216
1217
			include( 'admin/meta-boxes/views/html-order-shipping.php' );
1218
		}
1219
1220
		die();
1221
	}
1222
1223
	/**
1224
	 * Add order tax column via ajax.
1225
	 */
1226 View Code Duplication
	public static function add_order_tax() {
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...
1227
		check_ajax_referer( 'order-item', 'security' );
1228
1229
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1230
			die( -1 );
1231
		}
1232
1233
		$order_id = absint( $_POST['order_id'] );
1234
		$order    = wc_get_order( $order_id );
1235
1236
		if ( $order ) {
1237
			$item = new WC_Order_Item_Tax();
1238
1239
			$item->set_rate_id( absint( $_POST['rate_id'] ) );
1240
			$item->set_order_id( $order->get_id() );
1241
			$item->save();
1242
1243
			include( 'admin/meta-boxes/views/html-order-items.php' );
1244
		}
1245
1246
		die();
1247
	}
1248
1249
	/**
1250
	 * Remove an order item.
1251
	 */
1252
	public static function remove_order_item() {
1253
		check_ajax_referer( 'order-item', 'security' );
1254
1255
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1256
			die( -1 );
1257
		}
1258
1259
		$order_item_ids = $_POST['order_item_ids'];
1260
1261
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1262
			$order_item_ids = array( $order_item_ids );
1263
		}
1264
1265
		$order_item_ids = array_map( 'absint', $order_item_ids );
1266
1267
		if ( sizeof( $order_item_ids ) > 0 ) {
1268
			foreach( $order_item_ids as $id ) {
1269
				wc_delete_order_item( $id );
1270
			}
1271
		}
1272
1273
		die();
1274
	}
1275
1276
	/**
1277
	 * Remove an order tax.
1278
	 */
1279 View Code Duplication
	public static function remove_order_tax() {
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...
1280
		check_ajax_referer( 'order-item', 'security' );
1281
1282
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1283
			die( -1 );
1284
		}
1285
1286
		$order_id = absint( $_POST['order_id'] );
1287
		$rate_id  = absint( $_POST['rate_id'] );
1288
		$order    = wc_get_order( $order_id );
1289
1290
		wc_delete_order_item( $rate_id );
1291
1292
		// Return HTML items
1293
		include( 'admin/meta-boxes/views/html-order-items.php' );
1294
		die();
1295
	}
1296
1297
	/**
1298
	 * Reduce order item stock.
1299
	 */
1300 View Code Duplication
	public static function reduce_order_item_stock() {
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...
1301
		check_ajax_referer( 'order-item', 'security' );
1302
1303
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1304
			die( -1 );
1305
		}
1306
1307
		$order_id       = absint( $_POST['order_id'] );
1308
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1309
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1310
		$order          = wc_get_order( $order_id );
1311
		$order_items    = $order->get_items();
1312
		$return         = array();
1313
1314
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1315
1316
			foreach ( $order_items as $item_id => $order_item ) {
1317
1318
				// Only reduce checked items
1319
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1320
					continue;
1321
				}
1322
1323
				$_product = $order_item->get_product();
1324
1325
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1326
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1327
					$new_stock    = $_product->reduce_stock( $stock_change );
1328
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1329
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1330
					$return[]     = $note;
1331
					$order->add_order_note( $note );
1332
				}
1333
			}
1334
1335
			do_action( 'woocommerce_reduce_order_stock', $order );
1336
1337
			if ( empty( $return ) ) {
1338
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1339
			}
1340
1341
			echo implode( ', ', $return );
1342
		}
1343
1344
		die();
1345
	}
1346
1347
	/**
1348
	 * Increase order item stock.
1349
	 */
1350 View Code Duplication
	public static function increase_order_item_stock() {
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...
1351
		check_ajax_referer( 'order-item', 'security' );
1352
1353
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1354
			die( -1 );
1355
		}
1356
1357
		$order_id       = absint( $_POST['order_id'] );
1358
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1359
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1360
		$order          = wc_get_order( $order_id );
1361
		$order_items    = $order->get_items();
1362
		$return         = array();
1363
1364
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1365
1366
			foreach ( $order_items as $item_id => $order_item ) {
1367
1368
				// Only reduce checked items
1369
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1370
					continue;
1371
				}
1372
1373
				$_product = $order_item->get_product();
1374
1375
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1376
					$old_stock    = $_product->get_stock_quantity();
1377
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1378
					$new_quantity = $_product->increase_stock( $stock_change );
1379
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1380
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1381
					$return[]     = $note;
1382
1383
					$order->add_order_note( $note );
1384
				}
1385
			}
1386
1387
			do_action( 'woocommerce_restore_order_stock', $order );
1388
1389
			if ( empty( $return ) ) {
1390
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1391
			}
1392
1393
			echo implode( ', ', $return );
1394
		}
1395
1396
		die();
1397
	}
1398
1399
	/**
1400
	 * Add some meta to a line item.
1401
	 */
1402
	public static function add_order_item_meta() {
1403
		check_ajax_referer( 'order-item', 'security' );
1404
1405
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1406
			die( -1 );
1407
		}
1408
1409
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1410
1411
		if ( $meta_id ) {
1412
			echo '<tr data-meta_id="' . esc_attr( $meta_id ) . '"><td><input type="text" name="meta_key[' . esc_attr( absint( $_POST['order_item_id'] ) ) . '][' . esc_attr( $meta_id ) . ']" /><textarea name="meta_value[' . esc_attr( absint( $_POST['order_item_id'] ) ) . '][' . esc_attr( $meta_id ) . ']"></textarea></td><td width="1%"><button class="remove_order_item_meta button">&times;</button></td></tr>';
1413
		}
1414
1415
		die();
1416
	}
1417
1418
	/**
1419
	 * Remove meta from a line item.
1420
	 */
1421
	public static function remove_order_item_meta() {
1422
		global $wpdb;
1423
1424
		check_ajax_referer( 'order-item', 'security' );
1425
1426
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1427
			die( -1 );
1428
		}
1429
1430
		$meta_id = absint( $_POST['meta_id'] );
1431
1432
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_id = %d", $meta_id ) );
1433
1434
		die();
1435
	}
1436
1437
	/**
1438
	 * Calc line tax.
1439
	 */
1440
	public static function calc_line_taxes() {
1441
		global $wpdb;
1442
1443
		check_ajax_referer( 'calc-totals', 'security' );
1444
1445
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1446
			die( -1 );
1447
		}
1448
1449
		$order_id = absint( $_POST['order_id'] );
1450
1451
		// Parse and save items
1452
		$items = array();
1453
		parse_str( $_POST['items'], $items );
1454
1455
		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...
1456
1457
		// Get order and calc Taxes
1458
		$order = wc_get_order( $order_id );
1459
		$order->calculate_taxes( array(
1460
			'country'  => strtoupper( wc_clean( $_POST['country'] ) ),
1461
			'state'    => strtoupper( wc_clean( $_POST['state'] ) ),
1462
			'postcode' => strtoupper( wc_clean( $_POST['postcode'] ) ),
1463
			'city'     => wc_clean( $_POST['city'] ),
1464
		) );
1465
1466
		// Return HTML items
1467
		$data  = get_post_meta( $order_id );
1468
		include( 'admin/meta-boxes/views/html-order-items.php' );
1469
		die();
1470
	}
1471
1472
	/**
1473
	 * Save order items via ajax.
1474
	 */
1475
	public static function save_order_items() {
1476
		check_ajax_referer( 'order-item', 'security' );
1477
1478
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1479
			die( -1 );
1480
		}
1481
1482
		if ( isset( $_POST['order_id'] ) && isset( $_POST['items'] ) ) {
1483
			$order_id = absint( $_POST['order_id'] );
1484
1485
			// Parse the jQuery serialized items
1486
			$items = array();
1487
			parse_str( $_POST['items'], $items );
1488
1489
			// Save order items
1490
			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...
1491
1492
			// Return HTML items
1493
			$order = wc_get_order( $order_id );
1494
			$data  = get_post_meta( $order_id );
1495
			include( 'admin/meta-boxes/views/html-order-items.php' );
1496
		}
1497
1498
		die();
1499
	}
1500
1501
	/**
1502
	 * Load order items via ajax.
1503
	 */
1504 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...
1505
		check_ajax_referer( 'order-item', 'security' );
1506
1507
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1508
			die( -1 );
1509
		}
1510
1511
		// Return HTML items
1512
		$order_id = absint( $_POST['order_id'] );
1513
		$order    = wc_get_order( $order_id );
1514
		$data     = get_post_meta( $order_id );
1515
		include( 'admin/meta-boxes/views/html-order-items.php' );
1516
1517
		die();
1518
	}
1519
1520
	/**
1521
	 * Add order note via ajax.
1522
	 */
1523
	public static function add_order_note() {
1524
1525
		check_ajax_referer( 'add-order-note', 'security' );
1526
1527
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1528
			die( -1 );
1529
		}
1530
1531
		$post_id   = absint( $_POST['post_id'] );
1532
		$note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
1533
		$note_type = $_POST['note_type'];
1534
1535
		$is_customer_note = $note_type == 'customer' ? 1 : 0;
1536
1537
		if ( $post_id > 0 ) {
1538
			$order      = wc_get_order( $post_id );
1539
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1540
1541
			echo '<li rel="' . esc_attr( $comment_id ) . '" class="note ';
1542
			if ( $is_customer_note ) {
1543
				echo 'customer-note';
1544
			}
1545
			echo '"><div class="note_content">';
1546
			echo wpautop( wptexturize( $note ) );
1547
			echo '</div><p class="meta"><a href="#" class="delete_note">'.__( 'Delete note', 'woocommerce' ).'</a></p>';
1548
			echo '</li>';
1549
		}
1550
1551
		// Quit out
1552
		die();
1553
	}
1554
1555
	/**
1556
	 * Delete order note via ajax.
1557
	 */
1558
	public static function delete_order_note() {
1559
1560
		check_ajax_referer( 'delete-order-note', 'security' );
1561
1562
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1563
			die( -1 );
1564
		}
1565
1566
		$note_id = (int) $_POST['note_id'];
1567
1568
		if ( $note_id > 0 ) {
1569
			wp_delete_comment( $note_id );
1570
		}
1571
1572
		// Quit out
1573
		die();
1574
	}
1575
1576
	/**
1577
	 * Search for products and echo json.
1578
	 *
1579
	 * @param string $x (default: '')
1580
	 * @param string $post_types (default: array('product'))
1581
	 */
1582
	public static function json_search_products( $x = '', $post_types = array( 'product' ) ) {
0 ignored issues
show
Unused Code introduced by
The parameter $x is not used and could be removed.

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

Loading history...
1583
		global $wpdb;
1584
1585
		ob_start();
1586
1587
		check_ajax_referer( 'search-products', 'security' );
1588
1589
		$term = (string) wc_clean( stripslashes( $_GET['term'] ) );
1590
1591
		if ( empty( $term ) ) {
1592
			die();
1593
		}
1594
1595
		$like_term = '%' . $wpdb->esc_like( $term ) . '%';
1596
1597
		if ( is_numeric( $term ) ) {
1598
			$query = $wpdb->prepare( "
1599
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1600
				WHERE posts.post_status = 'publish'
1601
				AND (
1602
					posts.post_parent = %s
1603
					OR posts.ID = %s
1604
					OR posts.post_title LIKE %s
1605
					OR (
1606
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1607
					)
1608
				)
1609
			", $term, $term, $term, $like_term );
1610
		} else {
1611
			$query = $wpdb->prepare( "
1612
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1613
				WHERE posts.post_status = 'publish'
1614
				AND (
1615
					posts.post_title LIKE %s
1616
					or posts.post_content LIKE %s
1617
					OR (
1618
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1619
					)
1620
				)
1621
			", $like_term, $like_term, $like_term );
1622
		}
1623
1624
		$query .= " AND posts.post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "')";
1625
1626 View Code Duplication
		if ( ! empty( $_GET['exclude'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

Loading history...
1631
			$query .= " AND posts.ID IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['include'] ) ) ) . ")";
1632
		}
1633
1634
		if ( ! empty( $_GET['limit'] ) ) {
1635
			$query .= " LIMIT " . intval( $_GET['limit'] );
1636
		}
1637
1638
		$posts          = array_unique( $wpdb->get_col( $query ) );
1639
		$found_products = array();
1640
1641
		if ( ! empty( $posts ) ) {
1642
			foreach ( $posts as $post ) {
1643
				$product = wc_get_product( $post );
1644
1645
				if ( ! current_user_can( 'read_product', $post ) ) {
1646
					continue;
1647
				}
1648
1649
				if ( ! $product || ( $product->is_type( 'variation' ) && empty( $product->parent ) ) ) {
1650
					continue;
1651
				}
1652
1653
				$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1654
			}
1655
		}
1656
1657
		$found_products = apply_filters( 'woocommerce_json_search_found_products', $found_products );
1658
1659
		wp_send_json( $found_products );
1660
	}
1661
1662
	/**
1663
	 * Search for product variations and return json.
1664
	 *
1665
	 * @see WC_AJAX::json_search_products()
1666
	 */
1667
	public static function json_search_products_and_variations() {
1668
		self::json_search_products( '', array( 'product', 'product_variation' ) );
1669
	}
1670
1671
	/**
1672
	 * Search for grouped products and return json.
1673
	 */
1674
	public static function json_search_grouped_products() {
1675
		ob_start();
1676
1677
		check_ajax_referer( 'search-products', 'security' );
1678
1679
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1680
		$exclude = array();
1681
1682
		if ( empty( $term ) ) {
1683
			die();
1684
		}
1685
1686 View Code Duplication
		if ( ! empty( $_GET['exclude'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1687
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1688
		}
1689
1690
		$found_products = array();
1691
1692
		if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
1693
1694
			$posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
1695
1696
			if ( sizeof( $posts_in ) > 0 ) {
1697
1698
				$args = array(
1699
					'post_type'        => 'product',
1700
					'post_status'      => 'any',
1701
					'numberposts'      => -1,
1702
					'orderby'          => 'title',
1703
					'order'            => 'asc',
1704
					'post_parent'      => 0,
1705
					'suppress_filters' => 0,
1706
					'include'          => $posts_in,
1707
					's'                => $term,
1708
					'fields'           => 'ids',
1709
					'exclude'          => $exclude
1710
				);
1711
1712
				$posts = get_posts( $args );
1713
1714
				if ( ! empty( $posts ) ) {
1715
					foreach ( $posts as $post ) {
1716
						$product = wc_get_product( $post );
1717
1718
						if ( ! current_user_can( 'read_product', $post ) ) {
1719
							continue;
1720
						}
1721
1722
						$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1723
					}
1724
				}
1725
			}
1726
		}
1727
1728
		$found_products = apply_filters( 'woocommerce_json_search_found_grouped_products', $found_products );
1729
1730
		wp_send_json( $found_products );
1731
	}
1732
1733
	/**
1734
	 * Search for downloadable product variations and return json.
1735
	 *
1736
	 * @see WC_AJAX::json_search_products()
1737
	 */
1738
	public static function json_search_downloadable_products_and_variations() {
1739
		ob_start();
1740
1741
		check_ajax_referer( 'search-products', 'security' );
1742
1743
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1744
		$exclude = array();
1745
1746 View Code Duplication
		if ( ! empty( $_GET['exclude'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1747
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1748
		}
1749
1750
		$args = array(
1751
			'post_type'      => array( 'product', 'product_variation' ),
1752
			'posts_per_page' => -1,
1753
			'post_status'    => 'publish',
1754
			'order'          => 'ASC',
1755
			'orderby'        => 'parent title',
1756
			'meta_query'     => array(
1757
				array(
1758
					'key'   => '_downloadable',
1759
					'value' => 'yes'
1760
				)
1761
			),
1762
			's'              => $term,
1763
			'exclude'        => $exclude
1764
		);
1765
1766
		$posts = get_posts( $args );
1767
		$found_products = array();
1768
1769
		if ( ! empty( $posts ) ) {
1770
			foreach ( $posts as $post ) {
1771
				$product = wc_get_product( $post->ID );
1772
1773
				if ( ! current_user_can( 'read_product', $post->ID ) ) {
1774
					continue;
1775
				}
1776
1777
				$found_products[ $post->ID ] = $product->get_formatted_name();
1778
			}
1779
		}
1780
1781
		wp_send_json( $found_products );
1782
	}
1783
1784
	/**
1785
	 * Search for customers and return json.
1786
	 */
1787
	public static function json_search_customers() {
1788
		ob_start();
1789
1790
		check_ajax_referer( 'search-customers', 'security' );
1791
1792
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1793
			die( -1 );
1794
		}
1795
1796
		$term    = wc_clean( stripslashes( $_GET['term'] ) );
1797
		$exclude = array();
1798
1799
		if ( empty( $term ) ) {
1800
			die();
1801
		}
1802
1803
		if ( ! empty( $_GET['exclude'] ) ) {
1804
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1805
		}
1806
1807
		$found_customers = array();
1808
1809
		add_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1810
1811
		$customers_query = new WP_User_Query( apply_filters( 'woocommerce_json_search_customers_query', array(
1812
			'fields'         => 'all',
1813
			'orderby'        => 'display_name',
1814
			'search'         => '*' . $term . '*',
1815
			'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename' )
1816
		) ) );
1817
1818
		remove_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1819
1820
		$customers = $customers_query->get_results();
1821
1822
		if ( ! empty( $customers ) ) {
1823
			foreach ( $customers as $customer ) {
1824
				if ( ! in_array( $customer->ID, $exclude ) ) {
1825
					$found_customers[ $customer->ID ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
1826
				}
1827
			}
1828
		}
1829
1830
		wp_send_json( $found_customers );
1831
	}
1832
1833
	/**
1834
	 * When searching using the WP_User_Query, search names (user meta) too.
1835
	 * @param  object $query
1836
	 * @return object
1837
	 */
1838
	public static function json_search_customer_name( $query ) {
1839
		global $wpdb;
1840
1841
		$term = wc_clean( stripslashes( $_GET['term'] ) );
1842
		if ( method_exists( $wpdb, 'esc_like' ) ) {
1843
			$term = $wpdb->esc_like( $term );
1844
		} else {
1845
			$term = like_escape( $term );
1846
		}
1847
1848
		$query->query_from  .= " INNER JOIN {$wpdb->usermeta} AS user_name ON {$wpdb->users}.ID = user_name.user_id AND ( user_name.meta_key = 'first_name' OR user_name.meta_key = 'last_name' ) ";
1849
		$query->query_where .= $wpdb->prepare( " OR user_name.meta_value LIKE %s ", '%' . $term . '%' );
1850
	}
1851
1852
	/**
1853
	 * Ajax request handling for categories ordering.
1854
	 */
1855
	public static function term_ordering() {
1856
1857
		// check permissions again and make sure we have what we need
1858
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
1859
			die( -1 );
1860
		}
1861
1862
		$id       = (int) $_POST['id'];
1863
		$next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
1864
		$taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null;
1865
		$term     = get_term_by( 'id', $id, $taxonomy );
1866
1867
		if ( ! $id || ! $term || ! $taxonomy ) {
1868
			die(0);
1869
		}
1870
1871
		wc_reorder_terms( $term, $next_id, $taxonomy );
1872
1873
		$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
1874
1875
		if ( $term && sizeof( $children ) ) {
1876
			echo 'children';
1877
			die();
1878
		}
1879
	}
1880
1881
	/**
1882
	 * Ajax request handling for product ordering.
1883
	 *
1884
	 * Based on Simple Page Ordering by 10up (http://wordpress.org/extend/plugins/simple-page-ordering/).
1885
	 */
1886
	public static function product_ordering() {
1887
		global $wpdb;
1888
1889
		ob_start();
1890
1891
		// check permissions again and make sure we have what we need
1892
		if ( ! current_user_can('edit_products') || empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
1893
			die( -1 );
1894
		}
1895
1896
		// real post?
1897
		if ( ! $post = get_post( $_POST['id'] ) ) {
1898
			die( -1 );
1899
		}
1900
1901
		$previd  = isset( $_POST['previd'] ) ? $_POST['previd'] : false;
1902
		$nextid  = isset( $_POST['nextid'] ) ? $_POST['nextid'] : false;
1903
		$new_pos = array(); // store new positions for ajax
1904
1905
		$siblings = $wpdb->get_results( $wpdb->prepare( "
1906
			SELECT ID, menu_order FROM {$wpdb->posts} AS posts
1907
			WHERE 	posts.post_type 	= 'product'
1908
			AND 	posts.post_status 	IN ( 'publish', 'pending', 'draft', 'future', 'private' )
1909
			AND 	posts.ID			NOT IN (%d)
1910
			ORDER BY posts.menu_order ASC, posts.ID DESC
1911
		", $post->ID ) );
1912
1913
		$menu_order = 0;
1914
1915
		foreach ( $siblings as $sibling ) {
1916
1917
			// if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
1918 View Code Duplication
			if ( $nextid == $sibling->ID ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1919
				$wpdb->update(
1920
					$wpdb->posts,
1921
					array(
1922
						'menu_order' => $menu_order
1923
					),
1924
					array( 'ID' => $post->ID ),
1925
					array( '%d' ),
1926
					array( '%d' )
1927
				);
1928
				$new_pos[ $post->ID ] = $menu_order;
1929
				$menu_order++;
1930
			}
1931
1932
			// if repositioned post has been set, and new items are already in the right order, we can stop
1933
			if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $menu_order ) {
1934
				break;
1935
			}
1936
1937
			// set the menu order of the current sibling and increment the menu order
1938
			$wpdb->update(
1939
				$wpdb->posts,
1940
				array(
1941
					'menu_order' => $menu_order
1942
				),
1943
				array( 'ID' => $sibling->ID ),
1944
				array( '%d' ),
1945
				array( '%d' )
1946
			);
1947
			$new_pos[ $sibling->ID ] = $menu_order;
1948
			$menu_order++;
1949
1950 View Code Duplication
			if ( ! $nextid && $previd == $sibling->ID ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1951
				$wpdb->update(
1952
					$wpdb->posts,
1953
					array(
1954
						'menu_order' => $menu_order
1955
					),
1956
					array( 'ID' => $post->ID ),
1957
					array( '%d' ),
1958
					array( '%d' )
1959
				);
1960
				$new_pos[$post->ID] = $menu_order;
1961
				$menu_order++;
1962
			}
1963
1964
		}
1965
1966
		do_action( 'woocommerce_after_product_ordering' );
1967
1968
		wp_send_json( $new_pos );
1969
	}
1970
1971
	/**
1972
	 * Handle a refund via the edit order screen.
1973
	 */
1974
	public static function refund_line_items() {
1975
		ob_start();
1976
1977
		check_ajax_referer( 'order-item', 'security' );
1978
1979
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1980
			die( -1 );
1981
		}
1982
1983
		$order_id               = absint( $_POST['order_id'] );
1984
		$refund_amount          = wc_format_decimal( sanitize_text_field( $_POST['refund_amount'] ), wc_get_price_decimals() );
1985
		$refund_reason          = sanitize_text_field( $_POST['refund_reason'] );
1986
		$line_item_qtys         = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_qtys'] ) ), true );
1987
		$line_item_totals       = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_totals'] ) ), true );
1988
		$line_item_tax_totals   = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_tax_totals'] ) ), true );
1989
		$api_refund             = $_POST['api_refund'] === 'true' ? true : false;
1990
		$restock_refunded_items = $_POST['restock_refunded_items'] === 'true' ? true : false;
1991
		$refund                 = false;
1992
		$response_data          = array();
1993
1994
		try {
1995
			// Validate that the refund can occur
1996
			$order       = wc_get_order( $order_id );
1997
			$order_items = $order->get_items();
1998
			$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded(), wc_get_price_decimals() );
1999
2000
			if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) {
2001
				throw new exception( __( 'Invalid refund amount', 'woocommerce' ) );
2002
			}
2003
2004
			// Prepare line items which we are refunding
2005
			$line_items = array();
2006
			$item_ids   = array_unique( array_merge( array_keys( $line_item_qtys, $line_item_totals ) ) );
2007
2008
			foreach ( $item_ids as $item_id ) {
2009
				$line_items[ $item_id ] = array( 'qty' => 0, 'refund_total' => 0, 'refund_tax' => array() );
2010
			}
2011
			foreach ( $line_item_qtys as $item_id => $qty ) {
2012
				$line_items[ $item_id ]['qty'] = max( $qty, 0 );
2013
			}
2014
			foreach ( $line_item_totals as $item_id => $total ) {
2015
				$line_items[ $item_id ]['refund_total'] = wc_format_decimal( $total );
2016
			}
2017
			foreach ( $line_item_tax_totals as $item_id => $tax_totals ) {
2018
				$line_items[ $item_id ]['refund_tax'] = array_map( 'wc_format_decimal', $tax_totals );
2019
			}
2020
2021
			// Create the refund object
2022
			$refund = wc_create_refund( array(
2023
				'amount'     => $refund_amount,
2024
				'reason'     => $refund_reason,
2025
				'order_id'   => $order_id,
2026
				'line_items' => $line_items,
2027
			) );
2028
2029
			if ( is_wp_error( $refund ) ) {
2030
				throw new Exception( $refund->get_error_message() );
1 ignored issue
show
Bug introduced by
The method get_error_message() does not seem to exist on object<WC_Order_Refund>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2031
			}
2032
2033
			// Refund via API
2034 View Code Duplication
			if ( $api_refund ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2035
				if ( WC()->payment_gateways() ) {
2036
					$payment_gateways = WC()->payment_gateways->payment_gateways();
2037
				}
2038
				if ( isset( $payment_gateways[ $order->get_payment_method() ] ) && $payment_gateways[ $order->get_payment_method() ]->supports( 'refunds' ) ) {
2039
					$result = $payment_gateways[ $order->get_payment_method() ]->process_refund( $order_id, $refund_amount, $refund_reason );
2040
2041
					do_action( 'woocommerce_refund_processed', $refund, $result );
2042
2043
					if ( is_wp_error( $result ) ) {
2044
						throw new Exception( $result->get_error_message() );
2045
					} elseif ( ! $result ) {
2046
						throw new Exception( __( 'Refund failed', 'woocommerce' ) );
2047
					}
2048
				}
2049
			}
2050
2051
			// restock items
2052
			foreach ( $line_item_qtys as $item_id => $qty ) {
2053
				if ( $restock_refunded_items && $qty && isset( $order_items[ $item_id ] ) ) {
2054
					$order_item = $order_items[ $item_id ];
2055
					$_product   = $order_item->get_product();
2056
2057
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2058
						$old_stock    = wc_stock_amount( $_product->stock );
2059
						$new_quantity = $_product->increase_stock( $qty );
2060
2061
						$order->add_order_note( sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
2062
2063
						do_action( 'woocommerce_restock_refunded_item', $_product->id, $old_stock, $new_quantity, $order );
2064
					}
2065
				}
2066
			}
2067
2068
			// Trigger notifications and status changes
2069
			if ( $order->get_remaining_refund_amount() > 0 || ( $order->has_free_item() && $order->get_remaining_refund_items() > 0 ) ) {
2070
				/**
2071
				 * woocommerce_order_partially_refunded.
2072
				 *
2073
				 * @since 2.4.0
2074
				 * Note: 3rd arg was added in err. Kept for bw compat. 2.4.3.
2075
				 */
2076
				do_action( 'woocommerce_order_partially_refunded', $order_id, $refund->get_id(), $refund->get_id() );
2077
			} else {
2078
				do_action( 'woocommerce_order_fully_refunded', $order_id, $refund->get_id() );
2079
2080
				$order->update_status( apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order_id, $refund->get_id() ) );
2081
				$response_data['status'] = 'fully_refunded';
2082
			}
2083
2084
			do_action( 'woocommerce_order_refunded', $order_id, $refund->get_id() );
2085
2086
			// Clear transients
2087
			wc_delete_shop_order_transients( $order_id );
2088
			wp_send_json_success( $response_data );
2089
2090
		} catch ( Exception $e ) {
2091
			if ( $refund && is_a( $refund, 'WC_Order_Refund' ) ) {
2092
				wp_delete_post( $refund->get_id(), true );
2093
			}
2094
2095
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
2096
		}
2097
	}
2098
2099
	/**
2100
	 * Delete a refund.
2101
	 */
2102
	public static function delete_refund() {
2103
		check_ajax_referer( 'order-item', 'security' );
2104
2105
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2106
			die( -1 );
2107
		}
2108
2109
		$refund_id = absint( $_POST['refund_id'] );
2110
2111
		if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
2112
			$order_id = wp_get_post_parent_id( $refund_id );
2113
			wc_delete_shop_order_transients( $order_id );
2114
			wp_delete_post( $refund_id );
2115
2116
			do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
2117
		}
2118
2119
		die();
2120
	}
2121
2122
	/**
2123
	 * Triggered when clicking the rating footer.
2124
	 */
2125
	public static function rated() {
2126
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2127
			die( -1 );
2128
		}
2129
2130
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
2131
		die();
2132
	}
2133
2134
	/**
2135
	 * Create/Update API key.
2136
	 */
2137
	public static function update_api_key() {
2138
		ob_start();
2139
2140
		global $wpdb;
2141
2142
		check_ajax_referer( 'update-api-key', 'security' );
2143
2144
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2145
			die( -1 );
2146
		}
2147
2148
		try {
2149
			if ( empty( $_POST['description'] ) ) {
2150
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
2151
			}
2152
			if ( empty( $_POST['user'] ) ) {
2153
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
2154
			}
2155
			if ( empty( $_POST['permissions'] ) ) {
2156
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
2157
			}
2158
2159
			$key_id      = absint( $_POST['key_id'] );
2160
			$description = sanitize_text_field( wp_unslash( $_POST['description'] ) );
2161
			$permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read';
2162
			$user_id     = absint( $_POST['user'] );
2163
2164
			if ( 0 < $key_id ) {
2165
				$data = array(
2166
					'user_id'     => $user_id,
2167
					'description' => $description,
2168
					'permissions' => $permissions
2169
				);
2170
2171
				$wpdb->update(
2172
					$wpdb->prefix . 'woocommerce_api_keys',
2173
					$data,
2174
					array( 'key_id' => $key_id ),
2175
					array(
2176
						'%d',
2177
						'%s',
2178
						'%s'
2179
					),
2180
					array( '%d' )
2181
				);
2182
2183
				$data['consumer_key']    = '';
2184
				$data['consumer_secret'] = '';
2185
				$data['message']         = __( 'API Key updated successfully.', 'woocommerce' );
2186
			} else {
2187
				$status          = 2;
0 ignored issues
show
Unused Code introduced by
$status is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2188
				$consumer_key    = 'ck_' . wc_rand_hash();
2189
				$consumer_secret = 'cs_' . wc_rand_hash();
2190
2191
				$data = array(
2192
					'user_id'         => $user_id,
2193
					'description'     => $description,
2194
					'permissions'     => $permissions,
2195
					'consumer_key'    => wc_api_hash( $consumer_key ),
2196
					'consumer_secret' => $consumer_secret,
2197
					'truncated_key'   => substr( $consumer_key, -7 )
2198
				);
2199
2200
				$wpdb->insert(
2201
					$wpdb->prefix . 'woocommerce_api_keys',
2202
					$data,
2203
					array(
2204
						'%d',
2205
						'%s',
2206
						'%s',
2207
						'%s',
2208
						'%s',
2209
						'%s'
2210
					)
2211
				);
2212
2213
				$key_id                  = $wpdb->insert_id;
2214
				$data['consumer_key']    = $consumer_key;
2215
				$data['consumer_secret'] = $consumer_secret;
2216
				$data['message']         = __( 'API Key generated successfully. Make sure to copy your new API keys now. You won\'t be able to see it again!', 'woocommerce' );
2217
				$data['revoke_url']      = '<a style="color: #a00; text-decoration: none;" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'revoke-key' => $key_id ), admin_url( 'admin.php?page=wc-settings&tab=api&section=keys' ) ), 'revoke' ) ). '">' . __( 'Revoke Key', 'woocommerce' ) . '</a>';
2218
			}
2219
2220
			wp_send_json_success( $data );
2221
		} catch ( Exception $e ) {
2222
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
2223
		}
2224
	}
2225
2226
	/**
2227
	 * Locate user via AJAX.
2228
	 */
2229
	public static function get_customer_location() {
2230
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
2231
		wp_send_json_success( array( 'hash' => $location_hash ) );
2232
	}
2233
2234
	/**
2235
	 * Load variations via AJAX.
2236
	 */
2237
	public static function load_variations() {
2238
		ob_start();
2239
2240
		check_ajax_referer( 'load-variations', 'security' );
2241
2242
		// Check permissions again and make sure we have what we need
2243 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) || empty( $_POST['attributes'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2244
			die( -1 );
2245
		}
2246
2247
		global $post;
2248
2249
		$product_id = absint( $_POST['product_id'] );
2250
		$post       = get_post( $product_id ); // Set $post global so its available like within the admin screens
2251
		$per_page   = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
2252
		$page       = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
2253
2254
		// Get attributes
2255
		$attributes        = array();
2256
		$posted_attributes = wp_unslash( $_POST['attributes'] );
2257
2258
		foreach ( $posted_attributes as $key => $value ) {
2259
			$attributes[ $key ] = array_map( 'wc_clean', $value );
2260
		}
2261
2262
		// Get tax classes
2263
		$tax_classes           = WC_Tax::get_tax_classes();
2264
		$tax_class_options     = array();
2265
		$tax_class_options[''] = __( 'Standard', 'woocommerce' );
2266
2267 View Code Duplication
		if ( ! empty( $tax_classes ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2268
			foreach ( $tax_classes as $class ) {
2269
				$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
2270
			}
2271
		}
2272
2273
		// Set backorder options
2274
		$backorder_options = array(
2275
			'no'     => __( 'Do not allow', 'woocommerce' ),
2276
			'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
2277
			'yes'    => __( 'Allow', 'woocommerce' )
2278
		);
2279
2280
		// set stock status options
2281
		$stock_status_options = array(
2282
			'instock'    => __( 'In stock', 'woocommerce' ),
2283
			'outofstock' => __( 'Out of stock', 'woocommerce' )
2284
		);
2285
2286
		$parent_data = array(
2287
			'id'                   => $product_id,
2288
			'attributes'           => $attributes,
2289
			'tax_class_options'    => $tax_class_options,
2290
			'sku'                  => get_post_meta( $product_id, '_sku', true ),
2291
			'weight'               => wc_format_localized_decimal( get_post_meta( $product_id, '_weight', true ) ),
2292
			'length'               => wc_format_localized_decimal( get_post_meta( $product_id, '_length', true ) ),
2293
			'width'                => wc_format_localized_decimal( get_post_meta( $product_id, '_width', true ) ),
2294
			'height'               => wc_format_localized_decimal( get_post_meta( $product_id, '_height', true ) ),
2295
			'tax_class'            => get_post_meta( $product_id, '_tax_class', true ),
2296
			'backorder_options'    => $backorder_options,
2297
			'stock_status_options' => $stock_status_options
2298
		);
2299
2300
		if ( ! $parent_data['weight'] ) {
2301
			$parent_data['weight'] = wc_format_localized_decimal( 0 );
2302
		}
2303
2304
		if ( ! $parent_data['length'] ) {
2305
			$parent_data['length'] = wc_format_localized_decimal( 0 );
2306
		}
2307
2308
		if ( ! $parent_data['width'] ) {
2309
			$parent_data['width'] = wc_format_localized_decimal( 0 );
2310
		}
2311
2312
		if ( ! $parent_data['height'] ) {
2313
			$parent_data['height'] = wc_format_localized_decimal( 0 );
2314
		}
2315
2316
		// Get variations
2317
		$args = apply_filters( 'woocommerce_ajax_admin_get_variations_args', array(
2318
			'post_type'      => 'product_variation',
2319
			'post_status'    => array( 'private', 'publish' ),
2320
			'posts_per_page' => $per_page,
2321
			'paged'          => $page,
2322
			'orderby'        => array( 'menu_order' => 'ASC', 'ID' => 'DESC' ),
2323
			'post_parent'    => $product_id
2324
		), $product_id );
2325
2326
		$variations = get_posts( $args );
2327
		$loop = 0;
2328
2329
		if ( $variations ) {
2330
2331
			foreach ( $variations as $variation ) {
2332
				$variation_id     = absint( $variation->ID );
2333
				$variation_meta   = get_post_meta( $variation_id );
2334
				$variation_data   = array();
2335
				$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
2336
				$variation_fields = array(
2337
					'_sku'                   => '',
2338
					'_stock'                 => '',
2339
					'_regular_price'         => '',
2340
					'_sale_price'            => '',
2341
					'_weight'                => '',
2342
					'_length'                => '',
2343
					'_width'                 => '',
2344
					'_height'                => '',
2345
					'_download_limit'        => '',
2346
					'_download_expiry'       => '',
2347
					'_downloadable_files'    => '',
2348
					'_downloadable'          => '',
2349
					'_virtual'               => '',
2350
					'_thumbnail_id'          => '',
2351
					'_sale_price_dates_from' => '',
2352
					'_sale_price_dates_to'   => '',
2353
					'_manage_stock'          => '',
2354
					'_stock_status'          => '',
2355
					'_backorders'            => null,
2356
					'_tax_class'             => null,
2357
					'_variation_description' => ''
2358
				);
2359
2360 View Code Duplication
				foreach ( $variation_fields as $field => $value ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2361
					$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
2362
				}
2363
2364
				// Add the variation attributes
2365
				$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
2366
2367
				// Formatting
2368
				$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
2369
				$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
2370
				$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
2371
				$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
2372
				$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
2373
				$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
2374
				$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
2375
				$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
2376
				$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
2377
				$variation_data['menu_order']     = $variation->menu_order;
2378
				$variation_data['_stock']         = '' === $variation_data['_stock'] ? '' : wc_stock_amount( $variation_data['_stock'] );
2379
2380
				include( 'admin/meta-boxes/views/html-variation-admin.php' );
2381
2382
				$loop++;
2383
			}
2384
		}
2385
2386
		die();
2387
	}
2388
2389
	/**
2390
	 * Save variations via AJAX.
2391
	 */
2392
	public static function save_variations() {
2393
		ob_start();
2394
2395
		check_ajax_referer( 'save-variations', 'security' );
2396
2397
		// Check permissions again and make sure we have what we need
2398 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST ) || empty( $_POST['product_id'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2399
			die( -1 );
2400
		}
2401
2402
		// Remove previous meta box errors
2403
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
2404
2405
		$product_id   = absint( $_POST['product_id'] );
2406
		$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
2407
2408
		$product_type_terms = wp_get_object_terms( $product_id, 'product_type' );
2409
2410
		// If the product type hasn't been set or it has changed, update it before saving variations
2411
		if ( empty( $product_type_terms ) || $product_type !== sanitize_title( current( $product_type_terms )->name ) ) {
2412
			wp_set_object_terms( $product_id, $product_type, 'product_type' );
2413
		}
2414
2415
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
2416
2417
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
2418
2419
		// Clear cache/transients
2420
		wc_delete_product_transients( $product_id );
2421
2422
		if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
2423
			echo '<div class="error notice is-dismissible">';
2424
2425
			foreach ( $errors as $error ) {
2426
				echo '<p>' . wp_kses_post( $error ) . '</p>';
2427
			}
2428
2429
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
2430
			echo '</div>';
2431
2432
			delete_option( 'woocommerce_meta_box_errors' );
2433
		}
2434
2435
		die();
2436
	}
2437
2438
	/**
2439
	 * Bulk action - Toggle Enabled.
2440
	 * @access private
2441
	 * @param  array $variations
2442
	 * @param  array $data
2443
	 */
2444
	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...
2445
		global $wpdb;
2446
2447
		foreach ( $variations as $variation_id ) {
2448
			$post_status = get_post_status( $variation_id );
2449
			$new_status  = 'private' === $post_status ? 'publish' : 'private';
2450
			$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $variation_id ) );
2451
		}
2452
	}
2453
2454
	/**
2455
	 * Bulk action - Toggle Downloadable Checkbox.
2456
	 * @access private
2457
	 * @param  array $variations
2458
	 * @param  array $data
2459
	 */
2460 View Code Duplication
	private static function variation_bulk_action_toggle_downloadable( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Unused Code introduced by
The parameter $data is not used and could be removed.

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

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2461
		foreach ( $variations as $variation_id ) {
2462
			$_downloadable   = get_post_meta( $variation_id, '_downloadable', true );
2463
			$is_downloadable = 'no' === $_downloadable ? 'yes' : 'no';
2464
			update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
2465
		}
2466
	}
2467
2468
	/**
2469
	 * Bulk action - Toggle Virtual Checkbox.
2470
	 * @access private
2471
	 * @param  array $variations
2472
	 * @param  array $data
2473
	 */
2474 View Code Duplication
	private static function variation_bulk_action_toggle_virtual( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Unused Code introduced by
The parameter $data is not used and could be removed.

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

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2475
		foreach ( $variations as $variation_id ) {
2476
			$_virtual   = get_post_meta( $variation_id, '_virtual', true );
2477
			$is_virtual = 'no' === $_virtual ? 'yes' : 'no';
2478
			update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
2479
		}
2480
	}
2481
2482
	/**
2483
	 * Bulk action - Toggle Manage Stock Checkbox.
2484
	 * @access private
2485
	 * @param  array $variations
2486
	 * @param  array $data
2487
	 */
2488
	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...
2489
		foreach ( $variations as $variation_id ) {
2490
			$_manage_stock   = get_post_meta( $variation_id, '_manage_stock', true );
2491
			$is_manage_stock = 'no' === $_manage_stock || '' === $_manage_stock ? 'yes' : 'no';
2492
			update_post_meta( $variation_id, '_manage_stock', $is_manage_stock );
2493
		}
2494
	}
2495
2496
	/**
2497
	 * Bulk action - Set Regular Prices.
2498
	 * @access private
2499
	 * @param  array $variations
2500
	 * @param  array $data
2501
	 */
2502 View Code Duplication
	private static function variation_bulk_action_variable_regular_price( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2503
		if ( ! isset( $data['value'] ) ) {
2504
			return;
2505
		}
2506
2507
		foreach ( $variations as $variation_id ) {
2508
			// Price fields
2509
			$regular_price = wc_clean( $data['value'] );
2510
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2511
2512
			// Date fields
2513
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2514
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2515
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2516
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2517
2518
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
0 ignored issues
show
Documentation introduced by
$regular_price is of type string|array, but the function expects a double.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2519
		}
2520
	}
2521
2522
	/**
2523
	 * Bulk action - Set Sale Prices.
2524
	 * @access private
2525
	 * @param  array $variations
2526
	 * @param  array $data
2527
	 */
2528 View Code Duplication
	private static function variation_bulk_action_variable_sale_price( $variations, $data ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2529
		if ( ! isset( $data['value'] ) ) {
2530
			return;
2531
		}
2532
2533
		foreach ( $variations as $variation_id ) {
2534
			// Price fields
2535
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2536
			$sale_price    = wc_clean( $data['value'] );
2537
2538
			// Date fields
2539
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2540
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2541
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2542
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2543
2544
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
0 ignored issues
show
Bug introduced by
It seems like $sale_price defined by wc_clean($data['value']) on line 2536 can also be of type array; however, _wc_save_product_price() does only seem to accept string|double, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
2545
		}
2546
	}
2547
2548
	/**
2549
	 * Bulk action - Set Stock.
2550
	 * @access private
2551
	 * @param  array $variations
2552
	 * @param  array $data
2553
	 */
2554
	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...
2555
		if ( ! isset( $data['value'] ) ) {
2556
			return;
2557
		}
2558
2559
		$value = wc_clean( $data['value'] );
2560
2561
		foreach ( $variations as $variation_id ) {
2562
			if ( 'yes' === get_post_meta( $variation_id, '_manage_stock', true ) ) {
2563
				wc_update_product_stock( $variation_id, wc_stock_amount( $value ) );
0 ignored issues
show
Documentation introduced by
$value is of type string|array, but the function expects a integer|double.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2564
			} else {
2565
				delete_post_meta( $variation_id, '_stock' );
2566
			}
2567
		}
2568
	}
2569
2570
	/**
2571
	 * Bulk action - Set Weight.
2572
	 * @access private
2573
	 * @param  array $variations
2574
	 * @param  array $data
2575
	 */
2576
	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...
2577
		self::variation_bulk_set_meta( $variations, '_weight', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2578
	}
2579
2580
	/**
2581
	 * Bulk action - Set Length.
2582
	 * @access private
2583
	 * @param  array $variations
2584
	 * @param  array $data
2585
	 */
2586
	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...
2587
		self::variation_bulk_set_meta( $variations, '_length', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2588
	}
2589
2590
	/**
2591
	 * Bulk action - Set Width.
2592
	 * @access private
2593
	 * @param  array $variations
2594
	 * @param  array $data
2595
	 */
2596
	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...
2597
		self::variation_bulk_set_meta( $variations, '_width', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2598
	}
2599
2600
	/**
2601
	 * Bulk action - Set Height.
2602
	 * @access private
2603
	 * @param  array $variations
2604
	 * @param  array $data
2605
	 */
2606
	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...
2607
		self::variation_bulk_set_meta( $variations, '_height', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2608
	}
2609
2610
	/**
2611
	 * Bulk action - Set Download Limit.
2612
	 * @access private
2613
	 * @param  array $variations
2614
	 * @param  array $data
2615
	 */
2616
	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...
2617
		self::variation_bulk_set_meta( $variations, '_download_limit', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2618
	}
2619
2620
	/**
2621
	 * Bulk action - Set Download Expiry.
2622
	 * @access private
2623
	 * @param  array $variations
2624
	 * @param  array $data
2625
	 */
2626
	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...
2627
		self::variation_bulk_set_meta( $variations, '_download_expiry', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_set_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2628
	}
2629
2630
	/**
2631
	 * Bulk action - Delete all.
2632
	 * @access private
2633
	 * @param  array $variations
2634
	 * @param  array $data
2635
	 */
2636
	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...
2637
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2638
			foreach ( $variations as $variation_id ) {
2639
				wp_delete_post( $variation_id );
2640
			}
2641
		}
2642
	}
2643
2644
	/**
2645
	 * Bulk action - Sale Schedule.
2646
	 * @access private
2647
	 * @param  array $variations
2648
	 * @param  array $data
2649
	 */
2650
	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...
2651
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2652
			return;
2653
		}
2654
2655
		foreach ( $variations as $variation_id ) {
2656
			// Price fields
2657
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2658
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2659
2660
			// Date fields
2661
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2662
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2663
2664 View Code Duplication
			if ( 'false' === $data['date_from'] ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2665
				$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2666
			} else {
2667
				$date_from = $data['date_from'];
2668
			}
2669
2670 View Code Duplication
			if ( 'false' === $data['date_to'] ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2671
				$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2672
			} else {
2673
				$date_to = $data['date_to'];
2674
			}
2675
2676
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
2677
		}
2678
	}
2679
2680
	/**
2681
	 * Bulk action - Increase Regular Prices.
2682
	 * @access private
2683
	 * @param  array $variations
2684
	 * @param  array $data
2685
	 */
2686
	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...
2687
		self::variation_bulk_adjust_price( $variations, '_regular_price', '+', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_adjust_price() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2688
	}
2689
2690
	/**
2691
	 * Bulk action - Decrease Regular Prices.
2692
	 * @access private
2693
	 * @param  array $variations
2694
	 * @param  array $data
2695
	 */
2696
	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...
2697
		self::variation_bulk_adjust_price( $variations, '_regular_price', '-', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_adjust_price() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2698
	}
2699
2700
	/**
2701
	 * Bulk action - Increase Sale Prices.
2702
	 * @access private
2703
	 * @param  array $variations
2704
	 * @param  array $data
2705
	 */
2706
	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...
2707
		self::variation_bulk_adjust_price( $variations, '_sale_price', '+', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_adjust_price() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2708
	}
2709
2710
	/**
2711
	 * Bulk action - Decrease Sale Prices.
2712
	 * @access private
2713
	 * @param  array $variations
2714
	 * @param  array $data
2715
	 */
2716
	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...
2717
		self::variation_bulk_adjust_price( $variations, '_sale_price', '-', wc_clean( $data['value'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($data['value']) targeting wc_clean() can also be of type array; however, WC_AJAX::variation_bulk_adjust_price() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2718
	}
2719
2720
	/**
2721
	 * Bulk action - Set Price.
2722
	 * @access private
2723
	 * @param  array $variations
2724
	 * @param string $operator + or -
2725
	 * @param string $field price being adjusted
2726
	 * @param string $value Price or Percent
2727
	 */
2728
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2729
		foreach ( $variations as $variation_id ) {
2730
			// Get existing data
2731
			$_regular_price = get_post_meta( $variation_id, '_regular_price', true );
2732
			$_sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2733
			$date_from      = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2734
			$date_to        = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2735
			$date_from      = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2736
			$date_to        = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2737
2738
			if ( '%' === substr( $value, -1 ) ) {
2739
				$percent = wc_format_decimal( substr( $value, 0, -1 ) );
2740
				$$field  += ( ( $$field / 100 ) * $percent ) * "{$operator}1";
2741
			} else {
2742
				$$field  += $value * "{$operator}1";
2743
			}
2744
			_wc_save_product_price( $variation_id, $_regular_price, $_sale_price, $date_from, $date_to );
2745
		}
2746
	}
2747
2748
	/**
2749
	 * Bulk action - Set Meta.
2750
	 * @access private
2751
	 * @param array $variations
2752
	 * @param string $field
2753
	 * @param string $value
2754
	 */
2755
	private static function variation_bulk_set_meta( $variations, $field, $value ) {
2756
		foreach ( $variations as $variation_id ) {
2757
			update_post_meta( $variation_id, $field, $value );
2758
		}
2759
	}
2760
2761
2762
	/**
2763
	 * Bulk edit variations via AJAX.
2764
	 */
2765
	public static function bulk_edit_variations() {
2766
		ob_start();
2767
2768
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2769
2770
		// Check permissions again and make sure we have what we need
2771 View Code Duplication
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) || empty( $_POST['bulk_action'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2772
			die( -1 );
2773
		}
2774
2775
		$product_id  = absint( $_POST['product_id'] );
2776
		$bulk_action = wc_clean( $_POST['bulk_action'] );
2777
		$data        = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array();
2778
		$variations  = array();
2779
2780
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2781
			$variations = get_posts( array(
2782
				'post_parent'    => $product_id,
2783
				'posts_per_page' => -1,
2784
				'post_type'      => 'product_variation',
2785
				'fields'         => 'ids',
2786
				'post_status'    => array( 'publish', 'private' )
2787
			) );
2788
		}
2789
2790
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2791
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2792
		} else {
2793
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2794
		}
2795
2796
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2797
2798
		// Sync and update transients
2799
		WC_Product_Variable::sync( $product_id );
2800
		wc_delete_product_transients( $product_id );
2801
		die();
2802
	}
2803
2804
	/**
2805
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2806
	 */
2807
	public static function tax_rates_save_changes() {
2808
		if ( ! isset( $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2809
			wp_send_json_error( 'missing_fields' );
2810
			exit;
2811
		}
2812
2813
		$current_class = ! empty( $_POST['current_class'] ) ? $_POST['current_class'] : ''; // This is sanitized seven lines later.
2814
2815
		if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) {
2816
			wp_send_json_error( 'bad_nonce' );
2817
			exit;
2818
		}
2819
2820
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
2821
2822
		// Check User Caps
2823
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2824
			wp_send_json_error( 'missing_capabilities' );
2825
			exit;
2826
		}
2827
2828
		$changes = $_POST['changes'];
2829
		foreach ( $changes as $tax_rate_id => $data ) {
2830
			if ( isset( $data['deleted'] ) ) {
2831
				if ( isset( $data['newRow'] ) ) {
2832
					// So the user added and deleted a new row.
2833
					// That's fine, it's not in the database anyways. NEXT!
2834
					continue;
2835
				}
2836
				WC_Tax::_delete_tax_rate( $tax_rate_id );
2837
			}
2838
2839
			$tax_rate = array_intersect_key( $data, array(
2840
				'tax_rate_country'  => 1,
2841
				'tax_rate_state'    => 1,
2842
				'tax_rate'          => 1,
2843
				'tax_rate_name'     => 1,
2844
				'tax_rate_priority' => 1,
2845
				'tax_rate_compound' => 1,
2846
				'tax_rate_shipping' => 1,
2847
				'tax_rate_order'    => 1,
2848
			) );
2849
2850
			if ( isset( $data['newRow'] ) ) {
2851
				// Hurrah, shiny and new!
1 ignored issue
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2852
				$tax_rate['tax_rate_class'] = $current_class;
2853
				$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
2854
			} else {
2855
				// Updating an existing rate ...
2856
				if ( ! empty( $tax_rate ) ) {
2857
					WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
2858
				}
2859
			}
2860
2861
			if ( isset( $data['postcode'] ) ) {
2862
				WC_Tax::_update_tax_rate_postcodes( $tax_rate_id, array_map( 'wc_clean', $data['postcode'] ) );
0 ignored issues
show
Documentation introduced by
array_map('wc_clean', $data['postcode']) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2863
			}
2864
			if ( isset( $data['city'] ) ) {
2865
				WC_Tax::_update_tax_rate_cities( $tax_rate_id, array_map( 'wc_clean', $data['city'] ) );
0 ignored issues
show
Documentation introduced by
array_map('wc_clean', $data['city']) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2866
			}
2867
		}
2868
2869
		wp_send_json_success( array(
2870
			'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
2871
		) );
2872
	}
2873
2874
	/**
2875
	 * Handle submissions from assets/js/wc-shipping-zones.js Backbone model.
2876
	 */
2877
	public static function shipping_zones_save_changes() {
2878
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['changes'] ) ) {
2879
			wp_send_json_error( 'missing_fields' );
2880
			exit;
2881
		}
2882
2883
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
2884
			wp_send_json_error( 'bad_nonce' );
2885
			exit;
2886
		}
2887
2888
		// Check User Caps
2889
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2890
			wp_send_json_error( 'missing_capabilities' );
2891
			exit;
2892
		}
2893
2894
		$changes = $_POST['changes'];
2895
		foreach ( $changes as $zone_id => $data ) {
2896
			if ( isset( $data['deleted'] ) ) {
2897
				if ( isset( $data['newRow'] ) ) {
2898
					// So the user added and deleted a new row.
2899
					// That's fine, it's not in the database anyways. NEXT!
2900
					continue;
2901
				}
2902
				WC_Shipping_Zones::delete_zone( $zone_id );
2903
				continue;
2904
			}
2905
2906
			$zone_data = array_intersect_key( $data, array(
2907
				'zone_id'        => 1,
2908
				'zone_name'      => 1,
2909
				'zone_order'     => 1,
2910
				'zone_locations' => 1,
2911
				'zone_postcodes' => 1
2912
			) );
2913
2914
			if ( isset( $zone_data['zone_id'] ) ) {
2915
				$zone = new WC_Shipping_Zone( $zone_data['zone_id'] );
2916
2917
				if ( isset( $zone_data['zone_name'] ) ) {
2918
					$zone->set_zone_name( $zone_data['zone_name'] );
2919
				}
2920
2921
				if ( isset( $zone_data['zone_order'] ) ) {
2922
					$zone->set_zone_order( $zone_data['zone_order'] );
2923
				}
2924
2925
				if ( isset( $zone_data['zone_locations'] ) ) {
2926
					$zone->clear_locations( array( 'state', 'country', 'continent' ) );
2927
					$locations = array_filter( array_map( 'wc_clean', (array) $zone_data['zone_locations'] ) );
2928
					foreach ( $locations as $location ) {
2929
						// Each posted location will be in the format type:code
2930
						$location_parts = explode( ':', $location );
2931
						switch ( $location_parts[0] ) {
2932
							case 'state' :
2933
								$zone->add_location( $location_parts[1] . ':' . $location_parts[2], 'state' );
2934
							break;
2935
							case 'country' :
2936
								$zone->add_location( $location_parts[1], 'country' );
2937
							break;
2938
							case 'continent' :
2939
								$zone->add_location( $location_parts[1], 'continent' );
2940
							break;
2941
						}
2942
					}
2943
				}
2944
2945
				if ( isset( $zone_data['zone_postcodes'] ) ) {
2946
					$zone->clear_locations( 'postcode' );
2947
					$postcodes = array_filter( array_map( 'strtoupper', array_map( 'wc_clean', explode( "\n", $zone_data['zone_postcodes'] ) ) ) );
2948
					foreach ( $postcodes as $postcode ) {
2949
						$zone->add_location( $postcode, 'postcode' );
2950
					}
2951
				}
2952
2953
				$zone->save();
2954
			}
2955
		}
2956
2957
		wp_send_json_success( array(
2958
			'zones' => WC_Shipping_Zones::get_zones()
2959
		) );
2960
	}
2961
2962
	/**
2963
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
2964
	 */
2965
	public static function shipping_zone_add_method() {
2966 View Code Duplication
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['method_id'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2967
			wp_send_json_error( 'missing_fields' );
2968
			exit;
2969
		}
2970
2971
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
2972
			wp_send_json_error( 'bad_nonce' );
2973
			exit;
2974
		}
2975
2976
		// Check User Caps
2977
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2978
			wp_send_json_error( 'missing_capabilities' );
2979
			exit;
2980
		}
2981
2982
		$zone_id     = absint( $_POST['zone_id'] );
2983
		$zone        = WC_Shipping_Zones::get_zone( $zone_id );
2984
		$instance_id = $zone->add_shipping_method( wc_clean( $_POST['method_id'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($_POST['method_id']) targeting wc_clean() can also be of type array; however, WC_Shipping_Zone::add_shipping_method() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2985
2986
		wp_send_json_success( array(
2987
			'instance_id' => $instance_id,
2988
			'zone_id'     => $zone_id,
2989
			'methods'     => $zone->get_shipping_methods()
2990
		) );
2991
	}
2992
2993
	/**
2994
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
2995
	 */
2996
	public static function shipping_zone_methods_save_changes() {
2997 View Code Duplication
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['changes'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2998
			wp_send_json_error( 'missing_fields' );
2999
			exit;
3000
		}
3001
3002
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3003
			wp_send_json_error( 'bad_nonce' );
3004
			exit;
3005
		}
3006
3007
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3008
			wp_send_json_error( 'missing_capabilities' );
3009
			exit;
3010
		}
3011
3012
		global $wpdb;
3013
3014
		$zone_id = absint( $_POST['zone_id'] );
3015
		$zone    = new WC_Shipping_Zone( $zone_id );
3016
		$changes = $_POST['changes'];
3017
3018
		foreach ( $changes as $instance_id => $data ) {
3019
			if ( isset( $data['deleted'] ) ) {
3020
				$wpdb->delete( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'instance_id' => $instance_id ) );
3021
				continue;
3022
			}
3023
3024
			$method_data = array_intersect_key( $data, array(
3025
				'method_order' => 1
3026
			) );
3027
3028
			if ( isset( $method_data['method_order'] ) ) {
3029
				$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'method_order' => absint( $method_data['method_order'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
3030
			}
3031
		}
3032
3033
		wp_send_json_success( array(
3034
			'methods' => $zone->get_shipping_methods()
3035
		) );
3036
	}
3037
3038
	/**
3039
	 * Handle submissions from assets/js/wc-shipping-classes.js Backbone model.
3040
	 */
3041
	public static function shipping_classes_save_changes() {
3042
		if ( ! isset( $_POST['wc_shipping_classes_nonce'], $_POST['changes'] ) ) {
3043
			wp_send_json_error( 'missing_fields' );
3044
			exit;
3045
		}
3046
3047
		if ( ! wp_verify_nonce( $_POST['wc_shipping_classes_nonce'], 'wc_shipping_classes_nonce' ) ) {
3048
			wp_send_json_error( 'bad_nonce' );
3049
			exit;
3050
		}
3051
3052
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3053
			wp_send_json_error( 'missing_capabilities' );
3054
			exit;
3055
		}
3056
3057
		$changes = $_POST['changes'];
3058
3059
		foreach ( $changes as $term_id => $data ) {
3060
			$term_id = absint( $term_id );
3061
3062
			if ( isset( $data['deleted'] ) ) {
3063
				if ( isset( $data['newRow'] ) ) {
3064
					// So the user added and deleted a new row.
3065
					// That's fine, it's not in the database anyways. NEXT!
3066
					continue;
3067
				}
3068
				wp_delete_term( $term_id, 'product_shipping_class' );
3069
				continue;
3070
			}
3071
3072
			$update_args = array();
3073
3074
			if ( isset( $data['name'] ) ) {
3075
				$update_args['name'] = wc_clean( $data['name'] );
3076
			}
3077
3078
			if ( isset( $data['slug'] ) ) {
3079
				$update_args['slug'] = wc_clean( $data['slug'] );
3080
			}
3081
3082
			if ( isset( $data['description'] ) ) {
3083
				$update_args['description'] = wc_clean( $data['description'] );
3084
			}
3085
3086
			if ( isset( $data['newRow'] ) ) {
3087
				$update_args = array_filter( $update_args );
3088
				if ( empty( $update_args['name'] ) ) {
3089
					wp_send_json_error( __( 'Shipping Class name is required', 'woocommerce' ) );
3090
					exit;
3091
				}
3092
				$result      = wp_insert_term( $update_args['name'], 'product_shipping_class', $update_args );
3093
			} else {
3094
				$result = wp_update_term( $term_id, 'product_shipping_class', $update_args );
3095
			}
3096
3097
			if ( is_wp_error( $result ) ) {
3098
				wp_send_json_error( $result->get_error_message() );
3099
				exit;
3100
			}
3101
		}
3102
3103
		$wc_shipping = WC_Shipping::instance();
3104
3105
		wp_send_json_success( array(
3106
			'shipping_classes' => $wc_shipping->get_shipping_classes()
3107
		) );
3108
	}
3109
}
3110
3111
WC_AJAX::init();
3112