Completed
Push — master ( 2e553c...66decd )
by James
10:10
created

WC_AJAX::add_order_shipping()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 27
Code Lines 17

Duplication

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

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

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

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

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

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

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

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

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

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

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
65
		send_nosniff_header();
66
		nocache_headers();
67
		status_header( 200 );
68
	}
69
70
	/**
71
	 * Check for WC Ajax request and fire action.
72
	 */
73
	public static function do_wc_ajax() {
74
		global $wp_query;
75
76
		if ( ! empty( $_GET['wc-ajax'] ) ) {
77
			$wp_query->set( 'wc-ajax', sanitize_text_field( $_GET['wc-ajax'] ) );
78
		}
79
80
		if ( $action = $wp_query->get( 'wc-ajax' ) ) {
81
			self::wc_ajax_headers();
82
			do_action( 'wc_ajax_' . sanitize_text_field( $action ) );
83
			die();
84
		}
85
	}
86
87
	/**
88
	 * Hook in methods - uses WordPress ajax handlers (admin-ajax).
89
	 */
90
	public static function add_ajax_events() {
91
		// woocommerce_EVENT => nopriv
92
		$ajax_events = array(
93
			'get_refreshed_fragments'                          => true,
94
			'apply_coupon'                                     => true,
95
			'remove_coupon'                                    => true,
96
			'update_shipping_method'                           => true,
97
			'update_order_review'                              => true,
98
			'add_to_cart'                                      => true,
99
			'checkout'                                         => true,
100
			'get_variation'                                    => true,
101
			'feature_product'                                  => false,
102
			'mark_order_status'                                => false,
103
			'add_attribute'                                    => false,
104
			'add_new_attribute'                                => false,
105
			'remove_variation'                                 => false,
106
			'remove_variations'                                => false,
107
			'save_attributes'                                  => false,
108
			'add_variation'                                    => false,
109
			'link_all_variations'                              => false,
110
			'revoke_access_to_download'                        => false,
111
			'grant_access_to_download'                         => false,
112
			'get_customer_details'                             => false,
113
			'add_order_item'                                   => false,
114
			'add_order_fee'                                    => false,
115
			'add_order_shipping'                               => false,
116
			'add_order_tax'                                    => false,
117
			'remove_order_item'                                => false,
118
			'remove_order_tax'                                 => false,
119
			'reduce_order_item_stock'                          => false,
120
			'increase_order_item_stock'                        => false,
121
			'add_order_item_meta'                              => false,
122
			'remove_order_item_meta'                           => false,
123
			'calc_line_taxes'                                  => false,
124
			'save_order_items'                                 => false,
125
			'load_order_items'                                 => false,
126
			'add_order_note'                                   => false,
127
			'delete_order_note'                                => false,
128
			'json_search_products'                             => false,
129
			'json_search_products_and_variations'              => false,
130
			'json_search_grouped_products'                     => false,
131
			'json_search_downloadable_products_and_variations' => false,
132
			'json_search_customers'                            => false,
133
			'term_ordering'                                    => false,
134
			'product_ordering'                                 => false,
135
			'refund_line_items'                                => false,
136
			'delete_refund'                                    => false,
137
			'rated'                                            => false,
138
			'update_api_key'                                   => false,
139
			'get_customer_location'                            => true,
140
			'load_variations'                                  => false,
141
			'save_variations'                                  => false,
142
			'bulk_edit_variations'                             => false,
143
			'tax_rates_save_changes'                           => false,
144
		);
145
146
		foreach ( $ajax_events as $ajax_event => $nopriv ) {
147
			add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
148
149
			if ( $nopriv ) {
150
				add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) );
151
152
				// WC AJAX can be used for frontend ajax requests
153
				add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
154
			}
155
		}
156
	}
157
158
	/**
159
	 * Get a refreshed cart fragment.
160
	 */
161
	public static function get_refreshed_fragments() {
162
163
		// Get mini cart
164
		ob_start();
165
166
		woocommerce_mini_cart();
167
168
		$mini_cart = ob_get_clean();
169
170
		// Fragments and mini cart are returned
171
		$data = array(
172
			'fragments' => apply_filters( 'woocommerce_add_to_cart_fragments', array(
173
					'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>'
174
				)
175
			),
176
			'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() )
177
		);
178
179
		wp_send_json( $data );
180
181
	}
182
183
	/**
184
	 * AJAX apply coupon on checkout page.
185
	 */
186
	public static function apply_coupon() {
187
188
		check_ajax_referer( 'apply-coupon', 'security' );
189
190
		if ( ! empty( $_POST['coupon_code'] ) ) {
191
			WC()->cart->add_discount( sanitize_text_field( $_POST['coupon_code'] ) );
192
		} else {
193
			wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
194
		}
195
196
		wc_print_notices();
197
198
		die();
199
	}
200
201
	/**
202
	 * AJAX remove coupon on cart and checkout page.
203
	 */
204
	public static function remove_coupon() {
205
206
		check_ajax_referer( 'remove-coupon', 'security' );
207
208
		$coupon = wc_clean( $_POST['coupon'] );
209
210
		if ( ! isset( $coupon ) || empty( $coupon ) ) {
211
			wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ) );
212
213
		} else {
214
215
			WC()->cart->remove_coupon( $coupon );
1 ignored issue
show
Bug introduced by
It seems like $coupon defined by wc_clean($_POST['coupon']) on line 208 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...
216
217
			wc_add_notice( __( 'Coupon has been removed.', 'woocommerce' ) );
218
		}
219
220
		wc_print_notices();
221
222
		die();
223
	}
224
225
	/**
226
	 * AJAX update shipping method on cart page.
227
	 */
228
	public static function update_shipping_method() {
229
230
		check_ajax_referer( 'update-shipping-method', 'security' );
231
232
		if ( ! defined('WOOCOMMERCE_CART') ) {
233
			define( 'WOOCOMMERCE_CART', true );
234
		}
235
236
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
237
238 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...
239
			foreach ( $_POST['shipping_method'] as $i => $value ) {
240
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
241
			}
242
		}
243
244
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
245
246
		WC()->cart->calculate_totals();
247
248
		woocommerce_cart_totals();
249
250
		die();
251
	}
252
253
	/**
254
	 * AJAX update order review on checkout.
255
	 */
256
	public static function update_order_review() {
257
		ob_start();
258
259
		check_ajax_referer( 'update-order-review', 'security' );
260
261
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
262
			define( 'WOOCOMMERCE_CHECKOUT', true );
263
		}
264
265
		if ( WC()->cart->is_empty() ) {
266
			$data = array(
267
				'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
268
					'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>'
269
				) )
270
			);
271
272
			wp_send_json( $data );
273
274
			die();
275
		}
276
277
		do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] );
278
279
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
280
281 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...
282
			foreach ( $_POST['shipping_method'] as $i => $value ) {
283
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
284
			}
285
		}
286
287
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
288
		WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
289
290
		if ( isset( $_POST['country'] ) ) {
291
			WC()->customer->set_country( $_POST['country'] );
292
		}
293
294
		if ( isset( $_POST['state'] ) ) {
295
			WC()->customer->set_state( $_POST['state'] );
296
		}
297
298
		if ( isset( $_POST['postcode'] ) ) {
299
			WC()->customer->set_postcode( $_POST['postcode'] );
300
		}
301
302
		if ( isset( $_POST['city'] ) ) {
303
			WC()->customer->set_city( $_POST['city'] );
304
		}
305
306
		if ( isset( $_POST['address'] ) ) {
307
			WC()->customer->set_address( $_POST['address'] );
308
		}
309
310
		if ( isset( $_POST['address_2'] ) ) {
311
			WC()->customer->set_address_2( $_POST['address_2'] );
312
		}
313
314
		if ( wc_ship_to_billing_address_only() ) {
315
316
			if ( isset( $_POST['country'] ) ) {
317
				WC()->customer->set_shipping_country( $_POST['country'] );
318
			}
319
320
			if ( isset( $_POST['state'] ) ) {
321
				WC()->customer->set_shipping_state( $_POST['state'] );
322
			}
323
324
			if ( isset( $_POST['postcode'] ) ) {
325
				WC()->customer->set_shipping_postcode( $_POST['postcode'] );
326
			}
327
328
			if ( isset( $_POST['city'] ) ) {
329
				WC()->customer->set_shipping_city( $_POST['city'] );
330
			}
331
332
			if ( isset( $_POST['address'] ) ) {
333
				WC()->customer->set_shipping_address( $_POST['address'] );
334
			}
335
336
			if ( isset( $_POST['address_2'] ) ) {
337
				WC()->customer->set_shipping_address_2( $_POST['address_2'] );
338
			}
339
		} else {
340
341
			if ( isset( $_POST['s_country'] ) ) {
342
				WC()->customer->set_shipping_country( $_POST['s_country'] );
343
			}
344
345
			if ( isset( $_POST['s_state'] ) ) {
346
				WC()->customer->set_shipping_state( $_POST['s_state'] );
347
			}
348
349
			if ( isset( $_POST['s_postcode'] ) ) {
350
				WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
351
			}
352
353
			if ( isset( $_POST['s_city'] ) ) {
354
				WC()->customer->set_shipping_city( $_POST['s_city'] );
355
			}
356
357
			if ( isset( $_POST['s_address'] ) ) {
358
				WC()->customer->set_shipping_address( $_POST['s_address'] );
359
			}
360
361
			if ( isset( $_POST['s_address_2'] ) ) {
362
				WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
363
			}
364
		}
365
366
		WC()->cart->calculate_totals();
367
368
		// Get order review fragment
369
		ob_start();
370
		woocommerce_order_review();
371
		$woocommerce_order_review = ob_get_clean();
372
373
		// Get checkout payment fragment
374
		ob_start();
375
		woocommerce_checkout_payment();
376
		$woocommerce_checkout_payment = ob_get_clean();
377
378
		// Get messages if reload checkout is not true
379
		$messages = '';
380
		if ( ! isset( WC()->session->reload_checkout ) ) {
381
			ob_start();
382
			wc_print_notices();
383
			$messages = ob_get_clean();
384
		}
385
386
		$data = array(
387
			'result'    => empty( $messages ) ? 'success' : 'failure',
388
			'messages'  => $messages,
389
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
390
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
391
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
392
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
393
			) )
394
		);
395
396
		wp_send_json( $data );
397
398
		die();
399
	}
400
401
	/**
402
	 * AJAX add to cart.
403
	 */
404
	public static function add_to_cart() {
405
		ob_start();
406
407
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
408
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
409
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
410
		$product_status    = get_post_status( $product_id );
411
412
		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...
413
414
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
415
416
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
417
				wc_add_to_cart_message( $product_id );
418
			}
419
420
			// Return fragments
421
			self::get_refreshed_fragments();
422
423
		} else {
424
425
			// If there was an error adding to the cart, redirect to the product page to show any errors
426
			$data = array(
427
				'error'       => true,
428
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
429
			);
430
431
			wp_send_json( $data );
432
433
		}
434
435
		die();
