Completed
Push — master ( 09e43c...a8e479 )
by Mike
11:28
created

WC_AJAX::product_ordering()   D

Complexity

Conditions 14
Paths 14

Size

Total Lines 84
Code Lines 48

Duplication

Lines 26
Ratio 30.95 %

Importance

Changes 0
Metric Value
cc 14
dl 26
loc 84
rs 4.9516
c 0
b 0
f 0
eloc 48
nc 14
nop 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
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( apply_filters( 'woocommerce_ajax_get_endpoint', add_query_arg( 'wc-ajax', $request, remove_query_arg( array( 'remove_item', 'add-to-cart', 'added-to-cart' ) ) ), $request ) );
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 );
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_billing_country( $_POST['country'] );
314
		}
315
316
		if ( isset( $_POST['state'] ) ) {
317
			WC()->customer->set_billing_state( $_POST['state'] );
318
		}
319
320
		if ( isset( $_POST['postcode'] ) ) {
321
			WC()->customer->set_billing_postcode( $_POST['postcode'] );
322
		}
323
324
		if ( isset( $_POST['city'] ) ) {
325
			WC()->customer->set_billing_city( $_POST['city'] );
326
		}
327
328
		if ( isset( $_POST['address'] ) ) {
329
			WC()->customer->set_billing_address( $_POST['address'] );
330
		}
331
332
		if ( isset( $_POST['address_2'] ) ) {
333
			WC()->customer->set_billing_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->set_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->set_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()->customer->save();
391
		WC()->cart->calculate_totals();
392
393
		// Get order review fragment
394
		ob_start();
395
		woocommerce_order_review();
396
		$woocommerce_order_review = ob_get_clean();
397
398
		// Get checkout payment fragment
399
		ob_start();
400
		woocommerce_checkout_payment();
401
		$woocommerce_checkout_payment = ob_get_clean();
402
403
		// Get messages if reload checkout is not true
404
		$messages = '';
405
		if ( ! isset( WC()->session->reload_checkout ) ) {
406
			ob_start();
407
			wc_print_notices();
408
			$messages = ob_get_clean();
409
		}
410
411
		$data = array(
412
			'result'    => empty( $messages ) ? 'success' : 'failure',
413
			'messages'  => $messages,
414
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
415
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
416
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
417
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
418
			) )
419
		);
420
421
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
422
423
		wp_send_json( $data );
424
425
		die();
426
	}
427
428
	/**
429
	 * AJAX add to cart.
430
	 */
431
	public static function add_to_cart() {
432
		ob_start();
433
434
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
435
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
436
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
437
		$product_status    = get_post_status( $product_id );
438
439
		if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) && 'publish' === $product_status ) {
440
441
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
442
443
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
444
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
445
			}
446
447
			// Return fragments
448
			self::get_refreshed_fragments();
449
450
		} else {
451
452
			// If there was an error adding to the cart, redirect to the product page to show any errors
453
			$data = array(
454
				'error'       => true,
455
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
456
			);
457
458
			wp_send_json( $data );
459
460
		}
461
462
		die();
463
	}
464
465
	/**
466
	 * Process ajax checkout form.
467
	 */
468
	public static function checkout() {
469
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
470
			define( 'WOOCOMMERCE_CHECKOUT', true );
471
		}
472
473
		WC()->checkout()->process_checkout();
474
475
		die(0);
476
	}
477
478
	/**
479
	 * Get a matching variation based on posted attributes.
480
	 */
481
	public static function get_variation() {
482
		ob_start();
483
484
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
485
			die();
486
		}
487
488
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
489
490
		if ( $variation_id ) {
491
			$variation = $variable_product->get_available_variation( $variation_id );
492
		} else {
493
			$variation = false;
494
		}
495
496
		wp_send_json( $variation );
497
498
		die();
499
	}
500
501
	/**
502
	 * Feature a product from admin.
503
	 */
504
	public static function feature_product() {
505
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
506
			$product_id = absint( $_GET['product_id'] );
507
508
			if ( 'product' === get_post_type( $product_id ) ) {
509
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
510
511
				delete_transient( 'wc_featured_products' );
512
			}
513
		}
514
515
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=product' ) );
516
		die();
517
	}
518
519
	/**
520
	 * Mark an order with a status.
521
	 */
522
	public static function mark_order_status() {
523
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
524
			$status   = sanitize_text_field( $_GET['status'] );
525
			$order_id = absint( $_GET['order_id'] );
526
527
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
528
				$order = wc_get_order( $order_id );
529
				$order->update_status( $status, '', true );
530
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
531
			}
