WC_Tax::prepare_tax_rate()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
eloc 5
nc 3
nop 1
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_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 ( $rates ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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
	 * Loop through a set of tax rates and get the matching rates (1 per priority).
271
	 *
272
	 * @param  string $country
273
	 * @param  string $state
274
	 * @param  string $postcode
275
	 * @param  string $city
276
	 * @param  string $tax_class
277
	 * @return array
278
	 */
279
	private static function get_matched_tax_rates( $country, $state, $postcode, $city, $tax_class ) {
280
		global $wpdb;
281
282
		// Query criteria - these will be ANDed
283
		$criteria   = array();
284
		$criteria[] = $wpdb->prepare( "tax_rate_country IN ( %s, '' )", strtoupper( $country ) );
285
		$criteria[] = $wpdb->prepare( "tax_rate_state IN ( %s, '' )", strtoupper( $state ) );
286
		$criteria[] = $wpdb->prepare( "tax_rate_class = %s", sanitize_title( $tax_class ) );
287
288
		// Pre-query postcode ranges for PHP based matching.
289
		$postcode_search = wc_get_wildcard_postcodes( $postcode );
290
		$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 '%...%';" );
291
292
		if ( $postcode_ranges ) {
293
			$matches         = wc_postcode_location_matcher( $postcode, $postcode_ranges, 'tax_rate_id', 'location_code' );
294
			$postcode_search = array_unique( array_merge( $postcode_search, array_values( $matches ) ) );
295
		}
296
297
		/**
298
		 * Location matching criteria - ORed
299
		 * Needs to match:
300
		 * 	- rates with no postcodes and cities
301
		 * 	- rates with a matching postcode and city
302
		 * 	- rates with matching postcode, no city
303
		 * 	- rates with matching city, no postcode
304
		 */
305
		$locations_criteria   = array();
306
		$locations_criteria[] = "locations.location_type IS NULL";
307
		$locations_criteria[] = "
308
			locations.location_type = 'postcode' AND locations.location_code IN ('" . implode( "','", array_map( 'esc_sql', $postcode_search ) ) . "')
309
			AND (
310
				( locations2.location_type = 'city' AND locations2.location_code = '" . esc_sql( strtoupper( $city ) ) . "' )
311
				OR NOT EXISTS (
312
					SELECT sub.tax_rate_id FROM {$wpdb->prefix}woocommerce_tax_rate_locations as sub
313
					WHERE sub.location_type = 'city'
314
					AND sub.tax_rate_id = tax_rates.tax_rate_id
315
				)
316
			)
317
		";
318
		$locations_criteria[] = "
319
			locations.location_type = 'city' AND locations.location_code = '" . esc_sql( strtoupper( $city ) ) . "'
320
			AND NOT EXISTS (
321
				SELECT sub.tax_rate_id FROM {$wpdb->prefix}woocommerce_tax_rate_locations as sub
322
				WHERE sub.location_type = 'postcode'
323
				AND sub.tax_rate_id = tax_rates.tax_rate_id
324
			)
325
		";
326
		$criteria[] = '( ( ' . implode( ' ) OR ( ', $locations_criteria ) . ' ) )';
327
328
		$found_rates = $wpdb->get_results( "
329
			SELECT tax_rates.*
330
			FROM {$wpdb->prefix}woocommerce_tax_rates as tax_rates
331
			LEFT OUTER JOIN {$wpdb->prefix}woocommerce_tax_rate_locations as locations ON tax_rates.tax_rate_id = locations.tax_rate_id
332
			LEFT OUTER JOIN {$wpdb->prefix}woocommerce_tax_rate_locations as locations2 ON tax_rates.tax_rate_id = locations2.tax_rate_id
333
			WHERE 1=1 AND " . implode( ' AND ', $criteria ) . "
334
			GROUP BY tax_rate_id
335
			ORDER BY tax_rate_priority, tax_rate_order
336
		" );
337
338
		$matched_tax_rates = array();
339
		$found_priority    = array();
340
341
		foreach ( $found_rates as $found_rate ) {
342
			if ( in_array( $found_rate->tax_rate_priority, $found_priority ) ) {
343
				continue;
344
			}
345
346
			$matched_tax_rates[ $found_rate->tax_rate_id ] = array(
347
				'rate'     => $found_rate->tax_rate,
348
				'label'    => $found_rate->tax_rate_name,
349
				'shipping' => $found_rate->tax_rate_shipping ? 'yes' : 'no',
350
				'compound' => $found_rate->tax_rate_compound ? 'yes' : 'no'
351
			);
352
353
			$found_priority[] = $found_rate->tax_rate_priority;
354
		}
355
356
		return apply_filters( 'woocommerce_matched_tax_rates', $matched_tax_rates, $country, $state, $postcode, $city, $tax_class );
357
	}
358
359
	/**
360
	 * Get the customer tax location based on their status and the current page.
361
	 *
362
	 * Used by get_rates(), get_shipping_rates().
363
	 *
364
	 * @param  $tax_class string Optional, passed to the filter for advanced tax setups.
365
	 * @return array
366
	 */
367
	public static function get_tax_location( $tax_class = '' ) {
368
		$location = array();
369
370
		if ( ! empty( WC()->customer ) ) {
371
			$location = WC()->customer->get_taxable_address();
372
		} elseif ( wc_prices_include_tax() || 'base' === get_option( 'woocommerce_default_customer_address' ) || 'base' === get_option( 'woocommerce_tax_based_on' ) ) {
373
			$location = array(
374
				WC()->countries->get_base_country(),
375
				WC()->countries->get_base_state(),
376
				WC()->countries->get_base_postcode(),
377
				WC()->countries->get_base_city()
378
			);
379
		}
380
381
		return apply_filters( 'woocommerce_get_tax_location', $location, $tax_class );
382
	}
383
384
	/**
385
	 * Get's an array of matching rates for a tax class.
386
	 * @param string $tax_class
387
	 * @return  array
388
	 */
389
	public static function get_rates( $tax_class = '' ) {
390
		$tax_class         = sanitize_title( $tax_class );
391
		$location          = self::get_tax_location( $tax_class );
392
		$matched_tax_rates = array();
393
394
		if ( sizeof( $location ) === 4 ) {
395
			list( $country, $state, $postcode, $city ) = $location;
396
397
			$matched_tax_rates = self::find_rates( array(
398
				'country' 	=> $country,
399
				'state' 	=> $state,
400
				'postcode' 	=> $postcode,
401
				'city' 		=> $city,
402
				'tax_class' => $tax_class
403
			) );
404
		}
405
406
		return apply_filters( 'woocommerce_matched_rates', $matched_tax_rates, $tax_class );
407
	}
408
409
	/**
410
	 * Get's an array of matching rates for the shop's base country.
411
	 *
412
	 * @param   string	Tax Class
413
	 * @return  array
414
	 */
415
	public static function get_base_tax_rates( $tax_class = '' ) {
416
		return apply_filters( 'woocommerce_base_tax_rates', self::find_rates( array(
417
			'country' 	=> WC()->countries->get_base_country(),
418
			'state' 	=> WC()->countries->get_base_state(),
419
			'postcode' 	=> WC()->countries->get_base_postcode(),
420
			'city' 		=> WC()->countries->get_base_city(),
421
			'tax_class' => $tax_class
422
		) ), $tax_class );
423
	}
424
425
	/**
426
	 * Alias for get_base_tax_rates().
427
	 *
428
	 * @deprecated 2.3
429
	 * @param   string	Tax Class
430
	 * @return  array
431
	 */
432
	public static function get_shop_base_rate( $tax_class = '' ) {
433
		return self::get_base_tax_rates( $tax_class );
434
	}
435
436
	/**
437
	 * Gets an array of matching shipping tax rates for a given class.
438
	 *
439
	 * @param   string	Tax Class
440
	 * @return  mixed
441
	 */
442
	public static function get_shipping_tax_rates( $tax_class = null ) {
443
		// See if we have an explicitly set shipping tax class
444
		if ( $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' ) ) {
445
			$tax_class = 'standard' === $shipping_tax_class ? '' : $shipping_tax_class;
446
		}
447
448
		$location          = self::get_tax_location( $tax_class );
449
		$matched_tax_rates = array();
450
451
		if ( sizeof( $location ) === 4 ) {
452
			list( $country, $state, $postcode, $city ) = $location;
453
454
			if ( ! is_null( $tax_class ) ) {
455
				// This will be per item shipping
456
				$matched_tax_rates = self::find_shipping_rates( array(
457
					'country' 	=> $country,
458
					'state' 	=> $state,
459
					'postcode' 	=> $postcode,
460
					'city' 		=> $city,
461
					'tax_class' => $tax_class
462
				) );
463
464 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...
465
466
				// This will be per order shipping - loop through the order and find the highest tax class rate
467
				$cart_tax_classes = WC()->cart->get_cart_item_tax_classes();
468
469
				// If multiple classes are found, use the first one. Don't bother with standard rate, we can get that later.
470
				if ( sizeof( $cart_tax_classes ) > 1 && ! in_array( '', $cart_tax_classes ) ) {
471
					$tax_classes = self::get_tax_classes();
472
473
					foreach ( $tax_classes as $tax_class ) {
474
						$tax_class = sanitize_title( $tax_class );
475
						if ( in_array( $tax_class, $cart_tax_classes ) ) {
476
							$matched_tax_rates = self::find_shipping_rates( array(
477
								'country' 	=> $country,
478
								'state' 	=> $state,
479
								'postcode' 	=> $postcode,
480
								'city' 		=> $city,
481
								'tax_class' => $tax_class
482
							) );
483
							break;
484
						}
485
					}
486
487
				// If a single tax class is found, use it
488
				} elseif ( sizeof( $cart_tax_classes ) == 1 ) {
489
					$matched_tax_rates = self::find_shipping_rates( array(
490
						'country' 	=> $country,
491
						'state' 	=> $state,
492
						'postcode' 	=> $postcode,
493
						'city' 		=> $city,
494
						'tax_class' => $cart_tax_classes[0]
495
					) );
496
				}
497
			}
498
499
			// Get standard rate if no taxes were found
500
			if ( ! sizeof( $matched_tax_rates ) ) {
501
				$matched_tax_rates = self::find_shipping_rates( array(
502
					'country' 	=> $country,
503
					'state' 	=> $state,
504
					'postcode' 	=> $postcode,
505
					'city' 		=> $city
506
				) );
507
			}
508
		}
509
510
		return $matched_tax_rates;
511
	}
512
513
	/**
514
	 * Return true/false depending on if a rate is a compound rate.
515
	 *
516
	 * @param   int		key
517
	 * @return  bool
518
	 */
519
	public static function is_compound( $key ) {
520
		global $wpdb;
521
		return $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_compound FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) ) ? true : false;
522
	}
