WC_Tax

Performs tax calculations and loads tax rates.

Defined (1)

The class is defined in the following location(s).

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