Completed
Pull Request — master (#11821)
by Mike
14:32
created

WC_AJAX::get_cart_totals()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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