523
524
	/**
525
	 * Return a given rates label.
526
	 *
527
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format
528
	 * @return  string
529
	 */
530
	public static function get_rate_label( $key_or_rate ) {
531
		global $wpdb;
532
533 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...
534
			$key       = $key_or_rate->tax_rate_id;
535
			$rate_name = $key_or_rate->tax_rate_name;
536
		} else {
537
			$key       = $key_or_rate;
538
			$rate_name = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_name FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
539
		}
540
541
		if ( ! $rate_name ) {
542
			$rate_name = WC()->countries->tax_or_vat();
543
		}
544
545
		return apply_filters( 'woocommerce_rate_label', $rate_name, $key );
546
	}
547
548
	/**
549
	 * Return a given rates percent.
550
	 *
551
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format
552
	 * @return  string
553
	 */
554
	public static function get_rate_percent( $key_or_rate ) {
555
		global $wpdb;
556
557 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...
558
			$key      = $key_or_rate->tax_rate_id;
559
			$tax_rate = $key_or_rate->tax_rate;
560
		} else {
561
			$key      = $key_or_rate;
562
			$tax_rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
563
		}
564
565
		return apply_filters( 'woocommerce_rate_percent', floatval( $tax_rate ) . '%', $key );
566
	}
567
568
	/**
569
	 * Get a rates code. Code is made up of COUNTRY-STATE-NAME-Priority. E.g GB-VAT-1, US-AL-TAX-1.
570
	 *
571
	 * @access public
572
	 * @param mixed $key_or_rate Tax rate ID, or the db row itself in object format
573
	 * @return string
574
	 */