436
	}
437
438
	/**
439
	 * Process ajax checkout form.
440
	 */
441
	public static function checkout() {
442
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
443
			define( 'WOOCOMMERCE_CHECKOUT', true );
444
		}
445
446
		WC()->checkout()->process_checkout();
447
448
		die(0);
449
	}
450
451
	/**
452
	 * Get a matching variation based on posted attributes.
453
	 */
454
	public static function get_variation() {
455
		ob_start();
456
457
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
458
			die();
459
		}
460
461
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
462
463
		if ( $variation_id ) {
464
			$variation = $variable_product->get_available_variation( $variation_id );
465
		} else {
466
			$variation = false;
467
		}
468
469
		wp_send_json( $variation );
470
471
		die();
472
	}
473
474
	/**
475
	 * Feature a product from admin.
476
	 */
477
	public static function feature_product() {
478
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
479
			$product_id = absint( $_GET['product_id'] );
480
481
			if ( 'product' === get_post_type( $product_id ) ) {
482
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
483
484
				delete_transient( 'wc_featured_products' );
485
			}
486
		}
487
488
		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' ) );
489
		die();
490
	}
491
492
	/**
493
	 * Mark an order with a status.
494
	 */
495
	public static function mark_order_status() {
496
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
497
			$status   = sanitize_text_field( $_GET['status'] );
498
			$order_id = absint( $_GET['order_id'] );
499
500
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
501
				$order = wc_get_order( $order_id );
502
				$order->update_status( $status, '', true );
503
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
504
			}
505
		}
506
507
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
508
		die();
509
	}
510
511
	/**
512
	 * Add an attribute row.
513
	 */
514
	public static function add_attribute() {
515
		ob_start();
516
517
		check_ajax_referer( 'add-attribute', 'security' );
518
519
		if ( ! current_user_can( 'edit_products' ) ) {
520
			die(-1);
521
		}
522
523
		global $wc_product_attributes;
524
525
		$thepostid     = 0;
526
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
527
		$i             = absint( $_POST['i'] );
528
		$position      = 0;
529
		$metabox_class = array();
530
		$attribute     = array(
531
			'name'         => $taxonomy,
532
			'value'        => '',
533
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
534
			'is_variation' => 0,
535
			'is_taxonomy'  => $taxonomy ? 1 : 0
536
		);
537
538
		if ( $taxonomy ) {
539
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
540
			$metabox_class[]    = 'taxonomy';
541
			$metabox_class[]    = $taxonomy;
542
			$attribute_label    = wc_attribute_label( $taxonomy );
543
		} else {
544
			$attribute_label = '';
545
		}
546
547
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
548
		die();
549
	}
550
551
	/**
552
	 * Add a new attribute via ajax function.
553
	 */
554
	public static function add_new_attribute() {
555
		ob_start();
556
557
		check_ajax_referer( 'add-attribute', 'security' );
558
559
		if ( ! current_user_can( 'manage_product_terms' ) ) {
560
			die(-1);
561
		}
562
563
		$taxonomy = esc_attr( $_POST['taxonomy'] );
564
		$term     = wc_clean( $_POST['term'] );
565
566
		if ( taxonomy_exists( $taxonomy ) ) {
567
568
			$result = wp_insert_term( $term, $taxonomy );
569
570
			if ( is_wp_error( $result ) ) {
571
				wp_send_json( array(
572
					'error' => $result->get_error_message()
573
				) );
574
			} else {
575
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
576
				wp_send_json( array(
577
					'term_id' => $term->term_id,
578
					'name'    => $term->name,
579
					'slug'    => $term->slug
580
				) );
581
			}
582
		}
583
584
		die();
585
	}
586
587
	/**
588
	 * Delete variations via ajax function.
589
	 */
590
	public static function remove_variations() {
591
		check_ajax_referer( 'delete-variations', 'security' );
592
593
		if ( ! current_user_can( 'edit_products' ) ) {
594
			die(-1);
595
		}
596
597
		$variation_ids = (array) $_POST['variation_ids'];
598
599
		foreach ( $variation_ids as $variation_id ) {
600
			$variation = get_post( $variation_id );
601
602
			if ( $variation && 'product_variation' == $variation->post_type ) {
603
				wp_delete_post( $variation_id );
604
			}
605
		}
606
607
		die();
608
	}
609
610
	/**
611
	 * Save attributes via ajax.
612
	 */
613
	public static function save_attributes() {
614
615
		check_ajax_referer( 'save-attributes', 'security' );
616
617
		if ( ! current_user_can( 'edit_products' ) ) {
618
			die(-1);
619
		}
620
621
		// Get post data
622
		parse_str( $_POST['data'], $data );
623
		$post_id = absint( $_POST['post_id'] );
624
625
		// Save Attributes
626
		$attributes = array();
627
628
		if ( isset( $data['attribute_names'] ) ) {
629
630
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
631
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
632
633
			if ( isset( $data['attribute_visibility'] ) ) {
634
				$attribute_visibility = $data['attribute_visibility'];
635
			}
636
637
			if ( isset( $data['attribute_variation'] ) ) {
638
				$attribute_variation = $data['attribute_variation'];
639
			}
640
641
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
642
			$attribute_position      = $data['attribute_position'];
643
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
644
645
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
646
				if ( empty( $attribute_names[ $i ] ) ) {
647
					continue;
648
				}
649
650
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
651
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
652
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
653
654
				if ( $is_taxonomy ) {
655
656
					if ( isset( $attribute_values[ $i ] ) ) {
657
658
						// Select based attributes - Format values (posted values are slugs)
659
						if ( is_array( $attribute_values[ $i ] ) ) {
660
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
661
662
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
663
						} else {
664
							$values     = array();
665
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
666
667
							foreach ( $raw_values as $value ) {
668
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
669
								if ( ! $term ) {
670
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
671
672
									if ( $term && ! is_wp_error( $term ) ) {
673
										$values[] = $term['term_id'];
674
									}
675
								} else {
676
									$values[] = $term->term_id;
677
								}
678
							}
679
						}
680
681
						// Remove empty items in the array
682
						$values = array_filter( $values, 'strlen' );
683
684
					} else {
685
						$values = array();
686
					}
687
688
					// Update post terms
689
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
690
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
691
					}
692
693 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...
694
						// Add attribute to array, but don't set values
695
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
696
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
697
							'value' 		=> '',
698
							'position' 		=> $attribute_position[ $i ],
699
							'is_visible' 	=> $is_visible,
700
							'is_variation' 	=> $is_variation,
701
							'is_taxonomy' 	=> $is_taxonomy
702
						);
703
					}
704
705 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...
706
707
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
708
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
709
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
710
711
					// Custom attribute - Add attribute to array and set the values
712
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
713
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
714
						'value' 		=> $values,
715
						'position' 		=> $attribute_position[ $i ],
716
						'is_visible' 	=> $is_visible,
717
						'is_variation' 	=> $is_variation,
718
						'is_taxonomy' 	=> $is_taxonomy
719
					);
720
				}
721
722
			 }
723
		}
724
725
		if ( ! function_exists( 'attributes_cmp' ) ) {
726
			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 (L969-975) 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...
727
				if ( $a['position'] == $b['position'] ) {
728
					return 0;
729
				}
730
731
				return ( $a['position'] < $b['position'] ) ? -1 : 1;
732
			}
733
		}
734
		uasort( $attributes, 'attributes_cmp' );
735
736
		update_post_meta( $post_id, '_product_attributes', $attributes );
737
738
		die();
739
	}
740
741
	/**
742
	 * Add variation via ajax function.
743
	 */
744
	public static function add_variation() {
745
746
		check_ajax_referer( 'add-variation', 'security' );
747
748
		if ( ! current_user_can( 'edit_products' ) ) {
749
			die(-1);
750
		}
751
752
		global $post;
753
754
		$post_id = intval( $_POST['post_id'] );
755
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
756
		$loop    = intval( $_POST['loop'] );
757
758
		$variation = array(
759
			'post_title'   => 'Product #' . $post_id . ' Variation',
760
			'post_content' => '',
761
			'post_status'  => 'publish',
762
			'post_author'  => get_current_user_id(),
763
			'post_parent'  => $post_id,
764
			'post_type'    => 'product_variation',
765
			'menu_order'   => -1
766
		);
767
768
		$variation_id = wp_insert_post( $variation );
769
770
		do_action( 'woocommerce_create_product_variation', $variation_id );
771
772
		if ( $variation_id ) {
773
			$variation        = get_post( $variation_id );
774
			$variation_meta   = get_post_meta( $variation_id );
775
			$variation_data   = array();
776
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
777
			$variation_fields = array(
778
				'_sku'                   => '',
779
				'_stock'                 => '',
780
				'_regular_price'         => '',
781
				'_sale_price'            => '',
782
				'_weight'                => '',
783
				'_length'                => '',
784
				'_width'                 => '',
785
				'_height'                => '',
786
				'_download_limit'        => '',
787
				'_download_expiry'       => '',
788
				'_downloadable_files'    => '',
789
				'_downloadable'          => '',
790
				'_virtual'               => '',
791
				'_thumbnail_id'          => '',
792
				'_sale_price_dates_from' => '',
793
				'_sale_price_dates_to'   => '',
794
				'_manage_stock'          => '',
795
				'_stock_status'          => '',
796
				'_backorders'            => null,
797
				'_tax_class'             => null,
798
				'_variation_description' => ''
799
			);
800
801 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...
802
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
803
			}
804
805
			// Add the variation attributes
806
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
807
808
			// Formatting
809
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
810
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
811
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
812
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
813
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
814
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
815
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
816
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
817
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
818
			$variation_data['menu_order']     = $variation->menu_order;
819
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
820
821
			// Get tax classes
822
			$tax_classes           = WC_Tax::get_tax_classes();
823
			$tax_class_options     = array();
824
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
825
826 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...
827
				foreach ( $tax_classes as $class ) {
828
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
829
				}
830
			}
831
832
			// Set backorder options
833
			$backorder_options = array(
834
				'no'     => __( 'Do not allow', 'woocommerce' ),
835
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
836
				'yes'    => __( 'Allow', 'woocommerce' )
837
			);
838
839
			// set stock status options
840
			$stock_status_options = array(
841
				'instock'    => __( 'In stock', 'woocommerce' ),
842
				'outofstock' => __( 'Out of stock', 'woocommerce' )
843
			);
844
845
			// Get attributes
846
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
847
848
			$parent_data = array(
849
				'id'                   => $post_id,
850
				'attributes'           => $attributes,
851
				'tax_class_options'    => $tax_class_options,
852
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
853
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
854
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
855
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
856
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
857
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
858
				'backorder_options'    => $backorder_options,
859
				'stock_status_options' => $stock_status_options
860
			);
861
862
			if ( ! $parent_data['weight'] ) {
863
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
864
			}
865
866
			if ( ! $parent_data['length'] ) {
867
				$parent_data['length'] = wc_format_localized_decimal( 0 );
868
			}
