Completed
Push — master ( bbc130...d4d6f3 )
by Mike
10:59
created

WC_AJAX::add_attribute()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 36
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

    return array();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
732
733
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
734
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
735
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
736
737
					// Custom attribute - Add attribute to array and set the values
738
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
739
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
740
						'value' 		=> $values,
741
						'position' 		=> $attribute_position[ $i ],
742
						'is_visible' 	=> $is_visible,
743
						'is_variation' 	=> $is_variation,
744
						'is_taxonomy' 	=> $is_taxonomy
745
					);
746
				}
747
748
			 }
749
		}
750
751
		uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
752
753
		update_post_meta( $post_id, '_product_attributes', $attributes );
754
755
		die();
756
	}
757
758
	/**
759
	 * Add variation via ajax function.
760
	 */
761
	public static function add_variation() {
762
763
		check_ajax_referer( 'add-variation', 'security' );
764
765
		if ( ! current_user_can( 'edit_products' ) ) {
766
			die(-1);
767
		}
768
769
		global $post;
770
771
		$post_id = intval( $_POST['post_id'] );
772
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
773
		$loop    = intval( $_POST['loop'] );
774
775
		$variation = array(
776
			'post_title'   => 'Product #' . $post_id . ' Variation',
777
			'post_content' => '',
778
			'post_status'  => 'publish',
779
			'post_author'  => get_current_user_id(),
780
			'post_parent'  => $post_id,
781
			'post_type'    => 'product_variation',
782
			'menu_order'   => -1
783
		);
784
785
		$variation_id = wp_insert_post( $variation );
786
787
		do_action( 'woocommerce_create_product_variation', $variation_id );
788
789
		if ( $variation_id ) {
790
			$variation        = get_post( $variation_id );
791
			$variation_meta   = get_post_meta( $variation_id );
792
			$variation_data   = array();
793
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
794
			$variation_fields = array(
795
				'_sku'                   => '',
796
				'_stock'                 => '',
797
				'_regular_price'         => '',
798
				'_sale_price'            => '',
799
				'_weight'                => '',
800
				'_length'                => '',
801
				'_width'                 => '',
802
				'_height'                => '',
803
				'_download_limit'        => '',
804
				'_download_expiry'       => '',
805
				'_downloadable_files'    => '',
806
				'_downloadable'          => '',
807
				'_virtual'               => '',
808
				'_thumbnail_id'          => '',
809
				'_sale_price_dates_from' => '',
810
				'_sale_price_dates_to'   => '',
811
				'_manage_stock'          => '',
812
				'_stock_status'          => '',
813
				'_backorders'            => null,
814
				'_tax_class'             => null,
815
				'_variation_description' => ''
816
			);
817
818 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...
819
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
820
			}
821
822
			// Add the variation attributes
823
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
824
825
			// Formatting
826
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
827
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
828
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
829
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
830
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
831
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
832
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
833
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
834
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
835
			$variation_data['menu_order']     = $variation->menu_order;
836
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
837
838
			// Get tax classes
839
			$tax_classes           = WC_Tax::get_tax_classes();
840
			$tax_class_options     = array();
841
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
842
843 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...
844
				foreach ( $tax_classes as $class ) {
845
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
846
				}
847
			}
848
849
			// Set backorder options
850
			$backorder_options = array(
851
				'no'     => __( 'Do not allow', 'woocommerce' ),
852
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
853
				'yes'    => __( 'Allow', 'woocommerce' )
854
			);
855
856
			// set stock status options
857
			$stock_status_options = array(
858
				'instock'    => __( 'In stock', 'woocommerce' ),
859
				'outofstock' => __( 'Out of stock', 'woocommerce' )
860
			);
861
862
			// Get attributes
863
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
864
865
			$parent_data = array(
866
				'id'                   => $post_id,
867
				'attributes'           => $attributes,
868
				'tax_class_options'    => $tax_class_options,
869
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
870
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
871
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
872
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
873
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
874
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
875
				'backorder_options'    => $backorder_options,
876
				'stock_status_options' => $stock_status_options
877
			);
878
879
			if ( ! $parent_data['weight'] ) {
880
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
881
			}
882
883
			if ( ! $parent_data['length'] ) {
884
				$parent_data['length'] = wc_format_localized_decimal( 0 );
885
			}
886
887
			if ( ! $parent_data['width'] ) {
888
				$parent_data['width'] = wc_format_localized_decimal( 0 );
889
			}
890
891
			if ( ! $parent_data['height'] ) {
892
				$parent_data['height'] = wc_format_localized_decimal( 0 );
893
			}
894
895
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
896
		}
897
898
		die();
899
	}
900
901
	/**
902
	 * Link all variations via ajax function.
903
	 */
904
	public static function link_all_variations() {
905
906
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
907
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
908
		}
909
910
		check_ajax_referer( 'link-variations', 'security' );
911
912
		if ( ! current_user_can( 'edit_products' ) ) {
913
			die(-1);
914
		}
915
916
		wc_set_time_limit( 0 );
917
918
		$post_id = intval( $_POST['post_id'] );
919
920
		if ( ! $post_id ) {
921
			die();
922
		}
923
924
		$variations = array();
925
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
926
927
		// Put variation attributes into an array
928
		foreach ( $_product->get_attributes() as $attribute ) {
929
930
			if ( ! $attribute['is_variation'] ) {
931
				continue;
932
			}
933
934
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
935
936
			if ( $attribute['is_taxonomy'] ) {
937
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
938
			} else {
939
				$options = explode( WC_DELIMITER, $attribute['value'] );
940
			}
941
942
			$options = array_map( 'trim', $options );
943
944
			$variations[ $attribute_field_name ] = $options;
945
		}
946
947
		// Quit out if none were found
948
		if ( sizeof( $variations ) == 0 ) {
949
			die();
950
		}
951
952
		// Get existing variations so we don't create duplicates
953
		$available_variations = array();
954
955
		foreach( $_product->get_children() as $child_id ) {
956
			$child = $_product->get_child( $child_id );
957
958
			if ( ! empty( $child->variation_id ) ) {
959
				$available_variations[] = $child->get_variation_attributes();
960
			}
961
		}
962
963
		// Created posts will all have the following data
964
		$variation_post_data = array(
965
			'post_title'   => 'Product #' . $post_id . ' Variation',
966
			'post_content' => '',
967
			'post_status'  => 'publish',
968
			'post_author'  => get_current_user_id(),
969
			'post_parent'  => $post_id,
970
			'post_type'    => 'product_variation'
971
		);
972
973
		$variation_ids       = array();
974
		$added               = 0;
975
		$possible_variations = wc_array_cartesian( $variations );
976
977
		foreach ( $possible_variations as $variation ) {
978
979
			// Check if variation already exists
980
			if ( in_array( $variation, $available_variations ) ) {
981
				continue;
982
			}
983
984
			$variation_id = wp_insert_post( $variation_post_data );
985
986
			$variation_ids[] = $variation_id;
987
988
			foreach ( $variation as $key => $value ) {
989
				update_post_meta( $variation_id, $key, $value );
990
			}
991
992
			// Save stock status
993
			update_post_meta( $variation_id, '_stock_status', 'instock' );
994
995
			$added++;
996
997
			do_action( 'product_variation_linked', $variation_id );
998
999
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
1000
				break;
1001
			}
1002
		}
1003
1004
		delete_transient( 'wc_product_children_' . $post_id );
1005
1006
		echo $added;
1007
1008
		die();
1009
	}
1010
1011
	/**
1012
	 * Delete download permissions via ajax function.
1013
	 */
1014
	public static function revoke_access_to_download() {
1015
1016
		check_ajax_referer( 'revoke-access', 'security' );
1017
1018
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1019
			die(-1);
1020
		}
1021
1022
		global $wpdb;
1023
1024
		$download_id = $_POST['download_id'];
1025
		$product_id  = intval( $_POST['product_id'] );
1026
		$order_id    = intval( $_POST['order_id'] );
1027
1028
		$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 ) );
1029
1030
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
1031
1032
		die();
1033
	}
1034
1035
	/**
1036
	 * Grant download permissions via ajax function.
1037
	 */
1038
	public static function grant_access_to_download() {
1039
1040
		check_ajax_referer( 'grant-access', 'security' );
1041
1042
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1043
			die(-1);
1044
		}
1045
1046
		global $wpdb;
1047
1048
		$wpdb->hide_errors();
1049
1050
		$order_id     = intval( $_POST['order_id'] );
1051
		$product_ids  = $_POST['product_ids'];
1052
		$loop         = intval( $_POST['loop'] );
1053
		$file_counter = 0;
1054
		$order        = wc_get_order( $order_id );
1055
1056
		if ( ! is_array( $product_ids ) ) {
1057
			$product_ids = array( $product_ids );
1058
		}
1059
1060
		foreach ( $product_ids as $product_id ) {
1061
			$product = wc_get_product( $product_id );
1062
			$files   = $product->get_files();
1063
1064
			if ( ! $order->billing_email ) {
1065
				die();
1066
			}
1067
1068
			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...
1069
				foreach ( $files as $download_id => $file ) {
1070
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
1071
1072
						// insert complete - get inserted data
1073
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1074
1075
						$loop ++;
1076
						$file_counter ++;
1077
1078 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...
1079
							$file_count = $file['name'];
1080
						} else {
1081
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1082
						}
1083
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1084
					}
1085
				}
1086
			}
1087
		}
1088
1089
		die();
1090
	}
1091
1092
	/**
1093
	 * Get customer details via ajax.
1094
	 */
1095
	public static function get_customer_details() {
1096
		ob_start();
1097
1098
		check_ajax_referer( 'get-customer-details', 'security' );
1099
1100
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1101
			die(-1);
1102
		}
1103
1104
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1105
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1106
1107
		$customer_data = array(
1108
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1109
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1110
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1111
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1112
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1113
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1114
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1115
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1116
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1117
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1118
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1119
		);
1120
1121
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1122
1123
		wp_send_json( $customer_data );
1124
	}
1125
1126
	/**
1127
	 * Add order item via ajax.
1128
	 */
1129
	public static function add_order_item() {
1130
		check_ajax_referer( 'order-item', 'security' );
1131
1132
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1133
			die(-1);
1134
		}
1135
1136
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1137
		$order_id    = absint( $_POST['order_id'] );
1138
1139
		// Find the item
1140
		if ( ! is_numeric( $item_to_add ) ) {
1141
			die();
1142
		}
1143
1144
		$post = get_post( $item_to_add );
1145
1146
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1147
			die();
1148
		}
1149
1150
		$_product    = wc_get_product( $post->ID );
1151
		$order       = wc_get_order( $order_id );