575
	public static function get_rate_code( $key_or_rate ) {
576
		global $wpdb;
577
578 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...
579
			$key  = $key_or_rate->tax_rate_id;
580
			$rate = $key_or_rate;
581
		} else {
582
			$key  = $key_or_rate;
583
			$rate = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
584
		}
585
586
		$code_string = '';
587
588
		if ( null !== $rate ) {
589
			$code   = array();
590
			$code[] = $rate->tax_rate_country;
591
			$code[] = $rate->tax_rate_state;
592
			$code[] = $rate->tax_rate_name ? $rate->tax_rate_name : 'TAX';
593
			$code[] = absint( $rate->tax_rate_priority );
594
			$code_string = strtoupper( implode( '-', array_filter( $code ) ) );
595
		}
596
597
		return apply_filters( 'woocommerce_rate_code', $code_string, $key );
598
	}
599
600
	/**
601
	 * Round tax lines and return the sum.
602
	 *
603
	 * @param   array
604
	 * @return  float
605
	 */
606
	public static function get_tax_total( $taxes ) {
607
		return array_sum( array_map( array( __CLASS__, 'round' ), $taxes ) );
608
	}
609
610
	/**
611
	 * Get store tax classes.
612
	 * @return array
613
	 */
614
	public static function get_tax_classes() {
615
		return array_filter( array_map( 'trim', explode( "\n", get_option( 'woocommerce_tax_classes' ) ) ) );
616
	}
