Completed
Push — master ( 77da7c...c757fd )
by Mike
09:37
created

WC_AJAX   F

Complexity

Total Complexity 513

Size/Duplication

Total Lines 3297
Duplicated Lines 8.4 %

Coupling/Cohesion

Components 2
Dependencies 17

Importance

Changes 7
Bugs 1 Features 2
Metric Value
wmc 513
c 7
b 1
f 2
lcom 2
cbo 17
dl 277
loc 3297
rs 0.6314

84 Methods

Rating   Name   Duplication   Size   Complexity  
A get_endpoint() 0 3 1
A init() 0 5 1
B define_ajax() 0 15 7
A wc_ajax_headers() 0 8 1
A do_wc_ajax() 0 13 3
B save_order_items() 0 25 4
A load_order_items() 0 15 2
B add_order_note() 0 31 5
A delete_order_note() 0 17 3
C json_search_products() 6 79 12
A json_search_products_and_variations() 0 3 1
B json_search_grouped_products() 3 58 8
B json_search_downloadable_products_and_variations() 3 45 5
B add_to_cart() 0 33 6
A checkout() 0 9 2
A get_variation() 0 19 4
B feature_product() 0 14 6
B mark_order_status() 0 15 6
B add_attribute() 0 36 4
B add_new_attribute() 0 32 4
B remove_variations() 0 19 5
B save_attributes() 27 127 4
F add_variation() 8 139 14
F link_all_variations() 3 108 17
A revoke_access_to_download() 0 20 2
C grant_access_to_download() 5 53 9
B get_customer_details() 0 30 2
C add_order_item() 5 81 12
B add_order_fee() 0 28 2
A rated() 0 8 2
C update_api_key() 0 88 8
A get_customer_location() 0 4 1
F load_variations() 11 151 21
D save_variations() 3 45 9
A variation_bulk_action_toggle_enabled() 0 9 3
A variation_bulk_action_toggle_downloadable() 7 7 3
A variation_bulk_action_toggle_virtual() 7 7 3
A variation_bulk_action_toggle_manage_stock() 0 7 4
B variation_bulk_action_variable_regular_price() 19 19 5
B variation_bulk_action_variable_sale_price() 19 19 5
A variation_bulk_action_variable_stock() 0 15 4
A variation_bulk_action_variable_weight() 0 3 1
A variation_bulk_action_variable_length() 0 3 1
A variation_bulk_action_variable_width() 0 3 1
A variation_bulk_action_variable_height() 0 3 1
A variation_bulk_action_variable_download_limit() 0 3 1
A variation_bulk_action_variable_download_expiry() 0 3 1
A variation_bulk_action_delete_all() 0 7 4
C variation_bulk_action_variable_sale_schedule() 10 29 8
A variation_bulk_action_variable_regular_price_increase() 0 3 1
A variation_bulk_action_variable_regular_price_decrease() 0 3 1
A variation_bulk_action_variable_sale_price_increase() 0 3 1
A variation_bulk_action_variable_sale_price_decrease() 0 3 1
B variation_bulk_adjust_price() 0 19 5
A variation_bulk_set_meta() 0 5 2
C bulk_edit_variations() 3 38 7
C tax_rates_save_changes() 0 66 12
D shipping_zones_save_changes() 0 84 17
A add_ajax_events() 0 73 3
A get_refreshed_fragments() 0 21 2
A apply_coupon() 0 14 2
A remove_coupon() 0 20 3
B update_shipping_method() 5 24 5
A get_cart_totals() 0 12 2
F update_order_review() 13 148 29
A add_order_shipping() 0 23 3
A add_order_tax() 0 22 2
B remove_order_item() 0 21 6
A remove_order_tax() 0 20 2
C reduce_order_item_stock() 0 36 15
C increase_order_item_stock() 0 36 15
A add_order_item_meta() 0 15 3
A remove_order_item_meta() 0 15 2
F calc_line_taxes() 17 161 34
C json_search_customers() 0 47 7
A json_search_customer_name() 0 13 2
C term_ordering() 0 25 11
D product_ordering() 26 84 14
F refund_line_items() 16 124 31
B delete_refund() 0 18 6
B shipping_zone_add_method() 27 27 4
C shipping_zone_methods_save_changes() 6 46 8
B shipping_zone_methods_save_settings() 28 28 4
C shipping_classes_save_changes() 0 68 13

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_AJAX often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WC_AJAX, and based on these observations, apply Extract Interface, too.

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

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

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

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

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
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...
753
				if ( $a['position'] == $b['position'] ) {
754
					return 0;
755
				}
