Completed
Push — master ( 870827...c346a1 )
by Mike
10:38
created

WC_Tax::sort_rates_callback()   C

Complexity

Conditions 12
Paths 12

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 17
c 1
b 0
f 0
nc 12
nop 2
dl 0
loc 23
rs 5.2987

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
if ( ! defined( 'ABSPATH' ) ) {
4
	exit; // Exit if accessed directly
5
}
6
7
/**
8
 * Performs tax calculations and loads tax rates
9
 *
10
 * @class 		WC_Tax
11
 * @version		2.2.0
12
 * @package		WooCommerce/Classes
13
 * @category	Class
14
 * @author 		WooThemes
15
 */
16
class WC_Tax {
17
18
	/**
19
	 * Precision.
20
	 *
21
	 * @var int
22
	 */
23
	public static $precision;
24
25
	/**
26
	 * Round at subtotal.
27
	 *
28
	 * @var bool
29
	 */
30
	public static $round_at_subtotal;
31
32
	/**
33
	 * Load options.
34
	 *
35
	 * @access public
36
	 */
37
	public static function init() {
38
		self::$precision         = wc_get_rounding_precision();
39
		self::$round_at_subtotal = 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
40
	}
41
42
	/**
43
	 * Calculate tax for a line.
44
	 * @param  float  $price              Price to calc tax on
45
	 * @param  array  $rates              Rates to apply
46
	 * @param  boolean $price_includes_tax Whether the passed price has taxes included
47
	 * @param  boolean $suppress_rounding  Whether to suppress any rounding from taking place
48
	 * @return array                      Array of rates + prices after tax
49
	 */
50
	public static function calc_tax( $price, $rates, $price_includes_tax = false, $suppress_rounding = false ) {
51
		// Work in pence to X precision
52
		$price = self::precision( $price );
53
54
		if ( $price_includes_tax ) {
55
			$taxes = self::calc_inclusive_tax( $price, $rates );
56
		} else {
57
			$taxes = self::calc_exclusive_tax( $price, $rates );
58
		}
59
60
		// Round to precision
61
		if ( ! self::$round_at_subtotal && ! $suppress_rounding ) {
62
			$taxes = array_map( 'round', $taxes ); // Round to precision
63
		}
64
65
		// Remove precision
66
		$price     = self::remove_precision( $price );
67
		$taxes     = array_map( array( __CLASS__, 'remove_precision' ), $taxes );
68
69
		return apply_filters( 'woocommerce_calc_tax', $taxes, $price, $rates, $price_includes_tax, $suppress_rounding );
70
	}
71
72
	/**
73
	 * Calculate the shipping tax using a passed array of rates.
74
	 *
75
	 * @param   float		Price
76
	 * @param	array		Taxation Rate
77
	 * @return  array
78
	 */
79
	public static function calc_shipping_tax( $price, $rates ) {
80
		return self::calc_exclusive_tax( $price, $rates );
81
	}
82
83
	/**
84
	 * Multiply cost by pow precision.
85
	 * @param  float $price
86
	 * @return float
87
	 */
88
	private static function precision( $price ) {
89
		return $price * ( pow( 10, self::$precision ) );
90
	}
91
92
	/**
93
	 * Divide cost by pow precision.
94
	 * @param  float $price
95
	 * @return float
96
	 */
97
	private static function remove_precision( $price ) {
98
		return $price / ( pow( 10, self::$precision ) );
99
	}
100
101
	/**
102
	 * Round to precision.
103
	 *
104
	 * Filter example: to return rounding to .5 cents you'd use:
105
	 *
106
	 * function euro_5cent_rounding( $in ) {
107
	 *      return round( $in / 5, 2 ) * 5;
108
	 * }
109
	 * add_filter( 'woocommerce_tax_round', 'euro_5cent_rounding' );
110
	 * @return double
111
	 */
112
	public static function round( $in ) {
113
		return apply_filters( 'woocommerce_tax_round', round( $in, self::$precision ), $in );
114
	}
115
116
	/**
117
	 * Calc tax from inclusive price.
118
	 *
119
	 * @param  float $price
120
	 * @param  array $rates
121
	 * @return array
122
	 */
123
	public static function calc_inclusive_tax( $price, $rates ) {
124
		$taxes = array();
125
126
		$regular_tax_rates = $compound_tax_rates = 0;
127
128
		foreach ( $rates as $key => $rate )
129
			if ( $rate['compound'] == 'yes' )
130
				$compound_tax_rates = $compound_tax_rates + $rate['rate'];
131
			else
132
				$regular_tax_rates  = $regular_tax_rates + $rate['rate'];
133
134
		$regular_tax_rate 	= 1 + ( $regular_tax_rates / 100 );
135
		$compound_tax_rate 	= 1 + ( $compound_tax_rates / 100 );
136
		$non_compound_price = $price / $compound_tax_rate;
137
138
		foreach ( $rates as $key => $rate ) {
139
			if ( ! isset( $taxes[ $key ] ) )
140
				$taxes[ $key ] = 0;
141
142
			$the_rate      = $rate['rate'] / 100;
143
144
			if ( $rate['compound'] == 'yes' ) {
145
				$the_price = $price;
146
				$the_rate  = $the_rate / $compound_tax_rate;
147
			} else {
148
				$the_price = $non_compound_price;
149
				$the_rate  = $the_rate / $regular_tax_rate;
150
			}
151
152
			$net_price       = $price - ( $the_rate * $the_price );
153
			$tax_amount      = $price - $net_price;
154
			$taxes[ $key ]   += apply_filters( 'woocommerce_price_inc_tax_amount', $tax_amount, $key, $rate, $price );
155
		}
156
157
		return $taxes;
158
	}
159
160
	/**
161
	 * Calc tax from exclusive price.
162
	 *
163
	 * @param  float $price
164
	 * @param  array $rates
165
	 * @return array
166
	 */
167
	public static function calc_exclusive_tax( $price, $rates ) {
168
		$taxes = array();
169
170
		if ( ! empty( $rates ) ) {
171
			// Multiple taxes
172
			foreach ( $rates as $key => $rate ) {
173
174
				if ( $rate['compound'] == 'yes' )
175
					continue;
176
177
				$tax_amount = $price * ( $rate['rate'] / 100 );
178
179
				// ADVANCED: Allow third parties to modify this rate
180
				$tax_amount = apply_filters( 'woocommerce_price_ex_tax_amount', $tax_amount, $key, $rate, $price );
181
182
				// Add rate
183 View Code Duplication
				if ( ! isset( $taxes[ $key ] ) )
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...
184
					$taxes[ $key ] = $tax_amount;
185
				else
186
					$taxes[ $key ] += $tax_amount;
187
			}
188
189
			$pre_compound_total = array_sum( $taxes );
190
191
			// Compound taxes
192
			foreach ( $rates as $key => $rate ) {
193
194
				if ( $rate['compound'] == 'no' )
195
					continue;
196
197
				$the_price_inc_tax = $price + ( $pre_compound_total );
198
199
				$tax_amount = $the_price_inc_tax * ( $rate['rate'] / 100 );
200
201
				// ADVANCED: Allow third parties to modify this rate
202
				$tax_amount = apply_filters( 'woocommerce_price_ex_tax_amount', $tax_amount, $key, $rate, $price, $the_price_inc_tax, $pre_compound_total );
203
204
				// Add rate
205 View Code Duplication
				if ( ! isset( $taxes[ $key ] ) )
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...
206
					$taxes[ $key ] = $tax_amount;
207
				else
208
					$taxes[ $key ] += $tax_amount;
209
			}
210
		}
211
212
		return $taxes;
213
	}
214
215
	/**
216
	 * Searches for all matching country/state/postcode tax rates.
217
	 *
218
	 * @param array $args
219
	 * @return array
220
	 */
221
	public static function find_rates( $args = array() ) {
222
		$args = wp_parse_args( $args, array(
223
			'country'   => '',
224
			'state'     => '',
225
			'city'      => '',
226
			'postcode'  => '',
227
			'tax_class' => ''
228
		) );
229
230
		extract( $args, EXTR_SKIP );
231
232
		if ( ! $country ) {
233
			return array();
234
		}
235
236
		$postcode          = wc_normalize_postcode( wc_clean( $postcode ) );
237
		$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 ) );
