Completed
Pull Request — master (#11762)
by Mike
13:26
created

WC_AJAX::checkout()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
eloc 5
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
312
		if ( isset( $_POST['country'] ) ) {
313
			WC()->customer->set_billing_country( $_POST['country'] );
314
		}
315
316
		if ( isset( $_POST['state'] ) ) {
317
			WC()->customer->set_billing_state( $_POST['state'] );
318
		}
319
320
		if ( isset( $_POST['postcode'] ) ) {
321
			WC()->customer->set_billing_postcode( $_POST['postcode'] );
322
		}
323
324
		if ( isset( $_POST['city'] ) ) {
325
			WC()->customer->set_billing_city( $_POST['city'] );
326
		}
327
328
		if ( isset( $_POST['address'] ) ) {
329
			WC()->customer->set_billing_address( $_POST['address'] );
330
		}
331
332
		if ( isset( $_POST['address_2'] ) ) {
333
			WC()->customer->set_billing_address_2( $_POST['address_2'] );
334
		}
335
336
		if ( wc_ship_to_billing_address_only() ) {
337
338 View Code Duplication
			if ( ! empty( $_POST['country'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

Loading history...
365
				WC()->customer->set_shipping_country( $_POST['s_country'] );
366
				WC()->customer->set_calculated_shipping( true );
367
			}
368
369
			if ( isset( $_POST['s_state'] ) ) {
370
				WC()->customer->set_shipping_state( $_POST['s_state'] );
371
			}
372
373
			if ( isset( $_POST['s_postcode'] ) ) {
374
				WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
375
			}
376
377
			if ( isset( $_POST['s_city'] ) ) {
378
				WC()->customer->set_shipping_city( $_POST['s_city'] );
379
			}
380
381
			if ( isset( $_POST['s_address'] ) ) {
382
				WC()->customer->set_shipping_address( $_POST['s_address'] );
383
			}
384
385
			if ( isset( $_POST['s_address_2'] ) ) {
386
				WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
387
			}
388
		}
389
390
		WC()->customer->save();
391
		WC()->cart->calculate_totals();
392
393
		// Get order review fragment
394
		ob_start();
395
		woocommerce_order_review();
396
		$woocommerce_order_review = ob_get_clean();
397
398
		// Get checkout payment fragment
399
		ob_start();
400
		woocommerce_checkout_payment();
401
		$woocommerce_checkout_payment = ob_get_clean();
402
403
		// Get messages if reload checkout is not true
404
		$messages = '';
405
		if ( ! isset( WC()->session->reload_checkout ) ) {
406
			ob_start();
407
			wc_print_notices();
408
			$messages = ob_get_clean();
409
		}
410
411
		$data = array(
412
			'result'    => empty( $messages ) ? 'success' : 'failure',
413
			'messages'  => $messages,
414
			'reload'    => isset( WC()->session->reload_checkout ) ? 'true' : 'false',
415
			'fragments' => apply_filters( 'woocommerce_update_order_review_fragments', array(
416
				'.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
417
				'.woocommerce-checkout-payment'            => $woocommerce_checkout_payment
418
			) )
419
		);
420
421
		unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
422
423
		wp_send_json( $data );
424
425
		die();
426
	}
427
428
	/**
429
	 * AJAX add to cart.
430
	 */
431
	public static function add_to_cart() {
432
		ob_start();
433
434
		$product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
435
		$quantity          = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
436
		$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
437
		$product_status    = get_post_status( $product_id );
438
439
		if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) && 'publish' === $product_status ) {
440
441
			do_action( 'woocommerce_ajax_added_to_cart', $product_id );
442
443
			if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
444
				wc_add_to_cart_message( array( $product_id => $quantity ), true );
445
			}
446
447
			// Return fragments
448
			self::get_refreshed_fragments();
449
450
		} else {
451
452
			// If there was an error adding to the cart, redirect to the product page to show any errors
453
			$data = array(
454
				'error'       => true,
455
				'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
456
			);
457
458
			wp_send_json( $data );
459
460
		}
461
462
		die();
463
	}
464
465
	/**
466
	 * Process ajax checkout form.
467
	 */
468
	public static function checkout() {
469
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
470
			define( 'WOOCOMMERCE_CHECKOUT', true );
471
		}
472
473
		WC()->checkout()->process_checkout();
474
475
		die(0);
476
	}
477
478
	/**
479
	 * Get a matching variation based on posted attributes.
480
	 */
481
	public static function get_variation() {
482
		ob_start();
483
484
		if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ), array( 'product_type' => 'variable' ) ) ) ) {
485
			die();
486
		}
