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