756
757
				return ( $a['position'] < $b['position'] ) ? -1 : 1;
758
			}
759
		}
760
		uasort( $attributes, 'attributes_cmp' );
761
762
		update_post_meta( $post_id, '_product_attributes', $attributes );
763
764
		die();
765
	}
766
767
	/**
768
	 * Add variation via ajax function.
769
	 */
770
	public static function add_variation() {
771
772
		check_ajax_referer( 'add-variation', 'security' );
773
774
		if ( ! current_user_can( 'edit_products' ) ) {
775
			die(-1);
776
		}
777
778
		global $post;
779
780
		$post_id = intval( $_POST['post_id'] );
781
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
782
		$loop    = intval( $_POST['loop'] );
783
784
		$variation = array(
785
			'post_title'   => 'Product #' . $post_id . ' Variation',
786
			'post_content' => '',
787
			'post_status'  => 'publish',
788
			'post_author'  => get_current_user_id(),
789
			'post_parent'  => $post_id,
790
			'post_type'    => 'product_variation',
791
			'menu_order'   => -1
792
		);
793
794
		$variation_id = wp_insert_post( $variation );
795
796
		do_action( 'woocommerce_create_product_variation', $variation_id );
797
798
		if ( $variation_id ) {
799
			$variation        = get_post( $variation_id );
800
			$variation_meta   = get_post_meta( $variation_id );
801
			$variation_data   = array();
802
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
803
			$variation_fields = array(
804
				'_sku'                   => '',
805
				'_stock'                 => '',
806
				'_regular_price'         => '',
807
				'_sale_price'            => '',
808
				'_weight'                => '',
809
				'_length'                => '',
810
				'_width'                 => '',
811
				'_height'                => '',
812
				'_download_limit'        => '',
813
				'_download_expiry'       => '',
814
				'_downloadable_files'    => '',
815
				'_downloadable'          => '',
816
				'_virtual'               => '',
817
				'_thumbnail_id'          => '',
818
				'_sale_price_dates_from' => '',
819
				'_sale_price_dates_to'   => '',
820
				'_manage_stock'          => '',
821
				'_stock_status'          => '',
822
				'_backorders'            => null,
823
				'_tax_class'             => null,
824
				'_variation_description' => ''
825
			);
826
827 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...
828
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
829
			}
830
831
			// Add the variation attributes
832
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
833
834
			// Formatting
835
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
836
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
837
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
838
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
839
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
840
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
841
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
842
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
843
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
844
			$variation_data['menu_order']     = $variation->menu_order;
845
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
846
847
			// Get tax classes
848
			$tax_classes           = WC_Tax::get_tax_classes();
849
			$tax_class_options     = array();
850
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
851
852 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...
853
				foreach ( $tax_classes as $class ) {
854
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
855
				}
856
			}
857
858
			// Set backorder options
859
			$backorder_options = array(
860
				'no'     => __( 'Do not allow', 'woocommerce' ),
861
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
862
				'yes'    => __( 'Allow', 'woocommerce' )
863
			);
864
865
			// set stock status options
866
			$stock_status_options = array(
867
				'instock'    => __( 'In stock', 'woocommerce' ),
868
				'outofstock' => __( 'Out of stock', 'woocommerce' )
869
			);
870
871
			// Get attributes
872
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
873
874
			$parent_data = array(
875
				'id'                   => $post_id,
876
				'attributes'           => $attributes,
877
				'tax_class_options'    => $tax_class_options,
878
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
879
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
880
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
881
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
882
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
883
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
884
				'backorder_options'    => $backorder_options,
885
				'stock_status_options' => $stock_status_options
886
			);
887
888
			if ( ! $parent_data['weight'] ) {
889
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
890
			}
891
892
			if ( ! $parent_data['length'] ) {
893
				$parent_data['length'] = wc_format_localized_decimal( 0 );
894
			}
895
896
			if ( ! $parent_data['width'] ) {
897
				$parent_data['width'] = wc_format_localized_decimal( 0 );
898
			}