238
		$matched_tax_rates = wp_cache_get( $cache_key, 'taxes' );
239
240
		if ( false === $matched_tax_rates ) {
241
			$matched_tax_rates = self::get_matched_tax_rates( $country, $state, $postcode, $city, $tax_class );
242
			wp_cache_set( $cache_key, $matched_tax_rates, 'taxes' );
243
		}
244
245
		return apply_filters( 'woocommerce_find_rates', $matched_tax_rates, $args );
246
	}
247
248
	/**
249
	 * Searches for all matching country/state/postcode tax rates.
250
	 *
251
	 * @param array $args
252
	 * @return array
253
	 */
254
	public static function find_shipping_rates( $args = array() ) {
255
		$rates          = self::find_rates( $args );
256
		$shipping_rates = array();
257
258
		if ( is_array( $rates ) ) {
259
			foreach ( $rates as $key => $rate ) {
260
				if ( 'yes' === $rate['shipping'] ) {
261
					$shipping_rates[ $key ] = $rate;
262
				}
263
			}
264
		}
265
266
		return $shipping_rates;
267
	}
268
269
	/**
270
	 * Does the sort comparison.
271
	 */
272
	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...
273
		if ( $rate1->tax_rate_priority !== $rate2->tax_rate_priority ) {
274
			return $rate1->tax_rate_priority < $rate2->tax_rate_priority ? -1 : 1; // ASC
275
		} elseif ( $rate1->tax_rate_country !== $rate2->tax_rate_country ) {
276
			if ( '' === $rate1->tax_rate_country ) {
277
				return 1;
278
			}
279
			if ( '' === $rate2->tax_rate_country ) {
280
				return -1;
281
			}
282
			return strcmp( $rate1->tax_rate_country, $rate2->tax_rate_country ) > 0 ? 1 : -1;
283
		} elseif ( $rate1->tax_rate_state !== $rate2->tax_rate_state ) {
284
			if ( '' === $rate1->tax_rate_state ) {
285
				return 1;
286
			}
287
			if ( '' === $rate2->tax_rate_state ) {
288
				return -1;
289
			}
290
			return strcmp( $rate1->tax_rate_state, $rate2->tax_rate_state ) > 0 ? 1 : -1;
291
		} else {
292
			return $rate1->tax_rate_id < $rate2->tax_rate_id ? -1 : 1; // Identical - use ID
293
		}