1152
		$order_taxes = $order->get_taxes();
1153
		$class       = 'new_row';
1154
1155
		// Set values
1156
		$item = array();
1157
1158
		$item['product_id']        = $_product->id;
1159
		$item['variation_id']      = isset( $_product->variation_id ) ? $_product->variation_id : '';
1160
		$item['variation_data']    = $item['variation_id'] ? $_product->get_variation_attributes() : '';
1161
		$item['name']              = $_product->get_title();
1162
		$item['tax_class']         = $_product->get_tax_class();
1163
		$item['qty']               = 1;
1164
		$item['line_subtotal']     = wc_format_decimal( $_product->get_price_excluding_tax() );
1165
		$item['line_subtotal_tax'] = '';
1166
		$item['line_total']        = wc_format_decimal( $_product->get_price_excluding_tax() );
1167
		$item['line_tax']          = '';
1168
		$item['type']              = 'line_item';
1169
1170
		// Add line item
1171
		$item_id = wc_add_order_item( $order_id, array(
1172
			'order_item_name' 		=> $item['name'],
1173
			'order_item_type' 		=> 'line_item'
1174
		) );
1175
1176
		// Add line item meta
1177
		if ( $item_id ) {
1178
			wc_add_order_item_meta( $item_id, '_qty', $item['qty'] );
1179
			wc_add_order_item_meta( $item_id, '_tax_class', $item['tax_class'] );
1180
			wc_add_order_item_meta( $item_id, '_product_id', $item['product_id'] );
1181
			wc_add_order_item_meta( $item_id, '_variation_id', $item['variation_id'] );
1182
			wc_add_order_item_meta( $item_id, '_line_subtotal', $item['line_subtotal'] );
1183
			wc_add_order_item_meta( $item_id, '_line_subtotal_tax', $item['line_subtotal_tax'] );
1184
			wc_add_order_item_meta( $item_id, '_line_total', $item['line_total'] );
1185
			wc_add_order_item_meta( $item_id, '_line_tax', $item['line_tax'] );
1186
1187
			// Since 2.2
1188
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
1189
1190
			// Store variation data in meta
1191 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...
1192
				foreach ( $item['variation_data'] as $key => $value ) {
1193
					wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
1194
				}
1195
			}
1196
1197
			do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1198
		}
1199
1200
		$item['item_meta']       = $order->get_item_meta( $item_id );
1201
		$item['item_meta_array'] = $order->get_item_meta_array( $item_id );
1202
		$item                    = $order->expand_item_meta( $item );
1203
		$item                    = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
1204
1205
		include( 'admin/meta-boxes/views/html-order-item.php' );
1206
1207
		// Quit out
1208
		die();
1209
	}
1210
1211
	/**
1212
	 * Add order fee via ajax.
1213
	 */
1214
	public static function add_order_fee() {
1215
1216
		check_ajax_referer( 'order-item', 'security' );
1217
1218
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1219
			die(-1);
1220
		}
1221
1222
		$order_id      = absint( $_POST['order_id'] );
1223
		$order         = wc_get_order( $order_id );
1224
		$order_taxes   = $order->get_taxes();
1225
		$item          = array();
1226
1227
		// Add new fee
1228
		$fee            = new stdClass();
1229
		$fee->name      = '';
1230
		$fee->tax_class = '';
1231
		$fee->taxable   = $fee->tax_class !== '0';
1232
		$fee->amount    = '';
1233
		$fee->tax       = '';
1234
		$fee->tax_data  = array();
1235
		$item_id        = $order->add_fee( $fee );
1236
1237
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1238
1239
		// Quit out
1240
		die();
1241
	}
1242
1243
	/**
1244
	 * Add order shipping cost via ajax.
1245
	 */
1246
	public static function add_order_shipping() {
1247
1248
		check_ajax_referer( 'order-item', 'security' );
1249
1250
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1251
			die(-1);
1252
		}
1253
1254
		$order_id         = absint( $_POST['order_id'] );
1255
		$order            = wc_get_order( $order_id );
1256
		$order_taxes      = $order->get_taxes();
1257
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1258
		$item             = array();
1259
1260
		// Add new shipping
1261
		$shipping        = new WC_Shipping_Rate();
1262
		$item_id         = $order->add_shipping( $shipping );
1263
1264
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1265
1266
		// Quit out
1267
		die();
1268
	}
1269
1270
	/**
1271
	 * Add order tax column via ajax.
1272
	 */
1273
	public static function add_order_tax() {
1274
		global $wpdb;
1275
1276
		check_ajax_referer( 'order-item', 'security' );
1277
1278
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1279
			die(-1);
1280
		}
1281
1282
		$order_id = absint( $_POST['order_id'] );
1283
		$rate_id  = absint( $_POST['rate_id'] );
1284
		$order    = wc_get_order( $order_id );
1285
		$data     = get_post_meta( $order_id );
1286
1287
		// Add new tax
1288
		$order->add_tax( $rate_id, 0, 0 );
1289
1290
		// Return HTML items
1291
		include( 'admin/meta-boxes/views/html-order-items.php' );
1292
1293
		die();
1294
	}
1295
1296
	/**
1297
	 * Remove an order item.
1298
	 */
1299
	public static function remove_order_item() {
1300
		check_ajax_referer( 'order-item', 'security' );
1301
1302
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1303
			die(-1);
1304
		}
1305
1306
		$order_item_ids = $_POST['order_item_ids'];
1307
1308
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1309
			$order_item_ids = array( $order_item_ids );
1310
		}
1311
1312
		if ( sizeof( $order_item_ids ) > 0 ) {
1313
			foreach( $order_item_ids as $id ) {
1314
				wc_delete_order_item( absint( $id ) );
1315
			}
1316
		}
1317
1318
		die();
1319
	}
1320
1321
	/**
1322
	 * Remove an order tax.
1323
	 */
1324
	public static function remove_order_tax() {
1325
1326
		check_ajax_referer( 'order-item', 'security' );
1327
1328
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1329
			die(-1);
1330
		}
1331
1332
		$order_id = absint( $_POST['order_id'] );
1333
		$rate_id  = absint( $_POST['rate_id'] );
1334
1335
		wc_delete_order_item( $rate_id );
1336
1337
		// Return HTML items
1338
		$order = wc_get_order( $order_id );
1339
		$data  = get_post_meta( $order_id );
1340
		include( 'admin/meta-boxes/views/html-order-items.php' );
1341
1342
		die();
1343
	}
1344
1345
	/**
1346
	 * Reduce order item stock.
1347
	 */
1348
	public static function reduce_order_item_stock() {
1349
		check_ajax_referer( 'order-item', 'security' );
1350
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1351
			die(-1);
1352
		}
1353
		$order_id       = absint( $_POST['order_id'] );
1354
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1355
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1356
		$order          = wc_get_order( $order_id );
1357
		$order_items    = $order->get_items();
1358
		$return         = array();
1359
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1360
			foreach ( $order_items as $item_id => $order_item ) {
1361
				// Only reduce checked items
1362
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1363
					continue;
1364
				}
1365
				$_product = $order->get_product_from_item( $order_item );
1366
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1367
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1368
					$new_stock    = $_product->reduce_stock( $stock_change );
1369
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $order_item['product_id'];
1370
					$note         = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1371
					$return[]     = $note;
1372
					$order->add_order_note( $note );
1373
					$order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] );
1374
				}
1375
			}
1376
			do_action( 'woocommerce_reduce_order_stock', $order );
1377
			if ( empty( $return ) ) {
1378
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1379
			}
1380
			echo implode( ', ', $return );
1381
		}
1382
		die();
1383
	}
1384
1385
	/**
1386
	 * Increase order item stock.
1387
	 */
1388
	public static function increase_order_item_stock() {
1389
		check_ajax_referer( 'order-item', 'security' );
1390
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1391
			die(-1);
1392
		}
1393
		$order_id       = absint( $_POST['order_id'] );
1394
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1395
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1396
		$order          = wc_get_order( $order_id );
1397
		$order_items    = $order->get_items();
1398
		$return         = array();
1399
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1400
			foreach ( $order_items as $item_id => $order_item ) {
1401
				// Only reduce checked items
1402
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1403
					continue;
1404
				}
1405
				$_product = $order->get_product_from_item( $order_item );
1406
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1407
					$old_stock    = $_product->get_stock_quantity();
1408
					$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1409
					$new_quantity = $_product->increase_stock( $stock_change );
1410
					$item_name    = $_product->get_sku() ? $_product->get_sku(): $order_item['product_id'];
1411
					$note         = sprintf( __( 'Item %s stock increased from %s to %s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
1412
					$return[]     = $note;
1413
					$order->add_order_note( $note );
1414
				}
1415
			}
1416
			do_action( 'woocommerce_restore_order_stock', $order );
1417
			if ( empty( $return ) ) {
1418
				$return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1419
			}
1420
			echo implode( ', ', $return );
1421
		}
1422
		die();
1423
	}
1424
1425
	/**
1426
	 * Add some meta to a line item.
1427
	 */
1428
	public static function add_order_item_meta() {
1429
		check_ajax_referer( 'order-item', 'security' );
1430
1431
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1432
			die(-1);
1433
		}
1434
1435
		$meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1436
1437
		if ( $meta_id ) {
1438
			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>';
1439
		}
1440
1441
		die();
1442
	}
1443
1444
	/**
1445
	 * Remove meta from a line item.
1446
	 */
1447
	public static function remove_order_item_meta() {
1448
		check_ajax_referer( 'order-item', 'security' );
1449
1450
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1451
			die(-1);
1452
		}
1453
1454
		global $wpdb;
1455
1456
		$wpdb->delete( "{$wpdb->prefix}woocommerce_order_itemmeta", array(
1457
			'meta_id' => absint( $_POST['meta_id'] ),
1458
		) );
1459
1460
		die();
1461
	}
1462
1463
	/**
1464
	 * Calc line tax.
1465
	 */
1466
	public static function calc_line_taxes() {
1467
		global $wpdb;
1468
1469
		check_ajax_referer( 'calc-totals', 'security' );
1470
1471
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1472
			die(-1);
1473
		}
1474
1475
		$tax                    = new WC_Tax();
1476
		$tax_based_on           = get_option( 'woocommerce_tax_based_on' );
1477
		$order_id               = absint( $_POST['order_id'] );
1478
		$items                  = array();
1479
		$country                = strtoupper( esc_attr( $_POST['country'] ) );
1480
		$state                  = strtoupper( esc_attr( $_POST['state'] ) );
1481
		$postcode               = strtoupper( esc_attr( $_POST['postcode'] ) );
1482
		$city                   = wc_clean( esc_attr( $_POST['city'] ) );