899
900
			if ( ! $parent_data['height'] ) {
901
				$parent_data['height'] = wc_format_localized_decimal( 0 );
902
			}
903
904
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
905
		}
906
907
		die();
908
	}
909
910
	/**
911
	 * Link all variations via ajax function.
912
	 */
913
	public static function link_all_variations() {
914
915
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
916
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
917
		}
918
919
		check_ajax_referer( 'link-variations', 'security' );
920
921
		if ( ! current_user_can( 'edit_products' ) ) {
922
			die(-1);
923
		}
924
925 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...
926
			@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...
927
		}
928
929
		$post_id = intval( $_POST['post_id'] );
930
931
		if ( ! $post_id ) {
932
			die();
933
		}
934
935
		$variations = array();
936
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
937
938
		// Put variation attributes into an array
939
		foreach ( $_product->get_attributes() as $attribute ) {
940
941
			if ( ! $attribute['is_variation'] ) {
942
				continue;
943
			}
944
945
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
946
947
			if ( $attribute['is_taxonomy'] ) {
948
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
949
			} else {
950
				$options = explode( WC_DELIMITER, $attribute['value'] );
951
			}
952
953
			$options = array_map( 'trim', $options );
954
955
			$variations[ $attribute_field_name ] = $options;
956
		}
957
958
		// Quit out if none were found
959
		if ( sizeof( $variations ) == 0 ) {
960
			die();
961
		}
962
963
		// Get existing variations so we don't create duplicates
964
		$available_variations = array();
965
966
		foreach( $_product->get_children() as $child_id ) {
967
			$child = $_product->get_child( $child_id );
968
969
			if ( ! empty( $child->variation_id ) ) {
970
				$available_variations[] = $child->get_variation_attributes();
971
			}
972
		}
973
974
		// Created posts will all have the following data
975
		$variation_post_data = array(
976
			'post_title'   => 'Product #' . $post_id . ' Variation',
977
			'post_content' => '',
978
			'post_status'  => 'publish',
979
			'post_author'  => get_current_user_id(),
980
			'post_parent'  => $post_id,
981
			'post_type'    => 'product_variation'
982
		);
983
984
		$variation_ids       = array();
985
		$added               = 0;
986
		$possible_variations = wc_array_cartesian( $variations );
987
988
		foreach ( $possible_variations as $variation ) {
989
990
			// Check if variation already exists
991
			if ( in_array( $variation, $available_variations ) ) {
992
				continue;
993
			}
994
995
			$variation_id = wp_insert_post( $variation_post_data );
996
997
			$variation_ids[] = $variation_id;
998
999
			foreach ( $variation as $key => $value ) {
1000
				update_post_meta( $variation_id, $key, $value );
1001
			}
1002
1003
			// Save stock status
1004
			update_post_meta( $variation_id, '_stock_status', 'instock' );
1005
1006
			$added++;
1007
1008
			do_action( 'product_variation_linked', $variation_id );
1009
1010
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
1011
				break;
1012
			}
1013
		}
1014
1015
		delete_transient( 'wc_product_children_' . $post_id );
1016
1017
		echo $added;
1018
1019
		die();
1020
	}
1021
1022
	/**
1023
	 * Delete download permissions via ajax function.
1024
	 */
1025
	public static function revoke_access_to_download() {
1026
1027
		check_ajax_referer( 'revoke-access', 'security' );
1028
1029
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1030
			die(-1);
1031
		}
1032
1033
		global $wpdb;
1034
1035
		$download_id = $_POST['download_id'];
1036
		$product_id  = intval( $_POST['product_id'] );
1037
		$order_id    = intval( $_POST['order_id'] );
1038
1039
		$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 ) );
1040
1041
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
1042
1043
		die();
1044
	}
1045
1046
	/**
1047
	 * Grant download permissions via ajax function.
1048
	 */
1049
	public static function grant_access_to_download() {
1050
1051
		check_ajax_referer( 'grant-access', 'security' );
1052
1053
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1054
			die(-1);
1055
		}
1056
1057
		global $wpdb;
1058
1059
		$wpdb->hide_errors();
1060
1061
		$order_id     = intval( $_POST['order_id'] );
1062
		$product_ids  = $_POST['product_ids'];
1063
		$loop         = intval( $_POST['loop'] );
1064
		$file_counter = 0;
1065
		$order        = wc_get_order( $order_id );