617
618
	/**
619
	 * format the city.
620
	 * @param  string $city
621
	 * @return string
622
	 */
623
	private static function format_tax_rate_city( $city ) {
624
		return strtoupper( trim( $city ) );
625
	}
626
627
	/**
628
	 * format the state.
629
	 * @param  string $state
630
	 * @return string
631
	 */
632
	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...
633
		$state = strtoupper( $state );
634
		return $state === '*' ? '' : $state;
635
	}
636
637
	/**
638
	 * format the country.
639
	 * @param  string $country
640
	 * @return string
641
	 */
642
	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...
643
		$country = strtoupper( $country );
644
		return $country === '*' ? '' : $country;
645
	}
646
647
	/**
648
	 * format the tax rate name.
649
	 * @param  string $name
650
	 * @return string
651
	 */
652
	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...
653
		return $name ? $name : __( 'Tax', 'woocommerce' );
654
	}
655
656
	/**
657
	 * format the rate.
658
	 * @param  double $rate
659
	 * @return string
660
	 */
661
	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...
662
		return number_format( (double) $rate, 4, '.', '' );
663
	}
664
665
	/**
666
	 * format the priority.
667
	 * @param  string $priority
668
	 * @return int
669
	 */
670
	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...
671
		return absint( $priority );
672
	}
673
674
	/**
675
	 * format the class.
676
	 * @param  string $class
677
	 * @return string
678
	 */
679
	public static function format_tax_rate_class( $class ) {
680
		$class = sanitize_title( $class );
681
		$sanitized_classes = array_map( 'sanitize_title', self::get_tax_classes() );
682
		if ( ! in_array( $class, $sanitized_classes ) ) {
683
			$class = '';
684
		}
685
		return $class === 'standard' ? '' : $class;
686
	}
