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

WC_AJAX::add_order_shipping()   B

Complexity

Conditions 4
Paths 13

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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