487
488
		$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) );
489
490
		if ( $variation_id ) {
491
			$variation = $variable_product->get_available_variation( $variation_id );
492
		} else {
493
			$variation = false;
494
		}
495
496
		wp_send_json( $variation );
497
498
		die();
499
	}
500
501
	/**
502
	 * Feature a product from admin.
503
	 */
504
	public static function feature_product() {
505
		if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) {
506
			$product_id = absint( $_GET['product_id'] );
507
508
			if ( 'product' === get_post_type( $product_id ) ) {
509
				update_post_meta( $product_id, '_featured', get_post_meta( $product_id, '_featured', true ) === 'yes' ? 'no' : 'yes' );
510
511
				delete_transient( 'wc_featured_products' );
512
			}
513
		}
514
515
		wp_safe_redirect( wp_get_referer() ? remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) : admin_url( 'edit.php?post_type=product' ) );
516
		die();
517
	}
518
519
	/**
520
	 * Mark an order with a status.
521
	 */
522
	public static function mark_order_status() {
523
		if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) {
524
			$status   = sanitize_text_field( $_GET['status'] );
525
			$order_id = absint( $_GET['order_id'] );
526
527
			if ( wc_is_order_status( 'wc-' . $status ) && $order_id ) {
528
				$order = wc_get_order( $order_id );
529
				$order->update_status( $status, '', true );
530
				do_action( 'woocommerce_order_edit_status', $order_id, $status );
531
			}
532
		}
533
534
		wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=shop_order' ) );
535
		die();
536
	}
537
538
	/**
539
	 * Add an attribute row.
540
	 */
541
	public static function add_attribute() {
542
		ob_start();
543
544
		check_ajax_referer( 'add-attribute', 'security' );
545
546
		if ( ! current_user_can( 'edit_products' ) ) {
547
			die(-1);
548
		}
549
550
		global $wc_product_attributes;
551
552
		$thepostid     = 0;
553
		$taxonomy      = sanitize_text_field( $_POST['taxonomy'] );
554
		$i             = absint( $_POST['i'] );
555
		$position      = 0;
556
		$metabox_class = array();
557
		$attribute     = array(
558
			'name'         => $taxonomy,
559
			'value'        => '',
560
			'is_visible'   => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
561
			'is_variation' => apply_filters( 'woocommerce_attribute_default_is_variation', 0 ),
562
			'is_taxonomy'  => $taxonomy ? 1 : 0
563
		);
564
565
		if ( $taxonomy ) {
566
			$attribute_taxonomy = $wc_product_attributes[ $taxonomy ];
567
			$metabox_class[]    = 'taxonomy';
568
			$metabox_class[]    = $taxonomy;
569
			$attribute_label    = wc_attribute_label( $taxonomy );
570
		} else {
571
			$attribute_label = '';
572
		}
573
574
		include( 'admin/meta-boxes/views/html-product-attribute.php' );
575
		die();
576
	}
577
578
	/**
579
	 * Add a new attribute via ajax function.
580
	 */
581
	public static function add_new_attribute() {
582
		ob_start();
583
584
		check_ajax_referer( 'add-attribute', 'security' );
585
586
		if ( ! current_user_can( 'manage_product_terms' ) ) {
587
			die(-1);
588
		}
589
590
		$taxonomy = esc_attr( $_POST['taxonomy'] );
591
		$term     = wc_clean( $_POST['term'] );
592
593
		if ( taxonomy_exists( $taxonomy ) ) {
594
595
			$result = wp_insert_term( $term, $taxonomy );
596
597
			if ( is_wp_error( $result ) ) {
598
				wp_send_json( array(
599
					'error' => $result->get_error_message()
600
				) );
601
			} else {
602
				$term = get_term_by( 'id', $result['term_id'], $taxonomy );
603
				wp_send_json( array(
604
					'term_id' => $term->term_id,
605
					'name'    => $term->name,
606
					'slug'    => $term->slug
607
				) );
608
			}
609
		}
610
611
		die();
612
	}
613
614
	/**
615
	 * Delete variations via ajax function.
616
	 */
617
	public static function remove_variations() {
618
		check_ajax_referer( 'delete-variations', 'security' );
619
620
		if ( ! current_user_can( 'edit_products' ) ) {
621
			die(-1);
622
		}
623
624
		$variation_ids = (array) $_POST['variation_ids'];
625
626
		foreach ( $variation_ids as $variation_id ) {
627
			$variation = get_post( $variation_id );
628
629
			if ( $variation && 'product_variation' == $variation->post_type ) {
630
				wp_delete_post( $variation_id );
631
			}
632
		}
633
634
		die();
635
	}