869
870
			if ( ! $parent_data['width'] ) {
871
				$parent_data['width'] = wc_format_localized_decimal( 0 );
872
			}
873
874
			if ( ! $parent_data['height'] ) {
875
				$parent_data['height'] = wc_format_localized_decimal( 0 );
876
			}
877
878
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
879
		}
880
881
		die();
882
	}
883
884
	/**
885
	 * Link all variations via ajax function.
886
	 */
887
	public static function link_all_variations() {
888
889
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
890
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
891
		}
892
893
		check_ajax_referer( 'link-variations', 'security' );
894
895
		if ( ! current_user_can( 'edit_products' ) ) {
896
			die(-1);
897
		}
898
899 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...
900
			@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...
901
		}
902
903
		$post_id = intval( $_POST['post_id'] );
904
905
		if ( ! $post_id ) {
906
			die();
907
		}
908
909
		$variations = array();
910
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
911
912
		// Put variation attributes into an array
913
		foreach ( $_product->get_attributes() as $attribute ) {
914
915
			if ( ! $attribute['is_variation'] ) {
916
				continue;
917
			}
918
919
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
920
921
			if ( $attribute['is_taxonomy'] ) {
922
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
923
			} else {
924
				$options = explode( WC_DELIMITER, $attribute['value'] );
925
			}
926
927
			$options = array_map( 'trim', $options );
928
929
			$variations[ $attribute_field_name ] = $options;
930
		}
931
932
		// Quit out if none were found
933
		if ( sizeof( $variations ) == 0 ) {
934
			die();
935
		}
936
937
		// Get existing variations so we don't create duplicates
938
		$available_variations = array();
939
940
		foreach( $_product->get_children() as $child_id ) {
941
			$child = $_product->get_child( $child_id );
942
943
			if ( ! empty( $child->variation_id ) ) {
944
				$available_variations[] = $child->get_variation_attributes();
945
			}
946
		}
947
948
		// Created posts will all have the following data
949
		$variation_post_data = array(
950
			'post_title'   => 'Product #' . $post_id . ' Variation',
951
			'post_content' => '',
952
			'post_status'  => 'publish',
953
			'post_author'  => get_current_user_id(),
954
			'post_parent'  => $post_id,
955
			'post_type'    => 'product_variation'
956
		);
957
958
		$variation_ids       = array();
959
		$added               = 0;
960
		$possible_variations = wc_array_cartesian( $variations );
961
962
		foreach ( $possible_variations as $variation ) {
963
964
			// Check if variation already exists
965
			if ( in_array( $variation, $available_variations ) ) {
966
				continue;
967
			}
968
969
			$variation_id = wp_insert_post( $variation_post_data );
970
971
			$variation_ids[] = $variation_id;
972
973
			foreach ( $variation as $key => $value ) {
974
				update_post_meta( $variation_id, $key, $value );
975
			}
976
977
			// Save stock status
978
			update_post_meta( $variation_id, '_stock_status', 'instock' );
979
980
			$added++;
981
982
			do_action( 'product_variation_linked', $variation_id );
983
984
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
985
				break;
986
			}
987
		}
988
989
		delete_transient( 'wc_product_children_' . $post_id );
990
991
		echo $added;
992
993
		die();
994
	}
995
996
	/**
997
	 * Delete download permissions via ajax function.
998
	 */
999
	public static function revoke_access_to_download() {
1000
1001
		check_ajax_referer( 'revoke-access', 'security' );
1002
1003
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1004
			die(-1);
1005
		}
1006
1007
		global $wpdb;
1008
1009
		$download_id = $_POST['download_id'];
1010
		$product_id  = intval( $_POST['product_id'] );
1011
		$order_id    = intval( $_POST['order_id'] );
1012
1013
		$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 ) );
1014
1015
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
1016
1017
		die();
1018
	}
1019
1020
	/**
1021
	 * Grant download permissions via ajax function.
1022
	 */
1023
	public static function grant_access_to_download() {
1024
1025
		check_ajax_referer( 'grant-access', 'security' );
1026
1027
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1028
			die(-1);
1029
		}
1030
1031
		global $wpdb;
1032
1033
		$wpdb->hide_errors();
1034
1035
		$order_id     = intval( $_POST['order_id'] );
1036
		$product_ids  = $_POST['product_ids'];
1037
		$loop         = intval( $_POST['loop'] );
1038
		$file_counter = 0;
1039
		$order        = wc_get_order( $order_id );
1040
1041
		if ( ! is_array( $product_ids ) ) {
1042
			$product_ids = array( $product_ids );
1043
		}
1044
1045
		foreach ( $product_ids as $product_id ) {
1046
			$product = wc_get_product( $product_id );
1047
			$files   = $product->get_files();
1048
1049
			if ( ! $order->billing_email ) {
1050
				die();
1051
			}
1052
1053
			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...
1054
				foreach ( $files as $download_id => $file ) {
1055
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
1056
1057
						// insert complete - get inserted data
1058
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1059
1060
						$loop ++;
1061
						$file_counter ++;
1062
1063 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...
1064
							$file_count = $file['name'];
1065
						} else {
1066
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1067
						}
1068
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1069
					}
1070
				}
1071
			}
1072
		}
1073
1074
		die();
1075
	}
1076
1077
	/**
1078
	 * Get customer details via ajax.
1079
	 */
1080
	public static function get_customer_details() {
1081
		ob_start();
1082
1083
		check_ajax_referer( 'get-customer-details', 'security' );
1084
1085
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1086
			die(-1);
1087
		}
1088
1089
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1090
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1091
1092
		$customer_data = array(
1093
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1094
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1095
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1096
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1097
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1098
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1099
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1100
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1101
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1102
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1103
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1104
		);
1105
1106
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1107
1108
		wp_send_json( $customer_data );
1109
	}
1110
1111
	/**
1112
	 * Add order item via ajax.
1113
	 */
1114
	public static function add_order_item() {
1115
		check_ajax_referer( 'order-item', 'security' );
1116
1117
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1118
			die(-1);
1119
		}
1120
1121
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1122
		$order_id    = absint( $_POST['order_id'] );
1123
1124
		// Find the item
1125
		if ( ! is_numeric( $item_to_add ) ) {
1126
			die();
1127
		}
1128
1129
		$post = get_post( $item_to_add );
1130
1131
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1132
			die();
1133
		}
1134
1135
		$_product    = wc_get_product( $post->ID );
1136
		$order       = wc_get_order( $order_id );
1137
		$order_taxes = $order->get_taxes();
1138
		$class       = 'new_row';
1139
1140
		// Set values
1141
		$item = array();
1142
1143
		$item['product_id']        = $_product->id;
1144
		$item['variation_id']      = isset( $_product->variation_id ) ? $_product->variation_id : '';
1145
		$item['variation_data']    = $item['variation_id'] ? $_product->get_variation_attributes() : '';
1146
		$item['name']              = $_product->get_title();
1147
		$item['tax_class']         = $_product->get_tax_class();
1148
		$item['qty']               = 1;
1149
		$item['line_subtotal']     = wc_format_decimal( $_product->get_price_excluding_tax() );
1150
		$item['line_subtotal_tax'] = '';
1151
		$item['line_total']        = wc_format_decimal( $_product->get_price_excluding_tax() );
1152
		$item['line_tax']          = '';
1153
		$item['type']              = 'line_item';
1154
1155
		// Add line item
1156
		$item_id = wc_add_order_item( $order_id, array(
1157
			'order_item_name' 		=> $item['name'],
1158
			'order_item_type' 		=> 'line_item'
1159
		) );
1160
1161
		// Add line item meta
1162
		if ( $item_id ) {
1163
			wc_add_order_item_meta( $item_id, '_qty', $item['qty'] );
1164
			wc_add_order_item_meta( $item_id, '_tax_class', $item['tax_class'] );
1165
			wc_add_order_item_meta( $item_id, '_product_id', $item['product_id'] );
1166
			wc_add_order_item_meta( $item_id, '_variation_id', $item['variation_id'] );
1167
			wc_add_order_item_meta( $item_id, '_line_subtotal', $item['line_subtotal'] );
1168
			wc_add_order_item_meta( $item_id, '_line_subtotal_tax', $item['line_subtotal_tax'] );
1169
			wc_add_order_item_meta( $item_id, '_line_total', $item['line_total'] );
1170
			wc_add_order_item_meta( $item_id, '_line_tax', $item['line_tax'] );
1171
1172
			// Since 2.2
1173
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
1174
1175
			// Store variation data in meta
1176 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...
1177
				foreach ( $item['variation_data'] as $key => $value ) {
1178
					wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
1179
				}
1180
			}
1181
1182
			do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1183
		}
1184
1185
		$item['item_meta']       = $order->get_item_meta( $item_id );
1186
		$item['item_meta_array'] = $order->get_item_meta_array( $item_id );
1187
		$item                    = $order->expand_item_meta( $item );
1188
		$item                    = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
1189
1190
		include( 'admin/meta-boxes/views/html-order-item.php' );
1191
1192
		// Quit out
1193
		die();
1194
	}
1195
1196
	/**
1197
	 * Add order fee via ajax.
1198
	 */
1199
	public static function add_order_fee() {
1200
1201
		check_ajax_referer( 'order-item', 'security' );
1202
1203
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1204
			die(-1);
1205
		}
1206
1207
		$order_id      = absint( $_POST['order_id'] );
1208
		$order         = wc_get_order( $order_id );
1209
		$order_taxes   = $order->get_taxes();
1210
		$item          = array();
1211
1212
		// Add new fee
1213
		$fee            = new stdClass();
1214
		$fee->name      = '';
1215
		$fee->tax_class = '';
1216
		$fee->taxable   = $fee->tax_class !== '0';
1217
		$fee->amount    = '';
1218
		$fee->tax       = '';
1219
		$fee->tax_data  = array();
1220
		$item_id        = $order->add_fee( $fee );
1221
1222
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1223
1224
		// Quit out
1225
		die();
1226
	}
1227
1228
	/**
1229
	 * Add order shipping cost via ajax.
1230
	 */
1231
	public static function add_order_shipping() {
1232
1233
		check_ajax_referer( 'order-item', 'security' );
1234
1235
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1236
			die(-1);
1237
		}
1238
1239
		$order_id         = absint( $_POST['order_id'] );
1240
		$order            = wc_get_order( $order_id );
1241
		$order_taxes      = $order->get_taxes();
1242
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1243
		$item             = array();
1244
1245
		// Add new shipping
1246
		$shipping        = new stdClass();
1247
		$shipping->label = '';
1248
		$shipping->id    = '';
1249
		$shipping->cost  = '';
1250
		$shipping->taxes = array();
1251
		$item_id         = $order->add_shipping( $shipping );
1252
1253
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1254
1255
		// Quit out
1256
		die();
1257
	}
1258
1259
	/**
1260
	 * Add order tax column via ajax.
1261
	 */
1262
	public static function add_order_tax() {
1263
		global $wpdb;
1264
1265
		check_ajax_referer( 'order-item', 'security' );
1266
1267
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1268
			die(-1);
1269
		}