1066
1067
		if ( ! is_array( $product_ids ) ) {
1068
			$product_ids = array( $product_ids );
1069
		}
1070
1071
		foreach ( $product_ids as $product_id ) {
1072
			$product = wc_get_product( $product_id );
1073
			$files   = $product->get_files();
1074
1075
			if ( ! $order->billing_email ) {
1076
				die();
1077
			}
1078
1079
			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...
1080
				foreach ( $files as $download_id => $file ) {
1081
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
1082
1083
						// insert complete - get inserted data
1084
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1085
1086
						$loop ++;
1087
						$file_counter ++;
1088
1089 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...
1090
							$file_count = $file['name'];
1091
						} else {
1092
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1093
						}
1094
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1095
					}
1096
				}
1097
			}
1098
		}
1099
1100
		die();
1101
	}
1102
1103
	/**
1104
	 * Get customer details via ajax.
1105
	 */
1106
	public static function get_customer_details() {
1107
		ob_start();
1108
1109
		check_ajax_referer( 'get-customer-details', 'security' );
1110
1111
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1112
			die(-1);
1113
		}
1114
1115
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1116
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1117
1118
		$customer_data = array(
1119
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1120
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1121
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1122
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1123
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1124
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1125
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1126
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1127
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1128
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1129
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1130
		);
1131
1132
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1133
1134
		wp_send_json( $customer_data );
1135
	}
1136
1137
	/**
1138
	 * Add order item via ajax.
1139
	 */
1140
	public static function add_order_item() {
1141
		check_ajax_referer( 'order-item', 'security' );
1142
1143
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1144
			die(-1);
1145
		}
1146
1147
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1148
		$order_id    = absint( $_POST['order_id'] );
1149
1150
		// Find the item
1151
		if ( ! is_numeric( $item_to_add ) ) {
1152
			die();
1153
		}
1154
1155
		$post = get_post( $item_to_add );
1156
1157
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1158
			die();
1159
		}
1160
1161
		$_product    = wc_get_product( $post->ID );
1162
		$order       = wc_get_order( $order_id );
1163
		$order_taxes = $order->get_taxes();
1164
		$class       = 'new_row';
1165
1166
		// Set values
1167
		$item = array();
1168
1169
		$item['product_id']        = $_product->id;
1170
		$item['variation_id']      = isset( $_product->variation_id ) ? $_product->variation_id : '';
1171
		$item['variation_data']    = $item['variation_id'] ? $_product->get_variation_attributes() : '';
1172
		$item['name']              = $_product->get_title();
1173
		$item['tax_class']         = $_product->get_tax_class();
1174
		$item['qty']               = 1;
1175
		$item['line_subtotal']     = wc_format_decimal( $_product->get_price_excluding_tax() );
1176
		$item['line_subtotal_tax'] = '';
1177
		$item['line_total']        = wc_format_decimal( $_product->get_price_excluding_tax() );
1178
		$item['line_tax']          = '';
1179
		$item['type']              = 'line_item';
1180
1181
		// Add line item
1182
		$item_id = wc_add_order_item( $order_id, array(
1183
			'order_item_name' 		=> $item['name'],
1184
			'order_item_type' 		=> 'line_item'
1185
		) );
1186
1187
		// Add line item meta