636
637
	/**
638
	 * Save attributes via ajax.
639
	 */
640
	public static function save_attributes() {
641
642
		check_ajax_referer( 'save-attributes', 'security' );
643
644
		if ( ! current_user_can( 'edit_products' ) ) {
645
			die(-1);
646
		}
647
648
		// Get post data
649
		parse_str( $_POST['data'], $data );
650
		$post_id = absint( $_POST['post_id'] );
651
652
		// Save Attributes
653
		$attributes = array();
654
655
		if ( isset( $data['attribute_names'] ) ) {
656
657
			$attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
658
			$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
659
660
			if ( isset( $data['attribute_visibility'] ) ) {
661
				$attribute_visibility = $data['attribute_visibility'];
662
			}
663
664
			if ( isset( $data['attribute_variation'] ) ) {
665
				$attribute_variation = $data['attribute_variation'];
666
			}
667
668
			$attribute_is_taxonomy   = $data['attribute_is_taxonomy'];
669
			$attribute_position      = $data['attribute_position'];
670
			$attribute_names_max_key = max( array_keys( $attribute_names ) );
671
672
			for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
673
				if ( empty( $attribute_names[ $i ] ) ) {
674
					continue;
675
				}
676
677
				$is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
678
				$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
679
				$is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
680
681
				if ( $is_taxonomy ) {
682
683
					if ( isset( $attribute_values[ $i ] ) ) {
684
685
						// Select based attributes - Format values (posted values are slugs)
686
						if ( is_array( $attribute_values[ $i ] ) ) {
687
							$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
688
689
						// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
690
						} else {
691
							$values     = array();
692
							$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
693
694
							foreach ( $raw_values as $value ) {
695
								$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
696
								if ( ! $term ) {
697
									$term = wp_insert_term( $value, $attribute_names[ $i ] );
698
699
									if ( $term && ! is_wp_error( $term ) ) {
700
										$values[] = $term['term_id'];
701
									}
702
								} else {
703
									$values[] = $term->term_id;
704
								}
705
							}
706
						}
707
708
						// Remove empty items in the array
709
						$values = array_filter( $values, 'strlen' );
710
711
					} else {
712
						$values = array();
713
					}
714
715
					// Update post terms
716
					if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
717
						wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
718
					}
719
720 View Code Duplication
					if ( ! empty( $values ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

Loading history...
733
734
					// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
735
					$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
736
					$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
737
738
					// Custom attribute - Add attribute to array and set the values
739
					$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
740
						'name' 			=> wc_clean( $attribute_names[ $i ] ),
741
						'value' 		=> $values,
742
						'position' 		=> $attribute_position[ $i ],
743
						'is_visible' 	=> $is_visible,
744
						'is_variation' 	=> $is_variation,
745
						'is_taxonomy' 	=> $is_taxonomy
746
					);
747
				}
748
749
			 }
750
		}
751
752
		uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
753
754
		update_post_meta( $post_id, '_product_attributes', $attributes );
755
756
		die();
757
	}
758
759
	/**
760
	 * Add variation via ajax function.
761
	 */