687
688
	/**
689
	 * Prepare and format tax rate for DB insertion.
690
	 * @param  array $tax_rate
691
	 * @return array
692
	 */
693
	private static function prepare_tax_rate( $tax_rate ) {
694
		foreach ( $tax_rate as $key => $value ) {
695
			if ( method_exists( __CLASS__, 'format_' . $key ) ) {
696
				$tax_rate[ $key ] = call_user_func( array( __CLASS__, 'format_' . $key ), $value );
697
			}
698
		}
699
		return $tax_rate;
700
	}
701
702
	/**
703
	 * Insert a new tax rate.
704
	 *
705
	 * Internal use only.
706
	 *
707
	 * @since 2.3.0
708
	 * @access private
709
	 *
710
	 * @param  array $tax_rate
711
	 *
712
	 * @return int tax rate id
713
	 */
714
	public static function _insert_tax_rate( $tax_rate ) {
715
		global $wpdb;
716
717
		$wpdb->insert( $wpdb->prefix . 'woocommerce_tax_rates', self::prepare_tax_rate( $tax_rate ) );
718
719
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
720
721
		do_action( 'woocommerce_tax_rate_added', $wpdb->insert_id, $tax_rate );
722
723
		return $wpdb->insert_id;
724
	}
725
726
	/**
727
	 * Get tax rate.
728
	 *
729
	 * Internal use only.
730
	 *
731
	 * @since 2.5.0
732
	 * @access private
733
	 *
734
	 * @param int $tax_rate_id
735
	 * @param string $output_type
736
	 *
737
	 * @return array
738
	 */
739
	public static function _get_tax_rate( $tax_rate_id, $output_type = ARRAY_A ) {
740
		global $wpdb;
741
742
		return $wpdb->get_row( $wpdb->prepare( "
743
			SELECT *
744
			FROM {$wpdb->prefix}woocommerce_tax_rates
745
			WHERE tax_rate_id = %d
746
		", $tax_rate_id ), $output_type );
747
	}
748
749
	/**
750
	 * Update a tax rate.
751
	 *
752
	 * Internal use only.
753
	 *
754
	 * @since 2.3.0
755
	 * @access private
756
	 *
757
	 * @param int $tax_rate_id
758
	 * @param array $tax_rate
759
	 */
760
	public static function _update_tax_rate( $tax_rate_id, $tax_rate ) {
761
		global $wpdb;
762
763
		$tax_rate_id = absint( $tax_rate_id );
764
765
		$wpdb->update(
766
			$wpdb->prefix . "woocommerce_tax_rates",
767
			self::prepare_tax_rate( $tax_rate ),
768
			array(
769
				'tax_rate_id' => $tax_rate_id
770
			)
771
		);
772
773
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
774
775
		do_action( 'woocommerce_tax_rate_updated', $tax_rate_id, $tax_rate );
776
	}
777
778
	/**
779
	 * Delete a tax rate from the database.
780
	 *
781
	 * Internal use only.
782
	 *
783
	 * @since 2.3.0
784
	 * @access private
785
	 *
786
	 * @param  int $tax_rate_id
787
	 */
788
	public static function _delete_tax_rate( $tax_rate_id ) {
789
		global $wpdb;
790
791
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE tax_rate_id = %d;", $tax_rate_id ) );
792
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $tax_rate_id ) );
793
794
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
795
796
		do_action( 'woocommerce_tax_rate_deleted', $tax_rate_id );
797
	}
798
799
	/**
800
	 * Update postcodes for a tax rate in the DB.
801
	 *
802
	 * Internal use only.
803
	 *
804
	 * @since 2.3.0
805
	 * @access private
806
	 *
807
	 * @param  int $tax_rate_id
808
	 * @param  string $postcodes String of postcodes separated by ; characters
809
	 * @return string
810
	 */