532
		}
533
534
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
535
		die();
536
	}
537
538
	/**
539
	 * Add an attribute row.
540
	 */
541
	public static function add_attribute() {
542
		ob_start();
543
544
		check_ajax_referer( 'add-attribute', 'security' );
545
546
		if ( ! current_user_can( 'edit_products' ) ) {
547
			die(-1);
548
		}
549
550
		global $wc_product_attributes;
551
552
		$thepostid     = 0;
553
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
554
		$i             = absint( $_POST['i'] );
555
		$position      = 0;
556
		$metabox_class = array();
557
		$attribute     = array(
558
			'name'         => $taxonomy,
559
			'value'        => '',
560
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
561
			'is_variation' => apply_filters( 'woocommerce_attribute_default_is_variation', 0 ),
562
			'is_taxonomy'  => $taxonomy ? 1 : 0
563
		);
564
565
		if ( $taxonomy ) {
566
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
567
			$metabox_class[]    = 'taxonomy';
568
			$metabox_class[]    = $taxonomy;
569
			$attribute_label    = wc_attribute_label( $taxonomy );
570
		} else {
571
			$attribute_label = '';
572
		}
573
574
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
575
		die();
576
	}
577
578
	/**
579
	 * Add a new attribute via ajax function.
580
	 */
581
	public static function add_new_attribute() {
582
		ob_start();
583
584
		check_ajax_referer( 'add-attribute', 'security' );
585
586
		if ( ! current_user_can( 'manage_product_terms' ) ) {
587
			die(-1);
588
		}
589
590
		$taxonomy = esc_attr( $_POST['taxonomy'] );
591
		$term     = wc_clean( $_POST['term'] );
592
593
		if ( taxonomy_exists( $taxonomy ) ) {
594
595
			$result = wp_insert_term( $term, $taxonomy );
596
597
			if ( is_wp_error( $result ) ) {
598
				wp_send_json( array(
599
					'error' => $result->get_error_message()
600
				) );
601
			} else {
602
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
603
				wp_send_json( array(
604
					'term_id' => $term->term_id,
605
					'name'    => $term->name,
606
					'slug'    => $term->slug
607
				) );
608
			}
609
		}
610
611
		die();
612
	}
613
614
	/**
615
	 * Delete variations via ajax function.
616
	 */
617
	public static function remove_variations() {
618
		check_ajax_referer( 'delete-variations', 'security' );
619
620
		if ( ! current_user_can( 'edit_products' ) ) {
621
			die(-1);
622
		}
623
624
		$variation_ids = (array) $_POST['variation_ids'];
625
626
		foreach ( $variation_ids as $variation_id ) {
627
			$variation = get_post( $variation_id );
628
629
			if ( $variation && 'product_variation' == $variation->post_type ) {
630
				wp_delete_post( $variation_id );
631
			}
632
		}
633
634
		die();
635
	}
636
637
	/**
638
	 * Save attributes via ajax.
639
	 */
640
	public static function save_attributes() {
641
642
		check_ajax_referer( 'save-attributes', 'security' );
643
644
		if ( ! current_user_can( 'edit_products' ) ) {
645
			die(-1);
646
		}
647
648
		// Get post data
649
		parse_str( $_POST['data'], $data );
650
		$post_id = absint( $_POST['post_id'] );
651
652
		// Save Attributes
653
		$attributes = array();
654
655
		if ( isset( $data['attribute_names'] ) ) {
656
657
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
658
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
659
660
			if ( isset( $data['attribute_visibility'] ) ) {
661
				$attribute_visibility = $data['attribute_visibility'];
662
			}
663
664
			if ( isset( $data['attribute_variation'] ) ) {
665
				$attribute_variation = $data['attribute_variation'];
666
			}
667
668
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
669
			$attribute_position      = $data['attribute_position'];
670
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
671
672
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
673
				if ( empty( $attribute_names[ $i ] ) ) {
674
					continue;
675
				}
676
677
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
678
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
679
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
680
681
				if ( $is_taxonomy ) {
682
683
					if ( isset( $attribute_values[ $i ] ) ) {
684
685
						// Select based attributes - Format values (posted values are slugs)
686
						if ( is_array( $attribute_values[ $i ] ) ) {
687
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
688
689
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
690
						} else {
691
							$values     = array();
692
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
693
694
							foreach ( $raw_values as $value ) {
695
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
696
								if ( ! $term ) {
697
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
698
699
									if ( $term && ! is_wp_error( $term ) ) {
700
										$values[] = $term['term_id'];
701
									}
702
								} else {
703
									$values[] = $term->term_id;
704
								}
705
							}
706
						}
707
708
						// Remove empty items in the array
709
						$values = array_filter( $values, 'strlen' );
710
711
					} else {
712
						$values = array();
713
					}
714
715
					// Update post terms
716
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
717
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
718
					}
719
720 View Code Duplication
					if ( ! empty( $values ) ) {
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...
721
						// Add attribute to array, but don't set values
722
						$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
723
							'name' 			=> wc_clean( $attribute_names[ $i ] ),
724
							'value' 		=> '',
725
							'position' 		=> $attribute_position[ $i ],
726
							'is_visible' 	=> $is_visible,
727
							'is_variation' 	=> $is_variation,
728
							'is_taxonomy' 	=> $is_taxonomy
729
						);
730
					}
731
732 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...
733
734
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
735
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
736
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
737
738
					// Custom attribute - Add attribute to array and set the values
739
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
740
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
741
						'value' 		=> $values,
742
						'position' 		=> $attribute_position[ $i ],
743
						'is_visible' 	=> $is_visible,
744
						'is_variation' 	=> $is_variation,
745
						'is_taxonomy' 	=> $is_taxonomy
746
					);