762
	public static function add_variation() {
763
764
		check_ajax_referer( 'add-variation', 'security' );
765
766
		if ( ! current_user_can( 'edit_products' ) ) {
767
			die(-1);
768
		}
769
770
		global $post;
771
772
		$post_id = intval( $_POST['post_id'] );
773
		$post    = get_post( $post_id ); // Set $post global so its available like within the admin screens
774
		$loop    = intval( $_POST['loop'] );
775
776
		$variation = array(
777
			'post_title'   => 'Product #' . $post_id . ' Variation',
778
			'post_content' => '',
779
			'post_status'  => 'publish',
780
			'post_author'  => get_current_user_id(),
781
			'post_parent'  => $post_id,
782
			'post_type'    => 'product_variation',
783
			'menu_order'   => -1
784
		);
785
786
		$variation_id = wp_insert_post( $variation );
787
788
		do_action( 'woocommerce_create_product_variation', $variation_id );
789
790
		if ( $variation_id ) {
791
			$variation        = get_post( $variation_id );
792
			$variation_meta   = get_post_meta( $variation_id );
793
			$variation_data   = array();
794
			$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
795
			$variation_fields = array(
796
				'_sku'                   => '',
797
				'_stock'                 => '',
798
				'_regular_price'         => '',
799
				'_sale_price'            => '',
800
				'_weight'                => '',
801
				'_length'                => '',
802
				'_width'                 => '',
803
				'_height'                => '',
804
				'_download_limit'        => '',
805
				'_download_expiry'       => '',
806
				'_downloadable_files'    => '',
807
				'_downloadable'          => '',
808
				'_virtual'               => '',
809
				'_thumbnail_id'          => '',
810
				'_sale_price_dates_from' => '',
811
				'_sale_price_dates_to'   => '',
812
				'_manage_stock'          => '',
813
				'_stock_status'          => '',
814
				'_backorders'            => null,
815
				'_tax_class'             => null,
816
				'_variation_description' => ''
817
			);
818
819 View Code Duplication
			foreach ( $variation_fields as $field => $value ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
820
				$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
821
			}
822
823
			// Add the variation attributes
824
			$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
825
826
			// Formatting
827
			$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
828
			$variation_data['_sale_price']    = wc_format_localized_price( $variation_data['_sale_price'] );
829
			$variation_data['_weight']        = wc_format_localized_decimal( $variation_data['_weight'] );
830
			$variation_data['_length']        = wc_format_localized_decimal( $variation_data['_length'] );
831
			$variation_data['_width']         = wc_format_localized_decimal( $variation_data['_width'] );
832
			$variation_data['_height']        = wc_format_localized_decimal( $variation_data['_height'] );
833
			$variation_data['_thumbnail_id']  = absint( $variation_data['_thumbnail_id'] );
834
			$variation_data['image']          = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
835
			$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
836
			$variation_data['menu_order']     = $variation->menu_order;
837
			$variation_data['_stock']         = wc_stock_amount( $variation_data['_stock'] );
838
839
			// Get tax classes
840
			$tax_classes           = WC_Tax::get_tax_classes();
841
			$tax_class_options     = array();
842
			$tax_class_options[''] = __( 'Standard', 'woocommerce' );
843
844 View Code Duplication
			if ( ! empty( $tax_classes ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
845
				foreach ( $tax_classes as $class ) {
846
					$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
847
				}
848
			}
849
850
			// Set backorder options
851
			$backorder_options = array(
852
				'no'     => __( 'Do not allow', 'woocommerce' ),
853
				'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
854
				'yes'    => __( 'Allow', 'woocommerce' )
855
			);
856
857
			// set stock status options
858
			$stock_status_options = array(
859
				'instock'    => __( 'In stock', 'woocommerce' ),
860
				'outofstock' => __( 'Out of stock', 'woocommerce' )
861
			);
862
863
			// Get attributes
864
			$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
865
866
			$parent_data = array(
867
				'id'                   => $post_id,
868
				'attributes'           => $attributes,
869
				'tax_class_options'    => $tax_class_options,
870
				'sku'                  => get_post_meta( $post_id, '_sku', true ),
871
				'weight'               => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
872
				'length'               => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
873
				'width'                => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
874
				'height'               => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
875
				'tax_class'            => get_post_meta( $post_id, '_tax_class', true ),
876
				'backorder_options'    => $backorder_options,
877
				'stock_status_options' => $stock_status_options
878
			);
879
880
			if ( ! $parent_data['weight'] ) {
881
				$parent_data['weight'] = wc_format_localized_decimal( 0 );
882
			}
883
884
			if ( ! $parent_data['length'] ) {
885
				$parent_data['length'] = wc_format_localized_decimal( 0 );
886
			}
887
888
			if ( ! $parent_data['width'] ) {
889
				$parent_data['width'] = wc_format_localized_decimal( 0 );
890
			}
891
892
			if ( ! $parent_data['height'] ) {
893
				$parent_data['height'] = wc_format_localized_decimal( 0 );
894
			}
895
896
			include( 'admin/meta-boxes/views/html-variation-admin.php' );
897
		}
898
899
		die();
900
	}
901
902
	/**
903
	 * Link all variations via ajax function.
904
	 */
905
	public static function link_all_variations() {
906
907
		if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
908
			define( 'WC_MAX_LINKED_VARIATIONS', 49 );
909
		}
910
911
		check_ajax_referer( 'link-variations', 'security' );
912
913
		if ( ! current_user_can( 'edit_products' ) ) {
914
			die(-1);
915
		}
916
917
		wc_set_time_limit( 0 );
918
919
		$post_id = intval( $_POST['post_id'] );
920
921
		if ( ! $post_id ) {
922
			die();
923
		}
924
925
		$variations = array();
926
		$_product   = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
927
928
		// Put variation attributes into an array
929
		foreach ( $_product->get_attributes() as $attribute ) {
930
931
			if ( ! $attribute['is_variation'] ) {
932
				continue;
933
			}
934
935
			$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
936
937
			if ( $attribute['is_taxonomy'] ) {
938
				$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
939
			} else {
940
				$options = explode( WC_DELIMITER, $attribute['value'] );
941
			}
942
943
			$options = array_map( 'trim', $options );
944
945
			$variations[ $attribute_field_name ] = $options;
946
		}
947
948
		// Quit out if none were found
949
		if ( sizeof( $variations ) == 0 ) {
950
			die();
951
		}
952
953
		// Get existing variations so we don't create duplicates
954
		$available_variations = array();
955
956
		foreach( $_product->get_children() as $child_id ) {
957
			$child = $_product->get_child( $child_id );
958
959
			if ( ! empty( $child->variation_id ) ) {
960
				$available_variations[] = $child->get_variation_attributes();
961
			}
962
		}
963
964
		// Created posts will all have the following data
965
		$variation_post_data = array(
966
			'post_title'   => 'Product #' . $post_id . ' Variation',
967
			'post_content' => '',
968
			'post_status'  => 'publish',
969
			'post_author'  => get_current_user_id(),
970
			'post_parent'  => $post_id,
971
			'post_type'    => 'product_variation'
972
		);
973
974
		$variation_ids       = array();
975
		$added               = 0;
976
		$possible_variations = wc_array_cartesian( $variations );
977
978
		foreach ( $possible_variations as $variation ) {
979
980
			// Check if variation already exists
981
			if ( in_array( $variation, $available_variations ) ) {
982
				continue;
983
			}
984
985
			$variation_id = wp_insert_post( $variation_post_data );
986
987
			$variation_ids[] = $variation_id;
988
989
			foreach ( $variation as $key => $value ) {
990
				update_post_meta( $variation_id, $key, $value );
991
			}
992
993
			// Save stock status
994
			update_post_meta( $variation_id, '_stock_status', 'instock' );
995
996
			$added++;
997
998
			do_action( 'product_variation_linked', $variation_id );
999
1000
			if ( $added > WC_MAX_LINKED_VARIATIONS ) {
1001
				break;
1002
			}
1003
		}
1004
1005
		delete_transient( 'wc_product_children_' . $post_id );
1006
1007
		echo $added;
1008
1009
		die();
1010
	}