811
	public static function _update_tax_rate_postcodes( $tax_rate_id, $postcodes ) {
812
		if ( ! is_array( $postcodes ) ) {
813
			$postcodes = explode( ';', $postcodes );
814
		}
815
		self::_update_tax_rate_locations( $tax_rate_id, array_diff( array_map( 'wc_normalize_postcode', array_filter( $postcodes ) ), array( '*' ) ), 'postcode' );
816
	}
817
818
	/**
819
	 * Update cities for a tax rate in the DB.
820
	 *
821
	 * Internal use only.
822
	 *
823
	 * @since 2.3.0
824
	 * @access private
825
	 *
826
	 * @param  int $tax_rate_id
827
	 * @param  string $cities
828
	 * @return string
829
	 */
830
	public static function _update_tax_rate_cities( $tax_rate_id, $cities ) {
831
		if ( ! is_array( $cities ) ) {
832
			$cities = explode( ';', $cities );
833
		}
834
		$cities = array_filter( array_diff( array_map( array( __CLASS__, 'format_tax_rate_city' ), $cities ), array( '*' ) ) );
835
836
		self::_update_tax_rate_locations( $tax_rate_id, $cities, 'city' );
837
	}
838
839
	/**
840
	 * Updates locations (postcode and city).
841
	 *
842
	 * Internal use only.
843
	 *
844
	 * @since 2.3.0
845
	 * @access private
846
	 *
847
	 * @param  int $tax_rate_id
848
	 * @param string $type
849
	 * @return string
850
	 */
851
	private static function _update_tax_rate_locations( $tax_rate_id, $values, $type ) {
852
		global $wpdb;
853
854
		$tax_rate_id = absint( $tax_rate_id );
855
856
		$wpdb->query(
857
			$wpdb->prepare( "
858
				DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE tax_rate_id = %d AND location_type = %s;
859
				", $tax_rate_id, $type
860
			)
861
		);
862
863
		if ( sizeof( $values ) > 0 ) {
864
			$sql = "( '" . implode( "', $tax_rate_id, '" . esc_sql( $type ) . "' ),( '", array_map( 'esc_sql', $values ) ) . "', $tax_rate_id, '" . esc_sql( $type ) . "' )";
865
866
			$wpdb->query( "
867
				INSERT INTO {$wpdb->prefix}woocommerce_tax_rate_locations ( location_code, tax_rate_id, location_type ) VALUES $sql;
868
				" );
869
		}
870
871
		WC_Cache_Helper::incr_cache_prefix( 'taxes' );
872
	}
873
874
	/**
875
	 * Used by admin settings page.
876
	 *
877
	 * @param string $tax_class
878
	 *
879
	 * @return array|null|object
880
	 */
881
	public static function get_rates_for_tax_class( $tax_class ) {
882
		global $wpdb;
883
884
		// Get all the rates and locations. Snagging all at once should significantly cut down on the number of queries.
885
		$rates     = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}woocommerce_tax_rates` WHERE `tax_rate_class` = %s ORDER BY `tax_rate_order`;", sanitize_title( $tax_class ) ) );
886
		$locations = $wpdb->get_results( "SELECT * FROM `{$wpdb->prefix}woocommerce_tax_rate_locations`" );
887
888
		if ( ! empty( $rates ) ) {
889
			// Set the rates keys equal to their ids.
890
			$rates = array_combine( wp_list_pluck( $rates, 'tax_rate_id' ), $rates );
891
		}
892
893
		// Drop the locations into the rates array.
894
		foreach ( $locations as $location ) {
895
			// Don't set them for unexistent rates.
896
			if ( ! isset( $rates[ $location->tax_rate_id ] ) ) {
897
				continue;
898
			}
899
			// If the rate exists, initialize the array before appending to it.
900
			if ( ! isset( $rates[ $location->tax_rate_id ]->{$location->location_type} ) ) {
901
				$rates[ $location->tax_rate_id ]->{$location->location_type} = array();
902
			}
903
			$rates[ $location->tax_rate_id ]->{$location->location_type}[] = $location->location_code;
904
		}
905
906
		return $rates;
907
	}
908
}
909
WC_Tax::init();
910