747
				}
748
749
			 }
750
		}
751
752
		uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
753
754
		update_post_meta( $post_id, '_product_attributes', $attributes );
755
756
		die();
757
	}
758
759
	/**
760
	 * Add variation via ajax function.
761
	 */
762
	public static function add_variation() {
763
764
		check_ajax_referer( 'add-variation', 'security' );
765
766
		if ( ! current_user_can( 'edit_products' ) ) {
767
			die(-1);
768
		}
769
770
		global $post;
771
772
		$post_id = intval( $_POST['post_id'] );
773
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
774
		$loop    = intval( $_POST['loop'] );
775
776
		$variation = array(
777
			'post_title'   => 'Product #' . $post_id . ' Variation',
778
			'post_content' => '',
779
			'post_status'  => 'publish',
780
			'post_author'  => get_current_user_id(),
781
			'post_parent'  => $post_id,
782
			'post_type'    => 'product_variation',
783
			'menu_order'   => -1
784
		);
785
786
		$variation_id = wp_insert_post( $variation );
787
788
		do_action( 'woocommerce_create_product_variation', $variation_id );
789
790
		if ( $variation_id ) {
791
			$variation        = get_post( $variation_id );
792
			$variation_meta   = get_post_meta( $variation_id );
793
			$variation_data   = array();
794
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
795
			$variation_fields = array(
796
				'_sku'                   => '',
797
				'_stock'                 => '',
798
				'_regular_price'         => '',
799
				'_sale_price'            => '',
800
				'_weight'                => '',
801
				'_length'                => '',
802
				'_width'                 => '',
803
				'_height'                => '',
804
				'_download_limit'        => '',
805
				'_download_expiry'       => '',
806
				'_downloadable_files'    => '',
807
				'_downloadable'          => '',
808
				'_virtual'               => '',
809
				'_thumbnail_id'          => '',
810
				'_sale_price_dates_from' => '',
811
				'_sale_price_dates_to'   => '',
812
				'_manage_stock'          => '',
813
				'_stock_status'          => '',
814
				'_backorders'            => null,
815
				'_tax_class'             => null,
816
				'_variation_description' => ''
817
			);
818
819 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...
820
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
821
			}
822
823
			// Add the variation attributes
824
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
825
826
			// Formatting
827
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
828
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
829
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
830
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
831
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
832
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
833
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
834
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
835
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
836
			$variation_data['menu_order']     = $variation->menu_order;
837
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
838
839
			// Get tax classes
840
			$tax_classes           = WC_Tax::get_tax_classes();
841
			$tax_class_options     = array();
842
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
843
844 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...
845
				foreach ( $tax_classes as $class ) {
846
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
847
				}
848
			}
849
850
			// Set backorder options
851
			$backorder_options = array(
852
				'no'     => __( 'Do not allow', 'woocommerce' ),
853
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
854
				'yes'    => __( 'Allow', 'woocommerce' )
855
			);
856
857
			// set stock status options
858
			$stock_status_options = array(
859
				'instock'    => __( 'In stock', 'woocommerce' ),
860
				'outofstock' => __( 'Out of stock', 'woocommerce' )
861
			);
862
863
			// Get attributes