1011
1012
	/**
1013
	 * Delete download permissions via ajax function.
1014
	 */
1015
	public static function revoke_access_to_download() {
1016
		check_ajax_referer( 'revoke-access', 'security' );
1017
1018
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1019
			die( -1 );
1020
		}
1021
1022
		global $wpdb;
1023
1024
		$download_id   = $_POST['download_id'];
1025
		$product_id    = intval( $_POST['product_id'] );
1026
		$order_id      = intval( $_POST['order_id'] );
1027
		$permission_id = absint( $_POST['permission_id'] );
1028
1029
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d;", $permission_id ) );
1030
1031
		do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id, $permission_id );
1032
1033
		die();
1034
	}
1035
1036
	/**
1037
	 * Grant download permissions via ajax function.
1038
	 */
1039
	public static function grant_access_to_download() {
1040
1041
		check_ajax_referer( 'grant-access', 'security' );
1042
1043
		if ( ! current_user_can( 'edit_shop_orders' ) ) {
1044
			die(-1);
1045
		}
1046
1047
		global $wpdb;
1048
1049
		$wpdb->hide_errors();
1050
1051
		$order_id     = intval( $_POST['order_id'] );
1052
		$product_ids  = $_POST['product_ids'];
1053
		$loop         = intval( $_POST['loop'] );
1054
		$file_counter = 0;
1055
		$order        = wc_get_order( $order_id );
1056
1057
		if ( ! is_array( $product_ids ) ) {
1058
			$product_ids = array( $product_ids );
1059
		}
1060
1061
		foreach ( $product_ids as $product_id ) {
1062
			$product = wc_get_product( $product_id );
1063
			$files   = $product->get_files();
1064
1065
			if ( ! $order->get_billing_email() ) {
1066
				die();
1067
			}
1068
1069
			if ( ! empty( $files ) ) {
1070
				foreach ( $files as $download_id => $file ) {
1071
					if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
0 ignored issues
show
Documentation introduced by
$order is of type false|object, but the function expects a object<WC_Order>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1072
1073
						// insert complete - get inserted data
1074
						$download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
1075
1076
						$loop ++;
1077
						$file_counter ++;
1078
1079 View Code Duplication
						if ( isset( $file['name'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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