294
	}
295
296
	/**
297
	 * Logical sort order for tax rates based on the following in order of priority:
298
	 * 		- Priority
299
	 * 		- County code
300
	 * 		- State code
301
	 * @param  array $rates
302
	 * @return array
303
	 * @todo   remove tax_rate_order column
304
	 */
305
	private static function sort_rates( $rates ) {
306
		uasort( $rates, __CLASS__ . '::sort_rates_callback' );
307
		$i = 0;
308
		foreach ( $rates as $key => $rate ) {
309
			$rates[ $key ]->tax_rate_order = $i++;
310
		}
311
		return $rates;
312
	}
313
314
	/**
315
	 * Loop through a set of tax rates and get the matching rates (1 per priority).
316
	 *
317
	 * @param  string $country
318
	 * @param  string $state
319
	 * @param  string $postcode
320
	 * @param  string $city
321
	 * @param  string $tax_class
322
	 * @return array
323
	 */
324
	private static function get_matched_tax_rates( $country, $state, $postcode, $city, $tax_class ) {
325
		global $wpdb;
326
327
		// Query criteria - these will be ANDed
328
		$criteria   = array();
329
		$criteria[] = $wpdb->prepare( "tax_rate_country IN ( %s, '' )", strtoupper( $country ) );
330
		$criteria[] = $wpdb->prepare( "tax_rate_state IN ( %s, '' )", strtoupper( $state ) );
331
		$criteria[] = $wpdb->prepare( "tax_rate_class = %s", sanitize_title( $tax_class ) );
332
333
		// Pre-query postcode ranges for PHP based matching.
334
		$postcode_search = wc_get_wildcard_postcodes( $postcode, $country );
335
		$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 '%...%';" );
336
337
		if ( $postcode_ranges ) {
338
			$matches = wc_postcode_location_matcher( $postcode, $postcode_ranges, 'tax_rate_id', 'location_code', $country );
339
			if ( ! empty( $matches ) ) {
340
				foreach ( $matches as $matched_postcodes ) {
341
					$postcode_search = array_merge( $postcode_search, $matched_postcodes );
342
				}
343
			}
344
		}
345
346
		$postcode_search = array_unique( $postcode_search );
347
348
		/**
349
		 * Location matching criteria - ORed
350
		 * Needs to match:
351
		 * 	- rates with no postcodes and cities
352
		 * 	- rates with a matching postcode and city
353
		 * 	- rates with matching postcode, no city
354
		 * 	- rates with matching city, no postcode
355
		 */
356
		$locations_criteria   = array();
357
		$locations_criteria[] = "locations.location_type IS NULL";
358
		$locations_criteria[] = "
359
			locations.location_type = 'postcode' AND locations.location_code IN ('" . implode( "','", array_map( 'esc_sql', $postcode_search ) ) . "')
360
			AND (
361
				( locations2.location_type = 'city' AND locations2.location_code = '" . esc_sql( strtoupper( $city ) ) . "' )
362
				OR NOT EXISTS (
363
					SELECT sub.tax_rate_id FROM {$wpdb->prefix}woocommerce_tax_rate_locations as sub
364
					WHERE sub.location_type = 'city'
365
					AND sub.tax_rate_id = tax_rates.tax_rate_id
366
				)
367
			)
368
		";
369
		$locations_criteria[] = "
370
			locations.location_type = 'city' AND locations.location_code = '" . esc_sql( strtoupper( $city ) ) . "'
371
			AND NOT EXISTS (
372
				SELECT sub.tax_rate_id FROM {$wpdb->prefix}woocommerce_tax_rate_locations as sub
373
				WHERE sub.location_type = 'postcode'
374
				AND sub.tax_rate_id = tax_rates.tax_rate_id
375
			)
376
		";
377
		$criteria[] = '( ( ' . implode( ' ) OR ( ', $locations_criteria ) . ' ) )';
378
379
		$found_rates = $wpdb->get_results( "
380
			SELECT tax_rates.*
381
			FROM {$wpdb->prefix}woocommerce_tax_rates as tax_rates
382
			LEFT OUTER JOIN {$wpdb->prefix}woocommerce_tax_rate_locations as locations ON tax_rates.tax_rate_id = locations.tax_rate_id
383
			LEFT OUTER JOIN {$wpdb->prefix}woocommerce_tax_rate_locations as locations2 ON tax_rates.tax_rate_id = locations2.tax_rate_id
384
			WHERE 1=1 AND " . implode( ' AND ', $criteria ) . "
385
			GROUP BY tax_rate_id
386
			ORDER BY tax_rate_priority
387
		" );
388
389
		$found_rates       = self::sort_rates( $found_rates );
390
		$matched_tax_rates = array();
391
		$found_priority    = array();
392
393
		foreach ( $found_rates as $found_rate ) {
394
			if ( in_array( $found_rate->tax_rate_priority, $found_priority ) ) {
395
				continue;
396
			}
397
398
			$matched_tax_rates[ $found_rate->tax_rate_id ] = array(
399
				'rate'     => $found_rate->tax_rate,
400
				'label'    => $found_rate->tax_rate_name,
401
				'shipping' => $found_rate->tax_rate_shipping ? 'yes' : 'no',
402
				'compound' => $found_rate->tax_rate_compound ? 'yes' : 'no'
403
			);
404
405
			$found_priority[] = $found_rate->tax_rate_priority;
406
		}
407
408
		return apply_filters( 'woocommerce_matched_tax_rates', $matched_tax_rates, $country, $state, $postcode, $city, $tax_class );
409
	}
410
411
	/**
412
	 * Get the customer tax location based on their status and the current page.
413
	 *
414
	 * Used by get_rates(), get_shipping_rates().
415
	 *
416
	 * @param  $tax_class string Optional, passed to the filter for advanced tax setups.
417
	 * @return array
418
	 */
419
	public static function get_tax_location( $tax_class = '' ) {
420
		$location = array();
421
422
		if ( ! empty( WC()->customer ) ) {
423
			$location = WC()->customer->get_taxable_address();
424
		} elseif ( wc_prices_include_tax() || 'base' === get_option( 'woocommerce_default_customer_address' ) || 'base' === get_option( 'woocommerce_tax_based_on' ) ) {
425
			$location = array(
426
				WC()->countries->get_base_country(),
427
				WC()->countries->get_base_state(),
428
				WC()->countries->get_base_postcode(),
429
				WC()->countries->get_base_city()
430
			);
431
		}
432
433
		return apply_filters( 'woocommerce_get_tax_location', $location, $tax_class );
434
	}
435
436
	/**
437
	 * Get's an array of matching rates for a tax class.
438
	 * @param string $tax_class
439
	 * @return  array
440
	 */
441
	public static function get_rates( $tax_class = '' ) {
442
		$tax_class         = sanitize_title( $tax_class );
443
		$location          = self::get_tax_location( $tax_class );
444
		$matched_tax_rates = array();
445
446
		if ( sizeof( $location ) === 4 ) {
447
			list( $country, $state, $postcode, $city ) = $location;
448
449
			$matched_tax_rates = self::find_rates( array(
450
				'country' 	=> $country,
451
				'state' 	=> $state,
452
				'postcode' 	=> $postcode,
453
				'city' 		=> $city,
454
				'tax_class' => $tax_class
455
			) );
456
		}
457
458
		return apply_filters( 'woocommerce_matched_rates', $matched_tax_rates, $tax_class );
459
	}
460
461
	/**
462
	 * Get's an array of matching rates for the shop's base country.
463
	 *
464
	 * @param   string	Tax Class
465
	 * @return  array
466
	 */
467
	public static function get_base_tax_rates( $tax_class = '' ) {
468
		return apply_filters( 'woocommerce_base_tax_rates', self::find_rates( array(
469
			'country' 	=> WC()->countries->get_base_country(),
470
			'state' 	=> WC()->countries->get_base_state(),
471
			'postcode' 	=> WC()->countries->get_base_postcode(),
472
			'city' 		=> WC()->countries->get_base_city(),
473
			'tax_class' => $tax_class
474
		) ), $tax_class );
475
	}
476
477
	/**
478
	 * Alias for get_base_tax_rates().
479
	 *
480
	 * @deprecated 2.3
481
	 * @param   string	Tax Class
482
	 * @return  array
483
	 */
484
	public static function get_shop_base_rate( $tax_class = '' ) {
485
		return self::get_base_tax_rates( $tax_class );
486
	}
487
488
	/**
489
	 * Gets an array of matching shipping tax rates for a given class.
490
	 *
491
	 * @param   string	Tax Class
492
	 * @return  mixed
493
	 */
494
	public static function get_shipping_tax_rates( $tax_class = null ) {
495
		// See if we have an explicitly set shipping tax class
496
		if ( $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' ) ) {
497
			$tax_class = 'standard' === $shipping_tax_class ? '' : $shipping_tax_class;
498
		}
499
500
		$location          = self::get_tax_location( $tax_class );
501
		$matched_tax_rates = array();
502
503
		if ( sizeof( $location ) === 4 ) {
504
			list( $country, $state, $postcode, $city ) = $location;
505
506
			if ( ! is_null( $tax_class ) ) {
507
				// This will be per item shipping
508
				$matched_tax_rates = self::find_shipping_rates( array(
509
					'country' 	=> $country,
510
					'state' 	=> $state,
511
					'postcode' 	=> $postcode,
512
					'city' 		=> $city,
513
					'tax_class' => $tax_class
514
				) );
515
516 View Code Duplication
			} else {
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...
517
518
				// This will be per order shipping - loop through the order and find the highest tax class rate
519
				$cart_tax_classes = WC()->cart->get_cart_item_tax_classes();
520
521
				// If multiple classes are found, use the first one. Don't bother with standard rate, we can get that later.
522
				if ( sizeof( $cart_tax_classes ) > 1 && ! in_array( '', $cart_tax_classes ) ) {
523
					$tax_classes = self::get_tax_classes();
524
525
					foreach ( $tax_classes as $tax_class ) {
526
						$tax_class = sanitize_title( $tax_class );
527
						if ( in_array( $tax_class, $cart_tax_classes ) ) {
528
							$matched_tax_rates = self::find_shipping_rates( array(
529
								'country' 	=> $country,
530
								'state' 	=> $state,
531
								'postcode' 	=> $postcode,
532
								'city' 		=> $city,
533
								'tax_class' => $tax_class
534
							) );
535
							break;
536
						}
537
					}
538
539
				// If a single tax class is found, use it
540
				} elseif ( sizeof( $cart_tax_classes ) == 1 ) {
541
					$matched_tax_rates = self::find_shipping_rates( array(
542
						'country' 	=> $country,
543
						'state' 	=> $state,
544
						'postcode' 	=> $postcode,
545
						'city' 		=> $city,
546
						'tax_class' => $cart_tax_classes[0]
547
					) );
548
				}
549
			}
550
551
			// Get standard rate if no taxes were found
552
			if ( ! sizeof( $matched_tax_rates ) ) {
553
				$matched_tax_rates = self::find_shipping_rates( array(
554
					'country' 	=> $country,
555
					'state' 	=> $state,
556
					'postcode' 	=> $postcode,
557
					'city' 		=> $city
558
				) );
559
			}
560
		}
561
562
		return $matched_tax_rates;
563
	}
564
565
	/**
566
	 * Return true/false depending on if a rate is a compound rate.
567
	 *
568
	 * @param   int		key
569
	 * @return  bool
570
	 */
571
	public static function is_compound( $key ) {
572
		global $wpdb;
573
		return $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_compound FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) ) ? true : false;