864
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
865
866
			$parent_data = array(
867
				'id'                   => $post_id,
868
				'attributes'           => $attributes,
869
				'tax_class_options'    => $tax_class_options,
870
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
871
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
872
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
873
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
874
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
875
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
876
				'backorder_options'    => $backorder_options,
877
				'stock_status_options' => $stock_status_options
878
			);
879
880
			if ( ! $parent_data['weight'] ) {
881
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
882
			}
883
884
			if ( ! $parent_data['length'] ) {
885
				$parent_data['length'] = wc_format_localized_decimal( 0 );
886
			}
887
888
			if ( ! $parent_data['width'] ) {
889
				$parent_data['width'] = wc_format_localized_decimal( 0 );
890
			}
891
892
			if ( ! $parent_data['height'] ) {
893
				$parent_data['height'] = wc_format_localized_decimal( 0 );
894
			}
895
896
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
897
		}
898
899
		die();
900
	}
901
902
	/**
903
	 * Link all variations via ajax function.
904
	 */
905
	public static function link_all_variations() {
906
907
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
908
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
909
		}
910
911
		check_ajax_referer( 'link-variations', 'security' );
912
913
		if ( ! current_user_can( 'edit_products' ) ) {
914
			die(-1);
915
		}
916
917
		wc_set_time_limit( 0 );
918
919
		$post_id = intval( $_POST['post_id'] );
920
921
		if ( ! $post_id ) {
922
			die();
923
		}
924
925
		$variations = array();
926
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
927
928
		// Put variation attributes into an array
929
		foreach ( $_product->get_attributes() as $attribute ) {
930
931
			if ( ! $attribute['is_variation'] ) {
932
				continue;
933
			}
934
935
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
936
937
			if ( $attribute['is_taxonomy'] ) {
938
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
939
			} else {
940
				$options = explode( WC_DELIMITER, $attribute['value'] );
941
			}
942
943
			$options = array_map( 'trim', $options );
944
945
			$variations[ $attribute_field_name ] = $options;
946
		}
947
948
		// Quit out if none were found
949
		if ( sizeof( $variations ) == 0 ) {
950
			die();
951
		}
952
953
		// Get existing variations so we don't create duplicates
954
		$available_variations = array();
955
956
		foreach( $_product->get_children() as $child_id ) {
957
			$child = $_product->get_child( $child_id );
958
959
			if ( ! empty( $child->variation_id ) ) {
960
				$available_variations[] = $child->get_variation_attributes();
961
			}
962
		}
963
964
		// Created posts will all have the following data
965
		$variation_post_data = array(
966
			'post_title'   => 'Product #' . $post_id . ' Variation',
967
			'post_content' => '',
968
			'post_status'  => 'publish',
969
			'post_author'  => get_current_user_id(),
970
			'post_parent'  => $post_id,
971
			'post_type'    => 'product_variation'
972
		);
973
974
		$variation_ids       = array();
975
		$added               = 0;
976
		$possible_variations = wc_array_cartesian( $variations );
977
978
		foreach ( $possible_variations as $variation ) {
979
980
			// Check if variation already exists
981
			if ( in_array( $variation, $available_variations ) ) {
982
				continue;
983
			}
984
985
			$variation_id = wp_insert_post( $variation_post_data );
986
987
			$variation_ids[] = $variation_id;
988
989
			foreach ( $variation as $key => $value ) {
990
				update_post_meta( $variation_id, $key, $value );
991
			}
992
993
			// Save stock status
994
			update_post_meta( $variation_id, '_stock_status', 'instock' );
995
996
			$added++;
997
998
			do_action( 'product_variation_linked', $variation_id );
999
1000
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
1001
				break;
1002
			}
1003
		}
1004
1005
		delete_transient( 'wc_product_children_' . $post_id );
1006
1007
		echo $added;
1008
1009
		die();
1010
	}
1011
1012
	/**
1013
	 * Delete download permissions via ajax function.
1014
	 */
1015
	public static function revoke_access_to_download() {
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
		$permission_id = absint( $_POST['permission_id'] );
1028
1029
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d;", $permission_id ) );
1030
1031
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id, $permission_id );
1032
1033
		die();
1034
	}
1035
1036
	/**
1037
	 * Grant download permissions via ajax function.
1038
	 */
1039
	public static function grant_access_to_download() {
1040
1041
		check_ajax_referer( 'grant-access', 'security' );
1042
1043
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1044
			die(-1);
1045
		}
1046
1047
		global $wpdb;
1048
1049
		$wpdb->hide_errors();