1270
1271
		$order_id = absint( $_POST['order_id'] );
1272
		$rate_id  = absint( $_POST['rate_id'] );
1273
		$order    = wc_get_order( $order_id );
1274
		$data     = get_post_meta( $order_id );
1275
1276
		// Add new tax
1277
		$order->add_tax( $rate_id, 0, 0 );
1278
1279
		// Return HTML items
1280
		include( 'admin/meta-boxes/views/html-order-items.php' );
1281
1282
		die();
1283
	}
1284
1285
	/**
1286
	 * Remove an order item.
1287
	 */
1288
	public static function remove_order_item() {
1289
		check_ajax_referer( 'order-item', 'security' );
1290
1291
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1292
			die(-1);
1293
		}
1294
1295
		$order_item_ids = $_POST['order_item_ids'];
1296
1297
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1298
			$order_item_ids = array( $order_item_ids );
1299
		}
1300
1301
		if ( sizeof( $order_item_ids ) > 0 ) {
1302
			foreach( $order_item_ids as $id ) {
1303
				wc_delete_order_item( absint( $id ) );
1304
			}
1305
		}
1306
1307
		die();
1308
	}
1309
1310
	/**
1311
	 * Remove an order tax.
1312
	 */
1313
	public static function remove_order_tax() {
1314
1315
		check_ajax_referer( 'order-item', 'security' );
1316
1317
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1318
			die(-1);
1319
		}
1320
1321
		$order_id = absint( $_POST['order_id'] );
1322
		$rate_id  = absint( $_POST['rate_id'] );
1323
1324
		wc_delete_order_item( $rate_id );
1325
1326
		// Return HTML items
1327
		$order = wc_get_order( $order_id );
1328
		$data  = get_post_meta( $order_id );
1329
		include( 'admin/meta-boxes/views/html-order-items.php' );
1330
1331
		die();
1332
	}
1333
1334
	/**
1335
	 * Reduce order item stock.
1336
	 */
1337
	public static function reduce_order_item_stock() {
1338
		check_ajax_referer( 'order-item', 'security' );
1339
1340
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1341
			die(-1);
1342
		}
1343
1344
		$order_id       = absint( $_POST['order_id'] );
1345
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1346
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1347
		$order          = wc_get_order( $order_id );
1348
		$order_items    = $order->get_items();
1349
		$return         = array();
1350
1351
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1352
1353
			foreach ( $order_items as $item_id => $order_item ) {
1354
1355
				// Only reduce checked items
1356
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1357
					continue;
1358
				}
1359
1360
				$_product = $order->get_product_from_item( $order_item );
1361
1362
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1363
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1364
					$new_stock    = $_product->reduce_stock( $stock_change );
1365
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1366
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1367
					$return[]     = $note;
1368
1369
					$order->add_order_note( $note );
1370
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1371
				}
1372
			}
1373
1374
			do_action( 'woocommerce_reduce_order_stock', $order );
1375
1376
			if ( empty( $return ) ) {
1377
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1378
			}
1379
1380
			echo implode( ', ', $return );
1381
		}
1382
1383
		die();
1384
	}
1385
1386
	/**
1387
	 * Increase order item stock.
1388
	 */
1389
	public static function increase_order_item_stock() {
1390
		check_ajax_referer( 'order-item', 'security' );
1391
1392
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1393
			die(-1);
1394
		}
1395
1396
		$order_id       = absint( $_POST['order_id'] );
1397
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1398
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1399
		$order          = wc_get_order( $order_id );
1400
		$order_items    = $order->get_items();
1401
		$return         = array();
1402
1403
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1404
1405
			foreach ( $order_items as $item_id => $order_item ) {
1406
1407
				// Only reduce checked items
1408
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1409
					continue;
1410
				}
1411
1412
				$_product = $order->get_product_from_item( $order_item );
1413
1414
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1415
					$old_stock    = $_product->get_stock_quantity();
1416
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1417
					$new_quantity = $_product->increase_stock( $stock_change );
1418
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1419
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1420
					$return[]     = $note;
1421
1422
					$order->add_order_note( $note );
1423
				}
1424
			}
1425
1426
			do_action( 'woocommerce_restore_order_stock', $order );
1427
1428
			if ( empty( $return ) ) {
1429
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1430
			}
1431
1432
			echo implode( ', ', $return );
1433
		}
1434
1435
		die();
1436
	}
1437
1438
	/**
1439
	 * Add some meta to a line item.
1440
	 */
1441
	public static function add_order_item_meta() {
1442
		check_ajax_referer( 'order-item', 'security' );
1443
1444
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1445
			die(-1);
1446
		}
1447
1448
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1449
1450
		if ( $meta_id ) {
1451
			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>';
1452
		}
1453
1454
		die();
1455
	}
1456
1457
	/**
1458
	 * Remove meta from a line item.
1459
	 */
1460
	public static function remove_order_item_meta() {
1461
		global $wpdb;
1462
1463
		check_ajax_referer( 'order-item', 'security' );
1464
1465
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1466
			die(-1);
1467
		}
1468
1469
		$meta_id = absint( $_POST['meta_id'] );
1470
1471
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_id = %d", $meta_id ) );
1472
1473
		die();
1474
	}
1475
1476
	/**
1477
	 * Calc line tax.
1478
	 */
1479
	public static function calc_line_taxes() {
1480
		global $wpdb;
1481
1482
		check_ajax_referer( 'calc-totals', 'security' );
1483
1484
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1485
			die(-1);
1486
		}
1487
1488
		$tax            = new WC_Tax();
1489
		$tax_based_on   = get_option( 'woocommerce_tax_based_on' );
1490
		$order_id       = absint( $_POST['order_id'] );
1491
		$items          = array();
1492
		$country        = strtoupper( esc_attr( $_POST['country'] ) );
1493
		$state          = strtoupper( esc_attr( $_POST['state'] ) );
1494
		$postcode       = strtoupper( esc_attr( $_POST['postcode'] ) );
1495
		$city           = wc_clean( esc_attr( $_POST['city'] ) );
1496
		$order          = wc_get_order( $order_id );
1497
		$taxes          = array();
1498
		$shipping_taxes = array();
1499
1500
		// Default to base
1501 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...
1502
			$default  = wc_get_base_location();
1503
			$country  = $default['country'];
1504
			$state    = $default['state'];
1505
			$postcode = '';
1506
			$city     = '';
1507
		}
1508
1509
		// Parse the jQuery serialized items
1510
		parse_str( $_POST['items'], $items );
1511
1512
		// Prevent undefined warnings
1513
		if ( ! isset( $items['line_tax'] ) ) {
1514
			$items['line_tax'] = array();
1515
		}
1516
		if ( ! isset( $items['line_subtotal_tax'] ) ) {
1517
			$items['line_subtotal_tax'] = array();
1518
		}
1519
		$items['order_taxes'] = array();
1520
1521
		// Action
1522
		$items = apply_filters( 'woocommerce_ajax_calc_line_taxes', $items, $order_id, $country, $_POST );
1523
1524
		// Get items and fees taxes
1525
		if ( isset( $items['order_item_id'] ) ) {
1526
			$line_total = $line_subtotal = $order_item_tax_class = array();
1527
1528
			foreach ( $items['order_item_id'] as $item_id ) {
1529
				$item_id                          = absint( $item_id );
1530
				$line_total[ $item_id ]           = isset( $items['line_total'][ $item_id ] ) ? wc_format_decimal( $items['line_total'][ $item_id ] ) : 0;
1531
				$line_subtotal[ $item_id ]        = isset( $items['line_subtotal'][ $item_id ] ) ? wc_format_decimal( $items['line_subtotal'][ $item_id ] ) : $line_total[ $item_id ];
1532
				$order_item_tax_class[ $item_id ] = isset( $items['order_item_tax_class'][ $item_id ] ) ? sanitize_text_field( $items['order_item_tax_class'][ $item_id ] ) : '';
1533
				$product_id                       = $order->get_item_meta( $item_id, '_product_id', true );
1534
1535
				// Get product details
1536
				if ( get_post_type( $product_id ) == 'product' ) {
1537
					$_product        = wc_get_product( $product_id );
1538
					$item_tax_status = $_product->get_tax_status();
1539
				} else {
1540
					$item_tax_status = 'taxable';
1541
				}
1542
1543
				if ( '0' !== $order_item_tax_class[ $item_id ] && 'taxable' === $item_tax_status ) {
1544
					$tax_rates = WC_Tax::find_rates( array(
1545
						'country'   => $country,
1546
						'state'     => $state,
1547
						'postcode'  => $postcode,
1548
						'city'      => $city,
1549
						'tax_class' => $order_item_tax_class[ $item_id ]
1550
					) );
1551
1552
					$line_taxes          = WC_Tax::calc_tax( $line_total[ $item_id ], $tax_rates, false );
1553
					$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal[ $item_id ], $tax_rates, false );
1554
1555
					// Set the new line_tax
1556
					foreach ( $line_taxes as $_tax_id => $_tax_value ) {
1557
						$items['line_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1558
					}
1559
1560
					// Set the new line_subtotal_tax
1561
					foreach ( $line_subtotal_taxes as $_tax_id => $_tax_value ) {
1562
						$items['line_subtotal_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1563
					}
1564
1565
					// Sum the item taxes
1566
					foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1567
						$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1568
					}
1569
				}
1570
			}
1571
		}
1572
1573
		// Get shipping taxes
1574
		if ( isset( $items['shipping_method_id'] ) ) {
1575
			$matched_tax_rates = array();
1576
1577
			$tax_rates = WC_Tax::find_rates( array(
1578
				'country'   => $country,
1579
				'state'     => $state,
1580
				'postcode'  => $postcode,
1581
				'city'      => $city,
1582
				'tax_class' => ''
1583
			) );
1584
1585 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...
1586
				foreach ( $tax_rates as $key => $rate ) {
1587
					if ( isset( $rate['shipping'] ) && 'yes' == $rate['shipping'] ) {
1588
						$matched_tax_rates[ $key ] = $rate;
1589
					}
1590
				}
1591
			}
1592
1593
			$shipping_cost = $shipping_taxes = array();
1594
1595
			foreach ( $items['shipping_method_id'] as $item_id ) {
1596
				$item_id                   = absint( $item_id );
1597
				$shipping_cost[ $item_id ] = isset( $items['shipping_cost'][ $item_id ] ) ? wc_format_decimal( $items['shipping_cost'][ $item_id ] ) : 0;
1598
				$_shipping_taxes           = WC_Tax::calc_shipping_tax( $shipping_cost[ $item_id ], $matched_tax_rates );
1599
1600
				// Set the new shipping_taxes
1601
				foreach ( $_shipping_taxes as $_tax_id => $_tax_value ) {
1602
					$items['shipping_taxes'][ $item_id ][ $_tax_id ] = $_tax_value;
1603
1604
					$shipping_taxes[ $_tax_id ] = isset( $shipping_taxes[ $_tax_id ] ) ? $shipping_taxes[ $_tax_id ] + $_tax_value : $_tax_value;
1605
				}
1606
			}