1188
		if ( $item_id ) {
1189
			wc_add_order_item_meta( $item_id, '_qty', $item['qty'] );
1190
			wc_add_order_item_meta( $item_id, '_tax_class', $item['tax_class'] );
1191
			wc_add_order_item_meta( $item_id, '_product_id', $item['product_id'] );
1192
			wc_add_order_item_meta( $item_id, '_variation_id', $item['variation_id'] );
1193
			wc_add_order_item_meta( $item_id, '_line_subtotal', $item['line_subtotal'] );
1194
			wc_add_order_item_meta( $item_id, '_line_subtotal_tax', $item['line_subtotal_tax'] );
1195
			wc_add_order_item_meta( $item_id, '_line_total', $item['line_total'] );
1196
			wc_add_order_item_meta( $item_id, '_line_tax', $item['line_tax'] );
1197
1198
			// Since 2.2
1199
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
1200
1201
			// Store variation data in meta
1202 View Code Duplication
			if ( $item['variation_data'] && is_array( $item['variation_data'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1203
				foreach ( $item['variation_data'] as $key => $value ) {
1204
					wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
1205
				}
1206
			}
1207
1208
			do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1209
		}
1210
1211
		$item['item_meta']       = $order->get_item_meta( $item_id );
1212
		$item['item_meta_array'] = $order->get_item_meta_array( $item_id );
1213
		$item                    = $order->expand_item_meta( $item );
1214
		$item                    = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
1215
1216
		include( 'admin/meta-boxes/views/html-order-item.php' );
1217
1218
		// Quit out
1219
		die();
1220
	}
1221
1222
	/**
1223
	 * Add order fee via ajax.
1224
	 */
1225
	public static function add_order_fee() {
1226
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
		$order_taxes   = $order->get_taxes();
1236
		$item          = array();
1237
1238
		// Add new fee
1239
		$fee            = new stdClass();
1240
		$fee->name      = '';
1241
		$fee->tax_class = '';
1242
		$fee->taxable   = $fee->tax_class !== '0';
1243
		$fee->amount    = '';
1244
		$fee->tax       = '';
1245
		$fee->tax_data  = array();
1246
		$item_id        = $order->add_fee( $fee );
1247
1248
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1249
1250
		// Quit out
1251
		die();
1252
	}
1253
1254
	/**
1255
	 * Add order shipping cost via ajax.
1256
	 */
1257
	public static function add_order_shipping() {
1258
1259
		check_ajax_referer( 'order-item', 'security' );
1260
1261
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1262
			die(-1);
1263
		}
1264
1265
		$order_id         = absint( $_POST['order_id'] );
1266
		$order            = wc_get_order( $order_id );
1267
		$order_taxes      = $order->get_taxes();
1268
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1269
		$item             = array();
1270
1271
		// Add new shipping
1272
		$shipping        = new WC_Shipping_Rate();
1273
		$item_id         = $order->add_shipping( $shipping );
1274
1275
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1276
1277
		// Quit out
1278
		die();
1279
	}
1280
1281
	/**
1282
	 * Add order tax column via ajax.
1283
	 */
1284
	public static function add_order_tax() {
1285
		global $wpdb;
1286
1287
		check_ajax_referer( 'order-item', 'security' );
1288
1289
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1290
			die(-1);
1291
		}
1292
1293
		$order_id = absint( $_POST['order_id'] );
1294
		$rate_id  = absint( $_POST['rate_id'] );
1295
		$order    = wc_get_order( $order_id );
1296
		$data     = get_post_meta( $order_id );
1297
1298
		// Add new tax
1299
		$order->add_tax( $rate_id, 0, 0 );
1300
1301
		// Return HTML items
1302
		include( 'admin/meta-boxes/views/html-order-items.php' );
1303
1304
		die();
1305
	}
1306
1307
	/**
1308
	 * Remove an order item.
1309
	 */
1310
	public static function remove_order_item() {
1311
		check_ajax_referer( 'order-item', 'security' );
1312
1313
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1314
			die(-1);
1315
		}
1316
1317
		$order_item_ids = $_POST['order_item_ids'];
1318
1319
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1320
			$order_item_ids = array( $order_item_ids );
1321
		}
1322
1323
		if ( sizeof( $order_item_ids ) > 0 ) {
1324
			foreach( $order_item_ids as $id ) {
1325
				wc_delete_order_item( absint( $id ) );
1326
			}
1327
		}
1328
1329
		die();
1330
	}
1331
1332
	/**
1333
	 * Remove an order tax.
1334
	 */
1335
	public static function remove_order_tax() {
1336
1337
		check_ajax_referer( 'order-item', 'security' );
1338
1339
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1340
			die(-1);
1341
		}
1342
1343
		$order_id = absint( $_POST['order_id'] );
1344
		$rate_id  = absint( $_POST['rate_id'] );
1345
1346
		wc_delete_order_item( $rate_id );
1347
1348
		// Return HTML items
1349
		$order = wc_get_order( $order_id );
1350
		$data  = get_post_meta( $order_id );
1351
		include( 'admin/meta-boxes/views/html-order-items.php' );
1352
1353
		die();
1354
	}
1355
1356
	/**
1357
	 * Reduce order item stock.
1358
	 */
1359
	public static function reduce_order_item_stock() {
1360
		check_ajax_referer( 'order-item', 'security' );
1361
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1362
			die(-1);
1363
		}