1483
		$order                  = wc_get_order( $order_id );
1484
		$taxes                  = array();
1485
		$shipping_taxes         = array();
1486
		$order_item_tax_classes = array();
1487
1488
		// Default to base
1489 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...
1490
			$default  = wc_get_base_location();
1491
			$country  = $default['country'];
1492
			$state    = $default['state'];
1493
			$postcode = '';
1494
			$city     = '';
1495
		}
1496
1497
		// Parse the jQuery serialized items
1498
		parse_str( $_POST['items'], $items );
1499
1500
		// Prevent undefined warnings
1501
		if ( ! isset( $items['line_tax'] ) ) {
1502
			$items['line_tax'] = array();
1503
		}
1504
		if ( ! isset( $items['line_subtotal_tax'] ) ) {
1505
			$items['line_subtotal_tax'] = array();
1506
		}
1507
		$items['order_taxes'] = array();
1508
1509
		// Action
1510
		$items = apply_filters( 'woocommerce_ajax_calc_line_taxes', $items, $order_id, $country, $_POST );
1511
1512
		$is_vat_exempt = get_post_meta( $order_id, '_is_vat_exempt', true );
1513
1514
		// Tax is calculated only if tax is enabled and order is not vat exempted
1515
		if ( wc_tax_enabled() && $is_vat_exempt !== 'yes' ) {
1516
1517
			// Get items and fees taxes
1518
			if ( isset( $items['order_item_id'] ) ) {
1519
				$line_total = $line_subtotal = array();
1520
1521
				foreach ( $items['order_item_id'] as $item_id ) {
1522
					$item_id                            = absint( $item_id );
1523
					$line_total[ $item_id ]             = isset( $items['line_total'][ $item_id ] ) ? wc_format_decimal( $items['line_total'][ $item_id ] ) : 0;
1524
					$line_subtotal[ $item_id ]          = isset( $items['line_subtotal'][ $item_id ] ) ? wc_format_decimal( $items['line_subtotal'][ $item_id ] ) : $line_total[ $item_id ];
1525
					$order_item_tax_classes[ $item_id ] = isset( $items['order_item_tax_class'][ $item_id ] ) ? sanitize_text_field( $items['order_item_tax_class'][ $item_id ] ) : '';
1526
					$product_id                         = $order->get_item_meta( $item_id, '_product_id', true );
1527
1528
					// Get product details
1529
					if ( get_post_type( $product_id ) == 'product' ) {
1530
						$_product        = wc_get_product( $product_id );
1531
						$item_tax_status = $_product->get_tax_status();
1532
					} else {
1533
						$item_tax_status = 'taxable';
1534
					}
1535
1536
					if ( '0' !== $order_item_tax_classes[ $item_id ] && 'taxable' === $item_tax_status ) {
1537
						$tax_rates = WC_Tax::find_rates( array(
1538
							'country'   => $country,
1539
							'state'     => $state,
1540
							'postcode'  => $postcode,
1541
							'city'      => $city,
1542
							'tax_class' => $order_item_tax_classes[ $item_id ]
1543
						) );
1544
1545
						$line_taxes          = WC_Tax::calc_tax( $line_total[ $item_id ], $tax_rates, false );
1546
						$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal[ $item_id ], $tax_rates, false );
1547
1548
						// Set the new line_tax
1549
						foreach ( $line_taxes as $_tax_id => $_tax_value ) {
1550
							$items['line_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1551
						}
1552
1553
						// Set the new line_subtotal_tax
1554
						foreach ( $line_subtotal_taxes as $_tax_id => $_tax_value ) {
1555
							$items['line_subtotal_tax'][ $item_id ][ $_tax_id ] = $_tax_value;
1556
						}
1557
1558
						// Sum the item taxes
1559
						foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1560
							$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1561
						}
1562
					}
1563
				}
1564
			}
1565
1566
			// Get shipping taxes
1567
			if ( isset( $items['shipping_method_id'] ) ) {
1568
				$matched_tax_rates      = array();
1569
				$order_item_tax_classes = array_unique( array_values( $order_item_tax_classes ) );
1570
1571
				// If multiple classes are found, use the first one. Don't bother with standard rate, we can get that later.
1572 View Code Duplication
				if ( sizeof( $order_item_tax_classes ) > 1 && ! in_array( '', $order_item_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...
1573
					$tax_classes = WC_Tax::get_tax_classes();
1574
1575
					foreach ( $tax_classes as $tax_class ) {
1576
						$tax_class = sanitize_title( $tax_class );
1577
						if ( in_array( $tax_class, $order_item_tax_classes ) ) {
1578
							$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1579
								'country' 	=> $country,
1580
								'state' 	=> $state,
1581
								'postcode' 	=> $postcode,
1582
								'city' 		=> $city,
1583
								'tax_class' => $tax_class,
1584
							) );
1585
							break;
1586
						}
1587
					}
1588
				// If a single tax class is found, use it
1589
				} elseif ( sizeof( $order_item_tax_classes ) === 1 ) {
1590
					$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1591
						'country' 	=> $country,
1592
						'state' 	=> $state,
1593
						'postcode' 	=> $postcode,
1594
						'city' 		=> $city,
1595
						'tax_class' => $order_item_tax_classes[0]
1596
					) );
1597
				}
1598
1599
				// Get standard rate if no taxes were found
1600
				if ( ! sizeof( $matched_tax_rates ) ) {
1601
					$matched_tax_rates = WC_Tax::find_shipping_rates( array(
1602
						'country' 	=> $country,
1603
						'state' 	=> $state,
1604
						'postcode' 	=> $postcode,
1605
						'city' 		=> $city
1606
					) );
1607
				}
1608
1609
				$shipping_cost = $shipping_taxes = array();
1610
1611
				foreach ( $items['shipping_method_id'] as $item_id ) {
1612
					$item_id                   = absint( $item_id );
1613
					$shipping_cost[ $item_id ] = isset( $items['shipping_cost'][ $item_id ] ) ? wc_format_decimal( $items['shipping_cost'][ $item_id ] ) : 0;
1614
					$_shipping_taxes           = WC_Tax::calc_shipping_tax( $shipping_cost[ $item_id ], $matched_tax_rates );
1615
1616
					// Set the new shipping_taxes
1617
					foreach ( $_shipping_taxes as $_tax_id => $_tax_value ) {
1618
						$items['shipping_taxes'][ $item_id ][ $_tax_id ] = $_tax_value;
1619
1620
						$shipping_taxes[ $_tax_id ] = isset( $shipping_taxes[ $_tax_id ] ) ? $shipping_taxes[ $_tax_id ] + $_tax_value : $_tax_value;
1621
					}
1622
				}
1623
			}
1624
		}
1625
1626
		// Remove old tax rows
1627
		$order->remove_order_items( 'tax' );
1628
1629
		// Add tax rows
1630 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...
1631
			$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 );
1632
		}
1633
1634
		// Create the new order_taxes
1635
		foreach ( $order->get_taxes() as $tax_id => $tax_item ) {
1636
			$items['order_taxes'][ $tax_id ] = absint( $tax_item['rate_id'] );
1637
		}
1638
1639
		$items = apply_filters( 'woocommerce_ajax_after_calc_line_taxes', $items, $order_id, $country, $_POST );
1640
1641
		// Save order items
1642
		wc_save_order_items( $order_id, $items );
1643
1644
		// Return HTML items
1645
		$order = wc_get_order( $order_id );
1646
		$data  = get_post_meta( $order_id );
1647
		include( 'admin/meta-boxes/views/html-order-items.php' );
1648
1649
		die();
1650
	}
1651
1652
	/**
1653
	 * Save order items via ajax.
1654
	 */
1655
	public static function save_order_items() {
1656
		check_ajax_referer( 'order-item', 'security' );
1657
1658
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1659
			die(-1);
1660
		}
1661
1662
		if ( isset( $_POST['order_id'] ) && isset( $_POST['items'] ) ) {
1663
			$order_id = absint( $_POST['order_id'] );
1664
1665
			// Parse the jQuery serialized items
1666
			$items = array();
1667
			parse_str( $_POST['items'], $items );
1668
1669
			// Save order items
1670
			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...
1671
1672
			// Return HTML items
1673
			$order = wc_get_order( $order_id );
1674
			$data  = get_post_meta( $order_id );
1675
			include( 'admin/meta-boxes/views/html-order-items.php' );
1676
		}
1677
1678
		die();
1679
	}
1680
1681
	/**
1682
	 * Load order items via ajax.
1683
	 */
1684
	public static function load_order_items() {
1685
		check_ajax_referer( 'order-item', 'security' );
1686
1687
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1688
			die(-1);
1689
		}
1690
1691
		// Return HTML items
1692
		$order_id = absint( $_POST['order_id'] );
1693
		$order    = wc_get_order( $order_id );
1694
		$data     = get_post_meta( $order_id );
1695
		include( 'admin/meta-boxes/views/html-order-items.php' );
1696
1697
		die();
1698
	}
1699
1700
	/**
1701
	 * Add order note via ajax.
1702
	 */
1703
	public static function add_order_note() {
1704
1705
		check_ajax_referer( 'add-order-note', 'security' );
1706
1707
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1708
			die(-1);
1709
		}
1710
1711
		$post_id   = absint( $_POST['post_id'] );
1712
		$note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
1713
		$note_type = $_POST['note_type'];
1714
1715
		$is_customer_note = $note_type == 'customer' ? 1 : 0;
1716
1717
		if ( $post_id > 0 ) {
1718
			$order      = wc_get_order( $post_id );
1719
			$comment_id = $order->add_order_note( $note, $is_customer_note, true );
1720
1721
			echo '<li rel="' . esc_attr( $comment_id ) . '" class="note ';
1722
			if ( $is_customer_note ) {
1723
				echo 'customer-note';
1724
			}
1725
			echo '"><div class="note_content">';
1726
			echo wpautop( wptexturize( $note ) );
1727
			echo '</div><p class="meta"><a href="#" class="delete_note">'.__( 'Delete note', 'woocommerce' ).'</a></p>';
1728
			echo '</li>';
1729
		}
1730
1731
		// Quit out
1732
		die();
1733
	}
1734
1735
	/**
1736
	 * Delete order note via ajax.
1737
	 */
1738
	public static function delete_order_note() {
1739
1740
		check_ajax_referer( 'delete-order-note', 'security' );
1741
1742
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1743
			die(-1);
1744
		}
1745
1746
		$note_id = (int) $_POST['note_id'];
1747
1748
		if ( $note_id > 0 ) {
1749
			wp_delete_comment( $note_id );
1750
		}
1751
1752
		// Quit out