1050
1051
		$order_id     = intval( $_POST['order_id'] );
1052
		$product_ids  = $_POST['product_ids'];
1053
		$loop         = intval( $_POST['loop'] );
1054
		$file_counter = 0;
1055
		$order        = wc_get_order( $order_id );
1056
1057
		if ( ! is_array( $product_ids ) ) {
1058
			$product_ids = array( $product_ids );
1059
		}
1060
1061
		foreach ( $product_ids as $product_id ) {
1062
			$product = wc_get_product( $product_id );
1063
			$files   = $product->get_files();
1064
1065
			if ( ! $order->get_billing_email() ) {
1066
				die();
1067
			}
1068
1069
			if ( ! empty( $files ) ) {
1070
				foreach ( $files as $download_id => $file ) {
1071
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
0 ignored issues
show
Documentation introduced by
$order is of type false|object, but the function expects a object<WC_Order>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1072
1073
						// insert complete - get inserted data
1074
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1075
1076
						$loop ++;
1077
						$file_counter ++;
1078
1079 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...
1080
							$file_count = $file['name'];
1081
						} else {
1082
							$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
1083
						}
1084
						include( 'admin/meta-boxes/views/html-order-download-permission.php' );
1085
					}
1086
				}
1087
			}
1088
		}
1089
1090
		die();
1091
	}
1092
1093
	/**
1094
	 * Get customer details via ajax.
1095
	 */
1096
	public static function get_customer_details() {
1097
		ob_start();
1098
1099
		check_ajax_referer( 'get-customer-details', 'security' );
1100
1101
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1102
			die(-1);
1103
		}
1104
1105
		$user_id      = (int) trim(stripslashes($_POST['user_id']));
1106
		$type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
1107
1108
		$customer_data = array(
1109
			$type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
1110
			$type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
1111
			$type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
1112
			$type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
1113
			$type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
1114
			$type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
1115
			$type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
1116
			$type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
1117
			$type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
1118
			$type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
1119
			$type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
1120
		);
1121
1122
		$customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
1123
1124
		wp_send_json( $customer_data );
1125
	}
1126
1127
	/**
1128
	 * Add order item via ajax.
1129
	 */
1130
	public static function add_order_item() {
1131
		check_ajax_referer( 'order-item', 'security' );
1132
1133
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1134
			die(-1);
1135
		}
1136
1137
		$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
1138
		$order_id    = absint( $_POST['order_id'] );
1139
1140
		// Find the item
1141
		if ( ! is_numeric( $item_to_add ) ) {
1142
			die();
1143
		}
1144
1145
		$post = get_post( $item_to_add );
1146
1147
		if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
1148
			die();
1149
		}
1150
1151
		$product     = wc_get_product( $post->ID );
1152
		$order       = wc_get_order( $order_id );
1153
		$order_taxes = $order->get_taxes();
1154
		$class       = 'new_row';
1155
		$item        = new WC_Order_Item_Product( array(
0 ignored issues
show
Documentation introduced by
array('product' => $prod...d' => $order->get_id()) is of type array<string,?,{"product...duct>","order_id":"?"}>, but the function expects a integer.

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...
1156
			'product'  => $product,
1157
			'order_id' => $order->get_id(),
1158
		) );
1159
		$item_id = $item->save();
1160
		$item    = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
1161
1162
		do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
1163
1164
		include( 'admin/meta-boxes/views/html-order-item.php' );
1165
		die();
1166
	}
1167
1168
	/**
1169
	 * Add order fee via ajax.
1170
	 */
1171
	public static function add_order_fee() {
1172
1173
		check_ajax_referer( 'order-item', 'security' );
1174
1175
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1176
			die(-1);
1177
		}
1178
1179
		$order_id    = absint( $_POST['order_id'] );
1180
		$order       = wc_get_order( $order_id );
1181
		$order_taxes = $order->get_taxes();
1182
		$item        = new WC_Order_Item_Fee();
1183
		$item->set_order_id( $order_id );
1184
		$item_id     = $item->save();
1185
1186
		include( 'admin/meta-boxes/views/html-order-fee.php' );
1187
1188
		// Quit out
1189
		die();
1190
	}
1191
1192
	/**
1193
	 * Add order shipping cost via ajax.
1194
	 */
1195
	public static function add_order_shipping() {
1196
1197
		check_ajax_referer( 'order-item', 'security' );
1198
1199
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1200
			die(-1);
1201
		}
1202
1203
		$order_id         = absint( $_POST['order_id'] );
