Completed
Push — master ( 444dff...0128a0 )
by Rodrigo
11:24 queued 14s
created

WC_Tax::sort_rates_callback()   D

Complexity

Conditions 18
Paths 16

Size

Total Lines 35

Duplication

Lines 24
Ratio 68.57 %

Code Coverage

Tests 10
CRAP Score 58.5

Importance

Changes 0
Metric Value
cc 18
nc 16
nop 2
dl 24
loc 35
ccs 10
cts 20
cp 0.5
crap 58.5
rs 4.8666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Tax calculation and rate finding class.
4
 *
5
 * @package WooCommerce/Classes
6
 */
7
8
defined( 'ABSPATH' ) || exit;
9
10
/**
11
 * Performs tax calculations and loads tax rates
12
 *
13
 * @class WC_Tax
14
 */
15
class WC_Tax {
16
17
	/**
18
	 * Precision.
19
	 *
20
	 * @var int
21
	 */
22
	public static $precision;
23
24
	/**
25
	 * Round at subtotal.
26
	 *
27
	 * @var bool
28
	 */
29
	public static $round_at_subtotal = false;
30
31
	/**
32
	 * Load options.
33
	 */
34
	public static function init() {
35
		self::$precision         = wc_get_rounding_precision();
36
		self::$round_at_subtotal = 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
37
	}
38
39
	/**
40
	 * When the woocommerce_tax_classes option is changed, remove any orphan rates.
41
	 *
42
	 * @deprecated 3.7.0
43
	 * @param  string $old_value Old rates value.
44
	 * @param  string $value New rates value.
45
	 */
46
	public static function maybe_remove_tax_class_rates( $old_value, $value ) {
0 ignored issues
show
Unused Code introduced by
The parameter $old_value 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...
47
		wc_deprecated_function( 'WC_Tax::maybe_remove_tax_class_rates', '3.7', 'WC_Tax::delete_tax_class_by' );
48
49
		$tax_classes          = array_filter( array_map( 'trim', explode( "\n", $value ) ) );
50
		$existing_tax_classes = self::get_tax_classes();
51
		$removed              = array_diff( $existing_tax_classes, $tax_classes );
52
		foreach ( $removed as $name ) {
53
			self::delete_tax_class_by( 'name', $name );
54
		}
55
	}
56
57
	/**
58
	 * Calculate tax for a line.
59
	 *
60
	 * @param  float   $price              Price to calc tax on.
61
	 * @param  array   $rates              Rates to apply.
62
	 * @param  boolean $price_includes_tax Whether the passed price has taxes included.
63
	 * @param  boolean $deprecated         Whether to suppress any rounding from taking place. No longer used here.
64
	 * @return array                       Array of rates + prices after tax.
65
	 */
66 43
	public static function calc_tax( $price, $rates, $price_includes_tax = false, $deprecated = false ) {
67 43
		if ( $price_includes_tax ) {
68 14
			$taxes = self::calc_inclusive_tax( $price, $rates );
69
		} else {
70 36
			$taxes = self::calc_exclusive_tax( $price, $rates );
71
		}
72 43
		return apply_filters( 'woocommerce_calc_tax', $taxes, $price, $rates, $price_includes_tax, $deprecated );
73
	}
74
75
	/**
76
	 * Calculate the shipping tax using a passed array of rates.
77
	 *
78
	 * @param float $price Shipping cost.
79
	 * @param array $rates Taxation Rate.
80
	 * @return array
81
	 */
82 65
	public static function calc_shipping_tax( $price, $rates ) {
83 65
		$taxes = self::calc_exclusive_tax( $price, $rates );
84 65
		return apply_filters( 'woocommerce_calc_shipping_tax', $taxes, $price, $rates );
85
	}
86
87
	/**
88
	 * Round to precision.
89
	 *
90
	 * Filter example: to return rounding to .5 cents you'd use:
91
	 *
92
	 * function euro_5cent_rounding( $in ) {
93
	 *      return round( $in / 5, 2 ) * 5;
94
	 * }
95
	 * add_filter( 'woocommerce_tax_round', 'euro_5cent_rounding' );
96
	 *
97
	 * @param float|int $in Value to round.
98
	 * @return float
99
	 */
100 42
	public static function round( $in ) {
101 42
		return apply_filters( 'woocommerce_tax_round', round( $in, wc_get_rounding_precision() ), $in );
102
	}
103
104
	/**
105
	 * Calc tax from inclusive price.
106
	 *
107
	 * @param  float $price Price to calculate tax for.
108
	 * @param  array $rates Array of tax rates.
109
	 * @return array
110
	 */
111 14
	public static function calc_inclusive_tax( $price, $rates ) {
112 14
		$taxes          = array();
113 14
		$compound_rates = array();
114 14
		$regular_rates  = array();
115
116
		// Index array so taxes are output in correct order and see what compound/regular rates we have to calculate.
117 14
		foreach ( $rates as $key => $rate ) {
118 14
			$taxes[ $key ] = 0;
119
120 14
			if ( 'yes' === $rate['compound'] ) {
121 1
				$compound_rates[ $key ] = $rate['rate'];
122
			} else {
123 14
				$regular_rates[ $key ] = $rate['rate'];
124
			}
125
		}
126
127 14
		$compound_rates = array_reverse( $compound_rates, true ); // Working backwards.
128
129 14
		$non_compound_price = $price;
130
131 14
		foreach ( $compound_rates as $key => $compound_rate ) {
132 1
			$tax_amount         = apply_filters( 'woocommerce_price_inc_tax_amount', $non_compound_price - ( $non_compound_price / ( 1 + ( $compound_rate / 100 ) ) ), $key, $rates[ $key ], $price );
133 1
			$taxes[ $key ]     += $tax_amount;
134 1
			$non_compound_price = $non_compound_price - $tax_amount;
135
		}
136
137
		// Regular taxes.
138 14
		$regular_tax_rate = 1 + ( array_sum( $regular_rates ) / 100 );
139
140 14
		foreach ( $regular_rates as $key => $regular_rate ) {
141 14
			$the_rate       = ( $regular_rate / 100 ) / $regular_tax_rate;
142 14
			$net_price      = $price - ( $the_rate * $non_compound_price );
143 14
			$tax_amount     = apply_filters( 'woocommerce_price_inc_tax_amount', $price - $net_price, $key, $rates[ $key ], $price );
144 14
			$taxes[ $key ] += $tax_amount;
145
		}
146
147
		/**
148
		 * Round all taxes to precision (4DP) before passing them back. Note, this is not the same rounding
149
		 * as in the cart calculation class which, depending on settings, will round to 2DP when calculating
150
		 * final totals. Also unlike that class, this rounds .5 up for all cases.
151
		 */
152 14
		$taxes = array_map( array( __CLASS__, 'round' ), $taxes );
153
154 14
		return $taxes;
155
	}
156
157
	/**
158
	 * Calc tax from exclusive price.
159
	 *
160
	 * @param  float $price Price to calculate tax for.
161
	 * @param  array $rates Array of tax rates.
162
	 * @return array
163
	 */
164 92
	public static function calc_exclusive_tax( $price, $rates ) {
165 92
		$taxes = array();
166
167 92
		if ( ! empty( $rates ) ) {
168 36
			foreach ( $rates as $key => $rate ) {
169 36
				if ( 'yes' === $rate['compound'] ) {
170 1
					continue;
171
				}
172
173 36
				$tax_amount = $price * ( $rate['rate'] / 100 );
174 36
				$tax_amount = apply_filters( 'woocommerce_price_ex_tax_amount', $tax_amount, $key, $rate, $price ); // ADVANCED: Allow third parties to modify this rate.
175
176 36 View Code Duplication
				if ( ! isset( $taxes[ $key ] ) ) {
177 36
					$taxes[ $key ] = $tax_amount;
178
				} else {
179
					$taxes[ $key ] += $tax_amount;
180
				}
181
			}
182
183 36
			$pre_compound_total = array_sum( $taxes );
184
185
			// Compound taxes.
186 36
			foreach ( $rates as $key => $rate ) {
187 36
				if ( 'no' === $rate['compound'] ) {
188 36
					continue;
189
				}
190 1
				$the_price_inc_tax = $price + ( $pre_compound_total );
191 1
				$tax_amount        = $the_price_inc_tax * ( $rate['rate'] / 100 );
192 1
				$tax_amount        = apply_filters( 'woocommerce_price_ex_tax_amount', $tax_amount, $key, $rate, $price, $the_price_inc_tax, $pre_compound_total ); // ADVANCED: Allow third parties to modify this rate.
193
194 1 View Code Duplication
				if ( ! isset( $taxes[ $key ] ) ) {
195 1
					$taxes[ $key ] = $tax_amount;
196
				} else {
197
					$taxes[ $key ] += $tax_amount;
198
				}
199
200 1
				$pre_compound_total = array_sum( $taxes );
201
			}
202
		}
203
204
		/**
205
		 * Round all taxes to precision (4DP) before passing them back. Note, this is not the same rounding
206
		 * as in the cart calculation class which, depending on settings, will round to 2DP when calculating
207
		 * final totals. Also unlike that class, this rounds .5 up for all cases.
208
		 */
209 92
		$taxes = array_map( array( __CLASS__, 'round' ), $taxes );
210
211 92
		return $taxes;
212
	}
213
214
	/**
215
	 * Searches for all matching country/state/postcode tax rates.
216
	 *
217
	 * @param array $args Args that determine the rate to find.
218
	 * @return array
219
	 */
220 102
	public static function find_rates( $args = array() ) {
221 102
		$args = wp_parse_args(
222 102
			$args,
223
			array(
224 102
				'country'   => '',
225
				'state'     => '',
226
				'city'      => '',
227
				'postcode'  => '',
228
				'tax_class' => '',
229
			)
230
		);
231
232 102
		$country   = $args['country'];
233 102
		$state     = $args['state'];
234 102
		$city      = $args['city'];
235 102
		$postcode  = wc_normalize_postcode( wc_clean( $args['postcode'] ) );
0 ignored issues
show
Bug introduced by
It seems like wc_clean($args['postcode']) targeting wc_clean() can also be of type array; however, wc_normalize_postcode() does only seem to accept string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
236 102
		$tax_class = $args['tax_class'];
237
238 102
		if ( ! $country ) {
239
			return array();
240
		}
241
242 102
		$cache_key         = WC_Cache_Helper::get_cache_prefix( 'taxes' ) . 'wc_tax_rates_' . md5( sprintf( '%s+%s+%s+%s+%s', $country, $state, $city, $postcode, $tax_class ) );
243 102
		$matched_tax_rates = wp_cache_get( $cache_key, 'taxes' );
244
245 102
		if ( false === $matched_tax_rates ) {
246 102
			$matched_tax_rates = self::get_matched_tax_rates( $country, $state, $postcode, $city, $tax_class );
247 102
			wp_cache_set( $cache_key, $matched_tax_rates, 'taxes' );
248
		}
249
250 102
		return apply_filters( 'woocommerce_find_rates', $matched_tax_rates, $args );
251
	}
252
253
	/**
254
	 * Searches for all matching country/state/postcode tax rates.
255
	 *
256
	 * @param array $args Args that determine the rate to find.
257
	 * @return array
258
	 */
259 69
	public static function find_shipping_rates( $args = array() ) {
260 69
		$rates          = self::find_rates( $args );
261 69
		$shipping_rates = array();
262
263 69
		if ( is_array( $rates ) ) {
264 69
			foreach ( $rates as $key => $rate ) {
265 13
				if ( 'yes' === $rate['shipping'] ) {
266 12
					$shipping_rates[ $key ] = $rate;
267
				}
268
			}
269
		}
270
271 69
		return $shipping_rates;
272
	}
273
274
	/**
275
	 * Does the sort comparison. Compares (in this order):
276
	 * - Priority
277
	 * - Country
278
	 * - State
279
	 * - Number of postcodes
280
	 * - Number of cities
281
	 * - ID
282
	 *
283
	 * @param object $rate1 First rate to compare.
284
	 * @param object $rate2 Second rate to compare.
285
	 * @return int
286
	 */
287 11
	private static function sort_rates_callback( $rate1, $rate2 ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
288 11
		if ( $rate1->tax_rate_priority !== $rate2->tax_rate_priority ) {
289 4
			return $rate1->tax_rate_priority < $rate2->tax_rate_priority ? -1 : 1; // ASC.
290
		}
291
292 7 View Code Duplication
		if ( $rate1->tax_rate_country !== $rate2->tax_rate_country ) {
293 3
			if ( '' === $rate1->tax_rate_country ) {
294 3
				return 1;
295
			}
296
			if ( '' === $rate2->tax_rate_country ) {
297
				return -1;
298
			}
299
			return strcmp( $rate1->tax_rate_country, $rate2->tax_rate_country ) > 0 ? 1 : -1;
300
		}
301
302 4 View Code Duplication
		if ( $rate1->tax_rate_state !== $rate2->tax_rate_state ) {
303
			if ( '' === $rate1->tax_rate_state ) {
304
				return 1;
305
			}
306
			if ( '' === $rate2->tax_rate_state ) {
307
				return -1;
308
			}
309
			return strcmp( $rate1->tax_rate_state, $rate2->tax_rate_state ) > 0 ? 1 : -1;
310
		}
311
312 4 View Code Duplication
		if ( isset( $rate1->postcode_count, $rate2->postcode_count ) && $rate1->postcode_count !== $rate2->postcode_count ) {
313
			return $rate1->postcode_count < $rate2->postcode_count ? 1 : -1;
314
		}
315
316 4 View Code Duplication
		if ( isset( $rate1->city_count, $rate2->city_count ) && $rate1->city_count !== $rate2->city_count ) {
317
			return $rate1->city_count < $rate2->city_count ? 1 : -1;
318
		}
319
320 4
		return $rate1->tax_rate_id < $rate2->tax_rate_id ? -1 : 1;
321
	}
322
323
	/**
324
	 * Logical sort order for tax rates based on the following in order of priority.
325
	 *
326
	 * @param  array $rates Rates to be sorted.
327
	 * @return array
328
	 */
329 102
	private static function sort_rates( $rates ) {
330 102
		uasort( $rates, __CLASS__ . '::sort_rates_callback' );
331 102
		$i = 0;
332 102
		foreach ( $rates as $key => $rate ) {
333 46
			$rates[ $key ]->tax_rate_order = $i++;
334
		}
335 102
		return $rates;
336
	}
337
338
	/**
339
	 * Loop through a set of tax rates and get the matching rates (1 per priority).
340
	 *
341
	 * @param  string $country Country code to match against.
342
	 * @param  string $state State code to match against.
343
	 * @param  string $postcode Postcode to match against.
344
	 * @param  string $city City to match against.
345
	 * @param  string $tax_class Tax class to match against.
346
	 * @return array
347
	 */
348 102
	private static function get_matched_tax_rates( $country, $state, $postcode, $city, $tax_class ) {
349
		global $wpdb;
350
351
		// Query criteria - these will be ANDed.
352 102
		$criteria   = array();
353 102
		$criteria[] = $wpdb->prepare( "tax_rate_country IN ( %s, '' )", strtoupper( $country ) );
354 102
		$criteria[] = $wpdb->prepare( "tax_rate_state IN ( %s, '' )", strtoupper( $state ) );
355 102
		$criteria[] = $wpdb->prepare( 'tax_rate_class = %s', sanitize_title( $tax_class ) );
356
357
		// Pre-query postcode ranges for PHP based matching.
358 102
		$postcode_search = wc_get_wildcard_postcodes( $postcode, $country );
359 102
		$postcode_ranges = $wpdb->get_results( "SELECT tax_rate_id, location_code FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE location_type = 'postcode' AND location_code LIKE '%...%';" );
360
361 102
		if ( $postcode_ranges ) {
362
			$matches = wc_postcode_location_matcher( $postcode, $postcode_ranges, 'tax_rate_id', 'location_code', $country );
363
			if ( ! empty( $matches ) ) {
364
				foreach ( $matches as $matched_postcodes ) {
365
					$postcode_search = array_merge( $postcode_search, $matched_postcodes );
366
				}
367
			}
368
		}
369
370 102
		$postcode_search = array_unique( $postcode_search );
371
372
		/**
373
		 * Location matching criteria - ORed
374
		 * Needs to match:
375
		 * - rates with no postcodes and cities
376
		 * - rates with a matching postcode and city
377
		 * - rates with matching postcode, no city
378
		 * - rates with matching city, no postcode
379
		 */
380 102
		$locations_criteria   = array();
381 102
		$locations_criteria[] = 'locations.location_type IS NULL';
382 102
		$locations_criteria[] = "
383 102
			locations.location_type = 'postcode' AND locations.location_code IN ('" . implode( "','", array_map( 'esc_sql', $postcode_search ) ) . "')
384
			AND (
385 102
				( locations2.location_type = 'city' AND locations2.location_code = '" . esc_sql( strtoupper( $city ) ) . "' )
386
				OR NOT EXISTS (
387 102
					SELECT sub.tax_rate_id FROM {$wpdb->prefix}woocommerce_tax_rate_locations as sub
388
					WHERE sub.location_type = 'city'
389
					AND sub.tax_rate_id = tax_rates.tax_rate_id
390
				)
391
			)
392
		";
393 102
		$locations_criteria[] = "
394 102
			locations.location_type = 'city' AND locations.location_code = '" . esc_sql( strtoupper( $city ) ) . "'
395
			AND NOT EXISTS (
396 102
				SELECT sub.tax_rate_id FROM {$wpdb->prefix}woocommerce_tax_rate_locations as sub
397
				WHERE sub.location_type = 'postcode'
398
				AND sub.tax_rate_id = tax_rates.tax_rate_id
399
			)
400
		";
401
402 102
		$criteria[] = '( ( ' . implode( ' ) OR ( ', $locations_criteria ) . ' ) )';
403
404 102
		$criteria_string = implode( ' AND ', $criteria );
405
406
		// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
407 102
		$found_rates = $wpdb->get_results(
408
			"
409
			SELECT tax_rates.*, COUNT( locations.location_id ) as postcode_count, COUNT( locations2.location_id ) as city_count
410 102
			FROM {$wpdb->prefix}woocommerce_tax_rates as tax_rates
411 102
			LEFT OUTER JOIN {$wpdb->prefix}woocommerce_tax_rate_locations as locations ON tax_rates.tax_rate_id = locations.tax_rate_id
412 102
			LEFT OUTER JOIN {$wpdb->prefix}woocommerce_tax_rate_locations as locations2 ON tax_rates.tax_rate_id = locations2.tax_rate_id
413 102
			WHERE 1=1 AND {$criteria_string}
414
			GROUP BY tax_rates.tax_rate_id
415
			ORDER BY tax_rates.tax_rate_priority
416
			"
417
		);
418
		// phpcs:enable
419
420 102
		$found_rates       = self::sort_rates( $found_rates );
421 102
		$matched_tax_rates = array();
422 102
		$found_priority    = array();
423
424 102
		foreach ( $found_rates as $found_rate ) {
425 46
			if ( in_array( $found_rate->tax_rate_priority, $found_priority, true ) ) {
426 7
				continue;
427
			}
428
429 46
			$matched_tax_rates[ $found_rate->tax_rate_id ] = array(
430 46
				'rate'     => (float) $found_rate->tax_rate,
431 46
				'label'    => $found_rate->tax_rate_name,
432 46
				'shipping' => $found_rate->tax_rate_shipping ? 'yes' : 'no',
433 46
				'compound' => $found_rate->tax_rate_compound ? 'yes' : 'no',
434
			);
435
436 46
			$found_priority[] = $found_rate->tax_rate_priority;
437
		}
438
439 102
		return apply_filters( 'woocommerce_matched_tax_rates', $matched_tax_rates, $country, $state, $postcode, $city, $tax_class );
440
	}
441
442
	/**
443
	 * Get the customer tax location based on their status and the current page.
444
	 *
445
	 * Used by get_rates(), get_shipping_rates().
446
	 *
447
	 * @param  string $tax_class string Optional, passed to the filter for advanced tax setups.
448
	 * @param  object $customer Override the customer object to get their location.
449
	 * @return array
450
	 */
451 91
	public static function get_tax_location( $tax_class = '', $customer = null ) {
452 91
		$location = array();
453
454 91
		if ( is_null( $customer ) && WC()->customer ) {
455 76
			$customer = WC()->customer;
456
		}
457
458 91
		if ( ! empty( $customer ) ) {
459 91
			$location = $customer->get_taxable_address();
460
		} elseif ( wc_prices_include_tax() || 'base' === get_option( 'woocommerce_default_customer_address' ) || 'base' === get_option( 'woocommerce_tax_based_on' ) ) {
461
			$location = array(
462
				WC()->countries->get_base_country(),
463
				WC()->countries->get_base_state(),
464
				WC()->countries->get_base_postcode(),
465
				WC()->countries->get_base_city(),
466
			);
467
		}
468
469 91
		return apply_filters( 'woocommerce_get_tax_location', $location, $tax_class, $customer );
470
	}
471
472
	/**
473
	 * Get's an array of matching rates for a tax class.
474
	 *
475
	 * @param string $tax_class Tax class to get rates for.
476
	 * @param object $customer Override the customer object to get their location.
477
	 * @return  array
478
	 */
479 36
	public static function get_rates( $tax_class = '', $customer = null ) {
480 36
		$tax_class         = sanitize_title( $tax_class );
481 36
		$location          = self::get_tax_location( $tax_class, $customer );
482 36
		$matched_tax_rates = array();
483
484 36
		if ( count( $location ) === 4 ) {
485 36
			list( $country, $state, $postcode, $city ) = $location;
486
487 36
			$matched_tax_rates = self::find_rates(
488
				array(
489 36
					'country'   => $country,
490 36
					'state'     => $state,
491 36
					'postcode'  => $postcode,
492 36
					'city'      => $city,
493 36
					'tax_class' => $tax_class,
494
				)
495
			);
496
		}
497
498 36
		return apply_filters( 'woocommerce_matched_rates', $matched_tax_rates, $tax_class );
499
	}
500
501
	/**
502
	 * Get's an array of matching rates for the shop's base country.
503
	 *
504
	 * @param string $tax_class Tax Class.
505
	 * @return array
506
	 */
507 11
	public static function get_base_tax_rates( $tax_class = '' ) {
508 11
		return apply_filters(
509 11
			'woocommerce_base_tax_rates',
510 11
			self::find_rates(
511
				array(
512 11
					'country'   => WC()->countries->get_base_country(),
513 11
					'state'     => WC()->countries->get_base_state(),
514 11
					'postcode'  => WC()->countries->get_base_postcode(),
515 11
					'city'      => WC()->countries->get_base_city(),
516 11
					'tax_class' => $tax_class,
517
				)
518
			),
519 11
			$tax_class
520
		);
521
	}
522
523
	/**
524
	 * Alias for get_base_tax_rates().
525
	 *
526
	 * @deprecated 2.3
527
	 * @param string $tax_class Tax Class.
528
	 * @return array
529
	 */
530
	public static function get_shop_base_rate( $tax_class = '' ) {
531
		return self::get_base_tax_rates( $tax_class );
532
	}
533
534
	/**
535
	 * Gets an array of matching shipping tax rates for a given class.
536
	 *
537
	 * @param string $tax_class Tax class to get rates for.
538
	 * @param object $customer Override the customer object to get their location.
539
	 * @return mixed
540
	 */
541 65
	public static function get_shipping_tax_rates( $tax_class = null, $customer = null ) {
542
		// See if we have an explicitly set shipping tax class.
543 65
		$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
544
545 65
		if ( 'inherit' !== $shipping_tax_class ) {
546
			$tax_class = $shipping_tax_class;
547
		}
548
549 65
		$location          = self::get_tax_location( $tax_class, $customer );
550 65
		$matched_tax_rates = array();
551
552 65
		if ( 4 === count( $location ) ) {
553 65
			list( $country, $state, $postcode, $city ) = $location;
554
555 65
			if ( ! is_null( $tax_class ) ) {
556
				// This will be per item shipping.
557
				$matched_tax_rates = self::find_shipping_rates(
558
					array(
559
						'country'   => $country,
560
						'state'     => $state,
561
						'postcode'  => $postcode,
562
						'city'      => $city,
563
						'tax_class' => $tax_class,
564
					)
565
				);
566
567 65
			} elseif ( WC()->cart->get_cart() ) {
568
569
				// This will be per order shipping - loop through the order and find the highest tax class rate.
570 6
				$cart_tax_classes = WC()->cart->get_cart_item_tax_classes_for_shipping();
571
572
				// No tax classes = no taxable items.
573 6
				if ( empty( $cart_tax_classes ) ) {
574
					return array();
575
				}
576
577
				// If multiple classes are found, use the first one found unless a standard rate item is found. This will be the first listed in the 'additional tax class' section.
578 6
				if ( count( $cart_tax_classes ) > 1 && ! in_array( '', $cart_tax_classes, true ) ) {
579
					$tax_classes = self::get_tax_class_slugs();
580
581
					foreach ( $tax_classes as $tax_class ) {
582
						if ( in_array( $tax_class, $cart_tax_classes, true ) ) {
583
							$matched_tax_rates = self::find_shipping_rates(
584
								array(
585
									'country'   => $country,
586
									'state'     => $state,
587
									'postcode'  => $postcode,
588
									'city'      => $city,
589
									'tax_class' => $tax_class,
590
								)
591
							);
592
							break;
593
						}
594
					}
595 6
				} elseif ( 1 === count( $cart_tax_classes ) ) {
596
					// If a single tax class is found, use it.
597 6
					$matched_tax_rates = self::find_shipping_rates(
598
						array(
599 6
							'country'   => $country,
600 6
							'state'     => $state,
601 6
							'postcode'  => $postcode,
602 6
							'city'      => $city,
603 6
							'tax_class' => $cart_tax_classes[0],
604
						)
605
					);
606
				}
607
			}
608
609
			// Get standard rate if no taxes were found.
610 65
			if ( ! count( $matched_tax_rates ) ) {
611 59
				$matched_tax_rates = self::find_shipping_rates(
612
					array(
613 59
						'country'  => $country,
614 59
						'state'    => $state,
615 59
						'postcode' => $postcode,
616 59
						'city'     => $city,
617
					)
618
				);
619
			}
620
		}
621
622 65
		return $matched_tax_rates;
623
	}
624
625
	/**
626
	 * Return true/false depending on if a rate is a compound rate.
627
	 *
628
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format.
629
	 * @return  bool
630
	 */
631 12
	public static function is_compound( $key_or_rate ) {
632
		global $wpdb;
633
634 12 View Code Duplication
		if ( is_object( $key_or_rate ) ) {
635 10
			$key      = $key_or_rate->tax_rate_id;
636 10
			$compound = $key_or_rate->tax_rate_compound;
637
		} else {
638 3
			$key      = $key_or_rate;
639 3
			$compound = (bool) $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_compound FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
640
		}
641
642 12
		return (bool) apply_filters( 'woocommerce_rate_compound', $compound, $key );
643
	}
644
645
	/**
646
	 * Return a given rates label.
647
	 *
648
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format.
649
	 * @return  string
650
	 */
651 12
	public static function get_rate_label( $key_or_rate ) {
652
		global $wpdb;
653
654 12 View Code Duplication
		if ( is_object( $key_or_rate ) ) {
655 10
			$key       = $key_or_rate->tax_rate_id;
656 10
			$rate_name = $key_or_rate->tax_rate_name;
657
		} else {
658 3
			$key       = $key_or_rate;
659 3
			$rate_name = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_name FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
660
		}
661
662 12
		if ( ! $rate_name ) {
663 1
			$rate_name = WC()->countries->tax_or_vat();
664
		}
665
666 12
		return apply_filters( 'woocommerce_rate_label', $rate_name, $key );
667
	}
668
669
	/**
670
	 * Return a given rates percent.
671
	 *
672
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format.
673
	 * @return  string
674
	 */
675 1
	public static function get_rate_percent( $key_or_rate ) {
676 1
		$rate_percent_value = self::get_rate_percent_value( $key_or_rate );
677 1
		$tax_rate_id = is_object( $key_or_rate ) ? $key_or_rate->tax_rate_id : $key_or_rate;
678 1
		return apply_filters( 'woocommerce_rate_percent', $rate_percent_value . '%', $tax_rate_id );
679
	}
680
681
	/**
682
	 * Return a given rates percent.
683
	 *
684
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format.
685
	 * @return  float
686
	 */
687 11
	public static function get_rate_percent_value( $key_or_rate ) {
688
		global $wpdb;
689
690 11 View Code Duplication
		if ( is_object( $key_or_rate ) ) {
691 10
			$tax_rate = $key_or_rate->tax_rate;
692
		} else {
693 2
			$key      = $key_or_rate;
694 2
			$tax_rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
695
		}
696
697 11
		return floatval( $tax_rate );
698
	}
699
700
701
	/**
702
	 * Get a rates code. Code is made up of COUNTRY-STATE-NAME-Priority. E.g GB-VAT-1, US-AL-TAX-1.
703
	 *
704
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format.
705
	 * @return string
706
	 */
707 12
	public static function get_rate_code( $key_or_rate ) {
708
		global $wpdb;
709
710 12 View Code Duplication
		if ( is_object( $key_or_rate ) ) {
711 10
			$key  = $key_or_rate->tax_rate_id;
712 10
			$rate = $key_or_rate;
713
		} else {
714 3
			$key  = $key_or_rate;
715 3
			$rate = $wpdb->get_row( $wpdb->prepare( "SELECT tax_rate_country, tax_rate_state, tax_rate_name, tax_rate_priority FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
716
		}
717
718 12
		$code_string = '';
719
720 12
		if ( null !== $rate ) {
721 12
			$code        = array();
722 12
			$code[]      = $rate->tax_rate_country;
723 12
			$code[]      = $rate->tax_rate_state;
724 12
			$code[]      = $rate->tax_rate_name ? $rate->tax_rate_name : 'TAX';
725 12
			$code[]      = absint( $rate->tax_rate_priority );
726 12
			$code_string = strtoupper( implode( '-', array_filter( $code ) ) );
727
		}
728
729 12
		return apply_filters( 'woocommerce_rate_code', $code_string, $key );
730
	}
731
732
	/**
733
	 * Sums a set of taxes to form a single total. Values are pre-rounded to precision from 3.6.0.
734
	 *
735
	 * @param  array $taxes Array of taxes.
736
	 * @return float
737
	 */
738 1
	public static function get_tax_total( $taxes ) {
739 1
		return array_sum( $taxes );
740
	}
741
742
	/**
743
	 * Gets all tax rate classes from the database.
744
	 *
745
	 * @since 3.7.0
746
	 * @return array Array of tax class objects consisting of tax_rate_class_id, name, and slug.
747
	 */
748 265
	protected static function get_tax_rate_classes() {
749
		global $wpdb;
750
751 265
		$cache_key        = 'tax-rate-classes';
752 265
		$tax_rate_classes = wp_cache_get( $cache_key, 'taxes' );
753
754 265
		if ( ! is_array( $tax_rate_classes ) ) {
755 264
			$tax_rate_classes = $wpdb->get_results(
756
				"
757 264
				SELECT * FROM {$wpdb->wc_tax_rate_classes} ORDER BY name;
758
				"
759
			);
760 264
			wp_cache_set( $cache_key, $tax_rate_classes, 'taxes' );
761
		}
762
763 265
		return $tax_rate_classes;
764
	}
765
766
	/**
767
	 * Get store tax class names.
768
	 *
769
	 * @return array Array of class names ("Reduced rate", "Zero rate", etc).
770
	 */
771 15
	public static function get_tax_classes() {
772 15
		return wp_list_pluck( self::get_tax_rate_classes(), 'name' );
773
	}
774
775
	/**
776
	 * Get store tax classes as slugs.
777
	 *
778
	 * @since  3.0.0
779
	 * @return array Array of class slugs ("reduced-rate", "zero-rate", etc).
780
	 */
781 265
	public static function get_tax_class_slugs() {
782 265
		return wp_list_pluck( self::get_tax_rate_classes(), 'slug' );
783
	}
784
785
	/**
786
	 * Create a new tax class.
787
	 *
788
	 * @since 3.7.0
789
	 * @param string $name Name of the tax class to add.
790
	 * @param string $slug (optional) Slug of the tax class to add. Defaults to sanitized name.
791
	 * @return WP_Error|array Returns name and slug (array) if the tax class is created, or WP_Error if something went wrong.
792
	 */
793 2
	public static function create_tax_class( $name, $slug = '' ) {
794
		global $wpdb;
795
796 2
		if ( empty( $name ) ) {
797
			return new WP_Error( 'tax_class_invalid_name', __( 'Tax class requires a valid name', 'woocommerce' ) );
798
		}
799
800 2
		$existing       = self::get_tax_classes();
801 2
		$existing_slugs = self::get_tax_class_slugs();
802
803 2
		if ( in_array( $name, $existing, true ) ) {
804 2
			return new WP_Error( 'tax_class_exists', __( 'Tax class already exists', 'woocommerce' ) );
805
		}
806
807
		if ( ! $slug ) {
808
			$slug = sanitize_title( $name );
809
		}
810
811
		if ( in_array( $slug, $existing_slugs, true ) ) {
812
			return new WP_Error( 'tax_class_slug_exists', __( 'Tax class slug already exists', 'woocommerce' ) );
813
		}
814
815
		$insert = $wpdb->insert(
816
			$wpdb->wc_tax_rate_classes,
817
			array(
818
				'name' => $name,
819
				'slug' => $slug,
820
			)
821
		);
822
823
		if ( is_wp_error( $insert ) ) {
824
			return new WP_Error( 'tax_class_insert_error', $insert->get_error_message() );
825
		}
826
827
		wp_cache_delete( 'tax-rate-classes', 'taxes' );
828
829
		return array(
830
			'name' => $name,
831
			'slug' => $slug,
832
		);
833
	}
834
835
	/**
836
	 * Get an existing tax class.
837
	 *
838
	 * @since 3.7.0
839
	 * @param string     $field Field to get by. Valid values are id, name, or slug.
840
	 * @param string|int $item Item to get.
841
	 * @return array|bool Returns the tax class as an array. False if not found.
842
	 */
843
	public static function get_tax_class_by( $field, $item ) {
844 View Code Duplication
		if ( ! in_array( $field, array( 'id', 'name', 'slug' ), true ) ) {
845
			return new WP_Error( 'invalid_field', __( 'Invalid field', 'woocommerce' ) );
846
		}
847
848
		if ( 'id' === $field ) {
849
			$field = 'tax_rate_class_id';
850
		}
851
852
		$matches = wp_list_filter(
853
			self::get_tax_rate_classes(),
854
			array(
855
				$field => $item,
856
			)
857
		);
858
859
		if ( ! $matches ) {
860
			return false;
861
		}
862
863
		$tax_class = current( $matches );
864
865
		return array(
866
			'name' => $tax_class->name,
867
			'slug' => $tax_class->slug,
868
		);
869
	}
870
871
	/**
872
	 * Delete an existing tax class.
873
	 *
874
	 * @since 3.7.0
875
	 * @param string     $field Field to delete by. Valid values are id, name, or slug.
876
	 * @param string|int $item Item to delete.
877
	 * @return WP_Error|bool Returns true if deleted successfully, false if nothing was deleted, or WP_Error if there is an invalid request.
878
	 */
879
	public static function delete_tax_class_by( $field, $item ) {
880
		global $wpdb;
881
882 View Code Duplication
		if ( ! in_array( $field, array( 'id', 'name', 'slug' ), true ) ) {
883
			return new WP_Error( 'invalid_field', __( 'Invalid field', 'woocommerce' ) );
884
		}
885
886
		$tax_class = self::get_tax_class_by( $field, $item );
887
888
		if ( ! $tax_class ) {
889
			return new WP_Error( 'invalid_tax_class', __( 'Invalid tax class', 'woocommerce' ) );
890
		}
891
892
		if ( 'id' === $field ) {
893
			$field = 'tax_rate_class_id';
894
		}
895
896
		$delete = $wpdb->delete(
897
			$wpdb->wc_tax_rate_classes,
898
			array(
899
				$field => $item,
900
			)
901
		);
902
903
		if ( $delete ) {
904
			// Delete associated tax rates.
905
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_class = %s;", $tax_class['slug'] ) );
906
			$wpdb->query( "DELETE locations FROM {$wpdb->prefix}woocommerce_tax_rate_locations locations LEFT JOIN {$wpdb->prefix}woocommerce_tax_rates rates ON rates.tax_rate_id = locations.tax_rate_id WHERE rates.tax_rate_id IS NULL;" );
907
		}
908
909
		wp_cache_delete( 'tax-rate-classes', 'taxes' );
910
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
911
912
		return (bool) $delete;
913
	}
914
915
	/**
916
	 * Format the city.
917
	 *
918
	 * @param  string $city Value to format.
919
	 * @return string
920
	 */
921 2
	private static function format_tax_rate_city( $city ) {
922 2
		return strtoupper( trim( $city ) );
923
	}
924
925
	/**
926
	 * Format the state.
927
	 *
928
	 * @param  string $state Value to format.
929
	 * @return string
930
	 */
931 84
	private static function format_tax_rate_state( $state ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
932 84
		$state = strtoupper( $state );
933 84
		return ( '*' === $state ) ? '' : $state;
934
	}
935
936
	/**
937
	 * Format the country.
938
	 *
939
	 * @param  string $country Value to format.
940
	 * @return string
941
	 */
942 84
	private static function format_tax_rate_country( $country ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
943 84
		$country = strtoupper( $country );
944 84
		return ( '*' === $country ) ? '' : $country;
945
	}
946
947
	/**
948
	 * Format the tax rate name.
949
	 *
950
	 * @param  string $name Value to format.
951
	 * @return string
952
	 */
953 84
	private static function format_tax_rate_name( $name ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
954 84
		return $name ? $name : __( 'Tax', 'woocommerce' );
955
	}
956
957
	/**
958
	 * Format the rate.
959
	 *
960
	 * @param  float $rate Value to format.
961
	 * @return string
962
	 */
963 84
	private static function format_tax_rate( $rate ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
964 84
		return number_format( (float) $rate, 4, '.', '' );
965
	}
966
967
	/**
968
	 * Format the priority.
969
	 *
970
	 * @param  string $priority Value to format.
971
	 * @return int
972
	 */
973 84
	private static function format_tax_rate_priority( $priority ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
974 84
		return absint( $priority );
975
	}
976
977
	/**
978
	 * Format the class.
979
	 *
980
	 * @param  string $class Value to format.
981
	 * @return string
982
	 */
983 84
	public static function format_tax_rate_class( $class ) {
984 84
		$class   = sanitize_title( $class );
985 84
		$classes = self::get_tax_class_slugs();
986 84
		if ( ! in_array( $class, $classes, true ) ) {
987 84
			$class = '';
988
		}
989 84
		return ( 'standard' === $class ) ? '' : $class;
990
	}
991
992
	/**
993
	 * Prepare and format tax rate for DB insertion.
994
	 *
995
	 * @param  array $tax_rate Tax rate to format.
996
	 * @return array
997
	 */
998 84
	private static function prepare_tax_rate( $tax_rate ) {
999 84
		foreach ( $tax_rate as $key => $value ) {
1000 84
			if ( method_exists( __CLASS__, 'format_' . $key ) ) {
1001 84
				if ( 'tax_rate_state' === $key ) {
1002 84
					$tax_rate[ $key ] = call_user_func( array( __CLASS__, 'format_' . $key ), sanitize_key( $value ) );
1003
				} else {
1004 84
					$tax_rate[ $key ] = call_user_func( array( __CLASS__, 'format_' . $key ), $value );
1005
				}
1006
			}
1007
		}
1008 84
		return $tax_rate;
1009
	}
1010
1011
	/**
1012
	 * Insert a new tax rate.
1013
	 *
1014
	 * Internal use only.
1015
	 *
1016
	 * @since 2.3.0
1017
	 *
1018
	 * @param  array $tax_rate Tax rate to insert.
1019
	 * @return int tax rate id
1020
	 */
1021 84 View Code Duplication
	public static function _insert_tax_rate( $tax_rate ) {
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...
1022
		global $wpdb;
1023
1024 84
		$wpdb->insert( $wpdb->prefix . 'woocommerce_tax_rates', self::prepare_tax_rate( $tax_rate ) );
1025
1026 84
		$tax_rate_id = $wpdb->insert_id;
1027
1028 84
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
1029
1030 84
		do_action( 'woocommerce_tax_rate_added', $tax_rate_id, $tax_rate );
1031
1032 84
		return $tax_rate_id;
1033
	}
1034
1035
	/**
1036
	 * Get tax rate.
1037
	 *
1038
	 * Internal use only.
1039
	 *
1040
	 * @since 2.5.0
1041
	 *
1042
	 * @param int    $tax_rate_id Tax rate ID.
1043
	 * @param string $output_type Type of output.
1044
	 * @return array|object
1045
	 */
1046 10
	public static function _get_tax_rate( $tax_rate_id, $output_type = ARRAY_A ) {
1047
		global $wpdb;
1048
1049 10
		return $wpdb->get_row(
1050 10
			$wpdb->prepare(
1051 10
				"
1052
					SELECT *
1053 10
					FROM {$wpdb->prefix}woocommerce_tax_rates
1054
					WHERE tax_rate_id = %d
1055
				",
1056
				$tax_rate_id
1057
			),
1058
			$output_type
1059
		);
1060
	}
1061
1062
	/**
1063
	 * Update a tax rate.
1064
	 *
1065
	 * Internal use only.
1066
	 *
1067
	 * @since 2.3.0
1068
	 *
1069
	 * @param int   $tax_rate_id Tax rate to update.
1070
	 * @param array $tax_rate Tax rate values.
1071
	 */
1072 1 View Code Duplication
	public static function _update_tax_rate( $tax_rate_id, $tax_rate ) {
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...
1073
		global $wpdb;
1074
1075 1
		$tax_rate_id = absint( $tax_rate_id );
1076
1077 1
		$wpdb->update(
1078 1
			$wpdb->prefix . 'woocommerce_tax_rates',
1079 1
			self::prepare_tax_rate( $tax_rate ),
1080
			array(
1081 1
				'tax_rate_id' => $tax_rate_id,
1082
			)
1083
		);
1084
1085 1
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
1086
1087 1
		do_action( 'woocommerce_tax_rate_updated', $tax_rate_id, $tax_rate );
1088
	}
1089
1090
	/**
1091
	 * Delete a tax rate from the database.
1092
	 *
1093
	 * Internal use only.
1094
	 *
1095
	 * @since 2.3.0
1096
	 * @param  int $tax_rate_id Tax rate to delete.
1097
	 */
1098 4
	public static function _delete_tax_rate( $tax_rate_id ) {
1099
		global $wpdb;
1100
1101 4
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE tax_rate_id = %d;", $tax_rate_id ) );
1102 4
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $tax_rate_id ) );
1103
1104 4
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
1105
1106 4
		do_action( 'woocommerce_tax_rate_deleted', $tax_rate_id );
1107
	}
1108
1109
	/**
1110
	 * Update postcodes for a tax rate in the DB.
1111
	 *
1112
	 * Internal use only.
1113
	 *
1114
	 * @since 2.3.0
1115
	 *
1116
	 * @param int    $tax_rate_id Tax rate to update.
1117
	 * @param string $postcodes String of postcodes separated by ; characters.
1118
	 */
1119 2
	public static function _update_tax_rate_postcodes( $tax_rate_id, $postcodes ) {
1120 2
		if ( ! is_array( $postcodes ) ) {
1121 2
			$postcodes = explode( ';', $postcodes );
1122
		}
1123
		// No normalization - postcodes are matched against both normal and formatted versions to support wildcards.
1124 2
		foreach ( $postcodes as $key => $postcode ) {
1125 2
			$postcodes[ $key ] = strtoupper( trim( str_replace( chr( 226 ) . chr( 128 ) . chr( 166 ), '...', $postcode ) ) );
1126
		}
1127 2
		self::update_tax_rate_locations( $tax_rate_id, array_diff( array_filter( $postcodes ), array( '*' ) ), 'postcode' );
1128
	}
1129
1130
	/**
1131
	 * Update cities for a tax rate in the DB.
1132
	 *
1133
	 * Internal use only.
1134
	 *
1135
	 * @since 2.3.0
1136
	 *
1137
	 * @param int    $tax_rate_id Tax rate to update.
1138
	 * @param string $cities Cities to set.
1139
	 */
1140 2
	public static function _update_tax_rate_cities( $tax_rate_id, $cities ) {
1141 2
		if ( ! is_array( $cities ) ) {
1142 2
			$cities = explode( ';', $cities );
1143
		}
1144 2
		$cities = array_filter( array_diff( array_map( array( __CLASS__, 'format_tax_rate_city' ), $cities ), array( '*' ) ) );
1145
1146 2
		self::update_tax_rate_locations( $tax_rate_id, $cities, 'city' );
1147
	}
1148
1149
	/**
1150
	 * Updates locations (postcode and city).
1151
	 *
1152
	 * Internal use only.
1153
	 *
1154
	 * @since 2.3.0
1155
	 *
1156
	 * @param int    $tax_rate_id Tax rate ID to update.
1157
	 * @param array  $values Values to set.
1158
	 * @param string $type Location type.
1159
	 */
1160 3
	private static function update_tax_rate_locations( $tax_rate_id, $values, $type ) {
1161
		global $wpdb;
1162
1163 3
		$tax_rate_id = absint( $tax_rate_id );
1164
1165 3
		$wpdb->query(
1166 3
			$wpdb->prepare(
1167 3
				"DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE tax_rate_id = %d AND location_type = %s;",
1168
				$tax_rate_id,
1169
				$type
1170
			)
1171
		);
1172
1173 3
		if ( count( $values ) > 0 ) {
1174 3
			$sql = "( '" . implode( "', $tax_rate_id, '" . esc_sql( $type ) . "' ),( '", array_map( 'esc_sql', $values ) ) . "', $tax_rate_id, '" . esc_sql( $type ) . "' )";
1175
1176 3
			$wpdb->query( "INSERT INTO {$wpdb->prefix}woocommerce_tax_rate_locations ( location_code, tax_rate_id, location_type ) VALUES $sql;" ); // @codingStandardsIgnoreLine.
1177
		}
1178
1179 3
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
1180
	}
1181
1182
	/**
1183
	 * Used by admin settings page.
1184
	 *
1185
	 * @param string $tax_class Tax class slug.
1186
	 *
1187
	 * @return array|null|object
1188
	 */
1189
	public static function get_rates_for_tax_class( $tax_class ) {
1190
		global $wpdb;
1191
1192
		// Get all the rates and locations. Snagging all at once should significantly cut down on the number of queries.
1193
		$rates     = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}woocommerce_tax_rates` WHERE `tax_rate_class` = %s;", sanitize_title( $tax_class ) ) );
1194
		$locations = $wpdb->get_results( "SELECT * FROM `{$wpdb->prefix}woocommerce_tax_rate_locations`" );
1195
1196
		if ( ! empty( $rates ) ) {
1197
			// Set the rates keys equal to their ids.
1198
			$rates = array_combine( wp_list_pluck( $rates, 'tax_rate_id' ), $rates );
1199
		}
1200
1201
		// Drop the locations into the rates array.
1202
		foreach ( $locations as $location ) {
1203
			// Don't set them for unexistent rates.
1204
			if ( ! isset( $rates[ $location->tax_rate_id ] ) ) {
1205
				continue;
1206
			}
1207
			// If the rate exists, initialize the array before appending to it.
1208
			if ( ! isset( $rates[ $location->tax_rate_id ]->{$location->location_type} ) ) {
1209
				$rates[ $location->tax_rate_id ]->{$location->location_type} = array();
1210
			}
1211
			$rates[ $location->tax_rate_id ]->{$location->location_type}[] = $location->location_code;
1212
		}
1213
1214
		foreach ( $rates as $rate_id => $rate ) {
1215
			$rates[ $rate_id ]->postcode_count = isset( $rates[ $rate_id ]->postcode ) ? count( $rates[ $rate_id ]->postcode ) : 0;
1216
			$rates[ $rate_id ]->city_count     = isset( $rates[ $rate_id ]->city ) ? count( $rates[ $rate_id ]->city ) : 0;
1217
		}
1218
1219
		$rates = self::sort_rates( $rates );
1220
1221
		return $rates;
1222
	}
1223
}
1224
WC_Tax::init();
1225