1364
		$order_id       = absint( $_POST['order_id'] );
1365
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1366
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1367
		$order          = wc_get_order( $order_id );
1368
		$order_items    = $order->get_items();
1369
		$return         = array();
1370
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1371
			foreach ( $order_items as $item_id => $order_item ) {
1372
				// Only reduce checked items
1373
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1374
					continue;
1375
				}
1376
				$_product = $order->get_product_from_item( $order_item );
1377
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1378
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1379
					$new_stock    = $_product->reduce_stock( $stock_change );
1380
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1381
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1382
					$return[]     = $note;
1383
					$order->add_order_note( $note );
1384
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1385
				}
1386
			}
1387
			do_action( 'woocommerce_reduce_order_stock', $order );
1388
			if ( empty( $return ) ) {
1389
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1390
			}
1391
			echo implode( ', ', $return );
1392
		}
1393
		die();
1394
	}
1395
1396
	/**
1397
	 * Increase order item stock.
1398
	 */
1399
	public static function increase_order_item_stock() {
1400
		check_ajax_referer( 'order-item', 'security' );
1401
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1402
			die(-1);
1403
		}
1404
		$order_id       = absint( $_POST['order_id'] );
1405
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1406
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1407
		$order          = wc_get_order( $order_id );
1408
		$order_items    = $order->get_items();
1409
		$return         = array();
1410
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1411
			foreach ( $order_items as $item_id => $order_item ) {
1412
				// Only reduce checked items
1413
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1414
					continue;
1415
				}
1416
				$_product = $order->get_product_from_item( $order_item );
1417
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1418
					$old_stock    = $_product->get_stock_quantity();
1419
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1420
					$new_quantity = $_product->increase_stock( $stock_change );
1421
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1422
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1423
					$return[]     = $note;
1424
					$order->add_order_note( $note );
1425
				}
1426
			}
1427
			do_action( 'woocommerce_restore_order_stock', $order );
1428
			if ( empty( $return ) ) {
1429
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1430
			}
1431
			echo implode( ', ', $return );
1432
		}
1433
		die();
1434
	}
1435
1436
	/**
1437
	 * Add some meta to a line item.
1438
	 */
1439
	public static function add_order_item_meta() {
1440
		check_ajax_referer( 'order-item', 'security' );
1441
1442
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1443
			die(-1);
1444
		}
1445
1446
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1447
1448
		if ( $meta_id ) {
1449
			echo '<tr data-meta_id="' . esc_attr( $meta_id ) . '"><td><input type="text" name="meta_key[' . $meta_id . ']" /><textarea name="meta_value[' . $meta_id . ']"></textarea></td><td width="1%"><button class="remove_order_item_meta button">&times;</button></td></tr>';
1450
		}
1451
1452
		die();
1453
	}
1454
1455
	/**
1456
	 * Remove meta from a line item.
1457
	 */
1458
	public static function remove_order_item_meta() {
1459
		global $wpdb;
1460
1461
		check_ajax_referer( 'order-item', 'security' );
1462
1463
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1464
			die(-1);
1465
		}
1466
1467
		$meta_id = absint( $_POST['meta_id'] );
1468
1469
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_id = %d", $meta_id ) );
1470
1471
		die();
1472
	}
1473
1474
	/**
1475
	 * Calc line tax.
1476
	 */
1477
	public static function calc_line_taxes() {
1478
		global $wpdb;
1479
1480
		check_ajax_referer( 'calc-totals', 'security' );
1481
1482
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1483
			die(-1);
1484
		}
1485
1486
		$tax            = new WC_Tax();
1487
		$tax_based_on   = get_option( 'woocommerce_tax_based_on' );
1488
		$order_id       = absint( $_POST['order_id'] );
1489
		$items          = array();
1490
		$country        = strtoupper( esc_attr( $_POST['country'] ) );
1491
		$state          = strtoupper( esc_attr( $_POST['state'] ) );
1492
		$postcode       = strtoupper( esc_attr( $_POST['postcode'] ) );
1493
		$city           = wc_clean( esc_attr( $_POST['city'] ) );
1494
		$order          = wc_get_order( $order_id );
1495
		$taxes          = array();
1496
		$shipping_taxes = array();
1497
1498
		// Default to base
