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 .
*
2017-12-07 15:39:10 +00:00
* @ var int
2016-01-06 15:24:47 +00:00
*/
2014-06-12 15:47:43 +00:00
public static $precision ;
2016-01-06 15:24:47 +00:00
/**
* Round at subtotal .
*
2017-12-07 15:39:10 +00:00
* @ var bool
2016-01-06 15:24:47 +00:00
*/
2017-12-04 20:33:27 +00:00
public static $round_at_subtotal = false ;
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 () {
2016-07-11 15:26:54 +00:00
self :: $precision = wc_get_rounding_precision ();
2014-06-12 15:47:43 +00:00
self :: $round_at_subtotal = 'yes' === get_option ( 'woocommerce_tax_round_at_subtotal' );
2016-08-25 14:58:30 +00:00
add_action ( 'update_option_woocommerce_tax_classes' , array ( __CLASS__ , 'maybe_remove_tax_class_rates' ), 10 , 2 );
}
/**
* When the woocommerce_tax_classes option is changed , remove any orphan rates .
* @ param string $old_value
* @ param string $value
*/
public static function maybe_remove_tax_class_rates ( $old_value , $value ) {
$old = array_filter ( array_map ( 'trim' , explode ( " \n " , $old_value ) ) );
$new = array_filter ( array_map ( 'trim' , explode ( " \n " , $value ) ) );
$removed = array_filter ( array_map ( 'sanitize_title' , array_diff ( $old , $new ) ) );
if ( $removed ) {
global $wpdb ;
foreach ( $removed as $removed_tax_class ) {
$wpdb -> query ( $wpdb -> prepare ( " DELETE FROM { $wpdb -> prefix } woocommerce_tax_rates WHERE tax_rate_class = %s; " , $removed_tax_class ) );
$wpdb -> query ( " DELETE locations FROM { $wpdb -> prefix } woocommerce_tax_rate_locations locations LEFT JOIN { $wpdb -> prefix } woocommerce_tax_rates rates ON rates.tax_rate_id = locations.tax_rate_id WHERE rates.tax_rate_id IS NULL; " );
}
WC_Cache_Helper :: incr_cache_prefix ( 'taxes' );
}
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 .
2017-12-04 20:33:27 +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 .
* @ param boolean $deprecated Whether to suppress any rounding from taking place . No longer used here .
* @ return array Array of rates + prices after tax .
2013-10-24 12:15:42 +00:00
*/
2017-12-04 20:33:27 +00:00
public static function calc_tax ( $price , $rates , $price_includes_tax = false , $deprecated = false ) {
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
}
2017-12-04 20:33:27 +00:00
return apply_filters ( 'woocommerce_calc_tax' , $taxes , $price , $rates , $price_includes_tax , $deprecated );
2013-10-24 15:16:39 +00:00
}
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 ) {
2016-11-03 11:47:12 +00:00
$taxes = self :: calc_exclusive_tax ( $price , $rates );
return apply_filters ( 'woocommerce_calc_shipping_tax' , $taxes , $price , $rates );
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' );
2017-05-15 11:50:52 +00:00
*
* @ param float | int $in
*
* @ return float
2013-10-24 12:15:42 +00:00
*/
2014-06-12 15:47:43 +00:00
public static function round ( $in ) {
2017-12-04 20:33:27 +00:00
return apply_filters ( 'woocommerce_tax_round' , round ( $in , wc_get_rounding_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 ;
2016-09-07 22:32:24 +00:00
foreach ( $rates as $key => $rate ) {
if ( 'yes' === $rate [ 'compound' ] ) {
2013-10-24 12:15:42 +00:00
$compound_tax_rates = $compound_tax_rates + $rate [ 'rate' ];
2016-09-07 22:32:24 +00:00
} else {
2013-10-24 12:15:42 +00:00
$regular_tax_rates = $regular_tax_rates + $rate [ 'rate' ];
2016-09-07 22:32:24 +00:00
}
}
2013-10-24 12:15:42 +00:00
$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 ) {
2017-03-07 20:24:24 +00:00
if ( ! isset ( $taxes [ $key ] ) ) {
2013-10-24 12:15:42 +00:00
$taxes [ $key ] = 0 ;
2017-03-07 20:24:24 +00:00
}
2013-10-24 12:15:42 +00:00
$the_rate = $rate [ 'rate' ] / 100 ;
2016-09-07 22:32:24 +00:00
if ( 'yes' === $rate [ 'compound' ] ) {
2013-10-24 12:15:42 +00:00
$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
2016-09-07 22:32:24 +00:00
if ( 'yes' === $rate [ 'compound' ] ) {
2014-12-09 09:14:25 +00:00
continue ;
2016-09-07 22:32:24 +00:00
}
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
2017-03-07 20:24:24 +00:00
if ( ! isset ( $taxes [ $key ] ) ) {
2014-12-09 09:14:25 +00:00
$taxes [ $key ] = $tax_amount ;
2017-03-07 20:24:24 +00:00
} else {
2014-12-09 09:14:25 +00:00
$taxes [ $key ] += $tax_amount ;
2017-03-07 20:24:24 +00:00
}
2014-12-09 09:14:25 +00:00
}
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 ) {
2016-09-07 22:32:24 +00:00
if ( 'no' === $rate [ 'compound' ] ) {
2013-10-24 12:15:42 +00:00
continue ;
2016-09-07 22:32:24 +00:00
}
2013-10-24 12:15:42 +00:00
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
2016-09-07 22:32:24 +00:00
if ( ! isset ( $taxes [ $key ] ) ) {
2013-10-24 12:15:42 +00:00
$taxes [ $key ] = $tax_amount ;
2016-09-07 22:32:24 +00:00
} else {
2013-10-24 12:15:42 +00:00
$taxes [ $key ] += $tax_amount ;
2016-09-07 22:32:24 +00:00
}
2013-10-24 12:15:42 +00:00
}
}
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' => '' ,
2016-08-27 01:46:45 +00:00
'tax_class' => '' ,
2014-11-21 12:05:44 +00:00
) );
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 ;
}
2016-07-27 10:30:12 +00:00
/**
2017-10-12 13:04:10 +00:00
* Does the sort comparison . Compares ( in this order ) :
* - Priority
* - Country
* - State
* - Number of postcodes
* - Number of cities
* - ID
2017-05-15 11:50:52 +00:00
*
2017-10-12 13:04:10 +00:00
* @ param object $rate1 First rate to compare .
* @ param object $rate2 Second rate to compare .
2017-05-15 11:50:52 +00:00
* @ return int
2016-07-27 10:30:12 +00:00
*/
private static function sort_rates_callback ( $rate1 , $rate2 ) {
if ( $rate1 -> tax_rate_priority !== $rate2 -> tax_rate_priority ) {
return $rate1 -> tax_rate_priority < $rate2 -> tax_rate_priority ? - 1 : 1 ; // ASC
2017-10-12 13:04:10 +00:00
}
if ( $rate1 -> tax_rate_country !== $rate2 -> tax_rate_country ) {
2016-07-27 10:30:12 +00:00
if ( '' === $rate1 -> tax_rate_country ) {
return 1 ;
}
if ( '' === $rate2 -> tax_rate_country ) {
return - 1 ;
}
return strcmp ( $rate1 -> tax_rate_country , $rate2 -> tax_rate_country ) > 0 ? 1 : - 1 ;
2017-10-12 13:04:10 +00:00
}
if ( $rate1 -> tax_rate_state !== $rate2 -> tax_rate_state ) {
2016-07-27 10:30:12 +00:00
if ( '' === $rate1 -> tax_rate_state ) {
return 1 ;
}
if ( '' === $rate2 -> tax_rate_state ) {
return - 1 ;
}
return strcmp ( $rate1 -> tax_rate_state , $rate2 -> tax_rate_state ) > 0 ? 1 : - 1 ;
}
2017-10-12 13:04:10 +00:00
if ( isset ( $rate1 -> postcode_count , $rate2 -> postcode_count ) && $rate1 -> postcode_count !== $rate2 -> postcode_count ) {
return $rate1 -> postcode_count < $rate2 -> postcode_count ? 1 : - 1 ;
}
if ( isset ( $rate1 -> city_count , $rate2 -> city_count ) && $rate1 -> city_count !== $rate2 -> city_count ) {
return $rate1 -> city_count < $rate2 -> city_count ? 1 : - 1 ;
}
return $rate1 -> tax_rate_id < $rate2 -> tax_rate_id ? - 1 : 1 ;
2016-07-27 10:30:12 +00:00
}
2016-07-27 09:37:55 +00:00
/**
2017-10-12 13:04:10 +00:00
* Logical sort order for tax rates based on the following in order of priority .
*
* @ param array $rates Rates to be sorted .
2016-07-27 09:37:55 +00:00
* @ return array
*/
private static function sort_rates ( $rates ) {
2016-07-27 10:30:12 +00:00
uasort ( $rates , __CLASS__ . '::sort_rates_callback' );
$i = 0 ;
foreach ( $rates as $key => $rate ) {
$rates [ $key ] -> tax_rate_order = $i ++ ;
}
2016-07-27 09:37:55 +00:00
return $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
*
2017-10-12 13:04:10 +00:00
* @ param string $country Country code to match against .
* @ param string $state State code to match against .
* @ param string $postcode Postcode to match against .
* @ param string $city City to match against .
* @ param string $tax_class Tax class to match against .
2014-11-21 12:05:44 +00:00
* @ 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
2017-10-12 13:04:10 +00:00
// Query criteria - these will be ANDed.
2016-04-22 13:02:36 +00:00
$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 ) );
2017-10-12 13:04:10 +00:00
$criteria [] = $wpdb -> prepare ( 'tax_rate_class = %s' , sanitize_title ( $tax_class ) );
2016-04-22 13:02:36 +00:00
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 ();
2017-10-12 13:04:10 +00:00
$locations_criteria [] = 'locations.location_type IS NULL' ;
2016-04-22 14:42:20 +00:00
$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 ( "
2017-10-12 13:04:10 +00:00
SELECT tax_rates .* , COUNT ( locations . location_id ) as postcode_count , COUNT ( locations2 . location_id ) as city_count
2015-08-07 10:37:15 +00:00
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 ) . "
2017-01-31 22:58:54 +00:00
GROUP BY tax_rates . tax_rate_id
ORDER BY tax_rates . tax_rate_priority
2015-08-07 10:37:15 +00:00
" );
2014-09-20 19:02:51 +00:00
2016-07-27 09:37:55 +00:00
$found_rates = self :: sort_rates ( $found_rates );
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 ) {
2017-10-12 13:04:10 +00:00
if ( in_array ( $found_rate -> tax_rate_priority , $found_priority , true ) ) {
2014-11-21 12:05:44 +00:00
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' ,
2016-08-27 01:46:45 +00:00
'compound' => $found_rate -> tax_rate_compound ? 'yes' : 'no' ,
2014-11-21 12:05:44 +00:00
);
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 .
2017-07-27 12:48:58 +00:00
* @ param object $customer Override the customer object to get their location .
2014-11-21 13:06:30 +00:00
* @ return array
*/
2017-07-27 12:48:58 +00:00
public static function get_tax_location ( $tax_class = '' , $customer = null ) {
2014-12-09 16:41:26 +00:00
$location = array ();
2017-07-27 12:48:58 +00:00
if ( is_null ( $customer ) && ! empty ( WC () -> customer ) ) {
$customer = WC () -> customer ;
}
if ( ! empty ( $customer ) ) {
$location = $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 (),
2016-08-27 02:08:49 +00:00
WC () -> countries -> get_base_city (),
2014-11-21 13:06:30 +00:00
);
}
2014-12-09 16:41:26 +00:00
2017-07-27 12:48:58 +00:00
return apply_filters ( 'woocommerce_get_tax_location' , $location , $tax_class , $customer );
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 .
2017-07-27 12:48:58 +00:00
*
* @ param string $tax_class Tax class to get rates for .
* @ param object $customer Override the customer object to get their location .
2011-12-28 23:59:33 +00:00
* @ return array
2011-08-09 15:16:18 +00:00
*/
2017-07-27 12:48:58 +00:00
public static function get_rates ( $tax_class = '' , $customer = null ) {
2014-11-21 13:06:30 +00:00
$tax_class = sanitize_title ( $tax_class );
2017-07-27 12:48:58 +00:00
$location = self :: get_tax_location ( $tax_class , $customer );
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 ,
2016-08-27 01:46:45 +00:00
'tax_class' => $tax_class ,
2012-09-23 16:16:39 +00:00
) );
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 (),
2016-08-27 01:46:45 +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
*
2017-07-27 12:48:58 +00:00
* @ param string $tax_class Tax class to get rates for .
* @ param object $customer Override the customer object to get their location .
* @ return mixed
2011-08-09 15:16:18 +00:00
*/
2017-07-27 12:48:58 +00:00
public static function get_shipping_tax_rates ( $tax_class = null , $customer = null ) {
2012-11-15 14:01:16 +00:00
// See if we have an explicitly set shipping tax class
WIP - Product CRUD (#12065)
* Created function to get the catalog visibility options
* First methods for WP_Product crud
* Product set methods
* Fixed several erros while setting data
* First methods for WP_Product crud
* Product set methods
* Fixed several erros while setting data
* Hardcode the get_type per product class
* Initial look through getters and setters and abstract data
* Missing var
* Add related product functions and deprecate those in class.
* No need to exclude ID
* Fixed coding standards and improved the docblocks
* Get cached terms from wc_get_related_terms()
* Fixed wrong variable in wc_get_related_terms
* Use count() instead of sizeof()
* Sanitize ids later
* Remove unneeded comments
* wc_get_product_term_ids instead of related wording and use in other places.
get_the_terms is used here and also handles caching, something
wp_get_post_terms does not.
* Clean up the abstract product class a bit, deprecate two functions we have renamed, make update & create work properly, and add some tests for it.
* Bump template version
* Handle PR feedback: Remove duplicate regular_price update, allow changing of post status for products, remove deprecation for get_title since we might still offer it as a function
* Made abstract function useful
* External Product CRUD
* _virtual meta should be 'no', not taxable, in product unit test helper
* Grouped product class
* Tests
* Move children to meta and update test
* Use get_upsell_ids
* Spacing in query
* Moving and refactoring methods
* Availability html
* Tidy/add todos
* Rename method
* Put back review functions (still todo)
* missing $this
* get_price_including_tax/excluding_tax functions
* wc_get_price_to_display
* Price handling
* [Product CRUD] Variable (#12146)
* [Product CRUD] Variable Products
* Handle PR feedback.
* [Product CRUD] Grouped Handling (#12151)
* Handle grouped product saving
* Update routine
* [Product CRUD] Product crud terms (#12149)
* Category and tag id handling
* Replace template functions
* Remove todo
* Handle default name in save function
* Product crud admin save routine (#12174)
* Initial props
* Work on admin saving
* Set/get attributes
* Atom was moaning about this before but no longer.
* Update get_shipping_class
* WC_Product_Attribute
* Use getter in admin panel
* Fix attribute saving
* Spacing
* Fix comment
* wc_implode_text_attributes helper function
* [Product CRUD] Product crud admin use getters (#12196)
* Initial props
* Work on admin saving
* Set/get attributes
* Atom was moaning about this before but no longer.
* Update get_shipping_class
* WC_Product_Attribute
* Use getter in admin panel
* Fix attribute saving
* Move settings into new files
* Refactor panels and use getters
* Use getters for variation panel
* Revert save variation changes for now
* Add todos
* Fix downloads
* REST API CRUD Updates
* Additional API updates/fixes. Added some todos
* Fix final failing tests and implementing setters/getters and attributes functionality.
* Fix comparison for is_on_sale and remove download_type from WC_Product.
* Add a wc_get_products wrapper.
* Remove the download type input from the product data metabox for downloadable products. (#12221)
* [Product CRUD] Variations - setters, getters and admin. (#12228)
* Started on variation changes
* Stock functions
* Variation class
* Bulk change ->id to get_id() to fix variation form display
* Missing status
* Fix add to cart
* Start on stored data save
* save variation
* Save_variations
* Variation edit panel
* Save variations code works.
* Remove stored data code and fix save
* Improve legacy class
* wc_bool_to_string
* prepare_set_attributes
* Use wc_get_products
* More feedback fixes
* Feedback fixes
* Implement CRUD in the legacy REST API
* Handle PR feedback
* [Product CRUD] Getter setter proxy methods (#12236)
* Started on variation changes
* Stock functions
* Variation class
* Bulk change ->id to get_id() to fix variation form display
* Missing status
* Fix add to cart
* Start on stored data save
* save variation
* Save_variations
* Variation edit panel
* Save variations code works.
* Remove stored data code and fix save
* Improve legacy class
* wc_bool_to_string
* prepare_set_attributes
* Use wc_get_products
* More feedback fixes
* get_prop implementation in abstract and data classes
* Implement set_prop
* Change handling
* Array key exists
* set_object_read
* Use get_the_terms() instead of wp_get_post_terms()
wp_get_post_terms() is a wrapper around wp_get_object_terms() which does not
use the object cache, and generates a database query every time it is used.
get_the_terms() however can use data from the object cache if present.
* Allow WP_Query to preload post data, and meta in wc_get_products()
Allow WP_Query to bulk query for post data and meta if more than
just IDs are requested from wc_get_products(). Reduces query count
significantly.
* [Product CRUD] Variable, variation, notices, and stock handling (#12277)
* No longer needed
* Remove old todos
* Use getters in admin list
* Related and upsells update for CRUD
* Fix notice in gallery
* Variable fixes and todos
* Context
* Price sync
* Revert variation attributes change
* Return parent data in view context
* Defer term counting
* wc_find_matching_product_variation
* Stock manage tweaks
* Stock fixes
* Correct id
* correct id
* Better sync
* Data logic setter fix
* feedback
* First methods for WP_Product crud
* Product set methods
* Fixed several erros while setting data
* Hardcode the get_type per product class
* Initial look through getters and setters and abstract data
* Missing var
* Fixed coding standards and improved the docblocks
* Get cached terms from wc_get_related_terms()
* Fixed wrong variable in wc_get_related_terms
* Use count() instead of sizeof()
* Add related product functions and deprecate those in class.
* No need to exclude ID
* Sanitize ids later
* Clean up the abstract product class a bit, deprecate two functions we have renamed, make update & create work properly, and add some tests for it.
* Remove unneeded comments
* wc_get_product_term_ids instead of related wording and use in other places.
get_the_terms is used here and also handles caching, something
wp_get_post_terms does not.
* Handle PR feedback: Remove duplicate regular_price update, allow changing of post status for products, remove deprecation for get_title since we might still offer it as a function
* External Product CRUD
* _virtual meta should be 'no', not taxable, in product unit test helper
* Bump template version
* Made abstract function useful
* Grouped product class
* Tests
* Move children to meta and update test
* Use get_upsell_ids
* Spacing in query
* Moving and refactoring methods
* Availability html
* Tidy/add todos
* Rename method
* Put back review functions (still todo)
* missing $this
* get_price_including_tax/excluding_tax functions
* wc_get_price_to_display
* Price handling
* [Product CRUD] Variable (#12146)
* [Product CRUD] Variable Products
* Handle PR feedback.
* [Product CRUD] Grouped Handling (#12151)
* Handle grouped product saving
* Update routine
* [Product CRUD] Product crud terms (#12149)
* Category and tag id handling
* Replace template functions
* Remove todo
* Handle default name in save function
* Product crud admin save routine (#12174)
* Initial props
* Work on admin saving
* Set/get attributes
* Atom was moaning about this before but no longer.
* Update get_shipping_class
* WC_Product_Attribute
* Use getter in admin panel
* Fix attribute saving
* Spacing
* Fix comment
* wc_implode_text_attributes helper function
* [Product CRUD] Product crud admin use getters (#12196)
* Initial props
* Work on admin saving
* Set/get attributes
* Atom was moaning about this before but no longer.
* Update get_shipping_class
* WC_Product_Attribute
* Use getter in admin panel
* Fix attribute saving
* Move settings into new files
* Refactor panels and use getters
* Use getters for variation panel
* Revert save variation changes for now
* Add todos
* Fix downloads
* REST API CRUD Updates
* Additional API updates/fixes. Added some todos
* Fix final failing tests and implementing setters/getters and attributes functionality.
* Fix comparison for is_on_sale and remove download_type from WC_Product.
* Add a wc_get_products wrapper.
* Remove the download type input from the product data metabox for downloadable products. (#12221)
* [Product CRUD] Variations - setters, getters and admin. (#12228)
* Started on variation changes
* Stock functions
* Variation class
* Bulk change ->id to get_id() to fix variation form display
* Missing status
* Fix add to cart
* Start on stored data save
* save variation
* Save_variations
* Variation edit panel
* Save variations code works.
* Remove stored data code and fix save
* Improve legacy class
* wc_bool_to_string
* prepare_set_attributes
* Use wc_get_products
* More feedback fixes
* Feedback fixes
* Implement CRUD in the legacy REST API
* Handle PR feedback
* [Product CRUD] Getter setter proxy methods (#12236)
* Started on variation changes
* Stock functions
* Variation class
* Bulk change ->id to get_id() to fix variation form display
* Missing status
* Fix add to cart
* Start on stored data save
* save variation
* Save_variations
* Variation edit panel
* Save variations code works.
* Remove stored data code and fix save
* Improve legacy class
* wc_bool_to_string
* prepare_set_attributes
* Use wc_get_products
* More feedback fixes
* get_prop implementation in abstract and data classes
* Implement set_prop
* Change handling
* Array key exists
* set_object_read
* Use get_the_terms() instead of wp_get_post_terms()
wp_get_post_terms() is a wrapper around wp_get_object_terms() which does not
use the object cache, and generates a database query every time it is used.
get_the_terms() however can use data from the object cache if present.
* [Product CRUD] Variable, variation, notices, and stock handling (#12277)
* No longer needed
* Remove old todos
* Use getters in admin list
* Related and upsells update for CRUD
* Fix notice in gallery
* Variable fixes and todos
* Context
* Price sync
* Revert variation attributes change
* Return parent data in view context
* Defer term counting
* wc_find_matching_product_variation
* Stock manage tweaks
* Stock fixes
* Correct id
* correct id
* Better sync
* Data logic setter fix
* feedback
* Prevent notices
* Handle image_id from parent
* Fix error
* Remove _wc_save_product_price
* Remove todo
* Fixed wrong variation URLs
* Fixed undefined $image_id in WC_Product_Variation::get_image_id()
* Allow wc_rest_prepare_date_response() handle timestamps
* Updated get methods on REST API for variations
* Use variations CRUD to save variations metadata
* [Product CRUD] Abstract todos (#12305)
* Get dimensions and weights, with soft deprecation
* Product attributes
* Ratings
* Fix read method
* Downloads
* Feedback
* Revert "[Product CRUD] Abstract todos (#12305)"
This reverts commit 9a6136fcf88fec16f97457b7c8a4388f7587bfa2.
* Remove deprecated get_variation_id()
* New default attributes method
* [Product CRUD] Product Datastore (#12317)
* Fix up tests in the product/* folder.
* Handle data store updates for grouped, variable, external, simple, and general data store updates for products.
* Variations & variable changes.
* Update -functions.php calls to use data store.
* Add an interface for the public product data store methods.
* Finished product factory tests
* Correctly delete in the api, fix up some comments, and implement an interface for the public variable methods.
* Fix up delete in all versions of the api
* Handle feedback
* Match protected decloration to parent
* Product crud abstract todos (#12316)
* Get dimensions and weights, with soft deprecation
* Product attributes
* Ratings
* Fix read method
* Downloads
* Feedback
* Fix up store
* Fixed method returning in write context
* Fix error in variation admin
* Check for parent value - fixes tax class
* Remove old/complete todos
* Allow set tax class as "parent"
* Removed duplicated sync
* Fixed wrong variation URLs
* Fixed undefined $image_id in WC_Product_Variation::get_image_id()
* Allow wc_rest_prepare_date_response() handle timestamps
* Updated get methods on REST API for variations
* Use variations CRUD to save variations metadata
* Remove deprecated get_variation_id()
* New default attributes method
* Fixed method returning in write context
* Allow set tax class as "parent"
* Removed duplicated sync
* Fixed coding standards
* TODO is not accurate.
* Should pass WC_Product instancies to WC_Comments methods (#12327)
* Use new method in abstract order class to prevent headers sent issue in tests
* Fixed variable description in REST API
* Updated how create initial product variation
* Fixed a few fatal errors and warnings in Products CRUD (#12329)
* Fixed a few fatal errors and warnings in Products CRUD
* Fixed sync functions
* Add variations CRUD to legacy API (#12331)
* Apply crud to variable products in legacy API v1
* New REST API do not need fallback for default attributes
* Apply variations CRUD to legacy API v2
* Legacy v2 - save default attributes
* Variations in legacy API v2 do not have descriptions
* Fixed legacy API v2 variations params
* Applied variations CRUD to legacy API v3
* Sync before save in legacy apis
* Punc
* Removed API todos
* Removed test
* Products endpoint tweaks (#12354)
* Var type already normalized on CRUD
* Let Product CRUD handle with validation, sanitization and conditional checks
* Set downloads using WC_Product_Download
* Stop try catch exceptions more than one time
* Handle WC_Data_Exception in legacy API
* Complete remove products when fails on creating
* On creating I mean!
* Already have a method to complete delete products
* Fixed standards using WP CodeSniffer
* get_the_terms() returns false when empty
* get_manage_stock returns boolean
@claudiosanches
* Merge conflict
* Variations API endpoint fixes
* Product CRUD improvements (#12359)
* args is not used any more - remove todo
* Added test for attributes
* wc_get_price_excluding_tax usage
* parent usage
* Fix rating counts
* Test fixes
* Cleanup after tests
* Make sure status transition code runs even during API calls, not just in admin.
* Default visibility
* Fix attribute setting in API
* Use get name instead of get title
* variation id usage
* Improved cross sell templates
* variation_data
* Grouped product sync
* Notices
* Sync is not needed in API
* Delete
* Rename interfaces
* Update counts in data store
2016-11-16 12:38:24 +00:00
$shipping_tax_class = get_option ( 'woocommerce_shipping_tax_class' );
if ( 'inherit' !== $shipping_tax_class ) {
$tax_class = $shipping_tax_class ;
2012-05-09 17:29:22 +00:00
}
2012-08-08 10:42:42 +00:00
2017-07-27 12:48:58 +00:00
$location = self :: get_tax_location ( $tax_class , $customer );
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 ,
2016-08-27 01:46:45 +00:00
'tax_class' => $tax_class ,
2012-09-23 16:16:39 +00:00
) );
2017-08-07 16:49:46 +00:00
} elseif ( WC () -> cart -> get_cart () ) {
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
2017-11-20 19:14:23 +00:00
$cart_tax_classes = WC () -> cart -> get_cart_item_tax_classes_for_shipping ();
2012-08-08 10:42:42 +00:00
2017-08-03 06:44:02 +00:00
// No tax classes = no taxable items.
if ( empty ( $cart_tax_classes ) ) {
return array ();
}
2017-07-17 10:10:52 +00:00
// If multiple classes are found, use the first one found unless a standard rate item is found. This will be the first listed in the 'additional tax class' section.
2014-11-21 13:06:30 +00:00
if ( sizeof ( $cart_tax_classes ) > 1 && ! in_array ( '' , $cart_tax_classes ) ) {
2017-01-25 21:38:13 +00:00
$tax_classes = self :: get_tax_class_slugs ();
2012-09-23 16:16:39 +00:00
foreach ( $tax_classes as $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 ,
2016-08-27 01:46:45 +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 ,
2016-08-27 01:46:45 +00:00
'tax_class' => $cart_tax_classes [ 0 ],
2014-11-21 13:06:30 +00:00
) );
}
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 ,
2016-08-27 01:46:45 +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
*
2017-02-20 21:50:07 +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 bool
2011-12-30 19:36:44 +00:00
*/
2017-02-20 21:50:07 +00:00
public static function is_compound ( $key_or_rate ) {
2012-12-03 12:18:34 +00:00
global $wpdb ;
2017-02-20 21:50:07 +00:00
if ( is_object ( $key_or_rate ) ) {
$key = $key_or_rate -> tax_rate_id ;
$compound = $key_or_rate -> tax_rate_compound ;
} else {
$key = $key_or_rate ;
$compound = $wpdb -> get_var ( $wpdb -> prepare ( " SELECT tax_rate_compound FROM { $wpdb -> prefix } woocommerce_tax_rates WHERE tax_rate_id = %s " , $key ) ) ? true : false ;
}
return ( bool ) apply_filters ( 'woocommerce_rate_compound' , $compound , $key );
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 ;
2017-02-13 12:58:42 +00:00
$rate = $wpdb -> get_row ( $wpdb -> prepare ( " SELECT tax_rate_country, tax_rate_state, tax_rate_name, tax_rate_priority FROM { $wpdb -> prefix } woocommerce_tax_rates WHERE tax_rate_id = %s " , $key ) );
2014-07-23 10:30:06 +00:00
}
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 .
2017-10-17 05:05:48 +00:00
*
2017-01-25 21:48:29 +00:00
* @ return array Array of class names ( " Reduced rate " , " Zero rate " , etc ) .
2014-11-21 13:06:30 +00:00
*/
public static function get_tax_classes () {
2017-10-17 05:05:48 +00:00
return array_filter ( array_map ( 'trim' , explode ( " \n " , get_option ( 'woocommerce_tax_classes' ) ) ), array ( __CLASS__ , 'is_valid_tax_class' ) );
}
/**
* Filter out invalid tax classes .
*
* @ param string $tax_class Tax class name .
* @ return boolean
*/
private static function is_valid_tax_class ( $tax_class ) {
return ! empty ( $tax_class ) && sanitize_title ( $tax_class );
2014-11-21 13:06:30 +00:00
}
2017-01-25 21:38:13 +00:00
/**
* Get store tax classes as slugs .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2017-01-25 21:48:29 +00:00
* @ return array Array of class slugs ( " reduced-rate " , " zero-rate " , etc ) .
2017-01-25 21:38:13 +00:00
*/
public static function get_tax_class_slugs () {
2017-10-17 05:05:48 +00:00
return array_filter ( array_map ( 'sanitize_title' , self :: get_tax_classes () ) );
2017-01-25 21:38:13 +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 );
2016-09-07 22:32:24 +00:00
return ( '*' === $state ) ? '' : $state ;
2014-11-20 18:41:51 +00:00
}
/**
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 );
2016-09-07 22:32:24 +00:00
return ( '*' === $country ) ? '' : $country ;
2014-11-20 18:41:51 +00:00
}
/**
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 ) {
2017-01-25 21:38:13 +00:00
$class = sanitize_title ( $class );
$classes = self :: get_tax_class_slugs ();
if ( ! in_array ( $class , $classes ) ) {
2015-08-13 20:56:50 +00:00
$class = '' ;
}
2016-09-07 22:32:24 +00:00
return ( 'standard' === $class ) ? '' : $class ;
2014-11-20 18:41:51 +00:00
}
/**
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 ) ) {
2017-09-14 22:50:25 +00:00
if ( 'tax_rate_state' === $key ) {
$tax_rate [ $key ] = call_user_func ( array ( __CLASS__ , 'format_' . $key ), sanitize_key ( $value ) );
} else {
$tax_rate [ $key ] = call_user_func ( array ( __CLASS__ , 'format_' . $key ), $value );
}
2014-11-20 18:41:51 +00:00
}
}
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
*
2017-05-15 11:50:52 +00:00
* @ return array | object
2015-10-08 20:24:37 +00:00
*/
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 (
2016-08-27 01:46:45 +00:00
'tax_rate_id' => $tax_rate_id ,
2014-11-20 18:41:51 +00:00
)
);
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
*/
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
*/
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
*
2017-05-15 11:50:52 +00:00
* @ param int $tax_rate_id
* @ param array $values
2015-02-03 14:32:10 +00:00
* @ param string $type
2014-11-20 21:02:10 +00:00
*/
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.
2017-10-12 13:04:10 +00:00
$rates = $wpdb -> get_results ( $wpdb -> prepare ( " SELECT * FROM ` { $wpdb -> prefix } woocommerce_tax_rates` WHERE `tax_rate_class` = %s; " , sanitize_title ( $tax_class ) ) );
2015-08-13 21:49:59 +00:00
$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 ;
}
2017-10-12 13:04:10 +00:00
foreach ( $rates as $rate_id => $rate ) {
$rates [ $rate_id ] -> postcode_count = isset ( $rates [ $rate_id ] -> postcode ) ? count ( $rates [ $rate_id ] -> postcode ) : 0 ;
$rates [ $rate_id ] -> city_count = isset ( $rates [ $rate_id ] -> city ) ? count ( $rates [ $rate_id ] -> city ) : 0 ;
}
$rates = self :: sort_rates ( $rates );
2015-08-13 21:49:59 +00:00
return $rates ;
}
2014-02-25 11:54:58 +00:00
}
2014-09-20 19:02:51 +00:00
WC_Tax :: init ();