1753
		die();
1754
	}
1755
1756
	/**
1757
	 * Search for products and echo json.
1758
	 *
1759
	 * @param string $x (default: '')
1760
	 * @param string $post_types (default: array('product'))
1761
	 */
1762
	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...
1763
		global $wpdb;
1764
1765
		ob_start();
1766
1767
		check_ajax_referer( 'search-products', 'security' );
1768
1769
		$term = (string) wc_clean( stripslashes( $_GET['term'] ) );
1770
1771
		if ( empty( $term ) ) {
1772
			die();
1773
		}
1774
1775
		$like_term = '%' . $wpdb->esc_like( $term ) . '%';
1776
1777
		if ( is_numeric( $term ) ) {
1778
			$query = $wpdb->prepare( "
1779
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1780
				WHERE posts.post_status = 'publish'
1781
				AND (
1782
					posts.post_parent = %s
1783
					OR posts.ID = %s
1784
					OR posts.post_title LIKE %s
1785
					OR (
1786
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1787
					)
1788
				)
1789
			", $term, $term, $term, $like_term );
1790
		} else {
1791
			$query = $wpdb->prepare( "
1792
				SELECT ID FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
1793
				WHERE posts.post_status = 'publish'
1794
				AND (
1795
					posts.post_title LIKE %s
1796
					or posts.post_content LIKE %s
1797
					OR (
1798
						postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
1799
					)
1800
				)
1801
			", $like_term, $like_term, $like_term );
1802
		}
1803
1804
		$query .= " AND posts.post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "')";
1805
1806 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...
1807
			$query .= " AND posts.ID NOT IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['exclude'] ) ) ) . ")";
1808
		}
1809
1810 View Code Duplication
		if ( ! empty( $_GET['include'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1811
			$query .= " AND posts.ID IN (" . implode( ',', array_map( 'intval', explode( ',', $_GET['include'] ) ) ) . ")";
1812
		}
1813
1814
		if ( ! empty( $_GET['limit'] ) ) {
1815
			$query .= " LIMIT " . intval( $_GET['limit'] );
1816
		}
1817
1818
		$posts          = array_unique( $wpdb->get_col( $query ) );
1819
		$found_products = array();
1820
1821
		if ( ! empty( $posts ) ) {
1822
			foreach ( $posts as $post ) {
1823
				$product = wc_get_product( $post );
1824
1825
				if ( ! current_user_can( 'read_product', $post ) ) {
1826
					continue;
1827
				}
1828
1829
				if ( ! $product || ( $product->is_type( 'variation' ) && empty( $product->parent ) ) ) {
1830
					continue;
1831
				}
1832
1833
				$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1834
			}
1835
		}
1836
1837
		$found_products = apply_filters( 'woocommerce_json_search_found_products', $found_products );
1838
1839
		wp_send_json( $found_products );
1840
	}
1841
1842
	/**
1843
	 * Search for product variations and return json.
1844
	 *
1845
	 * @see WC_AJAX::json_search_products()
1846
	 */
1847
	public static function json_search_products_and_variations() {
1848
		self::json_search_products( '', array( 'product', 'product_variation' ) );
1849
	}
1850
1851
	/**
1852
	 * Search for grouped products and return json.
1853
	 */
1854
	public static function json_search_grouped_products() {
1855
		ob_start();
1856
1857
		check_ajax_referer( 'search-products', 'security' );
1858
1859
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1860
		$exclude = array();
1861
1862
		if ( empty( $term ) ) {
1863
			die();
1864
		}
1865
1866 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...
1867
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1868
		}
1869
1870
		$found_products = array();
1871
1872
		if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
1873
1874
			$posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
1875
1876
			if ( sizeof( $posts_in ) > 0 ) {
1877
1878
				$args = array(
1879
					'post_type'        => 'product',
1880
					'post_status'      => 'any',
1881
					'numberposts'      => -1,
1882
					'orderby'          => 'title',
1883
					'order'            => 'asc',
1884
					'post_parent'      => 0,
1885
					'suppress_filters' => 0,
1886
					'include'          => $posts_in,
1887
					's'                => $term,
1888
					'fields'           => 'ids',
1889
					'exclude'          => $exclude
1890
				);
1891
1892
				$posts = get_posts( $args );
1893
1894
				if ( ! empty( $posts ) ) {
1895
					foreach ( $posts as $post ) {
1896
						$product = wc_get_product( $post );
1897
1898
						if ( ! current_user_can( 'read_product', $post ) ) {
1899
							continue;
1900
						}
1901
1902
						$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
1903
					}
1904
				}
1905
			}
1906
		}
1907
1908
		$found_products = apply_filters( 'woocommerce_json_search_found_grouped_products', $found_products );
1909
1910
		wp_send_json( $found_products );
1911
	}
1912
1913
	/**
1914
	 * Search for downloadable product variations and return json.
1915
	 *
1916
	 * @see WC_AJAX::json_search_products()
1917
	 */
1918
	public static function json_search_downloadable_products_and_variations() {
1919
		ob_start();
1920
1921
		check_ajax_referer( 'search-products', 'security' );
1922
1923
		$term    = (string) wc_clean( stripslashes( $_GET['term'] ) );
1924
		$exclude = array();
1925
1926 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...
1927
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1928
		}
1929
1930
		$args = array(
1931
			'post_type'      => array( 'product', 'product_variation' ),
1932
			'posts_per_page' => -1,
1933
			'post_status'    => 'publish',
1934
			'order'          => 'ASC',
1935
			'orderby'        => 'parent title',
1936
			'meta_query'     => array(
1937
				array(
1938
					'key'   => '_downloadable',
1939
					'value' => 'yes'
1940
				)
1941
			),
1942
			's'              => $term,
1943
			'exclude'        => $exclude
1944
		);
1945
1946
		$posts = get_posts( $args );
1947
		$found_products = array();
1948
1949
		if ( ! empty( $posts ) ) {
1950
			foreach ( $posts as $post ) {
1951
				$product = wc_get_product( $post->ID );
1952
1953
				if ( ! current_user_can( 'read_product', $post->ID ) ) {
1954
					continue;
1955
				}
1956
1957
				$found_products[ $post->ID ] = $product->get_formatted_name();
1958
			}
1959
		}
1960
1961
		wp_send_json( $found_products );
1962
	}
1963
1964
	/**
1965
	 * Search for customers and return json.
1966
	 */
1967
	public static function json_search_customers() {
1968
		ob_start();
1969
1970
		check_ajax_referer( 'search-customers', 'security' );
1971
1972
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1973
			die(-1);
1974
		}
1975
1976
		$term    = wc_clean( stripslashes( $_GET['term'] ) );
1977
		$exclude = array();
1978
1979
		if ( empty( $term ) ) {
1980
			die();
1981
		}
1982
1983
		if ( ! empty( $_GET['exclude'] ) ) {
1984
			$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
1985
		}
1986
1987
		$found_customers = array();
1988
1989
		add_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1990
1991
		$customers_query = new WP_User_Query( apply_filters( 'woocommerce_json_search_customers_query', array(
1992
			'fields'         => 'all',
1993
			'orderby'        => 'display_name',
1994
			'search'         => '*' . $term . '*',
1995
			'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename' )
1996
		) ) );
1997
1998
		remove_action( 'pre_user_query', array( __CLASS__, 'json_search_customer_name' ) );
1999
2000
		$customers = $customers_query->get_results();
2001
2002
		if ( ! empty( $customers ) ) {
2003
			foreach ( $customers as $customer ) {
2004
				if ( ! in_array( $customer->ID, $exclude ) ) {
2005
					$found_customers[ $customer->ID ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
2006
				}
2007
			}
2008
		}
2009
2010
		$found_customers = apply_filters( 'woocommerce_json_search_found_customers', $found_customers );
2011
2012
		wp_send_json( $found_customers );
2013
	}
2014
2015
	/**
2016
	 * When searching using the WP_User_Query, search names (user meta) too.
2017
	 * @param  object $query
2018
	 * @return object
2019
	 */
2020
	public static function json_search_customer_name( $query ) {
2021
		global $wpdb;
2022
2023
		$term = wc_clean( stripslashes( $_GET['term'] ) );
2024
		if ( method_exists( $wpdb, 'esc_like' ) ) {
2025
			$term = $wpdb->esc_like( $term );
2026
		} else {
2027
			$term = like_escape( $term );
2028
		}
2029
2030
		$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' ) ";
2031
		$query->query_where .= $wpdb->prepare( " OR user_name.meta_value LIKE %s ", '%' . $term . '%' );
2032
	}
2033
2034
	/**
2035
	 * Ajax request handling for categories ordering.
2036
	 */
2037
	public static function term_ordering() {
2038
2039
		// check permissions again and make sure we have what we need
2040
		if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) {
2041
			die(-1);
2042
		}
2043
2044
		$id       = (int) $_POST['id'];
2045
		$next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
2046
		$taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null;
2047
		$term     = get_term_by( 'id', $id, $taxonomy );
2048
2049
		if ( ! $id || ! $term || ! $taxonomy ) {
2050
			die(0);
2051
		}
2052
2053
		wc_reorder_terms( $term, $next_id, $taxonomy );
2054
2055
		$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
2056
2057
		if ( $term && sizeof( $children ) ) {
2058
			echo 'children';
2059
			die();
2060
		}
2061
	}
2062
2063
	/**
2064
	 * Ajax request handling for product ordering.
2065
	 *
2066
	 * Based on Simple Page Ordering by 10up (https://wordpress.org/extend/plugins/simple-page-ordering/).
2067
	 */