1607
		}
1608
1609
		// Remove old tax rows
1610
		$order->remove_order_items( 'tax' );
1611
1612
		// Add tax rows
1613 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...
1614
			$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 );
1615
		}
1616
1617
		// Create the new order_taxes
1618
		foreach ( $order->get_taxes() as $tax_id => $tax_item ) {
1619
			$items['order_taxes'][ $tax_id ] = absint( $tax_item['rate_id'] );
1620
		}
1621
1622
		$items = apply_filters( 'woocommerce_ajax_after_calc_line_taxes', $items, $order_id, $country, $_POST );
1623
1624
		// Save order items
1625
		wc_save_order_items( $order_id, $items );
1626
1627
		// Return HTML items
1628
		$order = wc_get_order( $order_id );
1629
		$data  = get_post_meta( $order_id );
1630
		include( 'admin/meta-boxes/views/html-order-items.php' );
1631
1632
		die();
1633
	}
1634
1635
	/**
1636
	 * Save order items via ajax.
1637
	 */
1638
	public static function save_order_items() {
1639
		check_ajax_referer( 'order-item', 'security' );
1640
1641
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1642
			die(-1);
1643
		}
1644
1645
		if ( isset( $_POST['order_id'] ) && isset( $_POST['items'] ) ) {
1646
			$order_id = absint( $_POST['order_id'] );
1647
1648
			// Parse the jQuery serialized items
1649
			$items = array();
1650
			parse_str( $_POST['items'], $items );
1651
1652
			// Save order items
1653
			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...
1654
1655
			// Return HTML items
1656
			$order = wc_get_order( $order_id );
1657
			$data  = get_post_meta( $order_id );
1658
			include( 'admin/meta-boxes/views/html-order-items.php' );
1659
		}
1660
1661
		die();
1662
	}
1663
1664
	/**
1665
	 * Load order items via ajax.
1666
	 */
1667
	public static function load_order_items() {
1668
		check_ajax_referer( 'order-item', 'security' );
1669
1670
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1671
			die(-1);
1672
		}
1673
1674
		// Return HTML items
1675
		$order_id = absint( $_POST['order_id'] );
1676
		$order    = wc_get_order( $order_id );
1677
		$data     = get_post_meta( $order_id );
1678
		include( 'admin/meta-boxes/views/html-order-items.php' );
1679
1680
		die();
1681
	}
1682
1683
	/**
1684
	 * Add order note via ajax.
1685
	 */
1686
	public static function add_order_note() {
1687
1688
		check_ajax_referer( 'add-order-note', 'security' );
1689
1690
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1691
			die(-1);
1692
		}
1693
1694
		$post_id   = absint( $_POST['post_id'] );
1695
		$note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
1696
		$note_type = $_POST['note_type'];
1697
1698
		$is_customer_note = $note_type == 'customer' ? 1 : 0;
1699
1700
		if ( $post_id > 0 ) {
1701
			$order      = wc_get_order( $post_id );
1702
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1703
1704
			echo '<li rel="' . esc_attr( $comment_id ) . '" class="note ';
1705
			if ( $is_customer_note ) {
1706
				echo 'customer-note';
1707
			}
1708
			echo '"><div class="note_content">';
1709
			echo wpautop( wptexturize( $note ) );
1710
			echo '</div><p class="meta"><a href="#" class="delete_note">'.__( 'Delete note', 'woocommerce' ).'</a></p>';
1711
			echo '</li>';
1712
		}
1713
1714
		// Quit out
1715
		die();
1716
	}
1717
1718
	/**
1719
	 * Delete order note via ajax.
1720
	 */
1721
	public static function delete_order_note() {
1722
1723
		check_ajax_referer( 'delete-order-note', 'security' );
1724
1725
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1726
			die(-1);
1727
		}
1728
1729
		$note_id = (int) $_POST['note_id'];
1730
1731
		if ( $note_id > 0 ) {
1732
			wp_delete_comment( $note_id );
1733
		}
1734
1735
		// Quit out
1736
		die();
1737
	}
1738
1739
	/**
1740
	 * Search for products and echo json.
1741
	 *
1742
	 * @param string $x (default: '')
1743
	 * @param string $post_types (default: array('product'))
1744
	 */
1745
	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...
1746
		global $wpdb;
1747
1748
		ob_start();
1749
1750
		check_ajax_referer( 'search-products', 'security' );
1751
1752
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1753
		$exclude = array();
0 ignored issues
show
Unused Code introduced by
$exclude 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...
1754
1755
		if ( empty( $term ) ) {
1756
			die();
1757
		}
1758
1759
		$like_term = '%' . $wpdb->esc_like( $term ) . '%';
1760
1761
		if ( is_numeric( $term ) ) {
1762
			$query = $wpdb->prepare( "
1763
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1764
				WHERE posts.post_status = 'publish'
1765
				AND (
1766
					posts.post_parent = %s
1767
					OR posts.ID = %s
1768
					OR posts.post_title LIKE %s
1769
					OR (
1770
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1771
					)
1772
				)
1773
			", $term, $term, $term, $like_term );
1774
		} else {
1775
			$query = $wpdb->prepare( "
1776
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1777
				WHERE posts.post_status = 'publish'
1778
				AND (
1779
					posts.post_title LIKE %s
1780
					or posts.post_content LIKE %s
1781
					OR (
1782
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1783
					)
1784
				)
1785
			", $like_term, $like_term, $like_term );
1786
		}
1787
1788
		$query .= " AND posts.post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "')";
1789
1790
		if ( ! empty( $_GET['exclude'] ) ) {
1791
			$query .= " AND posts.ID NOT IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['exclude'] ) ) ) . ")";
1792
		}
1793
1794
		$posts          = array_unique( $wpdb->get_col( $query ) );
1795
		$found_products = array();
1796
1797 View Code Duplication
		if ( ! empty( $posts ) ) {
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
			foreach ( $posts as $post ) {
1799
				$product = wc_get_product( $post );
1800
1801
				if ( ! current_user_can( 'read_product', $post ) ) {
1802
					continue;
1803
				}
1804
1805
				$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1806
			}
1807
		}
1808
1809
		$found_products = apply_filters( 'woocommerce_json_search_found_products', $found_products );
1810
1811
		wp_send_json( $found_products );
1812
	}
1813
1814
	/**
1815
	 * Search for product variations and return json.
1816
	 *
1817
	 * @see WC_AJAX::json_search_products()
1818
	 */
1819
	public static function json_search_products_and_variations() {
1820
		self::json_search_products( '', array( 'product', 'product_variation' ) );
1821
	}
1822
1823
	/**
1824
	 * Search for gruoped products and return json.
1825
	 */
1826
	public static function json_search_grouped_products() {
1827
		ob_start();
1828
1829
		check_ajax_referer( 'search-products', 'security' );
1830
1831
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1832
		$exclude = array();
1833
1834
		if ( empty( $term ) ) {
1835
			die();
1836
		}
1837
1838 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...
1839
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1840
		}
1841
1842
		$found_products = array();
1843
1844
		if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
1845
1846
			$posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
1847
1848
			if ( sizeof( $posts_in ) > 0 ) {
1849
1850
				$args = array(
1851
					'post_type'        => 'product',
1852
					'post_status'      => 'any',
1853
					'numberposts'      => -1,
1854
					'orderby'          => 'title',
1855
					'order'            => 'asc',
1856
					'post_parent'      => 0,
1857
					'suppress_filters' => 0,
1858
					'include'          => $posts_in,
1859
					's'                => $term,
1860
					'fields'           => 'ids',
1861
					'exclude'          => $exclude
1862
				);
1863
1864
				$posts = get_posts( $args );
1865
1866 View Code Duplication
				if ( ! empty( $posts ) ) {
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...
1867
					foreach ( $posts as $post ) {
1868
						$product = wc_get_product( $post );
1869
1870
						if ( ! current_user_can( 'read_product', $post ) ) {
1871
							continue;
1872
						}
1873
1874
						$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1875
					}
1876
				}
1877
			}
1878
		}
1879
1880
		$found_products = apply_filters( 'woocommerce_json_search_found_grouped_products', $found_products );
1881
1882
		wp_send_json( $found_products );
1883
	}
1884
1885
	/**
1886
	 * Search for downloadable product variations and return json.
1887
	 *
1888
	 * @see WC_AJAX::json_search_products()
1889
	 */
1890
	public static function json_search_downloadable_products_and_variations() {
1891
		ob_start();
1892
1893
		check_ajax_referer( 'search-products', 'security' );
1894
1895
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1896
		$exclude = array();
1897
1898 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...
1899
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1900
		}
1901
1902
		$args = array(
1903
			'post_type'      => array( 'product', 'product_variation' ),
1904
			'posts_per_page' => -1,
1905
			'post_status'    => 'publish',
1906
			'order'          => 'ASC',
1907
			'orderby'        => 'parent title',
1908
			'meta_query'     => array(
1909
				array(
1910
					'key'   => '_downloadable',
1911
					'value' => 'yes'
1912
				)
1913
			),
1914
			's'              => $term,
1915
			'exclude'        => $exclude
1916
		);
1917
1918
		$posts = get_posts( $args );
1919
		$found_products = array();
1920
1921
		if ( ! empty( $posts ) ) {
1922
			foreach ( $posts as $post ) {
1923
				$product = wc_get_product( $post->ID );
1924
1925
				if ( ! current_user_can( 'read_product', $post->ID ) ) {
1926
					continue;
1927
				}
1928
1929
				$found_products[ $post->ID ] = $product->get_formatted_name();
1930
			}
1931
		}
1932
1933
		wp_send_json( $found_products );
1934
	}
1935
1936
	/**
1937
	 * Search for customers and return json.
1938
	 */
1939
	public static function json_search_customers() {
1940
		ob_start();
1941
1942
		check_ajax_referer( 'search-customers', 'security' );
1943
1944
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1945
			die(-1);
1946
		}
1947
1948
		$term = wc_clean( stripslashes( $_GET['term'] ) );
1949
1950
		if ( empty( $term ) ) {
1951
			die();
1952
		}
1953
1954
		$found_customers = array();
1955
1956
		add_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1957
1958
		$customers_query = new WP_User_Query( apply_filters( 'woocommerce_json_search_customers_query', array(
1959
			'fields'         => 'all',
1960
			'orderby'        => 'display_name',
1961
			'search'         => '*' . $term . '*',
1962
			'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename' )
1963
		) ) );
1964
1965
		remove_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1966
1967
		$customers = $customers_query->get_results();
1968
1969
		if ( ! empty( $customers ) ) {
1970
			foreach ( $customers as $customer ) {
1971
				$found_customers[ $customer->ID ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
1972
			}
1973
		}
1974
1975
		wp_send_json( $found_customers );
1976
	}
1977
1978
	/**
1979
	 * When searching using the WP_User_Query, search names (user meta) too.
1980
	 * @param  object $query
1981
	 * @return object
1982
	 */
