2011-08-09 15:16:18 +00:00
< ? php
2015-11-06 09:22:19 +00:00
if ( ! defined ( 'ABSPATH' ) ) {
exit ; // Exit if accessed directly
}
2011-08-09 15:16:18 +00:00
/**
2015-11-03 13:53:50 +00:00
* Performs tax calculations and loads tax rates
2011-08-09 15:16:18 +00:00
*
2012-01-27 16:38:39 +00:00
* @ class WC_Tax
2014-06-12 15:47:43 +00:00
* @ version 2.2 . 0
2012-08-14 22:43:48 +00:00
* @ package WooCommerce / Classes
2013-02-20 17:14:46 +00:00
* @ category Class
2012-08-14 20:19:41 +00:00
* @ author WooThemes
2011-08-09 15:16:18 +00:00
*/
2012-01-27 16:38:39 +00:00
class WC_Tax {
2012-08-08 10:42:42 +00:00
2016-01-06 15:24:47 +00:00
/**
* Precision .
*
* @ var int
*/
2014-06-12 15:47:43 +00:00
public static $precision ;
2016-01-06 15:24:47 +00:00
/**
* Round at subtotal .
*
* @ var bool
*/
2014-06-12 15:47:43 +00:00
public static $round_at_subtotal ;
2014-09-20 19:02:51 +00:00
2012-12-13 01:23:35 +00:00
/**
2015-11-03 13:31:20 +00:00
* Load options .
2012-12-13 01:23:35 +00:00
*
* @ access public
*/
2014-06-12 15:47:43 +00:00
public static function init () {
self :: $precision = WC_ROUNDING_PRECISION ;
self :: $round_at_subtotal = 'yes' === get_option ( 'woocommerce_tax_round_at_subtotal' );
2012-12-13 01:23:35 +00:00
}
2013-10-24 12:15:42 +00:00
/**
2015-11-03 13:31:20 +00:00
* Calculate tax for a line .
2013-10-24 15:16:39 +00:00
* @ param float $price Price to calc tax on
* @ param array $rates Rates to apply
* @ param boolean $price_includes_tax Whether the passed price has taxes included
2013-11-27 09:03:47 +00:00
* @ param boolean $suppress_rounding Whether to suppress any rounding from taking place
2013-10-24 15:16:39 +00:00
* @ return array Array of rates + prices after tax
2013-10-24 12:15:42 +00:00
*/
2014-06-12 15:47:43 +00:00
public static function calc_tax ( $price , $rates , $price_includes_tax = false , $suppress_rounding = false ) {
2013-10-24 12:15:42 +00:00
// Work in pence to X precision
2014-06-13 14:53:59 +00:00
$price = self :: precision ( $price );
2013-10-24 12:15:42 +00:00
2014-11-29 11:33:36 +00:00
if ( $price_includes_tax ) {
2014-06-12 15:47:43 +00:00
$taxes = self :: calc_inclusive_tax ( $price , $rates );
2014-11-29 11:33:36 +00:00
} else {
2014-06-12 15:47:43 +00:00
$taxes = self :: calc_exclusive_tax ( $price , $rates );
2014-11-29 11:33:36 +00:00
}
2013-10-24 12:15:42 +00:00
2013-10-24 13:53:01 +00:00
// Round to precision
2014-06-12 15:47:43 +00:00
if ( ! self :: $round_at_subtotal && ! $suppress_rounding ) {
2013-10-24 12:15:42 +00:00
$taxes = array_map ( 'round' , $taxes ); // Round to precision
}
// Remove precision
2014-06-12 15:47:43 +00:00
$price = self :: remove_precision ( $price );
2014-06-13 14:53:59 +00:00
$taxes = array_map ( array ( __CLASS__ , 'remove_precision' ), $taxes );
2013-10-24 12:15:42 +00:00
2013-10-24 15:16:39 +00:00
return apply_filters ( 'woocommerce_calc_tax' , $taxes , $price , $rates , $price_includes_tax , $suppress_rounding );
}
2013-10-24 12:15:42 +00:00
2013-10-24 15:16:39 +00:00
/**
* Calculate the shipping tax using a passed array of rates .
*
* @ param float Price
* @ param array Taxation Rate
* @ return array
*/
2014-06-12 15:47:43 +00:00
public static function calc_shipping_tax ( $price , $rates ) {
return self :: calc_exclusive_tax ( $price , $rates );
2013-10-24 12:15:42 +00:00
}
2013-10-24 15:16:39 +00:00
/**
2015-11-03 13:31:20 +00:00
* Multiply cost by pow precision .
2013-10-24 15:16:39 +00:00
* @ param float $price
* @ return float
*/
2014-06-12 15:47:43 +00:00
private static function precision ( $price ) {
return $price * ( pow ( 10 , self :: $precision ) );
2013-10-24 12:15:42 +00:00
}
2013-10-24 15:16:39 +00:00
/**
2015-11-03 13:31:20 +00:00
* Divide cost by pow precision .
2013-10-24 15:16:39 +00:00
* @ param float $price
* @ return float
*/
2014-06-12 15:47:43 +00:00
private static function remove_precision ( $price ) {
return $price / ( pow ( 10 , self :: $precision ) );
2013-10-24 12:15:42 +00:00
}
/**
* Round to precision .
*
* Filter example : to return rounding to . 5 cents you ' d use :
*
2016-04-29 10:44:28 +00:00
* function euro_5cent_rounding ( $in ) {
2013-10-24 12:15:42 +00:00
* return round ( $in / 5 , 2 ) * 5 ;
* }
* add_filter ( 'woocommerce_tax_round' , 'euro_5cent_rounding' );
2015-02-03 14:32:10 +00:00
* @ return double
2013-10-24 12:15:42 +00:00
*/
2014-06-12 15:47:43 +00:00
public static function round ( $in ) {
return apply_filters ( 'woocommerce_tax_round' , round ( $in , self :: $precision ), $in );
2013-10-24 12:15:42 +00:00
}
2013-10-24 15:16:39 +00:00
/**
2015-11-03 13:31:20 +00:00
* Calc tax from inclusive price .
2013-10-24 15:16:39 +00:00
*
* @ param float $price
* @ param array $rates
* @ return array
*/
2015-06-09 13:58:27 +00:00
public static function calc_inclusive_tax ( $price , $rates ) {
2013-10-24 12:15:42 +00:00
$taxes = array ();
$regular_tax_rates = $compound_tax_rates = 0 ;
foreach ( $rates as $key => $rate )
if ( $rate [ 'compound' ] == 'yes' )
$compound_tax_rates = $compound_tax_rates + $rate [ 'rate' ];
else
$regular_tax_rates = $regular_tax_rates + $rate [ 'rate' ];
$regular_tax_rate = 1 + ( $regular_tax_rates / 100 );
$compound_tax_rate = 1 + ( $compound_tax_rates / 100 );
$non_compound_price = $price / $compound_tax_rate ;
foreach ( $rates as $key => $rate ) {
if ( ! isset ( $taxes [ $key ] ) )
$taxes [ $key ] = 0 ;
$the_rate = $rate [ 'rate' ] / 100 ;
if ( $rate [ 'compound' ] == 'yes' ) {
$the_price = $price ;
$the_rate = $the_rate / $compound_tax_rate ;
} else {
$the_price = $non_compound_price ;
$the_rate = $the_rate / $regular_tax_rate ;
}
$net_price = $price - ( $the_rate * $the_price );
$tax_amount = $price - $net_price ;
$taxes [ $key ] += apply_filters ( 'woocommerce_price_inc_tax_amount' , $tax_amount , $key , $rate , $price );
}
return $taxes ;
}
2013-10-24 15:16:39 +00:00
/**
2015-11-03 13:31:20 +00:00
* Calc tax from exclusive price .
2013-10-24 15:16:39 +00:00
*
* @ param float $price
* @ param array $rates
* @ return array
*/
2015-06-09 13:58:27 +00:00
public static function calc_exclusive_tax ( $price , $rates ) {
2013-10-24 12:15:42 +00:00
$taxes = array ();
2016-06-06 18:39:23 +00:00
if ( ! empty ( $rates ) ) {
2014-12-09 09:14:25 +00:00
// Multiple taxes
foreach ( $rates as $key => $rate ) {
2013-10-24 12:15:42 +00:00
2014-12-09 09:14:25 +00:00
if ( $rate [ 'compound' ] == 'yes' )
continue ;
2013-10-24 12:15:42 +00:00
2014-12-09 09:14:25 +00:00
$tax_amount = $price * ( $rate [ 'rate' ] / 100 );
2013-10-24 12:15:42 +00:00
2014-12-09 09:14:25 +00:00
// ADVANCED: Allow third parties to modify this rate
$tax_amount = apply_filters ( 'woocommerce_price_ex_tax_amount' , $tax_amount , $key , $rate , $price );
2013-10-24 12:15:42 +00:00
2014-12-09 09:14:25 +00:00
// Add rate
if ( ! isset ( $taxes [ $key ] ) )
$taxes [ $key ] = $tax_amount ;
else
$taxes [ $key ] += $tax_amount ;
}
2013-10-24 12:15:42 +00:00
2014-12-09 09:14:25 +00:00
$pre_compound_total = array_sum ( $taxes );
2013-10-24 12:15:42 +00:00
2014-12-09 09:14:25 +00:00
// Compound taxes
2013-10-24 12:15:42 +00:00
foreach ( $rates as $key => $rate ) {
if ( $rate [ 'compound' ] == 'no' )
continue ;
2014-02-25 11:54:58 +00:00
$the_price_inc_tax = $price + ( $pre_compound_total );
2013-10-24 12:15:42 +00:00
$tax_amount = $the_price_inc_tax * ( $rate [ 'rate' ] / 100 );
2013-11-27 09:03:47 +00:00
// ADVANCED: Allow third parties to modify this rate
2013-10-24 12:15:42 +00:00
$tax_amount = apply_filters ( 'woocommerce_price_ex_tax_amount' , $tax_amount , $key , $rate , $price , $the_price_inc_tax , $pre_compound_total );
// Add rate
if ( ! isset ( $taxes [ $key ] ) )
$taxes [ $key ] = $tax_amount ;
else
$taxes [ $key ] += $tax_amount ;
}
}
return $taxes ;
}
2011-08-09 15:16:18 +00:00
/**
2012-08-14 20:19:41 +00:00
* Searches for all matching country / state / postcode tax rates .
2012-11-27 16:22:47 +00:00
*
2013-11-27 09:03:47 +00:00
* @ param array $args
2012-09-23 16:16:39 +00:00
* @ return array
2011-08-09 15:16:18 +00:00
*/
2014-06-12 15:47:43 +00:00
public static function find_rates ( $args = array () ) {
2014-11-21 12:05:44 +00:00
$args = wp_parse_args ( $args , array (
'country' => '' ,
'state' => '' ,
'city' => '' ,
'postcode' => '' ,
'tax_class' => ''
) );
2012-08-08 10:42:42 +00:00
2012-09-23 16:16:39 +00:00
extract ( $args , EXTR_SKIP );
2012-08-08 10:42:42 +00:00
2014-06-02 10:55:36 +00:00
if ( ! $country ) {
2012-09-23 16:16:39 +00:00
return array ();
2014-06-02 10:55:36 +00:00
}
2011-08-09 15:16:18 +00:00
2016-06-06 15:17:15 +00:00
$postcode = wc_normalize_postcode ( wc_clean ( $postcode ) );
2015-12-02 15:53:46 +00:00
$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 ) );
$matched_tax_rates = wp_cache_get ( $cache_key , 'taxes' );
2012-11-27 16:22:47 +00:00
2014-11-21 12:05:44 +00:00
if ( false === $matched_tax_rates ) {
2016-04-22 13:02:36 +00:00
$matched_tax_rates = self :: get_matched_tax_rates ( $country , $state , $postcode , $city , $tax_class );
2015-12-02 15:53:46 +00:00
wp_cache_set ( $cache_key , $matched_tax_rates , 'taxes' );
2012-09-23 16:16:39 +00:00
}
2011-12-28 23:59:33 +00:00
2014-12-02 11:57:01 +00:00
return apply_filters ( 'woocommerce_find_rates' , $matched_tax_rates , $args );
2014-11-21 12:05:44 +00:00
}
2014-01-10 22:57:19 +00:00
2014-11-21 13:06:30 +00:00
/**
* Searches for all matching country / state / postcode tax rates .
*
* @ param array $args
* @ return array
*/
public static function find_shipping_rates ( $args = array () ) {
$rates = self :: find_rates ( $args );
$shipping_rates = array ();
2015-08-31 04:18:21 +00:00
if ( is_array ( $rates ) ) {
foreach ( $rates as $key => $rate ) {
if ( 'yes' === $rate [ 'shipping' ] ) {
$shipping_rates [ $key ] = $rate ;
}
2014-11-21 13:06:30 +00:00
}
}
return $shipping_rates ;
}
2014-11-21 12:05:44 +00:00
/**
2015-11-03 13:31:20 +00:00
* Loop through a set of tax rates and get the matching rates ( 1 per priority ) .
2014-11-21 12:05:44 +00:00
*
* @ param string $country
* @ param string $state
* @ param string $postcode
* @ param string $city
* @ param string $tax_class
* @ return array
*/
2016-04-22 13:02:36 +00:00
private static function get_matched_tax_rates ( $country , $state , $postcode , $city , $tax_class ) {
2014-11-21 12:05:44 +00:00
global $wpdb ;
2014-01-10 22:57:19 +00:00
2016-04-22 13:02:36 +00:00
// Query criteria - these will be ANDed
$criteria = array ();
2016-04-22 14:42:20 +00:00
$criteria [] = $wpdb -> prepare ( " tax_rate_country IN ( %s, '' ) " , strtoupper ( $country ) );
$criteria [] = $wpdb -> prepare ( " tax_rate_state IN ( %s, '' ) " , strtoupper ( $state ) );
2016-04-22 13:02:36 +00:00
$criteria [] = $wpdb -> prepare ( " tax_rate_class = %s " , sanitize_title ( $tax_class ) );
2016-04-22 14:42:20 +00:00
// Pre-query postcode ranges for PHP based matching.
2016-06-16 10:28:53 +00:00
$postcode_search = wc_get_wildcard_postcodes ( $postcode , $country );
2016-05-23 16:30:37 +00:00
$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 '%...%'; " );
2016-04-22 14:42:20 +00:00
if ( $postcode_ranges ) {
2016-06-16 10:28:53 +00:00
$matches = wc_postcode_location_matcher ( $postcode , $postcode_ranges , 'tax_rate_id' , 'location_code' , $country );
2016-06-10 23:26:07 +00:00
if ( ! empty ( $matches ) ) {
foreach ( $matches as $matched_postcodes ) {
$postcode_search = array_merge ( $postcode_search , $matched_postcodes );
}
}
2016-04-22 13:02:36 +00:00
}
2016-06-10 23:26:07 +00:00
$postcode_search = array_unique ( $postcode_search );
2016-04-22 14:42:20 +00:00
/**
* Location matching criteria - ORed
* Needs to match :
* - rates with no postcodes and cities
* - rates with a matching postcode and city
* - rates with matching postcode , no city
* - rates with matching city , no postcode
*/
$locations_criteria = array ();
$locations_criteria [] = " locations.location_type IS NULL " ;
$locations_criteria [] = "
locations . location_type = 'postcode' AND locations . location_code IN ( '" . implode( "' , '", array_map( ' esc_sql ', $postcode_search ) ) . "' )
AND (
( locations2 . location_type = 'city' AND locations2 . location_code = '" . esc_sql( strtoupper( $city ) ) . "' )
OR NOT EXISTS (
SELECT sub . tax_rate_id FROM { $wpdb -> prefix } woocommerce_tax_rate_locations as sub
WHERE sub . location_type = 'city'
AND sub . tax_rate_id = tax_rates . tax_rate_id
)
)
" ;
$locations_criteria [] = "
locations . location_type = 'city' AND locations . location_code = '" . esc_sql( strtoupper( $city ) ) . "'
AND NOT EXISTS (
SELECT sub . tax_rate_id FROM { $wpdb -> prefix } woocommerce_tax_rate_locations as sub
WHERE sub . location_type = 'postcode'
AND sub . tax_rate_id = tax_rates . tax_rate_id
)
" ;
$criteria [] = '( ( ' . implode ( ' ) OR ( ' , $locations_criteria ) . ' ) )' ;
2016-04-22 13:02:36 +00:00
$found_rates = $wpdb -> get_results ( "
2015-08-07 10:37:15 +00:00
SELECT tax_rates .*
FROM { $wpdb -> prefix } woocommerce_tax_rates as tax_rates
LEFT OUTER JOIN { $wpdb -> prefix } woocommerce_tax_rate_locations as locations ON tax_rates . tax_rate_id = locations . tax_rate_id
LEFT OUTER JOIN { $wpdb -> prefix } woocommerce_tax_rate_locations as locations2 ON tax_rates . tax_rate_id = locations2 . tax_rate_id
2016-04-22 14:42:20 +00:00
WHERE 1 = 1 AND " . implode( ' AND ', $criteria ) . "
2015-08-07 10:37:15 +00:00
GROUP BY tax_rate_id
ORDER BY tax_rate_priority , tax_rate_order
" );
2014-09-20 19:02:51 +00:00
2014-11-21 12:05:44 +00:00
$matched_tax_rates = array ();
$found_priority = array ();
2014-01-10 22:57:19 +00:00
2014-11-21 12:05:44 +00:00
foreach ( $found_rates as $found_rate ) {
if ( in_array ( $found_rate -> tax_rate_priority , $found_priority ) ) {
continue ;
2014-06-02 10:55:36 +00:00
}
2014-11-21 12:05:44 +00:00
$matched_tax_rates [ $found_rate -> tax_rate_id ] = array (
'rate' => $found_rate -> tax_rate ,
'label' => $found_rate -> tax_rate_name ,
'shipping' => $found_rate -> tax_rate_shipping ? 'yes' : 'no' ,
'compound' => $found_rate -> tax_rate_compound ? 'yes' : 'no'
);
2014-01-10 22:57:19 +00:00
2014-11-21 12:05:44 +00:00
$found_priority [] = $found_rate -> tax_rate_priority ;
2014-01-10 22:57:19 +00:00
}
2014-11-21 12:05:44 +00:00
return apply_filters ( 'woocommerce_matched_tax_rates' , $matched_tax_rates , $country , $state , $postcode , $city , $tax_class );
2011-08-09 15:16:18 +00:00
}
2012-08-08 10:42:42 +00:00
2014-11-21 13:06:30 +00:00
/**
2015-11-03 13:31:20 +00:00
* Get the customer tax location based on their status and the current page .
2014-12-09 16:41:26 +00:00
*
2015-11-03 13:31:20 +00:00
* Used by get_rates (), get_shipping_rates () .
2014-12-09 16:41:26 +00:00
*
* @ param $tax_class string Optional , passed to the filter for advanced tax setups .
2014-11-21 13:06:30 +00:00
* @ return array
*/
2014-12-09 16:41:26 +00:00
public static function get_tax_location ( $tax_class = '' ) {
$location = array ();
2014-12-23 22:07:27 +00:00
if ( ! empty ( WC () -> customer ) ) {
2014-12-09 16:41:26 +00:00
$location = WC () -> customer -> get_taxable_address ();
2015-04-14 09:08:27 +00:00
} elseif ( wc_prices_include_tax () || 'base' === get_option ( 'woocommerce_default_customer_address' ) || 'base' === get_option ( 'woocommerce_tax_based_on' ) ) {
2014-12-09 16:41:26 +00:00
$location = array (
2014-11-21 13:06:30 +00:00
WC () -> countries -> get_base_country (),
WC () -> countries -> get_base_state (),
WC () -> countries -> get_base_postcode (),
WC () -> countries -> get_base_city ()
);
}
2014-12-09 16:41:26 +00:00
return apply_filters ( 'woocommerce_get_tax_location' , $location , $tax_class );
2014-11-21 13:06:30 +00:00
}
2011-08-09 15:16:18 +00:00
/**
2012-08-14 20:19:41 +00:00
* Get ' s an array of matching rates for a tax class .
2013-11-27 09:03:47 +00:00
* @ param string $tax_class
2011-12-28 23:59:33 +00:00
* @ return array
2011-08-09 15:16:18 +00:00
*/
2014-06-12 15:47:43 +00:00
public static function get_rates ( $tax_class = '' ) {
2014-11-21 13:06:30 +00:00
$tax_class = sanitize_title ( $tax_class );
2014-12-09 16:41:26 +00:00
$location = self :: get_tax_location ( $tax_class );
2014-11-21 13:06:30 +00:00
$matched_tax_rates = array ();
2012-11-27 16:22:47 +00:00
2014-11-21 13:06:30 +00:00
if ( sizeof ( $location ) === 4 ) {
list ( $country , $state , $postcode , $city ) = $location ;
2012-08-08 10:42:42 +00:00
2014-06-12 15:47:43 +00:00
$matched_tax_rates = self :: find_rates ( array (
2012-11-27 16:22:47 +00:00
'country' => $country ,
'state' => $state ,
'postcode' => $postcode ,
2012-09-23 16:16:39 +00:00
'city' => $city ,
'tax_class' => $tax_class
) );
2012-05-09 17:29:22 +00:00
}
2011-08-09 15:16:18 +00:00
2014-11-11 11:56:13 +00:00
return apply_filters ( 'woocommerce_matched_rates' , $matched_tax_rates , $tax_class );
2011-08-09 15:16:18 +00:00
}
2012-08-08 10:42:42 +00:00
2011-08-09 15:16:18 +00:00
/**
2012-08-14 20:19:41 +00:00
* Get 's an array of matching rates for the shop' s base country .
2011-08-09 15:16:18 +00:00
*
2011-12-28 23:59:33 +00:00
* @ param string Tax Class
* @ return array
2011-08-09 15:16:18 +00:00
*/
2014-11-11 11:56:13 +00:00
public static function get_base_tax_rates ( $tax_class = '' ) {
return apply_filters ( 'woocommerce_base_tax_rates' , self :: find_rates ( array (
2013-09-24 15:56:55 +00:00
'country' => WC () -> countries -> get_base_country (),
'state' => WC () -> countries -> get_base_state (),
'postcode' => WC () -> countries -> get_base_postcode (),
'city' => WC () -> countries -> get_base_city (),
2012-11-27 16:22:47 +00:00
'tax_class' => $tax_class
2014-11-11 11:56:13 +00:00
) ), $tax_class );
}
/**
2015-11-03 13:31:20 +00:00
* Alias for get_base_tax_rates () .
2014-11-11 11:56:13 +00:00
*
* @ deprecated 2.3
* @ param string Tax Class
* @ return array
*/
public static function get_shop_base_rate ( $tax_class = '' ) {
return self :: get_base_tax_rates ( $tax_class );
2012-02-01 21:49:08 +00:00
}
2012-08-08 10:42:42 +00:00
2011-08-09 15:16:18 +00:00
/**
2012-08-14 20:19:41 +00:00
* Gets an array of matching shipping tax rates for a given class .
2011-08-09 15:16:18 +00:00
*
2011-12-28 23:59:33 +00:00
* @ param string Tax Class
2012-08-08 10:42:42 +00:00
* @ return mixed
2011-08-09 15:16:18 +00:00
*/
2014-06-12 15:47:43 +00:00
public static function get_shipping_tax_rates ( $tax_class = null ) {
2012-11-15 14:01:16 +00:00
// See if we have an explicitly set shipping tax class
if ( $shipping_tax_class = get_option ( 'woocommerce_shipping_tax_class' ) ) {
2014-11-21 13:06:30 +00:00
$tax_class = 'standard' === $shipping_tax_class ? '' : $shipping_tax_class ;
2012-05-09 17:29:22 +00:00
}
2012-08-08 10:42:42 +00:00
2014-12-09 16:41:26 +00:00
$location = self :: get_tax_location ( $tax_class );
2014-11-21 13:06:30 +00:00
$matched_tax_rates = array ();
2012-09-23 16:16:39 +00:00
2014-11-21 13:06:30 +00:00
if ( sizeof ( $location ) === 4 ) {
list ( $country , $state , $postcode , $city ) = $location ;
2012-09-23 16:16:39 +00:00
2014-11-21 13:06:30 +00:00
if ( ! is_null ( $tax_class ) ) {
// This will be per item shipping
$matched_tax_rates = self :: find_shipping_rates ( array (
2012-11-27 16:22:47 +00:00
'country' => $country ,
'state' => $state ,
'postcode' => $postcode ,
2014-11-21 13:06:30 +00:00
'city' => $city ,
'tax_class' => $tax_class
2012-09-23 16:16:39 +00:00
) );
2014-11-21 13:06:30 +00:00
} else {
2012-08-08 10:42:42 +00:00
2014-11-21 13:06:30 +00:00
// This will be per order shipping - loop through the order and find the highest tax class rate
$cart_tax_classes = WC () -> cart -> get_cart_item_tax_classes ();
2012-08-08 10:42:42 +00:00
2015-12-22 14:59:42 +00:00
// If multiple classes are found, use the first one. Don't bother with standard rate, we can get that later.
2014-11-21 13:06:30 +00:00
if ( sizeof ( $cart_tax_classes ) > 1 && ! in_array ( '' , $cart_tax_classes ) ) {
$tax_classes = self :: get_tax_classes ();
2012-09-23 16:16:39 +00:00
foreach ( $tax_classes as $tax_class ) {
2015-12-22 14:59:42 +00:00
$tax_class = sanitize_title ( $tax_class );
2014-11-21 13:06:30 +00:00
if ( in_array ( $tax_class , $cart_tax_classes ) ) {
$matched_tax_rates = self :: find_shipping_rates ( array (
2012-11-27 16:22:47 +00:00
'country' => $country ,
'state' => $state ,
'postcode' => $postcode ,
2012-09-23 16:16:39 +00:00
'city' => $city ,
2012-11-27 16:22:47 +00:00
'tax_class' => $tax_class
2012-09-23 16:16:39 +00:00
) );
2012-01-04 16:24:26 +00:00
break ;
2012-09-23 16:16:39 +00:00
}
}
2012-08-08 10:42:42 +00:00
2014-11-21 13:06:30 +00:00
// If a single tax class is found, use it
} elseif ( sizeof ( $cart_tax_classes ) == 1 ) {
$matched_tax_rates = self :: find_shipping_rates ( array (
'country' => $country ,
'state' => $state ,
'postcode' => $postcode ,
'city' => $city ,
'tax_class' => $cart_tax_classes [ 0 ]
) );
}
2012-09-23 16:16:39 +00:00
}
2012-08-08 10:42:42 +00:00
2014-11-21 13:06:30 +00:00
// Get standard rate if no taxes were found
if ( ! sizeof ( $matched_tax_rates ) ) {
$matched_tax_rates = self :: find_shipping_rates ( array (
2012-11-27 16:22:47 +00:00
'country' => $country ,
'state' => $state ,
'postcode' => $postcode ,
2014-11-21 13:06:30 +00:00
'city' => $city
2012-09-23 16:16:39 +00:00
) );
2014-11-21 13:06:30 +00:00
}
2012-09-23 16:16:39 +00:00
}
2014-11-21 13:06:30 +00:00
return $matched_tax_rates ;
2011-08-09 15:16:18 +00:00
}
2012-08-08 10:42:42 +00:00
2011-12-30 19:36:44 +00:00
/**
2012-08-14 20:19:41 +00:00
* Return true / false depending on if a rate is a compound rate .
2011-12-30 21:11:18 +00:00
*
* @ param int key
2012-08-08 10:42:42 +00:00
* @ return bool
2011-12-30 19:36:44 +00:00
*/
2014-06-12 15:47:43 +00:00
public static function is_compound ( $key ) {
2012-12-03 12:18:34 +00:00
global $wpdb ;
return $wpdb -> get_var ( $wpdb -> prepare ( " SELECT tax_rate_compound FROM { $wpdb -> prefix } woocommerce_tax_rates WHERE tax_rate_id = %s " , $key ) ) ? true : false ;
2011-12-30 19:36:44 +00:00
}
2011-12-30 21:11:18 +00:00
2011-12-30 19:36:44 +00:00
/**
2012-08-14 20:19:41 +00:00
* Return a given rates label .
2011-12-30 21:11:18 +00:00
*
2014-07-23 10:30:06 +00:00
* @ param mixed $key_or_rate Tax rate ID , or the db row itself in object format
2012-08-08 10:42:42 +00:00
* @ return string
2011-12-30 19:36:44 +00:00
*/
2014-07-23 10:30:06 +00:00
public static function get_rate_label ( $key_or_rate ) {
2013-11-18 12:30:28 +00:00
global $wpdb ;
2013-09-10 10:04:26 +00:00
2014-07-23 10:30:06 +00:00
if ( is_object ( $key_or_rate ) ) {
$key = $key_or_rate -> tax_rate_id ;
$rate_name = $key_or_rate -> tax_rate_name ;
} else {
$key = $key_or_rate ;
$rate_name = $wpdb -> get_var ( $wpdb -> prepare ( " SELECT tax_rate_name FROM { $wpdb -> prefix } woocommerce_tax_rates WHERE tax_rate_id = %s " , $key ) );
}
2013-09-10 10:04:26 +00:00
2014-07-23 10:30:06 +00:00
if ( ! $rate_name ) {
2013-11-18 12:30:28 +00:00
$rate_name = WC () -> countries -> tax_or_vat ();
2014-07-23 10:30:06 +00:00
}
2013-09-10 10:04:26 +00:00
2014-06-13 14:53:59 +00:00
return apply_filters ( 'woocommerce_rate_label' , $rate_name , $key );
2011-12-30 19:36:44 +00:00
}
2012-08-08 10:42:42 +00:00
2014-07-10 10:01:00 +00:00
/**
* Return a given rates percent .
*
2014-07-23 10:30:06 +00:00
* @ param mixed $key_or_rate Tax rate ID , or the db row itself in object format
2014-07-10 10:01:00 +00:00
* @ return string
*/
2014-07-23 10:30:06 +00:00
public static function get_rate_percent ( $key_or_rate ) {
2014-07-10 10:01:00 +00:00
global $wpdb ;
2014-07-23 10:30:06 +00:00
if ( is_object ( $key_or_rate ) ) {
$key = $key_or_rate -> tax_rate_id ;
$tax_rate = $key_or_rate -> tax_rate ;
} else {
$key = $key_or_rate ;
$tax_rate = $wpdb -> get_var ( $wpdb -> prepare ( " SELECT tax_rate FROM { $wpdb -> prefix } woocommerce_tax_rates WHERE tax_rate_id = %s " , $key ) );
}
2014-07-10 10:01:00 +00:00
return apply_filters ( 'woocommerce_rate_percent' , floatval ( $tax_rate ) . '%' , $key );
}
2012-12-06 19:49:00 +00:00
/**
2015-11-03 13:31:20 +00:00
* Get a rates code . Code is made up of COUNTRY - STATE - NAME - Priority . E . g GB - VAT - 1 , US - AL - TAX - 1.
2012-12-06 19:49:00 +00:00
*
* @ access public
2014-07-23 10:30:06 +00:00
* @ param mixed $key_or_rate Tax rate ID , or the db row itself in object format
2013-11-27 09:03:47 +00:00
* @ return string
2012-12-06 19:49:00 +00:00
*/
2014-07-23 10:30:06 +00:00
public static function get_rate_code ( $key_or_rate ) {
2012-12-06 19:49:00 +00:00
global $wpdb ;
2014-07-23 10:30:06 +00:00
if ( is_object ( $key_or_rate ) ) {
$key = $key_or_rate -> tax_rate_id ;
$rate = $key_or_rate ;
} else {
$key = $key_or_rate ;
$rate = $wpdb -> get_row ( $wpdb -> prepare ( " SELECT * FROM { $wpdb -> prefix } woocommerce_tax_rates WHERE tax_rate_id = %s " , $key ) );
}
2012-12-06 19:49:00 +00:00
2014-10-28 11:24:09 +00:00
$code_string = '' ;
if ( null !== $rate ) {
$code = array ();
$code [] = $rate -> tax_rate_country ;
$code [] = $rate -> tax_rate_state ;
$code [] = $rate -> tax_rate_name ? $rate -> tax_rate_name : 'TAX' ;
$code [] = absint ( $rate -> tax_rate_priority );
$code_string = strtoupper ( implode ( '-' , array_filter ( $code ) ) );
2014-02-17 15:11:50 +00:00
}
2014-10-28 11:24:09 +00:00
return apply_filters ( 'woocommerce_rate_code' , $code_string , $key );
2012-12-06 19:49:00 +00:00
}
2012-01-04 16:24:26 +00:00
/**
2012-08-14 20:19:41 +00:00
* Round tax lines and return the sum .
2012-01-04 16:24:26 +00:00
*
* @ param array
2012-08-08 10:42:42 +00:00
* @ return float
2012-01-04 16:24:26 +00:00
*/
2014-06-12 15:47:43 +00:00
public static function get_tax_total ( $taxes ) {
2014-06-13 14:53:59 +00:00
return array_sum ( array_map ( array ( __CLASS__ , 'round' ), $taxes ) );
2012-01-04 16:24:26 +00:00
}
2014-11-20 17:08:17 +00:00
2014-11-21 13:06:30 +00:00
/**
2015-11-03 13:31:20 +00:00
* Get store tax classes .
2014-11-21 13:06:30 +00:00
* @ return array
*/
public static function get_tax_classes () {
2014-11-24 14:57:08 +00:00
return array_filter ( array_map ( 'trim' , explode ( " \n " , get_option ( 'woocommerce_tax_classes' ) ) ) );
2014-11-21 13:06:30 +00:00
}
2014-11-20 18:41:51 +00:00
/**
2015-11-03 13:31:20 +00:00
* format the city .
2014-11-21 21:40:42 +00:00
* @ param string $city
2014-11-20 18:41:51 +00:00
* @ return string
*/
private static function format_tax_rate_city ( $city ) {
return strtoupper ( trim ( $city ) );
}
/**
2015-11-03 13:31:20 +00:00
* format the state .
2014-11-20 18:41:51 +00:00
* @ param string $state
* @ return string
*/
private static function format_tax_rate_state ( $state ) {
$state = strtoupper ( $state );
return $state === '*' ? '' : $state ;
}
/**
2015-11-03 13:31:20 +00:00
* format the country .
2014-11-21 21:40:42 +00:00
* @ param string $country
2014-11-20 18:41:51 +00:00
* @ return string
*/
private static function format_tax_rate_country ( $country ) {
$country = strtoupper ( $country );
return $country === '*' ? '' : $country ;
}
/**
2015-11-03 13:31:20 +00:00
* format the tax rate name .
2014-11-21 21:40:42 +00:00
* @ param string $name
2014-11-20 18:41:51 +00:00
* @ return string
*/
private static function format_tax_rate_name ( $name ) {
return $name ? $name : __ ( 'Tax' , 'woocommerce' );
}
/**
2015-11-03 13:31:20 +00:00
* format the rate .
2014-11-21 21:40:42 +00:00
* @ param double $rate
2015-02-03 14:32:10 +00:00
* @ return string
2014-11-20 18:41:51 +00:00
*/
private static function format_tax_rate ( $rate ) {
return number_format ( ( double ) $rate , 4 , '.' , '' );
}
/**
2015-11-03 13:31:20 +00:00
* format the priority .
2014-11-20 18:41:51 +00:00
* @ param string $priority
* @ return int
*/
private static function format_tax_rate_priority ( $priority ) {
return absint ( $priority );
}
/**
2015-11-03 13:31:20 +00:00
* format the class .
2014-11-20 18:41:51 +00:00
* @ param string $class
* @ return string
*/
2015-08-13 21:34:00 +00:00
public static function format_tax_rate_class ( $class ) {
2014-11-20 18:41:51 +00:00
$class = sanitize_title ( $class );
2015-08-13 20:56:50 +00:00
$sanitized_classes = array_map ( 'sanitize_title' , self :: get_tax_classes () );
if ( ! in_array ( $class , $sanitized_classes ) ) {
$class = '' ;
}
2014-11-20 18:41:51 +00:00
return $class === 'standard' ? '' : $class ;
}
/**
2015-11-03 13:31:20 +00:00
* Prepare and format tax rate for DB insertion .
2014-11-20 18:41:51 +00:00
* @ param array $tax_rate
* @ return array
*/
private static function prepare_tax_rate ( $tax_rate ) {
foreach ( $tax_rate as $key => $value ) {
if ( method_exists ( __CLASS__ , 'format_' . $key ) ) {
$tax_rate [ $key ] = call_user_func ( array ( __CLASS__ , 'format_' . $key ), $value );
}
}
return $tax_rate ;
}
/**
2015-11-03 13:31:20 +00:00
* Insert a new tax rate .
2014-11-20 18:41:51 +00:00
*
* Internal use only .
*
* @ since 2.3 . 0
* @ access private
*
* @ param array $tax_rate
2015-10-08 20:24:37 +00:00
*
* @ return int tax rate id
2014-11-20 18:41:51 +00:00
*/
public static function _insert_tax_rate ( $tax_rate ) {
global $wpdb ;
$wpdb -> insert ( $wpdb -> prefix . 'woocommerce_tax_rates' , self :: prepare_tax_rate ( $tax_rate ) );
2015-12-02 15:53:46 +00:00
WC_Cache_Helper :: incr_cache_prefix ( 'taxes' );
2014-11-20 18:41:51 +00:00
do_action ( 'woocommerce_tax_rate_added' , $wpdb -> insert_id , $tax_rate );
return $wpdb -> insert_id ;
}
2015-10-08 20:24:37 +00:00
/**
2015-11-03 13:31:20 +00:00
* Get tax rate .
2015-10-08 20:24:37 +00:00
*
* Internal use only .
*
* @ since 2.5 . 0
* @ access private
*
2016-03-09 04:11:56 +00:00
* @ param int $tax_rate_id
* @ param string $output_type
2015-10-08 20:24:37 +00:00
*
* @ return array
*/
2016-03-09 04:11:56 +00:00
public static function _get_tax_rate ( $tax_rate_id , $output_type = ARRAY_A ) {
2015-10-08 20:24:37 +00:00
global $wpdb ;
return $wpdb -> get_row ( $wpdb -> prepare ( "
SELECT *
FROM { $wpdb -> prefix } woocommerce_tax_rates
WHERE tax_rate_id = % d
2016-03-09 04:11:56 +00:00
" , $tax_rate_id ), $output_type );
2015-10-08 20:24:37 +00:00
}
2014-11-20 18:41:51 +00:00
/**
2015-11-03 13:31:20 +00:00
* Update a tax rate .
2014-11-20 18:41:51 +00:00
*
* Internal use only .
*
* @ since 2.3 . 0
* @ access private
*
2015-10-08 20:24:37 +00:00
* @ param int $tax_rate_id
* @ param array $tax_rate
2014-11-20 18:41:51 +00:00
*/
public static function _update_tax_rate ( $tax_rate_id , $tax_rate ) {
global $wpdb ;
$tax_rate_id = absint ( $tax_rate_id );
$wpdb -> update (
$wpdb -> prefix . " woocommerce_tax_rates " ,
self :: prepare_tax_rate ( $tax_rate ),
array (
'tax_rate_id' => $tax_rate_id
)
);
2015-12-02 15:53:46 +00:00
WC_Cache_Helper :: incr_cache_prefix ( 'taxes' );
2014-11-20 18:41:51 +00:00
do_action ( 'woocommerce_tax_rate_updated' , $tax_rate_id , $tax_rate );
}
/**
2015-11-03 13:31:20 +00:00
* Delete a tax rate from the database .
2014-11-20 18:41:51 +00:00
*
* Internal use only .
*
* @ since 2.3 . 0
* @ access private
*
* @ param int $tax_rate_id
*/
public static function _delete_tax_rate ( $tax_rate_id ) {
global $wpdb ;
$wpdb -> query ( $wpdb -> prepare ( " DELETE FROM { $wpdb -> prefix } woocommerce_tax_rate_locations WHERE tax_rate_id = %d; " , $tax_rate_id ) );
$wpdb -> query ( $wpdb -> prepare ( " DELETE FROM { $wpdb -> prefix } woocommerce_tax_rates WHERE tax_rate_id = %d; " , $tax_rate_id ) );
2015-12-02 15:53:46 +00:00
WC_Cache_Helper :: incr_cache_prefix ( 'taxes' );
2014-11-20 18:41:51 +00:00
do_action ( 'woocommerce_tax_rate_deleted' , $tax_rate_id );
}
/**
2015-11-03 13:31:20 +00:00
* Update postcodes for a tax rate in the DB .
2014-11-20 18:41:51 +00:00
*
* Internal use only .
*
* @ since 2.3 . 0
* @ access private
*
* @ param int $tax_rate_id
* @ param string $postcodes String of postcodes separated by ; characters
* @ return string
*/
public static function _update_tax_rate_postcodes ( $tax_rate_id , $postcodes ) {
2015-08-13 21:49:59 +00:00
if ( ! is_array ( $postcodes ) ) {
$postcodes = explode ( ';' , $postcodes );
}
2016-06-16 10:28:53 +00:00
// No normalization - postcodes are matched against both normal and formatted versions to support wildcards.
2016-06-21 09:57:03 +00:00
foreach ( $postcodes as $key => $postcode ) {
$postcodes [ $key ] = strtoupper ( trim ( str_replace ( chr ( 226 ) . chr ( 128 ) . chr ( 166 ), '...' , $postcode ) ) );
}
self :: _update_tax_rate_locations ( $tax_rate_id , array_diff ( array_filter ( $postcodes ), array ( '*' ) ), 'postcode' );
2014-11-20 18:41:51 +00:00
}
/**
2015-11-03 13:31:20 +00:00
* Update cities for a tax rate in the DB .
2014-11-20 18:41:51 +00:00
*
* Internal use only .
*
* @ since 2.3 . 0
* @ access private
*
* @ param int $tax_rate_id
* @ param string $cities
* @ return string
*/
public static function _update_tax_rate_cities ( $tax_rate_id , $cities ) {
2015-08-13 21:49:59 +00:00
if ( ! is_array ( $cities ) ) {
$cities = explode ( ';' , $cities );
}
$cities = array_filter ( array_diff ( array_map ( array ( __CLASS__ , 'format_tax_rate_city' ), $cities ), array ( '*' ) ) );
2014-11-20 18:41:51 +00:00
2014-11-20 21:02:10 +00:00
self :: _update_tax_rate_locations ( $tax_rate_id , $cities , 'city' );
}
/**
2015-11-03 13:31:20 +00:00
* Updates locations ( postcode and city ) .
2014-11-20 21:02:10 +00:00
*
* Internal use only .
*
* @ since 2.3 . 0
* @ access private
*
* @ param int $tax_rate_id
2015-02-03 14:32:10 +00:00
* @ param string $type
2014-11-20 21:02:10 +00:00
* @ return string
*/
private static function _update_tax_rate_locations ( $tax_rate_id , $values , $type ) {
global $wpdb ;
2015-03-13 12:44:04 +00:00
$tax_rate_id = absint ( $tax_rate_id );
2014-11-20 18:41:51 +00:00
$wpdb -> query (
$wpdb -> prepare ( "
2014-11-20 21:02:10 +00:00
DELETE FROM { $wpdb -> prefix } woocommerce_tax_rate_locations WHERE tax_rate_id = % d AND location_type = % s ;
" , $tax_rate_id , $type
2014-11-20 18:41:51 +00:00
)
);
2014-11-21 13:06:30 +00:00
if ( sizeof ( $values ) > 0 ) {
2014-11-20 21:02:10 +00:00
$sql = " ( ' " . implode ( " ', $tax_rate_id , ' " . esc_sql ( $type ) . " ' ),( ' " , array_map ( 'esc_sql' , $values ) ) . " ', $tax_rate_id , ' " . esc_sql ( $type ) . " ' ) " ;
2014-11-20 18:41:51 +00:00
$wpdb -> query ( "
INSERT INTO { $wpdb -> prefix } woocommerce_tax_rate_locations ( location_code , tax_rate_id , location_type ) VALUES $sql ;
" );
}
2015-12-02 15:53:46 +00:00
WC_Cache_Helper :: incr_cache_prefix ( 'taxes' );
2014-11-20 18:41:51 +00:00
}
2015-08-13 21:49:59 +00:00
/**
* Used by admin settings page .
*
2015-11-05 16:05:03 +00:00
* @ param string $tax_class
2015-08-13 21:49:59 +00:00
*
* @ return array | null | object
*/
public static function get_rates_for_tax_class ( $tax_class ) {
global $wpdb ;
// Get all the rates and locations. Snagging all at once should significantly cut down on the number of queries.
$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 ) ) );
$locations = $wpdb -> get_results ( " SELECT * FROM ` { $wpdb -> prefix } woocommerce_tax_rate_locations` " );
2016-02-08 08:58:32 +00:00
if ( ! empty ( $rates ) ) {
// Set the rates keys equal to their ids.
$rates = array_combine ( wp_list_pluck ( $rates , 'tax_rate_id' ), $rates );
}
2015-08-13 21:49:59 +00:00
// Drop the locations into the rates array.
foreach ( $locations as $location ) {
// Don't set them for unexistent rates.
if ( ! isset ( $rates [ $location -> tax_rate_id ] ) ) {
continue ;
}
// If the rate exists, initialize the array before appending to it.
if ( ! isset ( $rates [ $location -> tax_rate_id ] -> { $location -> location_type } ) ) {
$rates [ $location -> tax_rate_id ] -> { $location -> location_type } = array ();
}
$rates [ $location -> tax_rate_id ] -> { $location -> location_type }[] = $location -> location_code ;
}
return $rates ;
}
2014-02-25 11:54:58 +00:00
}
2014-09-20 19:02:51 +00:00
WC_Tax :: init ();