2068
	public static function product_ordering() {
2069
		global $wpdb;
2070
2071
		ob_start();
2072
2073
		// check permissions again and make sure we have what we need
2074
		if ( ! current_user_can('edit_products') || empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
2075
			die(-1);
2076
		}
2077
2078
		// real post?
2079
		if ( ! $post = get_post( $_POST['id'] ) ) {
2080
			die(-1);
2081
		}
2082
2083
		$previd  = isset( $_POST['previd'] ) ? $_POST['previd'] : false;
2084
		$nextid  = isset( $_POST['nextid'] ) ? $_POST['nextid'] : false;
2085
		$new_pos = array(); // store new positions for ajax
2086
2087
		$siblings = $wpdb->get_results( $wpdb->prepare( "
2088
			SELECT ID, menu_order FROM {$wpdb->posts} AS posts
2089
			WHERE 	posts.post_type 	= 'product'
2090
			AND 	posts.post_status 	IN ( 'publish', 'pending', 'draft', 'future', 'private' )
2091
			AND 	posts.ID			NOT IN (%d)
2092
			ORDER BY posts.menu_order ASC, posts.ID DESC
2093
		", $post->ID ) );
2094
2095
		$menu_order = 0;
2096
2097
		foreach ( $siblings as $sibling ) {
2098
2099
			// if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
2100 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...
2101
				$wpdb->update(
2102
					$wpdb->posts,
2103
					array(
2104
						'menu_order' => $menu_order
2105
					),
2106
					array( 'ID' => $post->ID ),
2107
					array( '%d' ),
2108
					array( '%d' )
2109
				);
2110
				$new_pos[ $post->ID ] = $menu_order;
2111
				$menu_order++;
2112
			}
2113
2114
			// if repositioned post has been set, and new items are already in the right order, we can stop
2115
			if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $menu_order ) {
2116
				break;
2117
			}
2118
2119
			// set the menu order of the current sibling and increment the menu order
2120
			$wpdb->update(
2121
				$wpdb->posts,
2122
				array(
2123
					'menu_order' => $menu_order
2124
				),
2125
				array( 'ID' => $sibling->ID ),
2126
				array( '%d' ),
2127
				array( '%d' )
2128
			);
2129
			$new_pos[ $sibling->ID ] = $menu_order;
2130
			$menu_order++;
2131
2132 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...
2133
				$wpdb->update(
2134
					$wpdb->posts,
2135
					array(
2136
						'menu_order' => $menu_order
2137
					),
2138
					array( 'ID' => $post->ID ),
2139
					array( '%d' ),
2140
					array( '%d' )
2141
				);
2142
				$new_pos[$post->ID] = $menu_order;
2143
				$menu_order++;
2144
			}
2145
2146
		}
2147
2148
		do_action( 'woocommerce_after_product_ordering' );
2149
2150
		wp_send_json( $new_pos );
2151
	}
2152
2153
	/**
2154
	 * Handle a refund via the edit order screen.
2155
	 */
2156
	public static function refund_line_items() {
2157
		ob_start();
2158
2159
		check_ajax_referer( 'order-item', 'security' );
2160
2161
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2162
			die(-1);
2163
		}
2164
2165
		$order_id               = absint( $_POST['order_id'] );
2166
		$refund_amount          = wc_format_decimal( sanitize_text_field( $_POST['refund_amount'] ), wc_get_price_decimals() );
2167
		$refund_reason          = sanitize_text_field( $_POST['refund_reason'] );
2168
		$line_item_qtys         = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_qtys'] ) ), true );
2169
		$line_item_totals       = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_totals'] ) ), true );
2170
		$line_item_tax_totals   = json_decode( sanitize_text_field( stripslashes( $_POST['line_item_tax_totals'] ) ), true );
2171
		$api_refund             = $_POST['api_refund'] === 'true' ? true : false;
2172
		$restock_refunded_items = $_POST['restock_refunded_items'] === 'true' ? true : false;
2173
		$refund                 = false;
2174
		$response_data          = array();
2175
2176
		try {
2177
			// Validate that the refund can occur
2178
			$order       = wc_get_order( $order_id );
2179
			$order_items = $order->get_items();
2180
			$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded(), wc_get_price_decimals() );
2181
2182
			if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) {
2183
				throw new exception( __( 'Invalid refund amount', 'woocommerce' ) );
2184
			}
2185
2186
			// Prepare line items which we are refunding
2187
			$line_items = array();
2188
			$item_ids   = array_unique( array_merge( array_keys( $line_item_qtys, $line_item_totals ) ) );
2189
2190
			foreach ( $item_ids as $item_id ) {
2191
				$line_items[ $item_id ] = array( 'qty' => 0, 'refund_total' => 0, 'refund_tax' => array() );
2192
			}
2193
			foreach ( $line_item_qtys as $item_id => $qty ) {
2194
				$line_items[ $item_id ]['qty'] = max( $qty, 0 );
2195
			}
2196
			foreach ( $line_item_totals as $item_id => $total ) {
2197
				$line_items[ $item_id ]['refund_total'] = wc_format_decimal( $total );
2198
			}
2199
			foreach ( $line_item_tax_totals as $item_id => $tax_totals ) {
2200
				$line_items[ $item_id ]['refund_tax'] = array_map( 'wc_format_decimal', $tax_totals );
2201
			}
2202
2203
			// Create the refund object
2204
			$refund = wc_create_refund( array(
2205
				'amount'     => $refund_amount,
2206
				'reason'     => $refund_reason,
2207
				'order_id'   => $order_id,
2208
				'line_items' => $line_items,
2209
			) );
2210
2211
			if ( is_wp_error( $refund ) ) {
2212
				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...
2213
			}
2214
2215
			// Refund via API
2216
			if ( $api_refund ) {
2217
				if ( WC()->payment_gateways() ) {
2218
					$payment_gateways = WC()->payment_gateways->payment_gateways();
2219
				}
2220
				if ( isset( $payment_gateways[ $order->payment_method ] ) && $payment_gateways[ $order->payment_method ]->supports( 'refunds' ) ) {
2221
					$result = $payment_gateways[ $order->payment_method ]->process_refund( $order_id, $refund_amount, $refund_reason );
2222
2223
					do_action( 'woocommerce_refund_processed', $refund, $result );
2224
2225
					if ( is_wp_error( $result ) ) {
2226
						throw new Exception( $result->get_error_message() );
2227
					} elseif ( ! $result ) {
2228
						throw new Exception( __( 'Refund failed', 'woocommerce' ) );
2229
					}
2230
				}
2231
			}
2232
2233
			// restock items
2234
			foreach ( $line_item_qtys as $item_id => $qty ) {
2235
				if ( $restock_refunded_items && $qty && isset( $order_items[ $item_id ] ) ) {
2236
					$order_item = $order_items[ $item_id ];
2237
					$_product   = $order->get_product_from_item( $order_item );
2238
2239
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2240
						$old_stock    = wc_stock_amount( $_product->stock );
2241
						$new_quantity = $_product->increase_stock( $qty );
2242
2243
						$order->add_order_note( sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
2244
2245
						do_action( 'woocommerce_restock_refunded_item', $_product->id, $old_stock, $new_quantity, $order );
2246
					}
2247
				}
2248
			}
2249
2250
			// Trigger notifications and status changes
2251
			if ( $order->get_remaining_refund_amount() > 0 || ( $order->has_free_item() && $order->get_remaining_refund_items() > 0 ) ) {
2252
				/**
2253
				 * woocommerce_order_partially_refunded.
2254
				 *
2255
				 * @since 2.4.0
2256
				 * Note: 3rd arg was added in err. Kept for bw compat. 2.4.3.
2257
				 */
2258
				do_action( 'woocommerce_order_partially_refunded', $order_id, $refund->id, $refund->id );
2259
			} else {
2260
				do_action( 'woocommerce_order_fully_refunded', $order_id, $refund->id );
2261
2262
				$order->update_status( apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order_id, $refund->id ) );
2263
				$response_data['status'] = 'fully_refunded';
2264
			}
2265
2266
			do_action( 'woocommerce_order_refunded', $order_id, $refund->id );
2267
2268
			// Clear transients
2269
			wc_delete_shop_order_transients( $order_id );
2270
			wp_send_json_success( $response_data );
2271
2272
		} catch ( Exception $e ) {
2273
			if ( $refund && is_a( $refund, 'WC_Order_Refund' ) ) {
2274
				wp_delete_post( $refund->id, true );
2275
			}
2276
2277
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
2278
		}
2279
	}
2280
2281
	/**
2282
	 * Delete a refund.
2283
	 */
2284
	public static function delete_refund() {
2285
		check_ajax_referer( 'order-item', 'security' );
2286
2287
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
2288
			die(-1);
2289
		}
2290
2291
		$refund_ids = array_map( 'absint', is_array( $_POST['refund_id'] ) ? $_POST['refund_id'] : array( $_POST['refund_id'] ) );
2292
		foreach ( $refund_ids as $refund_id ) {
2293
			if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) {
2294
				$order_id = wp_get_post_parent_id( $refund_id );
2295
				wc_delete_shop_order_transients( $order_id );
2296
				wp_delete_post( $refund_id );
2297
				do_action( 'woocommerce_refund_deleted', $refund_id, $order_id );
2298
			}
2299
		}
2300
		die();
2301
	}
2302
2303
	/**
2304
	 * Triggered when clicking the rating footer.
2305
	 */
2306
	public static function rated() {
2307
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2308
			die(-1);
2309
		}
2310
2311
		update_option( 'woocommerce_admin_footer_text_rated', 1 );
2312
		die();
2313
	}
2314
2315
	/**
2316
	 * Create/Update API key.
2317
	 */
2318
	public static function update_api_key() {
2319
		ob_start();
2320
2321
		global $wpdb;
2322
2323
		check_ajax_referer( 'update-api-key', 'security' );
2324
2325
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
2326
			die(-1);
2327
		}
2328
2329
		try {
2330
			if ( empty( $_POST['description'] ) ) {
2331
				throw new Exception( __( 'Description is missing.', 'woocommerce' ) );
2332
			}
2333
			if ( empty( $_POST['user'] ) ) {
2334
				throw new Exception( __( 'User is missing.', 'woocommerce' ) );
2335
			}
2336
			if ( empty( $_POST['permissions'] ) ) {
2337
				throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) );
2338
			}
2339
2340
			$key_id      = absint( $_POST['key_id'] );
2341
			$description = sanitize_text_field( wp_unslash( $_POST['description'] ) );
2342
			$permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read';
2343
			$user_id     = absint( $_POST['user'] );
2344
2345
			if ( 0 < $key_id ) {
2346
				$data = array(
2347
					'user_id'     => $user_id,
2348
					'description' => $description,
2349
					'permissions' => $permissions
2350
				);
2351
2352
				$wpdb->update(
2353
					$wpdb->prefix . 'woocommerce_api_keys',
2354
					$data,
2355
					array( 'key_id' => $key_id ),
2356
					array(
2357
						'%d',
2358
						'%s',
2359
						'%s'
2360
					),
2361
					array( '%d' )
2362
				);
2363
2364
				$data['consumer_key']    = '';
2365
				$data['consumer_secret'] = '';
2366
				$data['message']         = __( 'API Key updated successfully.', 'woocommerce' );
2367
			} else {
2368
				$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...
2369
				$consumer_key    = 'ck_' . wc_rand_hash();
2370
				$consumer_secret = 'cs_' . wc_rand_hash();
2371
2372
				$data = array(
2373
					'user_id'         => $user_id,
2374
					'description'     => $description,
2375
					'permissions'     => $permissions,
2376
					'consumer_key'    => wc_api_hash( $consumer_key ),
2377
					'consumer_secret' => $consumer_secret,
2378
					'truncated_key'   => substr( $consumer_key, -7 )
2379
				);
2380
2381
				$wpdb->insert(
2382
					$wpdb->prefix . 'woocommerce_api_keys',
2383
					$data,
2384
					array(
2385
						'%d',
2386
						'%s',
2387
						'%s',
2388
						'%s',
2389
						'%s',
2390
						'%s'
2391
					)
2392
				);
2393
2394
				$key_id                  = $wpdb->insert_id;
2395
				$data['consumer_key']    = $consumer_key;
2396
				$data['consumer_secret'] = $consumer_secret;
2397
				$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' );
2398
				$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>';
2399
			}
