1 <?php
2 3 4 5 6 7 8 9 10
11 class WC_Tax {
12
13
14 public $matched_rates;
15
16
17 var $log = array();
18
19 20 21 22 23
24 public function __construct() {
25 $this->precision = WC_ROUNDING_PRECISION;
26 $this->dp = (int) get_option( 'woocommerce_price_num_decimals' );
27 $this->round_at_subtotal = get_option('woocommerce_tax_round_at_subtotal') == 'yes';
28 }
29
30 31 32 33 34 35 36 37
38 public function calc_tax( $price, $rates, $price_includes_tax = false, $suppress_rounding = false ) {
39
40 $price = $this->precision( $price );
41
42 if ( $price_includes_tax )
43 $taxes = $this->calc_inclusive_tax( $price, $rates );
44 else
45 $taxes = $this->calc_exclusive_tax( $price, $rates );
46
47
48 if ( ! $this->round_at_subtotal && ! $suppress_rounding ) {
49 $taxes = array_map( 'round', $taxes );
50 }
51
52
53 $price = $this->remove_precision( $price );
54 $taxes = array_map( array( $this, 'remove_precision' ), $taxes );
55
56 return apply_filters( 'woocommerce_calc_tax', $taxes, $price, $rates, $price_includes_tax, $suppress_rounding );
57 }
58
59 60 61 62 63 64 65
66 public function calc_shipping_tax( $price, $rates ) {
67 return $this->calc_exclusive_tax( $price, $rates );
68 }
69
70 71 72 73 74
75 private function precision( $price ) {
76 return $price * ( pow( 10, $this->precision ) );
77 }
78
79 80 81 82 83
84 private function remove_precision( $price ) {
85 return $price / ( pow( 10, $this->precision ) );
86 }
87
88 89 90 91 92 93 94 95 96 97
98 public function round( $in ) {
99 return apply_filters( 'woocommerce_tax_round', round( $in, $this->precision ), $in );
100 }
101
102 103 104 105 106 107 108
109 private function calc_inclusive_tax( $price, $rates ) {
110 $taxes = array();
111
112 $regular_tax_rates = $compound_tax_rates = 0;
113
114 foreach ( $rates as $key => $rate )
115 if ( $rate['compound'] == 'yes' )
116 $compound_tax_rates = $compound_tax_rates + $rate['rate'];
117 else
118 $regular_tax_rates = $regular_tax_rates + $rate['rate'];
119
120 $regular_tax_rate = 1 + ( $regular_tax_rates / 100 );
121 $compound_tax_rate = 1 + ( $compound_tax_rates / 100 );
122 $non_compound_price = $price / $compound_tax_rate;
123
124 foreach ( $rates as $key => $rate ) {
125 if ( ! isset( $taxes[ $key ] ) )
126 $taxes[ $key ] = 0;
127
128 $the_rate = $rate['rate'] / 100;
129
130 if ( $rate['compound'] == 'yes' ) {
131 $the_price = $price;
132 $the_rate = $the_rate / $compound_tax_rate;
133 } else {
134 $the_price = $non_compound_price;
135 $the_rate = $the_rate / $regular_tax_rate;
136 }
137
138 $net_price = $price - ( $the_rate * $the_price );
139 $tax_amount = $price - $net_price;
140 $taxes[ $key ] += apply_filters( 'woocommerce_price_inc_tax_amount', $tax_amount, $key, $rate, $price );
141 }
142
143 return $taxes;
144 }
145
146 147 148 149 150 151 152
153 private function calc_exclusive_tax( $price, $rates ) {
154 $taxes = array();
155
156
157 foreach ( $rates as $key => $rate ) {
158
159 if ( $rate['compound'] == 'yes' )
160 continue;
161
162 $tax_amount = $price * ( $rate['rate'] / 100 );
163
164
165 $tax_amount = apply_filters( 'woocommerce_price_ex_tax_amount', $tax_amount, $key, $rate, $price );
166
167
168 if ( ! isset( $taxes[ $key ] ) )
169 $taxes[ $key ] = $tax_amount;
170 else
171 $taxes[ $key ] += $tax_amount;
172 }
173
174 $pre_compound_total = array_sum( $taxes );
175
176
177 if ( $rates ) {
178 foreach ( $rates as $key => $rate ) {
179
180 if ( $rate['compound'] == 'no' )
181 continue;
182
183 $the_price_inc_tax = $price + ( $pre_compound_total );
184
185 $tax_amount = $the_price_inc_tax * ( $rate['rate'] / 100 );
186
187
188 $tax_amount = apply_filters( 'woocommerce_price_ex_tax_amount', $tax_amount, $key, $rate, $price, $the_price_inc_tax, $pre_compound_total );
189
190
191 if ( ! isset( $taxes[ $key ] ) )
192 $taxes[ $key ] = $tax_amount;
193 else
194 $taxes[ $key ] += $tax_amount;
195 }
196 }
197
198 return $taxes;
199 }
200
201 202 203 204 205 206 207
208 public function find_rates( $args = array() ) {
209 global $wpdb;
210
211 $defaults = array(
212 'country' => '',
213 'state' => '',
214 'city' => '',
215 'postcode' => '',
216 'tax_class' => ''
217 );
218
219 $args = wp_parse_args( $args, $defaults );
220
221 extract( $args, EXTR_SKIP );
222
223 if ( ! $country )
224 return array();
225
226
227 $valid_postcodes = array( '*', strtoupper( wc_clean( $postcode ) ) );
228
229
230 $postcode_length = strlen( $postcode );
231 $wildcard_postcode = strtoupper( wc_clean( $postcode ) );
232
233 for ( $i = 0; $i < $postcode_length; $i ++ ) {
234
235 $wildcard_postcode = substr( $wildcard_postcode, 0, -1 );
236
237 $valid_postcodes[] = $wildcard_postcode . '*';
238 }
239
240
241 $rates_transient_key = 'wc_tax_rates_' . md5( sprintf( '%s+%s+%s+%s+%s', $country, $state, $city, implode( ',', $valid_postcodes), $tax_class ) );
242 $matched_tax_rates = get_transient( $rates_transient_key );
243
244 if ( false === $matched_tax_rates ) {
245
246
247 $found_rates = $wpdb->get_results( $wpdb->prepare( "
248 SELECT * FROM (
249 SELECT tax_rates.* FROM
250 {$wpdb->prefix}woocommerce_tax_rates as tax_rates
251 LEFT OUTER JOIN
252 {$wpdb->prefix}woocommerce_tax_rate_locations as locations ON tax_rates.tax_rate_id = locations.tax_rate_id
253 LEFT OUTER JOIN
254 {$wpdb->prefix}woocommerce_tax_rate_locations as locations2 ON tax_rates.tax_rate_id = locations2.tax_rate_id
255 WHERE
256 tax_rate_country IN ( %s, '' )
257 AND tax_rate_state IN ( %s, '' )
258 AND tax_rate_class = %s
259 AND
260 (
261 (
262 locations.location_type IS NULL
263 )
264 OR
265 (
266 locations.location_type = 'postcode'
267 AND locations.location_code IN ('" . implode( "','", $valid_postcodes ) . "')
268 AND locations2.location_type = 'city'
269 AND locations2.location_code = %s
270 )
271 OR
272 (
273 locations.location_type = 'postcode'
274 AND locations.location_code IN ('" . implode( "','", $valid_postcodes ) . "')
275 AND 0 = (
276 SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_tax_rate_locations as sublocations
277 WHERE sublocations.location_type = 'city'
278 AND sublocations.tax_rate_id = tax_rates.tax_rate_id
279 )
280 )
281 OR
282 (
283 locations.location_type = 'city'
284 AND locations.location_code = %s
285 AND 0 = (
286 SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_tax_rate_locations as sublocations
287 WHERE sublocations.location_type = 'postcode'
288 AND sublocations.tax_rate_id = tax_rates.tax_rate_id
289 )
290 )
291 )
292 GROUP BY
293 tax_rate_id
294 ORDER BY
295 tax_rate_priority, tax_rate_order
296 ) as ordered_taxes
297 GROUP BY
298 tax_rate_priority
299 ",
300 strtoupper( $country ),
301 strtoupper( $state ),
302 sanitize_title( $tax_class ),
303 strtoupper( $city ),
304 strtoupper( $city )
305 ) );
306
307
308 $matched_tax_rates = array();
309
310 foreach ( $found_rates as $found_rate )
311 $matched_tax_rates[ $found_rate->tax_rate_id ] = array(
312 'rate' => $found_rate->tax_rate,
313 'label' => $found_rate->tax_rate_name,
314 'shipping' => $found_rate->tax_rate_shipping ? 'yes' : 'no',
315 'compound' => $found_rate->tax_rate_compound ? 'yes' : 'no'
316 );
317
318 $matched_tax_rates = apply_filters( 'woocommerce_matched_tax_rates', $matched_tax_rates, $country, $state, $postcode, $city, $tax_class );
319
320 set_transient( $rates_transient_key, $matched_tax_rates, DAY_IN_SECONDS );
321 }
322
323 return $matched_tax_rates;
324 }
325
326 327 328 329 330
331 public function get_rates( $tax_class = '' ) {
332
333 $tax_class = sanitize_title( $tax_class );
334
335
336 if ( ( defined('WOOCOMMERCE_CHECKOUT') && WOOCOMMERCE_CHECKOUT ) || ( ! empty( WC()->customer ) && WC()->customer->has_calculated_shipping() ) ) {
337
338 list( $country, $state, $postcode, $city ) = WC()->customer->get_taxable_address();
339
340 $matched_tax_rates = $this->find_rates( array(
341 'country' => $country,
342 'state' => $state,
343 'postcode' => $postcode,
344 'city' => $city,
345 'tax_class' => $tax_class
346 ) );
347
348 } else {
349
350
351
352
353 $matched_tax_rates = get_option( 'woocommerce_prices_include_tax' ) == 'yes' || get_option( 'woocommerce_default_customer_address' ) == 'base'
354 ? $this->get_shop_base_rate( $tax_class )
355 : array();
356
357 }
358
359 return apply_filters('woocommerce_matched_rates', $matched_tax_rates, $tax_class);
360 }
361
362 363 364 365 366 367
368 public function get_shop_base_rate( $tax_class = '' ) {
369 return $this->find_rates( array(
370 'country' => WC()->countries->get_base_country(),
371 'state' => WC()->countries->get_base_state(),
372 'postcode' => WC()->countries->get_base_postcode(),
373 'city' => WC()->countries->get_base_city(),
374 'tax_class' => $tax_class
375 ) );
376 }
377
378 379 380 381 382 383
384 public function get_shipping_tax_rates( $tax_class = null ) {
385
386
387 if ( $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' ) ) {
388 $tax_class = $shipping_tax_class == 'standard' ? '' : $shipping_tax_class;
389 }
390
391 if ( ( defined('WOOCOMMERCE_CHECKOUT') && WOOCOMMERCE_CHECKOUT ) || ( ! empty( WC()->customer ) && WC()->customer->has_calculated_shipping() ) ) {
392
393 list( $country, $state, $postcode, $city ) = WC()->customer->get_taxable_address();
394
395 } else {
396
397
398
399 if ( get_option( 'woocommerce_prices_include_tax' ) == 'yes' || get_option( 'woocommerce_default_customer_address' ) == 'base' ) {
400 $country = WC()->countries->get_base_country();
401 $state = WC()->countries->get_base_state();
402 $postcode = '';
403 $city = '';
404 } else {
405 return array();
406 }
407
408 }
409
410
411 if ( ! is_null( $tax_class ) ) {
412
413 $matched_tax_rates = array();
414
415
416 $rates = $this->find_rates( array(
417 'country' => $country,
418 'state' => $state,
419 'postcode' => $postcode,
420 'city' => $city,
421 'tax_class' => $tax_class
422 ) );
423
424 if ( $rates )
425 foreach ( $rates as $key => $rate )
426 if ( isset( $rate['shipping'] ) && $rate['shipping'] == 'yes' )
427 $matched_tax_rates[ $key ] = $rate;
428
429 if ( sizeof( $matched_tax_rates ) == 0 ) {
430
431 $rates = $this->find_rates( array(
432 'country' => $country,
433 'state' => $state,
434 'city' => $city,
435 'postcode' => $postcode,
436 ) );
437
438 if ( $rates )
439 foreach ( $rates as $key => $rate )
440 if ( isset( $rate['shipping'] ) && $rate['shipping'] == 'yes' )
441 $matched_tax_rates[ $key ] = $rate;
442 }
443
444 return $matched_tax_rates;
445
446 } else {
447
448
449 $found_tax_classes = array();
450 $matched_tax_rates = array();
451 $rates = false;
452
453
454 if ( sizeof( WC()->cart->get_cart() ) > 0 )
455 foreach ( WC()->cart->get_cart() as $item )
456 $found_tax_classes[] = $item['data']->get_tax_class();
457
458 $found_tax_classes = array_unique( $found_tax_classes );
459
460
461 if ( sizeof( $found_tax_classes ) > 1 ) {
462
463 if ( in_array( '', $found_tax_classes ) ) {
464 $rates = $this->find_rates( array(
465 'country' => $country,
466 'state' => $state,
467 'city' => $city,
468 'postcode' => $postcode,
469 ) );
470 } else {
471 $tax_classes = array_filter( array_map( 'trim', explode( "\n", get_option( 'woocommerce_tax_classes' ) ) ) );
472
473 foreach ( $tax_classes as $tax_class ) {
474 if ( in_array( $tax_class, $found_tax_classes ) ) {
475 $rates = $this->find_rates( array(
476 'country' => $country,
477 'state' => $state,
478 'postcode' => $postcode,
479 'city' => $city,
480 'tax_class' => $tax_class
481 ) );
482 break;
483 }
484 }
485 }
486
487
488 } elseif ( sizeof( $found_tax_classes ) == 1 ) {
489
490 $rates = $this->find_rates( array(
491 'country' => $country,
492 'state' => $state,
493 'postcode' => $postcode,
494 'city' => $city,
495 'tax_class' => $found_tax_classes[0]
496 ) );
497
498 }
499
500
501 if ( ! $rates )
502 $rates = $this->find_rates( array(
503 'country' => $country,
504 'state' => $state,
505 'postcode' => $postcode,
506 'city' => $city,
507 ) );
508
509 if ( $rates )
510 foreach ( $rates as $key => $rate )
511 if ( isset( $rate['shipping'] ) && $rate['shipping'] == 'yes' )
512 $matched_tax_rates[ $key ] = $rate;
513
514 return $matched_tax_rates;
515 }
516
517 return array();
518 }
519
520 521 522 523 524 525
526 public function is_compound( $key ) {
527 global $wpdb;
528 return $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_compound FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) ) ? true : false;
529 }
530
531 532 533 534 535 536
537 public function get_rate_label( $key ) {
538 global $wpdb;
539
540 $rate_name = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_name FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
541
542 if ( ! $rate_name )
543 $rate_name = WC()->countries->tax_or_vat();
544
545 return apply_filters( 'woocommerce_rate_label', $rate_name, $key, $this );
546 }
547
548 549 550 551 552 553 554
555 public function get_rate_code( $key ) {
556 global $wpdb;
557
558 $rate = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) );
559
560 if ( ! $rate ) {
561 return '';
562 }
563
564 $code = array();
565
566 $code[] = $rate->tax_rate_country;
567 $code[] = $rate->tax_rate_state;
568 $code[] = $rate->tax_rate_name ? $rate->tax_rate_name : 'TAX';
569 $code[] = absint( $rate->tax_rate_priority );
570
571 return apply_filters( 'woocommerce_rate_code', strtoupper( implode( '-', array_filter( $code ) ) ), $key, $this );
572 }
573
574 575 576 577 578 579
580 public function get_tax_total( $taxes ) {
581 return array_sum( array_map( array( $this, 'round' ), $taxes ) );
582 }
583 }
584