1983
	public static function json_search_customer_name( $query ) {
1984
		global $wpdb;
1985
1986
		$term = wc_clean( stripslashes( $_GET['term'] ) );
1987
		if ( method_exists( $wpdb, 'esc_like' ) ) {
1988
			$term = $wpdb->esc_like( $term );
1989
		} else {
1990
			$term = like_escape( $term );
1991
		}
1992
1993
		$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' ) ";
1994
		$query->query_where .= $wpdb->prepare( " OR user_name.meta_value LIKE %s ", '%' . $term . '%' );
1995
	}
1996
1997
	/**
1998
	 * Ajax request handling for categories ordering.
1999
	 */
2000
	public static function term_ordering() {
2001
2002
		// check permissions again and make sure we have what we need
2003
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
2004
			die(-1);
2005
		}
2006
2007
		$id       = (int) $_POST['id'];
2008
		$next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
2009
		$taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null;
2010
		$term     = get_term_by( 'id', $id, $taxonomy );
2011
2012
		if ( ! $id || ! $term || ! $taxonomy ) {
2013
			die(0);
2014
		}
2015
2016
		wc_reorder_terms( $term, $next_id, $taxonomy );
2017
2018
		$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
2019
2020
		if ( $term && sizeof( $children ) ) {
2021
			echo 'children';
2022
			die();
2023
		}
2024
	}
2025
2026
	/**
2027
	 * Ajax request handling for product ordering.
2028
	 *
2029
	 * Based on Simple Page Ordering by 10up (http://wordpress.org/extend/plugins/simple-page-ordering/).
2030
	 */
2031
	public static function product_ordering() {
2032
		global $wpdb;
2033
2034
		ob_start();
2035
2036
		// check permissions again and make sure we have what we need
2037
		if ( ! current_user_can('edit_products') || empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
2038
			die(-1);
2039
		}
2040
2041
		// real post?
2042
		if ( ! $post = get_post( $_POST['id'] ) ) {
2043
			die(-1);
2044
		}
2045
2046
		$previd  = isset( $_POST['previd'] ) ? $_POST['previd'] : false;
2047
		$nextid  = isset( $_POST['nextid'] ) ? $_POST['nextid'] : false;
2048
		$new_pos = array(); // store new positions for ajax
2049
2050
		$siblings = $wpdb->get_results( $wpdb->prepare( "
2051
			SELECT ID, menu_order FROM {$wpdb->posts} AS posts
2052
			WHERE 	posts.post_type 	= 'product'
2053
			AND 	posts.post_status 	IN ( 'publish', 'pending', 'draft', 'future', 'private' )
2054
			AND 	posts.ID			NOT IN (%d)
2055
			ORDER BY posts.menu_order ASC, posts.ID DESC
2056
		", $post->ID ) );
2057
2058
		$menu_order = 0;
2059
2060
		foreach ( $siblings as $sibling ) {
2061
2062
			// if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
2063 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...
2064
				$wpdb->update(
2065
					$wpdb->posts,
2066
					array(
2067
						'menu_order' => $menu_order
2068
					),
2069
					array( 'ID' => $post->ID ),
2070
					array( '%d' ),
2071
					array( '%d' )
2072
				);
2073
				$new_pos[ $post->ID ] = $menu_order;
2074
				$menu_order++;
2075
			}
2076
2077
			// if repositioned post has been set, and new items are already in the right order, we can stop
2078
			if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $menu_order ) {
2079
				break;
2080
			}
2081
2082
			// set the menu order of the current sibling and increment the menu order
2083
			$wpdb->update(
2084
				$wpdb->posts,
2085
				array(
2086
					'menu_order' => $menu_order
2087
				),
2088
				array( 'ID' => $sibling->ID ),
2089
				array( '%d' ),
2090
				array( '%d' )
2091
			);
2092
			$new_pos[ $sibling->ID ] = $menu_order;
2093
			$menu_order++;
2094
2095 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...
2096
				$wpdb->update(
2097
					$wpdb->posts,
2098
					array(
2099
						'menu_order' => $menu_order
2100
					),
2101
					array( 'ID' => $post->ID ),
2102
					array( '%d' ),
2103
					array( '%d' )
2104
				);
2105
				$new_pos[$post->ID] = $menu_order;
2106
				$menu_order++;
2107
			}
2108
2109
		}
2110
2111
		do_action( 'woocommerce_after_product_ordering' );
2112
2113
		wp_send_json( $new_pos );
2114
	}
2115
2116
	/**
2117
	 * Handle a refund via the edit order screen.
2118
	 */
2119
	public static function refund_line_items() {
2120
		ob_start();
2121
2122
		check_ajax_referer( 'order-item', 'security' );
2123
2124
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2125
			die(-1);
2126
		}
2127
2128
		$order_id               = absint( $_POST['order_id'] );
2129
		$refund_amount          = wc_format_decimal( sanitize_text_field( $_POST['refund_amount'] ) );
2130
		$refund_reason          = sanitize_text_field( $_POST['refund_reason'] );
2131
		$line_item_qtys         = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_qtys'] ) ), true );
2132
		$line_item_totals       = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_totals'] ) ), true );
2133
		$line_item_tax_totals   = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_tax_totals'] ) ), true );
2134
		$api_refund             = $_POST['api_refund'] === 'true' ? true : false;
2135
		$restock_refunded_items = $_POST['restock_refunded_items'] === 'true' ? true : false;
2136
		$refund                 = false;
2137
		$response_data          = array();
2138
2139
		try {
2140
			// Validate that the refund can occur
2141
			$order       = wc_get_order( $order_id );
2142
			$order_items = $order->get_items();
2143
			$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );
2144
2145
			if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) {
2146
				throw new exception( __( 'Invalid refund amount', 'woocommerce' ) );
2147
			}
2148
2149
			// Prepare line items which we are refunding
2150
			$line_items = array();
2151
			$item_ids   = array_unique( array_merge( array_keys( $line_item_qtys, $line_item_totals ) ) );
2152
2153
			foreach ( $item_ids as $item_id ) {
2154
				$line_items[ $item_id ] = array( 'qty' => 0, 'refund_total' => 0, 'refund_tax' => array() );
2155
			}
2156
			foreach ( $line_item_qtys as $item_id => $qty ) {
2157
				$line_items[ $item_id ]['qty'] = max( $qty, 0 );
2158
			}
2159
			foreach ( $line_item_totals as $item_id => $total ) {
2160
				$line_items[ $item_id ]['refund_total'] = wc_format_decimal( $total );
2161
			}
2162
			foreach ( $line_item_tax_totals as $item_id => $tax_totals ) {
2163
				$line_items[ $item_id ]['refund_tax'] = array_map( 'wc_format_decimal', $tax_totals );
2164
			}
2165
2166
			// Create the refund object
2167
			$refund = wc_create_refund( array(
2168
				'amount'     => $refund_amount,
2169
				'reason'     => $refund_reason,
2170
				'order_id'   => $order_id,
2171
				'line_items' => $line_items,
2172
			) );
2173
2174
			if ( is_wp_error( $refund ) ) {
2175
				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...
2176
			}
2177
2178
			// Refund via API
2179 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...
2180
				if ( WC()->payment_gateways() ) {
2181
					$payment_gateways = WC()->payment_gateways->payment_gateways();
2182
				}
2183
				if ( isset( $payment_gateways[ $order->payment_method ] ) && $payment_gateways[ $order->payment_method ]->supports( 'refunds' ) ) {
2184
					$result = $payment_gateways[ $order->payment_method ]->process_refund( $order_id, $refund_amount, $refund_reason );
2185
2186
					do_action( 'woocommerce_refund_processed', $refund, $result );
2187
2188
					if ( is_wp_error( $result ) ) {
2189
						throw new Exception( $result->get_error_message() );
2190
					} elseif ( ! $result ) {
2191
						throw new Exception( __( 'Refund failed', 'woocommerce' ) );
2192
					}
2193
				}
2194
			}
2195
2196
			// restock items
2197
			foreach ( $line_item_qtys as $item_id => $qty ) {
2198
				if ( $restock_refunded_items && $qty && isset( $order_items[ $item_id ] ) ) {
2199
					$order_item = $order_items[ $item_id ];
2200
					$_product   = $order->get_product_from_item( $order_item );
2201
2202
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2203
						$old_stock    = wc_stock_amount( $_product->stock );
2204
						$new_quantity = $_product->increase_stock( $qty );
2205
2206
						$order->add_order_note( sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
2207
2208
						do_action( 'woocommerce_restock_refunded_item', $_product->id, $old_stock, $new_quantity, $order );
2209
					}
2210
				}
2211
			}
2212
2213
			// Trigger notifications and status changes
2214
			if ( $order->get_remaining_refund_amount() > 0 || ( $order->has_free_item() && $order->get_remaining_refund_items() > 0 ) ) {
2215
				/**
2216
				 * woocommerce_order_partially_refunded.
2217
				 *
2218
				 * @since 2.4.0
2219
				 * Note: 3rd arg was added in err. Kept for bw compat. 2.4.3.
2220
				 */
2221
				do_action( 'woocommerce_order_partially_refunded', $order_id, $refund->id, $refund->id );
2222
			} else {
2223
				do_action( 'woocommerce_order_fully_refunded', $order_id, $refund->id );
2224
2225
				$order->update_status( apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order_id, $refund->id ) );
2226
				$response_data['status'] = 'fully_refunded';
2227
			}
2228
2229
			do_action( 'woocommerce_order_refunded', $order_id, $refund->id );
2230
2231
			// Clear transients
2232
			wc_delete_shop_order_transients( $order_id );
2233
			wp_send_json_success( $response_data );
2234
2235
		} catch ( Exception $e ) {
2236
			if ( $refund && is_a( $refund, 'WC_Order_Refund' ) ) {
2237
				wp_delete_post( $refund->id, true );
2238
			}
2239
2240
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
2241
		}
2242
	}
2243
2244
	/**
2245
	 * Delete a refund.
2246
	 */
2247
	public static function delete_refund() {
2248
		check_ajax_referer( 'order-item', 'security' );
2249
2250
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2251
			die(-1);
2252
		}
2253
2254
		$refund_id = absint( $_POST['refund_id'] );
2255
2256
		if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
2257
			$order_id = wp_get_post_parent_id( $refund_id );
2258
			wc_delete_shop_order_transients( $order_id );
2259
			wp_delete_post( $refund_id );
2260
2261
			do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
2262
		}
2263
2264
		die();
2265
	}
2266
2267
	/**
2268
	 * Triggered when clicking the rating footer.
2269
	 */
2270
	public static function rated() {
2271
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2272
			die(-1);
2273
		}
2274
2275
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
2276
		die();
2277
	}
2278
2279
	/**
2280
	 * Create/Update API key.
2281
	 */
2282
	public static function update_api_key() {
2283
		ob_start();
2284
2285
		global $wpdb;
2286
2287
		check_ajax_referer( 'update-api-key', 'security' );
2288
2289
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2290
			die(-1);
2291
		}