574
	}
575
576
	/**
577
	 * Return a given rates label.
578
	 *
579
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format
580
	 * @return  string
581
	 */
582
	public static function get_rate_label( $key_or_rate ) {
583
		global $wpdb;
584
585 View Code Duplication
		if ( is_object( $key_or_rate ) ) {
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...
586
			$key       = $key_or_rate->tax_rate_id;
587
			$rate_name = $key_or_rate->tax_rate_name;
588
		} else {
589
			$key       = $key_or_rate;
590
			$rate_name = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_name FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
591
		}
592
593
		if ( ! $rate_name ) {
594
			$rate_name = WC()->countries->tax_or_vat();
595
		}
596
597
		return apply_filters( 'woocommerce_rate_label', $rate_name, $key );
598
	}
599
600
	/**
601
	 * Return a given rates percent.
602
	 *
603
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format
604
	 * @return  string
605
	 */
606
	public static function get_rate_percent( $key_or_rate ) {
607
		global $wpdb;
608
609 View Code Duplication
		if ( is_object( $key_or_rate ) ) {
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...
610
			$key      = $key_or_rate->tax_rate_id;
611
			$tax_rate = $key_or_rate->tax_rate;
612
		} else {
613
			$key      = $key_or_rate;
614
			$tax_rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
615
		}
616
617
		return apply_filters( 'woocommerce_rate_percent', floatval( $tax_rate ) . '%', $key );
618
	}