1499 View Code Duplication
		if ( 'base' === $tax_based_on || empty( $country ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
3198
				$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'method_order' => absint( $method_data['method_order'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
3199
			}
3200
3201 View Code Duplication
			if ( isset( $method_data['enabled'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3202
				$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'is_enabled' => absint( 'yes' === $method_data['enabled'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
3203
			}
3204
		}
3205
3206
		wp_send_json_success( array(
3207
			'methods' => $zone->get_shipping_methods()
3208
		) );
3209
	}
3210
3211
	/**
3212
	 * Save method settings
3213
	 */
3214 View Code Duplication
	public static function shipping_zone_methods_save_settings() {
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...
3215
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['instance_id'], $_POST['data'] ) ) {
3216
			wp_send_json_error( 'missing_fields' );
3217
			exit;
3218
		}
3219
3220
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3221
			wp_send_json_error( 'bad_nonce' );
3222
			exit;
3223
		}
3224
3225
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3226
			wp_send_json_error( 'missing_capabilities' );
3227
			exit;
3228
		}
3229
3230
		$instance_id     = absint( $_POST['instance_id'] );
3231
		$zone            = WC_Shipping_Zones::get_zone_by( 'instance_id', $instance_id );
3232
		$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
3233
		$data            = $_POST['data'];
3234
3235
		$shipping_method->process_admin_options( $data );
3236
3237
		wp_send_json_success( array(
3238
			'methods' => $zone->get_shipping_methods(),
3239
			'errors'  => $shipping_method->get_errors(),
3240
		) );
3241
	}
3242
3243
	/**
3244
	 * Handle submissions from assets/js/wc-shipping-classes.js Backbone model.
3245
	 */
3246
	public static function shipping_classes_save_changes() {
3247
		if ( ! isset( $_POST['wc_shipping_classes_nonce'], $_POST['changes'] ) ) {
3248
			wp_send_json_error( 'missing_fields' );
3249
			exit;
3250
		}
3251
3252
		if ( ! wp_verify_nonce( $_POST['wc_shipping_classes_nonce'], 'wc_shipping_classes_nonce' ) ) {
3253
			wp_send_json_error( 'bad_nonce' );
3254
			exit;
3255
		}
3256
3257
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3258
			wp_send_json_error( 'missing_capabilities' );
3259
			exit;
3260
		}
3261
3262
		$changes = $_POST['changes'];
3263
3264
		foreach ( $changes as $term_id => $data ) {
3265
			$term_id = absint( $term_id );
3266
3267
			if ( isset( $data['deleted'] ) ) {
3268
				if ( isset( $data['newRow'] ) ) {
3269
					// So the user added and deleted a new row.
3270
					// That's fine, it's not in the database anyways. NEXT!
3271
					continue;
3272
				}
3273
				wp_delete_term( $term_id, 'product_shipping_class' );
3274
				continue;
3275
			}
3276
3277
			$update_args = array();
3278
3279
			if ( isset( $data['name'] ) ) {
3280
				$update_args['name'] = wc_clean( $data['name'] );
3281
			}
3282
3283
			if ( isset( $data['slug'] ) ) {
3284
				$update_args['slug'] = wc_clean( $data['slug'] );
3285
			}
3286
3287
			if ( isset( $data['description'] ) ) {
3288
				$update_args['description'] = wc_clean( $data['description'] );
3289
			}
3290
3291
			if ( isset( $data['newRow'] ) ) {
3292
				$update_args = array_filter( $update_args );
3293
				if ( empty( $update_args['name'] ) ) {
3294
					wp_send_json_error( __( 'Shipping Class name is required', 'woocommerce' ) );
3295
					exit;
3296
				}
3297
				$result      = wp_insert_term( $update_args['name'], 'product_shipping_class', $update_args );
3298
			} else {
3299
				$result = wp_update_term( $term_id, 'product_shipping_class', $update_args );
3300
			}
3301
3302
			if ( is_wp_error( $result ) ) {
3303
				wp_send_json_error( $result->get_error_message() );
3304
				exit;
3305
			}
3306
		}
3307
3308
		$wc_shipping = WC_Shipping::instance();
3309
3310
		wp_send_json_success( array(
3311
			'shipping_classes' => $wc_shipping->get_shipping_classes()
3312
		) );
3313
	}
3314
}
3315
3316
WC_AJAX::init();
3317