2292
2293
		try {
2294
			if ( empty( $_POST['description'] ) ) {
2295
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
2296
			}
2297
			if ( empty( $_POST['user'] ) ) {
2298
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
2299
			}
2300
			if ( empty( $_POST['permissions'] ) ) {
2301
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
2302
			}
2303
2304
			$key_id      = absint( $_POST['key_id'] );
2305
			$description = sanitize_text_field( wp_unslash( $_POST['description'] ) );
2306
			$permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read';
2307
			$user_id     = absint( $_POST['user'] );
2308
2309
			if ( 0 < $key_id ) {
2310
				$data = array(
2311
					'user_id'     => $user_id,
2312
					'description' => $description,
2313
					'permissions' => $permissions
2314
				);
2315
2316
				$wpdb->update(
2317
					$wpdb->prefix . 'woocommerce_api_keys',
2318
					$data,
2319
					array( 'key_id' => $key_id ),
2320
					array(
2321
						'%d',
2322
						'%s',
2323
						'%s'
2324
					),
2325
					array( '%d' )
2326
				);
2327
2328
				$data['consumer_key']    = '';
2329
				$data['consumer_secret'] = '';
2330
				$data['message']         = __( 'API Key updated successfully.', 'woocommerce' );
2331
			} else {
2332
				$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...
2333
				$consumer_key    = 'ck_' . wc_rand_hash();
2334
				$consumer_secret = 'cs_' . wc_rand_hash();
2335
2336
				$data = array(
2337
					'user_id'         => $user_id,
2338
					'description'     => $description,
2339
					'permissions'     => $permissions,
2340
					'consumer_key'    => wc_api_hash( $consumer_key ),
2341
					'consumer_secret' => $consumer_secret,
2342
					'truncated_key'   => substr( $consumer_key, -7 )
2343
				);
2344
2345
				$wpdb->insert(
2346
					$wpdb->prefix . 'woocommerce_api_keys',
2347
					$data,
2348
					array(
2349
						'%d',
2350
						'%s',
2351
						'%s',
2352
						'%s',
2353
						'%s',
2354
						'%s'
2355
					)
2356
				);
2357
2358
				$key_id                  = $wpdb->insert_id;
2359
				$data['consumer_key']    = $consumer_key;
2360
				$data['consumer_secret'] = $consumer_secret;
2361
				$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' );
2362
				$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>';
2363
			}
2364
2365
			wp_send_json_success( $data );
2366
		} catch ( Exception $e ) {
2367
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
2368
		}
2369
	}
2370
2371
	/**
2372
	 * Locate user via AJAX.
2373
	 */
2374
	public static function get_customer_location() {
2375
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
2376
		wp_send_json_success( array( 'hash' => $location_hash ) );
2377
	}
2378
2379
	/**
2380
	 * Load variations via AJAX.
2381
	 */
2382
	public static function load_variations() {
2383
		ob_start();
2384
2385
		check_ajax_referer( 'load-variations', 'security' );
2386
2387
		// Check permissions again and make sure we have what we need
2388 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...
2389
			die( -1 );
2390
		}
2391
2392
		global $post;
2393
2394
		$product_id = absint( $_POST['product_id'] );
2395
		$post       = get_post( $product_id ); // Set $post global so its available like within the admin screens
2396
		$per_page   = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
2397
		$page       = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
2398
2399
		// Get attributes
2400
		$attributes        = array();
2401
		$posted_attributes = wp_unslash( $_POST['attributes'] );
2402
2403
		foreach ( $posted_attributes as $key => $value ) {
2404
			$attributes[ $key ] = array_map( 'wc_clean', $value );
2405
		}
2406
2407
		// Get tax classes
2408
		$tax_classes           = WC_Tax::get_tax_classes();
2409
		$tax_class_options     = array();
2410
		$tax_class_options[''] = __( 'Standard', 'woocommerce' );
2411
2412 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...
2413
			foreach ( $tax_classes as $class ) {
2414
				$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
2415
			}
2416
		}
2417
2418
		// Set backorder options
2419
		$backorder_options = array(
2420
			'no'     => __( 'Do not allow', 'woocommerce' ),
2421
			'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
2422
			'yes'    => __( 'Allow', 'woocommerce' )
2423
		);
2424
2425
		// set stock status options
2426
		$stock_status_options = array(
2427
			'instock'    => __( 'In stock', 'woocommerce' ),
2428
			'outofstock' => __( 'Out of stock', 'woocommerce' )
2429
		);
2430
2431
		$parent_data = array(
2432
			'id'                   => $product_id,
2433
			'attributes'           => $attributes,
2434
			'tax_class_options'    => $tax_class_options,
2435
			'sku'                  => get_post_meta( $product_id, '_sku', true ),
2436
			'weight'               => wc_format_localized_decimal( get_post_meta( $product_id, '_weight', true ) ),
2437
			'length'               => wc_format_localized_decimal( get_post_meta( $product_id, '_length', true ) ),
2438
			'width'                => wc_format_localized_decimal( get_post_meta( $product_id, '_width', true ) ),
2439
			'height'               => wc_format_localized_decimal( get_post_meta( $product_id, '_height', true ) ),
2440
			'tax_class'            => get_post_meta( $product_id, '_tax_class', true ),
2441
			'backorder_options'    => $backorder_options,
2442
			'stock_status_options' => $stock_status_options
2443
		);
2444
2445
		if ( ! $parent_data['weight'] ) {
2446
			$parent_data['weight'] = wc_format_localized_decimal( 0 );
2447
		}
2448
2449
		if ( ! $parent_data['length'] ) {
2450
			$parent_data['length'] = wc_format_localized_decimal( 0 );
2451
		}
2452
2453
		if ( ! $parent_data['width'] ) {
2454
			$parent_data['width'] = wc_format_localized_decimal( 0 );
2455
		}
2456
2457
		if ( ! $parent_data['height'] ) {
2458
			$parent_data['height'] = wc_format_localized_decimal( 0 );
2459
		}
2460
2461
		// Get variations
2462
		$args = apply_filters( 'woocommerce_ajax_admin_get_variations_args', array(
2463
			'post_type'      => 'product_variation',
2464
			'post_status'    => array( 'private', 'publish' ),
2465
			'posts_per_page' => $per_page,
2466
			'paged'          => $page,
2467
			'orderby'        => array( 'menu_order' => 'ASC', 'ID' => 'DESC' ),
2468
			'post_parent'    => $product_id
2469
		), $product_id );
2470
2471
		$variations = get_posts( $args );
2472
		$loop = 0;
2473
2474
		if ( $variations ) {
2475
2476
			foreach ( $variations as $variation ) {
2477
				$variation_id     = absint( $variation->ID );
2478
				$variation_meta   = get_post_meta( $variation_id );
2479
				$variation_data   = array();
2480
				$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
2481
				$variation_fields = array(
2482
					'_sku'                   => '',
2483
					'_stock'                 => '',
2484
					'_regular_price'         => '',
2485
					'_sale_price'            => '',
2486
					'_weight'                => '',
2487
					'_length'                => '',
2488
					'_width'                 => '',
2489
					'_height'                => '',
2490
					'_download_limit'        => '',
2491
					'_download_expiry'       => '',
2492
					'_downloadable_files'    => '',
2493
					'_downloadable'          => '',
2494
					'_virtual'               => '',
2495
					'_thumbnail_id'          => '',
2496
					'_sale_price_dates_from' => '',
2497
					'_sale_price_dates_to'   => '',
2498
					'_manage_stock'          => '',
2499
					'_stock_status'          => '',
2500
					'_backorders'            => null,
2501
					'_tax_class'             => null,
2502
					'_variation_description' => ''
2503
				);
2504
2505 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...
2506
					$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
2507
				}
2508
2509
				// Add the variation attributes
2510
				$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
2511
2512
				// Formatting
2513
				$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
2514
				$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
2515
				$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
2516
				$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
2517
				$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
2518
				$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
2519
				$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
2520
				$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
2521
				$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
2522
				$variation_data['menu_order']     = $variation->menu_order;
2523
2524
				// Stock BW compat
2525
				if ( '' !== $variation_data['_stock'] ) {
2526
					$variation_data['_manage_stock'] = 'yes';
2527
				}
2528
2529
				include( 'admin/meta-boxes/views/html-variation-admin.php' );
2530
2531
				$loop++;
2532
			}
2533
		}
2534
2535
		die();
2536
	}
2537
2538
	/**
2539
	 * Save variations via AJAX.
2540
	 */
2541
	public static function save_variations() {
2542
		ob_start();
2543
2544
		check_ajax_referer( 'save-variations', 'security' );
2545
2546
		// Check permissions again and make sure we have what we need
2547 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...
2548
			die( -1 );
2549
		}
2550
2551
		// Remove previous meta box errors
2552
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
2553
2554
		$product_id   = absint( $_POST['product_id'] );
2555
		$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
2556
2557
		$product_type_terms = wp_get_object_terms( $product_id, 'product_type' );
2558
2559
		// If the product type hasn't been set or it has changed, update it before saving variations
2560
		if ( empty( $product_type_terms ) || $product_type !== sanitize_title( current( $product_type_terms )->name ) ) {
2561
			wp_set_object_terms( $product_id, $product_type, 'product_type' );
2562
		}
2563
2564
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
2565
2566
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
2567
2568
		// Clear cache/transients
2569
		wc_delete_product_transients( $product_id );
2570
2571
		if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
2572
			echo '<div class="error notice is-dismissible">';
2573
2574
			foreach ( $errors as $error ) {
2575
				echo '<p>' . wp_kses_post( $error ) . '</p>';
2576
			}
2577
2578
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
2579
			echo '</div>';
2580
2581
			delete_option( 'woocommerce_meta_box_errors' );
2582
		}
2583
2584
		die();
2585
	}
2586
2587
	/**
2588
	 * Bulk action - Toggle Enabled.
2589
	 * @access private
2590
	 * @param  array $variations
2591
	 * @param  array $data
2592
	 */
2593
	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...
2594
		global $wpdb;
2595
2596
		foreach ( $variations as $variation_id ) {
2597
			$post_status = get_post_status( $variation_id );
2598
			$new_status  = 'private' === $post_status ? 'publish' : 'private';
2599
			$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $variation_id ) );
2600
		}
2601
	}
2602
2603
	/**
2604
	 * Bulk action - Toggle Downloadable Checkbox.
2605
	 * @access private
2606
	 * @param  array $variations
2607
	 * @param  array $data
2608
	 */
2609 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...
2610
		foreach ( $variations as $variation_id ) {
2611
			$_downloadable   = get_post_meta( $variation_id, '_downloadable', true );
2612
			$is_downloadable = 'no' === $_downloadable ? 'yes' : 'no';
2613
			update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
2614
		}
2615
	}
2616
2617
	/**
2618
	 * Bulk action - Toggle Virtual Checkbox.
2619
	 * @access private
2620
	 * @param  array $variations
2621
	 * @param  array $data
2622
	 */