619
620
	/**
621
	 * Get a rates code. Code is made up of COUNTRY-STATE-NAME-Priority. E.g GB-VAT-1, US-AL-TAX-1.
622
	 *
623
	 * @access public
624
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format
625
	 * @return string
626
	 */
627
	public static function get_rate_code( $key_or_rate ) {
628
		global $wpdb;
629
630 View Code Duplication
		if ( is_object( $key_or_rate ) ) {
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...
631
			$key  = $key_or_rate->tax_rate_id;
632
			$rate = $key_or_rate;
633
		} else {
634
			$key  = $key_or_rate;
635
			$rate = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
636
		}
637
638
		$code_string = '';
639
640
		if ( null !== $rate ) {
641
			$code   = array();
642
			$code[] = $rate->tax_rate_country;
643
			$code[] = $rate->tax_rate_state;
644
			$code[] = $rate->tax_rate_name ? $rate->tax_rate_name : 'TAX';
645
			$code[] = absint( $rate->tax_rate_priority );
646
			$code_string = strtoupper( implode( '-', array_filter( $code ) ) );
647
		}
648
649
		return apply_filters( 'woocommerce_rate_code', $code_string, $key );
650
	}
651
652
	/**
653
	 * Round tax lines and return the sum.
654
	 *
655
	 * @param   array
656
	 * @return  float
657
	 */