1204
		$order            = wc_get_order( $order_id );
1205
		$order_taxes      = $order->get_taxes();
1206
		$shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array();
1207
1208
		// Add new shipping
1209
		$item = new WC_Order_Item_Shipping();
1210
		$item->set_shipping_rate( new WC_Shipping_Rate() );
1211
		$order->add_item( $item );
1212
1213
		include( 'admin/meta-boxes/views/html-order-shipping.php' );
1214
1215
		// Quit out
1216
		die();
1217
	}
1218
1219
	/**
1220
	 * Add order tax column via ajax.
1221
	 */
1222
	public static function add_order_tax() {
1223
		global $wpdb;
1224
1225
		check_ajax_referer( 'order-item', 'security' );
1226
1227
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1228
			die(-1);
1229
		}
1230
1231
		$order_id = absint( $_POST['order_id'] );
1232
		$rate_id  = absint( $_POST['rate_id'] );
1233
		$order    = wc_get_order( $order_id );
1234
		$data     = get_post_meta( $order_id );
1235
1236
		// Add new tax
1237
		$item = new WC_Order_Item_Tax();
1238
		$item->set_rate( $rate_id );
1239
		$item->set_order_id( $order_id );
1240
		$item->save();
1241
1242
		// Return HTML items
1243
		include( 'admin/meta-boxes/views/html-order-items.php' );
1244
1245
		die();
1246
	}
1247
1248
	/**
1249
	 * Remove an order item.
1250
	 */
1251
	public static function remove_order_item() {
1252
		check_ajax_referer( 'order-item', 'security' );
1253
1254
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1255
			die(-1);
1256
		}
1257
1258
		$order_item_ids = $_POST['order_item_ids'];
1259
1260
		if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
1261
			$order_item_ids = array( $order_item_ids );
1262
		}
1263
1264
		if ( sizeof( $order_item_ids ) > 0 ) {
1265
			foreach( $order_item_ids as $id ) {
1266
				wc_delete_order_item( absint( $id ) );
1267
			}
1268
		}
1269
1270
		die();
1271
	}
1272
1273
	/**
1274
	 * Remove an order tax.
1275
	 */
1276
	public static function remove_order_tax() {
1277
1278
		check_ajax_referer( 'order-item', 'security' );
1279
1280
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1281
			die(-1);
1282
		}
1283
1284
		$order_id = absint( $_POST['order_id'] );
1285
		$rate_id  = absint( $_POST['rate_id'] );
1286
1287
		wc_delete_order_item( $rate_id );
1288
1289
		// Return HTML items
1290
		$order = wc_get_order( $order_id );
1291
		$data  = get_post_meta( $order_id );
1292
		include( 'admin/meta-boxes/views/html-order-items.php' );
1293
1294
		die();
1295
	}
1296
1297
	/**
1298
	 * Reduce order item stock.
1299
	 */
1300 View Code Duplication
	public static function reduce_order_item_stock() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1301
		check_ajax_referer( 'order-item', 'security' );
1302
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1303
			die(-1);
1304
		}
1305
		$order_id       = absint( $_POST['order_id'] );
1306
		$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1307
		$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1308
		$order          = wc_get_order( $order_id );
1309
		$order_items    = $order->get_items();
1310
		$return         = array();
1311
		if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1312
			foreach ( $order_items as $item_id => $order_item ) {
1313
				// Only reduce checked items
1314
				if ( ! in_array( $item_id, $order_item_ids ) ) {
1315
					continue;
1316
				}
1317
				$_product = $order_item->get_product();
1318
				if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1319
					$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1320
					$new_stock    = $_product->reduce_stock( $stock_change );
1321
					$item_name    = $_product->get_sku() ? $_product->get_sku() : $_product->id;
1322
1323
					if ( ! empty( $_product->variation_id ) ) {
1324
						$note = sprintf( __( 'Item %s variation #%s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $_product->variation_id, $new_stock + $stock_change, $new_stock );
1325
					} else {
1326
						$note = sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
1327
					}
1328
1329
					$return[]     = $note;
1330
					$order->add_order_note( $note );
1331
				}
1332
			}
1333
			do_action( 'woocommerce_reduce_order_stock', $order );
1334
			if ( empty( $return ) ) {
1335
				$return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1336
			}
1337
			echo implode( ', ', $return );
1338
		}
1339
		die();
1340
	}
1341
1342
	/**
1343
	 * Increase order item stock.
1344
	 */
1345 View Code Duplication
	public static function increase_order_item_stock() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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