2623 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...
2624
		foreach ( $variations as $variation_id ) {
2625
			$_virtual   = get_post_meta( $variation_id, '_virtual', true );
2626
			$is_virtual = 'no' === $_virtual ? 'yes' : 'no';
2627
			update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
2628
		}
2629
	}
2630
2631
	/**
2632
	 * Bulk action - Toggle Manage Stock Checkbox.
2633
	 * @access private
2634
	 * @param  array $variations
2635
	 * @param  array $data
2636
	 */
2637
	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...
2638
		foreach ( $variations as $variation_id ) {
2639
			$_manage_stock   = get_post_meta( $variation_id, '_manage_stock', true );
2640
			$is_manage_stock = 'no' === $_manage_stock || '' === $_manage_stock ? 'yes' : 'no';
2641
			update_post_meta( $variation_id, '_manage_stock', $is_manage_stock );
2642
		}
2643
	}
2644
2645
	/**
2646
	 * Bulk action - Set Regular Prices.
2647
	 * @access private
2648
	 * @param  array $variations
2649
	 * @param  array $data
2650
	 */
2651 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...
2652
		if ( ! isset( $data['value'] ) ) {
2653
			return;
2654
		}
2655
2656
		foreach ( $variations as $variation_id ) {
2657
			// Price fields
2658
			$regular_price = wc_clean( $data['value'] );
2659
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2660
2661
			// Date fields
2662
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2663
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2664
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2665
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2666
2667
			_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...
2668
		}
2669
	}
2670
2671
	/**
2672
	 * Bulk action - Set Sale Prices.
2673
	 * @access private
2674
	 * @param  array $variations
2675
	 * @param  array $data
2676
	 */
2677 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...
2678
		if ( ! isset( $data['value'] ) ) {
2679
			return;
2680
		}
2681
2682
		foreach ( $variations as $variation_id ) {
2683
			// Price fields
2684
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2685
			$sale_price    = wc_clean( $data['value'] );
2686
2687
			// Date fields
2688
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2689
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2690
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2691
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2692
2693
			_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 2685 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...
2694
		}
2695
	}
2696
2697
	/**
2698
	 * Bulk action - Set Stock.
2699
	 * @access private
2700
	 * @param  array $variations
2701
	 * @param  array $data
2702
	 */
2703
	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...
2704
		if ( ! isset( $data['value'] ) ) {
2705
			return;
2706
		}
2707
2708
		$value = wc_clean( $data['value'] );
2709
2710
		foreach ( $variations as $variation_id ) {
2711
			if ( 'yes' === get_post_meta( $variation_id, '_manage_stock', true ) ) {
2712
				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...
2713
			} else {
2714
				delete_post_meta( $variation_id, '_stock' );
2715
			}
2716
		}
2717
	}
2718
2719
	/**
2720
	 * Bulk action - Set Weight.
2721
	 * @access private
2722
	 * @param  array $variations
2723
	 * @param  array $data
2724
	 */
2725
	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...
2726
		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...
2727
	}
2728
2729
	/**
2730
	 * Bulk action - Set Length.
2731
	 * @access private
2732
	 * @param  array $variations
2733
	 * @param  array $data
2734
	 */
2735
	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...
2736
		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...
2737
	}
2738
2739
	/**
2740
	 * Bulk action - Set Width.
2741
	 * @access private
2742
	 * @param  array $variations
2743
	 * @param  array $data
2744
	 */
2745
	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...
2746
		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...
2747
	}
2748
2749
	/**
2750
	 * Bulk action - Set Height.
2751
	 * @access private
2752
	 * @param  array $variations
2753
	 * @param  array $data
2754
	 */
2755
	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...
2756
		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...
2757
	}
2758
2759
	/**
2760
	 * Bulk action - Set Download Limit.
2761
	 * @access private
2762
	 * @param  array $variations
2763
	 * @param  array $data
2764
	 */
2765
	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...
2766
		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...
2767
	}
2768
2769
	/**
2770
	 * Bulk action - Set Download Expiry.
2771
	 * @access private
2772
	 * @param  array $variations
2773
	 * @param  array $data
2774
	 */
2775
	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...
2776
		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...
2777
	}
2778
2779
	/**
2780
	 * Bulk action - Delete all.
2781
	 * @access private
2782
	 * @param  array $variations
2783
	 * @param  array $data
2784
	 */
2785
	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...
2786
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2787
			foreach ( $variations as $variation_id ) {
2788
				wp_delete_post( $variation_id );
2789
			}
2790
		}
2791
	}
2792
2793
	/**
2794
	 * Bulk action - Sale Schedule.
2795
	 * @access private
2796
	 * @param  array $variations
2797
	 * @param  array $data
2798
	 */
2799
	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...
2800
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2801
			return;
2802
		}
2803
2804
		foreach ( $variations as $variation_id ) {
2805
			// Price fields
2806
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2807
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2808
2809
			// Date fields
2810
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2811
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2812
2813 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...
2814
				$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2815
			} else {
2816
				$date_from = $data['date_from'];
2817
			}
2818
2819 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...
2820
				$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2821
			} else {
2822
				$date_to = $data['date_to'];
2823
			}
2824
2825
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
2826
		}
2827
	}
2828
2829
	/**
2830
	 * Bulk action - Increase Regular Prices.
2831
	 * @access private
2832
	 * @param  array $variations
2833
	 * @param  array $data
2834
	 */
2835
	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...
2836
		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...
2837
	}
2838
2839
	/**
2840
	 * Bulk action - Decrease Regular Prices.
2841
	 * @access private
2842
	 * @param  array $variations
2843
	 * @param  array $data
2844
	 */
2845
	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...
2846
		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...
2847
	}
2848
2849
	/**
2850
	 * Bulk action - Increase Sale Prices.
2851
	 * @access private
2852
	 * @param  array $variations
2853
	 * @param  array $data
2854
	 */
2855
	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...
2856
		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...
2857
	}
2858
2859
	/**
2860
	 * Bulk action - Decrease Sale Prices.
2861
	 * @access private
2862
	 * @param  array $variations
2863
	 * @param  array $data
2864
	 */
2865
	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...
2866
		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...
2867
	}
2868
2869
	/**
2870
	 * Bulk action - Set Price.
2871
	 * @access private
2872
	 * @param  array $variations
2873
	 * @param string $operator + or -
2874
	 * @param string $field price being adjusted
2875
	 * @param string $value Price or Percent
2876
	 */
2877
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2878
		foreach ( $variations as $variation_id ) {
2879
			// Get existing data
2880
			$_regular_price = get_post_meta( $variation_id, '_regular_price', true );
2881
			$_sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2882
			$date_from      = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2883
			$date_to        = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2884
			$date_from      = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2885
			$date_to        = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2886
2887
			if ( '%' === substr( $value, -1 ) ) {
2888
				$percent = wc_format_decimal( substr( $value, 0, -1 ) );
2889
				$$field  += ( ( $$field / 100 ) * $percent ) * "{$operator}1";
2890
			} else {
2891
				$$field  += $value * "{$operator}1";
2892
			}
2893
			_wc_save_product_price( $variation_id, $_regular_price, $_sale_price, $date_from, $date_to );
2894
		}
2895
	}
2896
2897
	/**
2898
	 * Bulk action - Set Meta.
2899
	 * @access private
2900
	 * @param array $variations
2901
	 * @param string $field
2902
	 * @param string $value
2903
	 */
2904
	private static function variation_bulk_set_meta( $variations, $field, $value ) {
2905
		foreach ( $variations as $variation_id ) {
2906
			update_post_meta( $variation_id, $field, $value );
2907
		}
2908
	}
2909
2910
2911
	/**
2912
	 * Bulk edit variations via AJAX.
2913
	 */
2914
	public static function bulk_edit_variations() {
2915
		ob_start();
2916
2917
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2918
2919
		// Check permissions again and make sure we have what we need
2920 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...
2921
			die( -1 );
2922
		}
2923
2924
		$product_id  = absint( $_POST['product_id'] );
2925
		$bulk_action = wc_clean( $_POST['bulk_action'] );
2926
		$data        = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array();
2927
		$variations  = array();
2928
2929
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2930
			$variations = get_posts( array(
2931
				'post_parent'    => $product_id,
2932
				'posts_per_page' => -1,
2933
				'post_type'      => 'product_variation',
2934
				'fields'         => 'ids',
2935
				'post_status'    => array( 'publish', 'private' )
2936
			) );
2937
		}
2938
2939
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2940
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2941
		} else {
2942
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2943
		}
2944
2945
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2946
2947
		// Sync and update transients
2948
		WC_Product_Variable::sync( $product_id );
2949
		wc_delete_product_transients( $product_id );
2950
		die();
2951
	}
2952
2953
	/**
2954
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2955
	 */
2956
	public static function tax_rates_save_changes() {
2957
		if ( ! isset( $_POST['current_class'], $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2958
			wp_send_json_error( 'missing_fields' );
2959
			exit;
2960
		}
2961
2962
		$current_class = $_POST['current_class']; // This is sanitized seven lines later.
2963
2964
		if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) {
2965
			wp_send_json_error( 'bad_nonce' );
2966
			exit;
2967
		}
2968
2969
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
2970
2971
		// Check User Caps
2972
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2973
			wp_send_json_error( 'missing_capabilities' );
2974
			exit;
2975
		}
2976
2977
		$changes = $_POST['changes'];
2978
		foreach ( $changes as $tax_rate_id => $data ) {
2979
			if ( isset( $data['deleted'] ) ) {
2980
				if ( isset( $data['newRow'] ) ) {
2981
					// So the user added and deleted a new row.
2982
					// That's fine, it's not in the database anyways. NEXT!
2983
					continue;
2984
				}
2985
				WC_Tax::_delete_tax_rate( $tax_rate_id );
2986
			}
2987
2988
			$tax_rate = array_intersect_key( $data, array(
2989
				'tax_rate_country'  => 1,
2990
				'tax_rate_state'    => 1,
2991
				'tax_rate'          => 1,
2992
				'tax_rate_name'     => 1,
2993
				'tax_rate_priority' => 1,
2994
				'tax_rate_compound' => 1,
2995
				'tax_rate_shipping' => 1,
2996
				'tax_rate_order'    => 1,
2997
			) );
2998
2999
			if ( isset( $data['newRow'] ) ) {
3000
				// Hurrah, shiny and new!
0 ignored issues
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...
3001
				$tax_rate['tax_rate_class'] = $current_class;
3002
				$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
3003
			} else {
3004
				// Updating an existing rate ...
3005
				if ( ! empty( $tax_rate ) ) {
3006
					WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
3007
				}
3008
			}
3009
3010
			if ( isset( $data['postcode'] ) ) {
3011
				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...
3012
			}
3013
			if ( isset( $data['city'] ) ) {
3014
				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...
3015
			}
3016
		}
3017
3018
		wp_send_json_success( array(
3019
			'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
3020
		) );
3021
	}
3022
}
3023
3024
WC_AJAX::init();
3025