658
	public static function get_tax_total( $taxes ) {
659
		return array_sum( array_map( array( __CLASS__, 'round' ), $taxes ) );
660
	}
661
662
	/**
663
	 * Get store tax classes.
664
	 * @return array
665
	 */
666
	public static function get_tax_classes() {
667
		return array_filter( array_map( 'trim', explode( "\n", get_option( 'woocommerce_tax_classes' ) ) ) );
668
	}
669
670
	/**
671
	 * format the city.
672
	 * @param  string $city
673
	 * @return string
674
	 */
675
	private static function format_tax_rate_city( $city ) {
676
		return strtoupper( trim( $city ) );
677
	}
678
679
	/**
680
	 * format the state.
681
	 * @param  string $state
682
	 * @return string
683
	 */
684
	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...
685
		$state = strtoupper( $state );
686
		return $state === '*' ? '' : $state;
687
	}
688
689
	/**
690
	 * format the country.
691
	 * @param  string $country
692
	 * @return string
693
	 */
694
	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...
695
		$country = strtoupper( $country );
696
		return $country === '*' ? '' : $country;
697
	}
698
699
	/**
700
	 * format the tax rate name.
701
	 * @param  string $name
702
	 * @return string
703
	 */
704
	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...
705
		return $name ? $name : __( 'Tax', 'woocommerce' );
706
	}
707
708
	/**
709
	 * format the rate.
710
	 * @param  double $rate
711
	 * @return string
712
	 */