2400
2401
			wp_send_json_success( $data );
2402
		} catch ( Exception $e ) {
2403
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
2404
		}
2405
	}
2406
2407
	/**
2408
	 * Locate user via AJAX.
2409
	 */
2410
	public static function get_customer_location() {
2411
		$location_hash = WC_Cache_Helper::geolocation_ajax_get_location_hash();
2412
		wp_send_json_success( array( 'hash' => $location_hash ) );
2413
	}
2414
2415
	/**
2416
	 * Load variations via AJAX.
2417
	 */
2418
	public static function load_variations() {
2419
		ob_start();
2420
2421
		check_ajax_referer( 'load-variations', 'security' );
2422
2423
		// Check permissions again and make sure we have what we need
2424 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...
2425
			die( -1 );
2426
		}
2427
2428
		global $post;
2429
2430
		$product_id = absint( $_POST['product_id'] );
2431
		$post       = get_post( $product_id ); // Set $post global so its available like within the admin screens
2432
		$per_page   = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
2433
		$page       = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
2434
2435
		// Get attributes
2436
		$attributes        = array();
2437
		$posted_attributes = wp_unslash( $_POST['attributes'] );
2438
2439
		foreach ( $posted_attributes as $key => $value ) {
2440
			$attributes[ $key ] = array_map( 'wc_clean', $value );
2441
		}
2442
2443
		// Get tax classes
2444
		$tax_classes           = WC_Tax::get_tax_classes();
2445
		$tax_class_options     = array();
2446
		$tax_class_options[''] = __( 'Standard', 'woocommerce' );
2447
2448 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...
2449
			foreach ( $tax_classes as $class ) {
2450
				$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
2451
			}
2452
		}
2453
2454
		// Set backorder options
2455
		$backorder_options = array(
2456
			'no'     => __( 'Do not allow', 'woocommerce' ),
2457
			'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
2458
			'yes'    => __( 'Allow', 'woocommerce' )
2459
		);
2460
2461
		// set stock status options
2462
		$stock_status_options = array(
2463
			'instock'    => __( 'In stock', 'woocommerce' ),
2464
			'outofstock' => __( 'Out of stock', 'woocommerce' )
2465
		);
2466
2467
		$parent_data = array(
2468
			'id'                   => $product_id,
2469
			'attributes'           => $attributes,
2470
			'tax_class_options'    => $tax_class_options,
2471
			'sku'                  => get_post_meta( $product_id, '_sku', true ),
2472
			'weight'               => wc_format_localized_decimal( get_post_meta( $product_id, '_weight', true ) ),
2473
			'length'               => wc_format_localized_decimal( get_post_meta( $product_id, '_length', true ) ),
2474
			'width'                => wc_format_localized_decimal( get_post_meta( $product_id, '_width', true ) ),
2475
			'height'               => wc_format_localized_decimal( get_post_meta( $product_id, '_height', true ) ),
2476
			'tax_class'            => get_post_meta( $product_id, '_tax_class', true ),
2477
			'backorder_options'    => $backorder_options,
2478
			'stock_status_options' => $stock_status_options
2479
		);
2480
2481
		if ( ! $parent_data['weight'] ) {
2482
			$parent_data['weight'] = wc_format_localized_decimal( 0 );
2483
		}
2484
2485
		if ( ! $parent_data['length'] ) {
2486
			$parent_data['length'] = wc_format_localized_decimal( 0 );
2487
		}
2488
2489
		if ( ! $parent_data['width'] ) {
2490
			$parent_data['width'] = wc_format_localized_decimal( 0 );
2491
		}
2492
2493
		if ( ! $parent_data['height'] ) {
2494
			$parent_data['height'] = wc_format_localized_decimal( 0 );
2495
		}
2496
2497
		// Get variations
2498
		$args = apply_filters( 'woocommerce_ajax_admin_get_variations_args', array(
2499
			'post_type'      => 'product_variation',
2500
			'post_status'    => array( 'private', 'publish' ),
2501
			'posts_per_page' => $per_page,
2502
			'paged'          => $page,
2503
			'orderby'        => array( 'menu_order' => 'ASC', 'ID' => 'DESC' ),
2504
			'post_parent'    => $product_id
2505
		), $product_id );
2506
2507
		$variations = get_posts( $args );
2508
		$loop = 0;
2509
2510
		if ( $variations ) {
2511
2512
			foreach ( $variations as $variation ) {
2513
				$variation_id     = absint( $variation->ID );
2514
				$variation_meta   = get_post_meta( $variation_id );
2515
				$variation_data   = array();
2516
				$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
2517
				$variation_fields = array(
2518
					'_sku'                   => '',
2519
					'_stock'                 => '',
2520
					'_regular_price'         => '',
2521
					'_sale_price'            => '',
2522
					'_weight'                => '',
2523
					'_length'                => '',
2524
					'_width'                 => '',
2525
					'_height'                => '',
2526
					'_download_limit'        => '',
2527
					'_download_expiry'       => '',
2528
					'_downloadable_files'    => '',
2529
					'_downloadable'          => '',
2530
					'_virtual'               => '',
2531
					'_thumbnail_id'          => '',
2532
					'_sale_price_dates_from' => '',
2533
					'_sale_price_dates_to'   => '',
2534
					'_manage_stock'          => '',
2535
					'_stock_status'          => '',
2536
					'_backorders'            => null,
2537
					'_tax_class'             => null,
2538
					'_variation_description' => ''
2539
				);
2540
2541 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...
2542
					$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
2543
				}
2544
2545
				// Add the variation attributes
2546
				$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
2547
2548
				// Formatting
2549
				$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
2550
				$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
2551
				$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
2552
				$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
2553
				$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
2554
				$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
2555
				$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
2556
				$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
2557
				$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
2558
				$variation_data['menu_order']     = $variation->menu_order;
2559
				$variation_data['_stock']         = '' === $variation_data['_stock'] ? '' : wc_stock_amount( $variation_data['_stock'] );
2560
2561
				include( 'admin/meta-boxes/views/html-variation-admin.php' );
2562
2563
				$loop++;
2564
			}
2565
		}
2566
2567
		die();
2568
	}
2569
2570
	/**
2571
	 * Save variations via AJAX.
2572
	 */
2573
	public static function save_variations() {
2574
		ob_start();
2575
2576
		check_ajax_referer( 'save-variations', 'security' );
2577
2578
		// Check permissions again and make sure we have what we need
2579 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...
2580
			die( -1 );
2581
		}
2582
2583
		// Remove previous meta box errors
2584
		WC_Admin_Meta_Boxes::$meta_box_errors = array();
2585
2586
		$product_id   = absint( $_POST['product_id'] );
2587
		$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
2588
2589
		$product_type_terms = wp_get_object_terms( $product_id, 'product_type' );
2590
2591
		// If the product type hasn't been set or it has changed, update it before saving variations
2592
		if ( empty( $product_type_terms ) || $product_type !== sanitize_title( current( $product_type_terms )->name ) ) {
2593
			wp_set_object_terms( $product_id, $product_type, 'product_type' );
2594
		}
2595
2596
		WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
2597
2598
		do_action( 'woocommerce_ajax_save_product_variations', $product_id );
2599
2600
		// Clear cache/transients
2601
		wc_delete_product_transients( $product_id );
2602
2603
		if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
2604
			echo '<div class="error notice is-dismissible">';
2605
2606
			foreach ( $errors as $error ) {
2607
				echo '<p>' . wp_kses_post( $error ) . '</p>';
2608
			}
2609
2610
			echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
2611
			echo '</div>';
2612
2613
			delete_option( 'woocommerce_meta_box_errors' );
2614
		}
2615
2616
		die();
2617
	}
2618
2619
	/**
2620
	 * Bulk action - Toggle Enabled.
2621
	 * @access private
2622
	 * @param  array $variations
2623
	 * @param  array $data
2624
	 */
2625
	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...
2626
		global $wpdb;
2627
2628
		foreach ( $variations as $variation_id ) {
2629
			$post_status = get_post_status( $variation_id );
2630
			$new_status  = 'private' === $post_status ? 'publish' : 'private';
2631
			$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $variation_id ) );
2632
		}
2633
	}
2634
2635
	/**
2636
	 * Bulk action - Toggle Downloadable Checkbox.
2637
	 * @access private
2638
	 * @param  array $variations
2639
	 * @param  array $data
2640
	 */
2641 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...
2642
		foreach ( $variations as $variation_id ) {
2643
			$_downloadable   = get_post_meta( $variation_id, '_downloadable', true );
2644
			$is_downloadable = 'no' === $_downloadable ? 'yes' : 'no';
2645
			update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
2646
		}
2647
	}
2648
2649
	/**
2650
	 * Bulk action - Toggle Virtual Checkbox.
2651
	 * @access private
2652
	 * @param  array $variations
2653
	 * @param  array $data
2654
	 */
2655 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...
2656
		foreach ( $variations as $variation_id ) {
2657
			$_virtual   = get_post_meta( $variation_id, '_virtual', true );
2658
			$is_virtual = 'no' === $_virtual ? 'yes' : 'no';
2659
			update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
2660
		}
2661
	}
2662
2663
	/**
2664
	 * Bulk action - Toggle Manage Stock Checkbox.
2665
	 * @access private
2666
	 * @param  array $variations
2667
	 * @param  array $data
2668
	 */
2669
	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...
2670
		foreach ( $variations as $variation_id ) {
2671
			$_manage_stock   = get_post_meta( $variation_id, '_manage_stock', true );
2672
			$is_manage_stock = 'no' === $_manage_stock || '' === $_manage_stock ? 'yes' : 'no';
2673
			update_post_meta( $variation_id, '_manage_stock', $is_manage_stock );
2674
		}
2675
	}
2676
2677
	/**
2678
	 * Bulk action - Set Regular Prices.
2679
	 * @access private
2680
	 * @param  array $variations
2681
	 * @param  array $data
2682
	 */
2683 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...
2684
		if ( ! isset( $data['value'] ) ) {
2685
			return;
2686
		}