713
	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...
714
		return number_format( (double) $rate, 4, '.', '' );
715
	}
716
717
	/**
718
	 * format the priority.
719
	 * @param  string $priority
720
	 * @return int
721
	 */
722
	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...
723
		return absint( $priority );
724
	}
725
726
	/**
727
	 * format the class.
728
	 * @param  string $class
729
	 * @return string
730
	 */
731
	public static function format_tax_rate_class( $class ) {
732
		$class = sanitize_title( $class );
733
		$sanitized_classes = array_map( 'sanitize_title', self::get_tax_classes() );
734
		if ( ! in_array( $class, $sanitized_classes ) ) {
735
			$class = '';
736
		}
737
		return $class === 'standard' ? '' : $class;
738
	}
739
740
	/**
741
	 * Prepare and format tax rate for DB insertion.
742
	 * @param  array $tax_rate
743
	 * @return array
744
	 */
745
	private static function prepare_tax_rate( $tax_rate ) {
746
		foreach ( $tax_rate as $key => $value ) {
747
			if ( method_exists( __CLASS__, 'format_' . $key ) ) {
748
				$tax_rate[ $key ] = call_user_func( array( __CLASS__, 'format_' . $key ), $value );
749
			}
750
		}
751
		return $tax_rate;
752
	}
753
754
	/**
755
	 * Insert a new tax rate.
756
	 *
757
	 * Internal use only.
758
	 *
759
	 * @since 2.3.0
760
	 * @access private
761
	 *
762
	 * @param  array $tax_rate
763
	 *
764
	 * @return int tax rate id
765
	 */
766
	public static function _insert_tax_rate( $tax_rate ) {
767
		global $wpdb;
768
769
		$wpdb->insert( $wpdb->prefix . 'woocommerce_tax_rates', self::prepare_tax_rate( $tax_rate ) );
770
771
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
772
773
		do_action( 'woocommerce_tax_rate_added', $wpdb->insert_id, $tax_rate );
774
775
		return $wpdb->insert_id;
776
	}
777
778
	/**
779
	 * Get tax rate.
780
	 *
781
	 * Internal use only.
782
	 *
783
	 * @since 2.5.0
784
	 * @access private
785
	 *
786
	 * @param int $tax_rate_id
787
	 * @param string $output_type
788
	 *
789
	 * @return array
790
	 */
791
	public static function _get_tax_rate( $tax_rate_id, $output_type = ARRAY_A ) {
792
		global $wpdb;
793
794
		return $wpdb->get_row( $wpdb->prepare( "
795
			SELECT *
796
			FROM {$wpdb->prefix}woocommerce_tax_rates
797
			WHERE tax_rate_id = %d
798
		", $tax_rate_id ), $output_type );
799
	}
800
801
	/**
802
	 * Update a tax rate.
803
	 *
804
	 * Internal use only.
805
	 *
806
	 * @since 2.3.0
807
	 * @access private
808
	 *
809
	 * @param int $tax_rate_id
810
	 * @param array $tax_rate
811
	 */
812
	public static function _update_tax_rate( $tax_rate_id, $tax_rate ) {
813
		global $wpdb;
814
815
		$tax_rate_id = absint( $tax_rate_id );
816
817
		$wpdb->update(
818
			$wpdb->prefix . "woocommerce_tax_rates",
819
			self::prepare_tax_rate( $tax_rate ),
820
			array(
821
				'tax_rate_id' => $tax_rate_id
822
			)
823
		);
824
825
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
826
827
		do_action( 'woocommerce_tax_rate_updated', $tax_rate_id, $tax_rate );
828
	}
829
830
	/**
831
	 * Delete a tax rate from the database.
832
	 *
833
	 * Internal use only.
834
	 *
835
	 * @since 2.3.0
836
	 * @access private
837
	 *
838
	 * @param  int $tax_rate_id
839
	 */
840
	public static function _delete_tax_rate( $tax_rate_id ) {
841
		global $wpdb;
842
843
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE tax_rate_id = %d;", $tax_rate_id ) );
844
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $tax_rate_id ) );
845
846
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
847
848
		do_action( 'woocommerce_tax_rate_deleted', $tax_rate_id );
849
	}
850
851
	/**
852
	 * Update postcodes for a tax rate in the DB.
853
	 *
854
	 * Internal use only.
855
	 *
856
	 * @since 2.3.0
857
	 * @access private
858
	 *
859
	 * @param  int $tax_rate_id
860
	 * @param  string $postcodes String of postcodes separated by ; characters
861
	 * @return string
862
	 */
863
	public static function _update_tax_rate_postcodes( $tax_rate_id, $postcodes ) {
864
		if ( ! is_array( $postcodes ) ) {
865
			$postcodes = explode( ';', $postcodes );
866
		}
867
		// No normalization - postcodes are matched against both normal and formatted versions to support wildcards.
868
		foreach ( $postcodes as $key => $postcode ) {
869
			$postcodes[ $key ] = strtoupper( trim( str_replace( chr( 226 ) . chr( 128 ) . chr( 166 ), '...', $postcode ) ) );
870
		}
871
		self::_update_tax_rate_locations( $tax_rate_id, array_diff( array_filter( $postcodes ), array( '*' ) ), 'postcode' );
872
	}
873
874
	/**
875
	 * Update cities for a tax rate in the DB.
876
	 *
877
	 * Internal use only.
878
	 *
879
	 * @since 2.3.0
880
	 * @access private
881
	 *
882
	 * @param  int $tax_rate_id
883
	 * @param  string $cities
884
	 * @return string
885
	 */
886
	public static function _update_tax_rate_cities( $tax_rate_id, $cities ) {
887
		if ( ! is_array( $cities ) ) {
888
			$cities = explode( ';', $cities );
889
		}
890
		$cities = array_filter( array_diff( array_map( array( __CLASS__, 'format_tax_rate_city' ), $cities ), array( '*' ) ) );
891
892
		self::_update_tax_rate_locations( $tax_rate_id, $cities, 'city' );
893
	}
894
895
	/**
896
	 * Updates locations (postcode and city).
897
	 *
898
	 * Internal use only.
899
	 *
900
	 * @since 2.3.0
901
	 * @access private
902
	 *
903
	 * @param  int $tax_rate_id
904
	 * @param string $type
905
	 * @return string
906
	 */
907
	private static function _update_tax_rate_locations( $tax_rate_id, $values, $type ) {
908
		global $wpdb;
909
910
		$tax_rate_id = absint( $tax_rate_id );
911
912
		$wpdb->query(
913
			$wpdb->prepare( "
914
				DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE tax_rate_id = %d AND location_type = %s;
915
				", $tax_rate_id, $type
916
			)
917
		);
918
919
		if ( sizeof( $values ) > 0 ) {
920
			$sql = "( '" . implode( "', $tax_rate_id, '" . esc_sql( $type ) . "' ),( '", array_map( 'esc_sql', $values ) ) . "', $tax_rate_id, '" . esc_sql( $type ) . "' )";
921
922
			$wpdb->query( "
923
				INSERT INTO {$wpdb->prefix}woocommerce_tax_rate_locations ( location_code, tax_rate_id, location_type ) VALUES $sql;
924
				" );
925
		}
926
927
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
928
	}
929
930
	/**
931
	 * Used by admin settings page.
932
	 *
933
	 * @param string $tax_class
934
	 *
935
	 * @return array|null|object
936
	 */
937
	public static function get_rates_for_tax_class( $tax_class ) {
938
		global $wpdb;
939
940
		// Get all the rates and locations. Snagging all at once should significantly cut down on the number of queries.
941
		$rates     = self::sort_rates( $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}woocommerce_tax_rates` WHERE `tax_rate_class` = %s;", sanitize_title( $tax_class ) ) ) );
942
		$locations = $wpdb->get_results( "SELECT * FROM `{$wpdb->prefix}woocommerce_tax_rate_locations`" );
943
944
		if ( ! empty( $rates ) ) {
945
			// Set the rates keys equal to their ids.
946
			$rates = array_combine( wp_list_pluck( $rates, 'tax_rate_id' ), $rates );
947
		}
948
949
		// Drop the locations into the rates array.
950
		foreach ( $locations as $location ) {
951
			// Don't set them for unexistent rates.
952
			if ( ! isset( $rates[ $location->tax_rate_id ] ) ) {
953
				continue;
954
			}
955
			// If the rate exists, initialize the array before appending to it.
956
			if ( ! isset( $rates[ $location->tax_rate_id ]->{$location->location_type} ) ) {
957
				$rates[ $location->tax_rate_id ]->{$location->location_type} = array();
958
			}
959
			$rates[ $location->tax_rate_id ]->{$location->location_type}[] = $location->location_code;
960
		}
961
962
		return $rates;
963
	}
964
}
965
WC_Tax::init();
966