2687
2688
		foreach ( $variations as $variation_id ) {
2689
			// Price fields
2690
			$regular_price = wc_clean( $data['value'] );
2691
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2692
2693
			// Date fields
2694
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2695
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2696
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2697
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2698
2699
			_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...
2700
		}
2701
	}
2702
2703
	/**
2704
	 * Bulk action - Set Sale Prices.
2705
	 * @access private
2706
	 * @param  array $variations
2707
	 * @param  array $data
2708
	 */
2709 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...
2710
		if ( ! isset( $data['value'] ) ) {
2711
			return;
2712
		}
2713
2714
		foreach ( $variations as $variation_id ) {
2715
			// Price fields
2716
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2717
			$sale_price    = wc_clean( $data['value'] );
2718
2719
			// Date fields
2720
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2721
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2722
			$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2723
			$date_to   = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2724
2725
			_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 2717 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...
2726
		}
2727
	}
2728
2729
	/**
2730
	 * Bulk action - Set Stock.
2731
	 * @access private
2732
	 * @param  array $variations
2733
	 * @param  array $data
2734
	 */
2735
	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...
2736
		if ( ! isset( $data['value'] ) ) {
2737
			return;
2738
		}
2739
2740
		$value = wc_clean( $data['value'] );
2741
2742
		foreach ( $variations as $variation_id ) {
2743
			if ( 'yes' === get_post_meta( $variation_id, '_manage_stock', true ) ) {
2744
				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...
2745
			} else {
2746
				delete_post_meta( $variation_id, '_stock' );
2747
			}
2748
		}
2749
	}
2750
2751
	/**
2752
	 * Bulk action - Set Weight.
2753
	 * @access private
2754
	 * @param  array $variations
2755
	 * @param  array $data
2756
	 */
2757
	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...
2758
		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...
2759
	}
2760
2761
	/**
2762
	 * Bulk action - Set Length.
2763
	 * @access private
2764
	 * @param  array $variations
2765
	 * @param  array $data
2766
	 */
2767
	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...
2768
		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...
2769
	}
2770
2771
	/**
2772
	 * Bulk action - Set Width.
2773
	 * @access private
2774
	 * @param  array $variations
2775
	 * @param  array $data
2776
	 */
2777
	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...
2778
		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...
2779
	}
2780
2781
	/**
2782
	 * Bulk action - Set Height.
2783
	 * @access private
2784
	 * @param  array $variations
2785
	 * @param  array $data
2786
	 */
2787
	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...
2788
		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...
2789
	}
2790
2791
	/**
2792
	 * Bulk action - Set Download Limit.
2793
	 * @access private
2794
	 * @param  array $variations
2795
	 * @param  array $data
2796
	 */
2797
	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...
2798
		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...
2799
	}
2800
2801
	/**
2802
	 * Bulk action - Set Download Expiry.
2803
	 * @access private
2804
	 * @param  array $variations
2805
	 * @param  array $data
2806
	 */
2807
	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...
2808
		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...
2809
	}
2810
2811
	/**
2812
	 * Bulk action - Delete all.
2813
	 * @access private
2814
	 * @param  array $variations
2815
	 * @param  array $data
2816
	 */
2817
	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...
2818
		if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) {
2819
			foreach ( $variations as $variation_id ) {
2820
				wp_delete_post( $variation_id );
2821
			}
2822
		}
2823
	}
2824
2825
	/**
2826
	 * Bulk action - Sale Schedule.
2827
	 * @access private
2828
	 * @param  array $variations
2829
	 * @param  array $data
2830
	 */
2831
	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...
2832
		if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) {
2833
			return;
2834
		}
2835
2836
		foreach ( $variations as $variation_id ) {
2837
			// Price fields
2838
			$regular_price = get_post_meta( $variation_id, '_regular_price', true );
2839
			$sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2840
2841
			// Date fields
2842
			$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2843
			$date_to   = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2844
2845 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...
2846
				$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2847
			} else {
2848
				$date_from = $data['date_from'];
2849
			}
2850
2851 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...
2852
				$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2853
			} else {
2854
				$date_to = $data['date_to'];
2855
			}
2856
2857
			_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
2858
		}
2859
	}
2860
2861
	/**
2862
	 * Bulk action - Increase Regular Prices.
2863
	 * @access private
2864
	 * @param  array $variations
2865
	 * @param  array $data
2866
	 */
2867
	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...
2868
		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...
2869
	}
2870
2871
	/**
2872
	 * Bulk action - Decrease Regular Prices.
2873
	 * @access private
2874
	 * @param  array $variations
2875
	 * @param  array $data
2876
	 */
2877
	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...
2878
		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...
2879
	}
2880
2881
	/**
2882
	 * Bulk action - Increase Sale Prices.
2883
	 * @access private
2884
	 * @param  array $variations
2885
	 * @param  array $data
2886
	 */
2887
	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...
2888
		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...
2889
	}
2890
2891
	/**
2892
	 * Bulk action - Decrease Sale Prices.
2893
	 * @access private
2894
	 * @param  array $variations
2895
	 * @param  array $data
2896
	 */
2897
	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...
2898
		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...
2899
	}
2900
2901
	/**
2902
	 * Bulk action - Set Price.
2903
	 * @access private
2904
	 * @param  array $variations
2905
	 * @param string $operator + or -
2906
	 * @param string $field price being adjusted
2907
	 * @param string $value Price or Percent
2908
	 */
2909
	private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
2910
		foreach ( $variations as $variation_id ) {
2911
			// Get existing data
2912
			$_regular_price = get_post_meta( $variation_id, '_regular_price', true );
2913
			$_sale_price    = get_post_meta( $variation_id, '_sale_price', true );
2914
			$date_from      = get_post_meta( $variation_id, '_sale_price_dates_from', true );
2915
			$date_to        = get_post_meta( $variation_id, '_sale_price_dates_to', true );
2916
			$date_from      = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
2917
			$date_to        = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
2918
2919
			if ( '%' === substr( $value, -1 ) ) {
2920
				$percent = wc_format_decimal( substr( $value, 0, -1 ) );
2921
				$$field  += ( ( $$field / 100 ) * $percent ) * "{$operator}1";
2922
			} else {
2923
				$$field  += $value * "{$operator}1";
2924
			}
2925
			_wc_save_product_price( $variation_id, $_regular_price, $_sale_price, $date_from, $date_to );
2926
		}
2927
	}
2928
2929
	/**
2930
	 * Bulk action - Set Meta.
2931
	 * @access private
2932
	 * @param array $variations
2933
	 * @param string $field
2934
	 * @param string $value
2935
	 */
2936
	private static function variation_bulk_set_meta( $variations, $field, $value ) {
2937
		foreach ( $variations as $variation_id ) {
2938
			update_post_meta( $variation_id, $field, $value );
2939
		}
2940
	}
2941
2942
2943
	/**
2944
	 * Bulk edit variations via AJAX.
2945
	 */
2946
	public static function bulk_edit_variations() {
2947
		ob_start();
2948
2949
		check_ajax_referer( 'bulk-edit-variations', 'security' );
2950
2951
		// Check permissions again and make sure we have what we need
2952 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...
2953
			die( -1 );
2954
		}
2955
2956
		$product_id  = absint( $_POST['product_id'] );
2957
		$bulk_action = wc_clean( $_POST['bulk_action'] );
2958
		$data        = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array();
2959
		$variations  = array();
2960
2961
		if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) {
2962
			$variations = get_posts( array(
2963
				'post_parent'    => $product_id,
2964
				'posts_per_page' => -1,
2965
				'post_type'      => 'product_variation',
2966
				'fields'         => 'ids',
2967
				'post_status'    => array( 'publish', 'private' )
2968
			) );
2969
		}
2970
2971
		if ( method_exists( __CLASS__, "variation_bulk_action_$bulk_action" ) ) {
2972
			call_user_func( array( __CLASS__, "variation_bulk_action_$bulk_action" ), $variations, $data );
2973
		} else {
2974
			do_action( 'woocommerce_bulk_edit_variations_default', $bulk_action, $data, $product_id, $variations );
2975
		}
2976
2977
		do_action( 'woocommerce_bulk_edit_variations', $bulk_action, $data, $product_id, $variations );
2978
2979
		// Sync and update transients
2980
		WC_Product_Variable::sync( $product_id );
2981
		wc_delete_product_transients( $product_id );
2982
		die();
2983
	}
2984
2985
	/**
2986
	 * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model.
2987
	 */
2988
	public static function tax_rates_save_changes() {
2989
		if ( ! isset( $_POST['wc_tax_nonce'], $_POST['changes'] ) ) {
2990
			wp_send_json_error( 'missing_fields' );
2991
			exit;
2992
		}
2993
2994
		$current_class = ! empty( $_POST['current_class'] ) ? $_POST['current_class'] : ''; // This is sanitized seven lines later.
2995
2996
		if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) {
2997
			wp_send_json_error( 'bad_nonce' );
2998
			exit;
2999
		}
3000
3001
		$current_class = WC_Tax::format_tax_rate_class( $current_class );
3002
3003
		// Check User Caps
3004
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3005
			wp_send_json_error( 'missing_capabilities' );
3006
			exit;
3007
		}
3008
3009
		$changes = $_POST['changes'];
3010
		foreach ( $changes as $tax_rate_id => $data ) {
3011
			if ( isset( $data['deleted'] ) ) {
3012
				if ( isset( $data['newRow'] ) ) {
3013
					// So the user added and deleted a new row.
3014
					// That's fine, it's not in the database anyways. NEXT!
3015
					continue;
3016
				}
3017
				WC_Tax::_delete_tax_rate( $tax_rate_id );
3018
			}
3019
3020
			$tax_rate = array_intersect_key( $data, array(
3021
				'tax_rate_country'  => 1,
3022
				'tax_rate_state'    => 1,
3023
				'tax_rate'          => 1,
3024
				'tax_rate_name'     => 1,
3025
				'tax_rate_priority' => 1,
3026
				'tax_rate_compound' => 1,
3027
				'tax_rate_shipping' => 1,
3028
				'tax_rate_order'    => 1,
3029
			) );
3030
3031
			if ( isset( $data['newRow'] ) ) {
3032
				// Hurrah, shiny and new!
1 ignored issue
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3033
				$tax_rate['tax_rate_class'] = $current_class;
3034
				$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
3035
			} else {
3036
				// Updating an existing rate ...
3037
				if ( ! empty( $tax_rate ) ) {
3038
					WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
3039
				}
3040
			}
3041
3042
			if ( isset( $data['postcode'] ) ) {
3043
				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...
3044
			}
3045
			if ( isset( $data['city'] ) ) {
3046
				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...
3047
			}
3048
		}
3049
3050
		wp_send_json_success( array(
3051
			'rates' => WC_Tax::get_rates_for_tax_class( $current_class ),
3052
		) );
3053
	}
3054
3055
	/**
3056
	 * Handle submissions from assets/js/wc-shipping-zones.js Backbone model.
3057
	 */
3058
	public static function shipping_zones_save_changes() {
3059
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['changes'] ) ) {
3060
			wp_send_json_error( 'missing_fields' );
3061
			exit;
3062
		}
3063
3064
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3065
			wp_send_json_error( 'bad_nonce' );
3066
			exit;
3067
		}
3068
3069
		// Check User Caps
3070
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3071
			wp_send_json_error( 'missing_capabilities' );
3072
			exit;
3073
		}
3074
3075
		$changes = $_POST['changes'];
3076
		foreach ( $changes as $zone_id => $data ) {
3077
			if ( isset( $data['deleted'] ) ) {
3078
				if ( isset( $data['newRow'] ) ) {
3079
					// So the user added and deleted a new row.
3080
					// That's fine, it's not in the database anyways. NEXT!
3081
					continue;
3082
				}
3083
				WC_Shipping_Zones::delete_zone( $zone_id );
3084
				continue;
3085
			}
3086
3087
			$zone_data = array_intersect_key( $data, array(
3088
				'zone_id'        => 1,
3089
				'zone_name'      => 1,
3090
				'zone_order'     => 1,
3091
				'zone_locations' => 1,
3092
				'zone_postcodes' => 1
3093
			) );
3094
3095
			if ( isset( $zone_data['zone_id'] ) ) {
3096
				$zone = new WC_Shipping_Zone( $zone_data['zone_id'] );
3097
3098
				if ( isset( $zone_data['zone_name'] ) ) {
3099
					$zone->set_zone_name( $zone_data['zone_name'] );
3100
				}
3101
3102
				if ( isset( $zone_data['zone_order'] ) ) {
3103
					$zone->set_zone_order( $zone_data['zone_order'] );
3104
				}
3105
3106
				if ( isset( $zone_data['zone_locations'] ) ) {
3107
					$zone->clear_locations( array( 'state', 'country', 'continent' ) );
3108
					$locations = array_filter( array_map( 'wc_clean', (array) $zone_data['zone_locations'] ) );
3109
					foreach ( $locations as $location ) {
3110
						// Each posted location will be in the format type:code
3111
						$location_parts = explode( ':', $location );
3112
						switch ( $location_parts[0] ) {
3113
							case 'state' :
3114
								$zone->add_location( $location_parts[1] . ':' . $location_parts[2], 'state' );
3115
							break;
3116
							case 'country' :
3117
								$zone->add_location( $location_parts[1], 'country' );
3118
							break;
3119
							case 'continent' :
3120
								$zone->add_location( $location_parts[1], 'continent' );
3121
							break;
3122
						}
3123
					}
3124
				}
3125
3126
				if ( isset( $zone_data['zone_postcodes'] ) ) {
3127
					$zone->clear_locations( 'postcode' );
3128
					$postcodes = array_filter( array_map( 'strtoupper', array_map( 'wc_clean', explode( "\n", $zone_data['zone_postcodes'] ) ) ) );
3129
					foreach ( $postcodes as $postcode ) {
3130
						$zone->add_location( $postcode, 'postcode' );
3131
					}
3132
				}
3133
3134
				$zone->save();
3135
			}
3136
		}
3137
3138
		wp_send_json_success( array(
3139
			'zones' => WC_Shipping_Zones::get_zones()
3140
		) );
3141
	}
3142
3143
	/**
3144
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
3145
	 */
3146
	public static function shipping_zone_add_method() {
3147
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['method_id'] ) ) {
3148
			wp_send_json_error( 'missing_fields' );
3149
			exit;
3150
		}
3151
3152
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3153
			wp_send_json_error( 'bad_nonce' );
3154
			exit;
3155
		}
3156
3157
		// Check User Caps
3158
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3159
			wp_send_json_error( 'missing_capabilities' );
3160
			exit;
3161
		}
3162
3163
		$zone_id     = absint( $_POST['zone_id'] );
3164
		$zone        = WC_Shipping_Zones::get_zone( $zone_id );
3165
		$instance_id = $zone->add_shipping_method( wc_clean( $_POST['method_id'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($_POST['method_id']) targeting wc_clean() can also be of type array; however, WC_Shipping_Zone::add_shipping_method() does only seem to accept string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
3166
3167
		wp_send_json_success( array(
3168
			'instance_id' => $instance_id,
3169
			'zone_id'     => $zone_id,
3170
			'methods'     => $zone->get_shipping_methods()
3171
		) );
3172
	}
3173
3174
	/**
3175
	 * Handle submissions from assets/js/wc-shipping-zone-methods.js Backbone model.
3176
	 */
3177
	public static function shipping_zone_methods_save_changes() {
3178
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['zone_id'], $_POST['changes'] ) ) {
3179
			wp_send_json_error( 'missing_fields' );
3180
			exit;
3181
		}
3182
3183
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3184
			wp_send_json_error( 'bad_nonce' );
3185
			exit;
3186
		}
3187
3188
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3189
			wp_send_json_error( 'missing_capabilities' );
3190
			exit;
3191
		}
3192
3193
		global $wpdb;
3194
3195
		$zone_id = absint( $_POST['zone_id'] );
3196
		$zone    = new WC_Shipping_Zone( $zone_id );
3197
		$changes = $_POST['changes'];
3198
3199
		foreach ( $changes as $instance_id => $data ) {
3200
			$method_id = $wpdb->get_var( $wpdb->prepare( "SELECT method_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE instance_id = %d", $instance_id ) );
3201
3202
			if ( isset( $data['deleted'] ) ) {
3203
				if ( $wpdb->delete( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'instance_id' => $instance_id ) ) ) {
3204
					do_action( 'woocommerce_shipping_zone_method_deleted', $instance_id, $method_id, $zone_id );
3205
				}
3206
				continue;
3207
			}
3208
3209
			$method_data = array_intersect_key( $data, array(
3210
				'method_order' => 1,
3211
				'enabled'      => 1
3212
			) );
3213
3214
			if ( isset( $method_data['method_order'] ) ) {
3215
				$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'method_order' => absint( $method_data['method_order'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
3216
			}
3217
3218
			if ( isset( $method_data['enabled'] ) ) {
3219
				$is_enabled = absint( 'yes' === $method_data['enabled'] );
3220
				if ( $wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'is_enabled' => $is_enabled ), array( 'instance_id' => absint( $instance_id ) ) ) ) {
3221
					do_action( 'woocommerce_shipping_zone_method_status_toggled', $instance_id, $method_id, $zone_id, $is_enabled );
3222
				}
3223
			}
3224
		}
3225
3226
		wp_send_json_success( array(
3227
			'methods' => $zone->get_shipping_methods()
3228
		) );
3229
	}
3230
3231
	/**
3232
	 * Save method settings
3233
	 */
3234
	public static function shipping_zone_methods_save_settings() {
3235
		if ( ! isset( $_POST['wc_shipping_zones_nonce'], $_POST['instance_id'], $_POST['data'] ) ) {
3236
			wp_send_json_error( 'missing_fields' );
3237
			exit;
3238
		}
3239
3240
		if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) {
3241
			wp_send_json_error( 'bad_nonce' );
3242
			exit;
3243
		}
3244
3245
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3246
			wp_send_json_error( 'missing_capabilities' );
3247
			exit;
3248
		}
3249
3250
		$instance_id     = absint( $_POST['instance_id'] );
3251
		$zone            = WC_Shipping_Zones::get_zone_by( 'instance_id', $instance_id );
3252
		$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
3253
		$shipping_method->set_post_data( $_POST['data'] );
3254
		$shipping_method->process_admin_options();
3255
3256
		wp_send_json_success( array(
3257
			'methods' => $zone->get_shipping_methods(),
3258
			'errors'  => $shipping_method->get_errors(),
3259
		) );
3260
	}
3261
3262
	/**
3263
	 * Handle submissions from assets/js/wc-shipping-classes.js Backbone model.
3264
	 */
3265
	public static function shipping_classes_save_changes() {
3266
		if ( ! isset( $_POST['wc_shipping_classes_nonce'], $_POST['changes'] ) ) {
3267
			wp_send_json_error( 'missing_fields' );
3268
			exit;
3269
		}
3270
3271
		if ( ! wp_verify_nonce( $_POST['wc_shipping_classes_nonce'], 'wc_shipping_classes_nonce' ) ) {
3272
			wp_send_json_error( 'bad_nonce' );
3273
			exit;
3274
		}
3275
3276
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
3277
			wp_send_json_error( 'missing_capabilities' );
3278
			exit;
3279
		}
3280
3281
		$changes = $_POST['changes'];
3282
3283
		foreach ( $changes as $term_id => $data ) {
3284
			$term_id = absint( $term_id );
3285
3286
			if ( isset( $data['deleted'] ) ) {
3287
				if ( isset( $data['newRow'] ) ) {
3288
					// So the user added and deleted a new row.
3289
					// That's fine, it's not in the database anyways. NEXT!
3290
					continue;
3291
				}
3292
				wp_delete_term( $term_id, 'product_shipping_class' );
3293
				continue;
3294
			}
3295
3296
			$update_args = array();
3297
3298
			if ( isset( $data['name'] ) ) {
3299
				$update_args['name'] = wc_clean( $data['name'] );
3300
			}
3301
3302
			if ( isset( $data['slug'] ) ) {
3303
				$update_args['slug'] = wc_clean( $data['slug'] );
3304
			}
3305
3306
			if ( isset( $data['description'] ) ) {
3307
				$update_args['description'] = wc_clean( $data['description'] );
3308
			}
3309
3310
			if ( isset( $data['newRow'] ) ) {
3311
				$update_args = array_filter( $update_args );
3312
				if ( empty( $update_args['name'] ) ) {
3313
					continue;
3314
				}
3315
				$term_id = wp_insert_term( $update_args['name'], 'product_shipping_class', $update_args );
3316
			} else {
3317
				wp_update_term( $term_id, 'product_shipping_class', $update_args );
3318
			}
3319
3320
			do_action( 'woocommerce_shipping_classes_save_class', $term_id, $data );
3321
		}
3322
3323
		$wc_shipping = WC_Shipping::instance();
3324
3325
		wp_send_json_success( array(
3326
			'shipping_classes' => $wc_shipping->get_shipping_classes()
3327
		) );
3328
	}
3329
}
3330
3331
WC_AJAX::init();
3332