1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Tax calculation and rate finding class. |
4
|
|
|
* |
5
|
|
|
* @package WooCommerce/Classes |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
defined( 'ABSPATH' ) || exit; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Performs tax calculations and loads tax rates |
12
|
|
|
* |
13
|
|
|
* @class WC_Tax |
14
|
|
|
*/ |
15
|
|
|
class WC_Tax { |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Precision. |
19
|
|
|
* |
20
|
|
|
* @var int |
21
|
|
|
*/ |
22
|
|
|
public static $precision; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Round at subtotal. |
26
|
|
|
* |
27
|
|
|
* @var bool |
28
|
|
|
*/ |
29
|
|
|
public static $round_at_subtotal = false; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Load options. |
33
|
|
|
*/ |
34
|
|
|
public static function init() { |
35
|
|
|
self::$precision = wc_get_rounding_precision(); |
36
|
|
|
self::$round_at_subtotal = 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ); |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* When the woocommerce_tax_classes option is changed, remove any orphan rates. |
41
|
|
|
* |
42
|
|
|
* @deprecated 3.7.0 |
43
|
|
|
* @param string $old_value Old rates value. |
44
|
|
|
* @param string $value New rates value. |
45
|
|
|
*/ |
46
|
|
|
public static function maybe_remove_tax_class_rates( $old_value, $value ) { |
|
|
|
|
47
|
|
|
wc_deprecated_function( 'WC_Tax::maybe_remove_tax_class_rates', '3.7', 'WC_Tax::delete_tax_class_by' ); |
48
|
|
|
|
49
|
|
|
$tax_classes = array_filter( array_map( 'trim', explode( "\n", $value ) ) ); |
50
|
|
|
$existing_tax_classes = self::get_tax_classes(); |
51
|
|
|
$removed = array_diff( $existing_tax_classes, $tax_classes ); |
52
|
|
|
foreach ( $removed as $name ) { |
53
|
|
|
self::delete_tax_class_by( 'name', $name ); |
54
|
|
|
} |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Calculate tax for a line. |
59
|
|
|
* |
60
|
|
|
* @param float $price Price to calc tax on. |
61
|
|
|
* @param array $rates Rates to apply. |
62
|
|
|
* @param boolean $price_includes_tax Whether the passed price has taxes included. |
63
|
|
|
* @param boolean $deprecated Whether to suppress any rounding from taking place. No longer used here. |
64
|
|
|
* @return array Array of rates + prices after tax. |
65
|
|
|
*/ |
66
|
43 |
|
public static function calc_tax( $price, $rates, $price_includes_tax = false, $deprecated = false ) { |
67
|
43 |
|
if ( $price_includes_tax ) { |
68
|
14 |
|
$taxes = self::calc_inclusive_tax( $price, $rates ); |
69
|
|
|
} else { |
70
|
36 |
|
$taxes = self::calc_exclusive_tax( $price, $rates ); |
71
|
|
|
} |
72
|
43 |
|
return apply_filters( 'woocommerce_calc_tax', $taxes, $price, $rates, $price_includes_tax, $deprecated ); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Calculate the shipping tax using a passed array of rates. |
77
|
|
|
* |
78
|
|
|
* @param float $price Shipping cost. |
79
|
|
|
* @param array $rates Taxation Rate. |
80
|
|
|
* @return array |
81
|
|
|
*/ |
82
|
65 |
|
public static function calc_shipping_tax( $price, $rates ) { |
83
|
65 |
|
$taxes = self::calc_exclusive_tax( $price, $rates ); |
84
|
65 |
|
return apply_filters( 'woocommerce_calc_shipping_tax', $taxes, $price, $rates ); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Round to precision. |
89
|
|
|
* |
90
|
|
|
* Filter example: to return rounding to .5 cents you'd use: |
91
|
|
|
* |
92
|
|
|
* function euro_5cent_rounding( $in ) { |
93
|
|
|
* return round( $in / 5, 2 ) * 5; |
94
|
|
|
* } |
95
|
|
|
* add_filter( 'woocommerce_tax_round', 'euro_5cent_rounding' ); |
96
|
|
|
* |
97
|
|
|
* @param float|int $in Value to round. |
98
|
|
|
* @return float |
99
|
|
|
*/ |
100
|
42 |
|
public static function round( $in ) { |
101
|
42 |
|
return apply_filters( 'woocommerce_tax_round', round( $in, wc_get_rounding_precision() ), $in ); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Calc tax from inclusive price. |
106
|
|
|
* |
107
|
|
|
* @param float $price Price to calculate tax for. |
108
|
|
|
* @param array $rates Array of tax rates. |
109
|
|
|
* @return array |
110
|
|
|
*/ |
111
|
14 |
|
public static function calc_inclusive_tax( $price, $rates ) { |
112
|
14 |
|
$taxes = array(); |
113
|
14 |
|
$compound_rates = array(); |
114
|
14 |
|
$regular_rates = array(); |
115
|
|
|
|
116
|
|
|
// Index array so taxes are output in correct order and see what compound/regular rates we have to calculate. |
117
|
14 |
|
foreach ( $rates as $key => $rate ) { |
118
|
14 |
|
$taxes[ $key ] = 0; |
119
|
|
|
|
120
|
14 |
|
if ( 'yes' === $rate['compound'] ) { |
121
|
1 |
|
$compound_rates[ $key ] = $rate['rate']; |
122
|
|
|
} else { |
123
|
14 |
|
$regular_rates[ $key ] = $rate['rate']; |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
14 |
|
$compound_rates = array_reverse( $compound_rates, true ); // Working backwards. |
128
|
|
|
|
129
|
14 |
|
$non_compound_price = $price; |
130
|
|
|
|
131
|
14 |
|
foreach ( $compound_rates as $key => $compound_rate ) { |
132
|
1 |
|
$tax_amount = apply_filters( 'woocommerce_price_inc_tax_amount', $non_compound_price - ( $non_compound_price / ( 1 + ( $compound_rate / 100 ) ) ), $key, $rates[ $key ], $price ); |
133
|
1 |
|
$taxes[ $key ] += $tax_amount; |
134
|
1 |
|
$non_compound_price = $non_compound_price - $tax_amount; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
// Regular taxes. |
138
|
14 |
|
$regular_tax_rate = 1 + ( array_sum( $regular_rates ) / 100 ); |
139
|
|
|
|
140
|
14 |
|
foreach ( $regular_rates as $key => $regular_rate ) { |
141
|
14 |
|
$the_rate = ( $regular_rate / 100 ) / $regular_tax_rate; |
142
|
14 |
|
$net_price = $price - ( $the_rate * $non_compound_price ); |
143
|
14 |
|
$tax_amount = apply_filters( 'woocommerce_price_inc_tax_amount', $price - $net_price, $key, $rates[ $key ], $price ); |
144
|
14 |
|
$taxes[ $key ] += $tax_amount; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Round all taxes to precision (4DP) before passing them back. Note, this is not the same rounding |
149
|
|
|
* as in the cart calculation class which, depending on settings, will round to 2DP when calculating |
150
|
|
|
* final totals. Also unlike that class, this rounds .5 up for all cases. |
151
|
|
|
*/ |
152
|
14 |
|
$taxes = array_map( array( __CLASS__, 'round' ), $taxes ); |
153
|
|
|
|
154
|
14 |
|
return $taxes; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Calc tax from exclusive price. |
159
|
|
|
* |
160
|
|
|
* @param float $price Price to calculate tax for. |
161
|
|
|
* @param array $rates Array of tax rates. |
162
|
|
|
* @return array |
163
|
|
|
*/ |
164
|
92 |
|
public static function calc_exclusive_tax( $price, $rates ) { |
165
|
92 |
|
$taxes = array(); |
166
|
|
|
|
167
|
92 |
|
if ( ! empty( $rates ) ) { |
168
|
36 |
|
foreach ( $rates as $key => $rate ) { |
169
|
36 |
|
if ( 'yes' === $rate['compound'] ) { |
170
|
1 |
|
continue; |
171
|
|
|
} |
172
|
|
|
|
173
|
36 |
|
$tax_amount = $price * ( $rate['rate'] / 100 ); |
174
|
36 |
|
$tax_amount = apply_filters( 'woocommerce_price_ex_tax_amount', $tax_amount, $key, $rate, $price ); // ADVANCED: Allow third parties to modify this rate. |
175
|
|
|
|
176
|
36 |
View Code Duplication |
if ( ! isset( $taxes[ $key ] ) ) { |
177
|
36 |
|
$taxes[ $key ] = $tax_amount; |
178
|
|
|
} else { |
179
|
|
|
$taxes[ $key ] += $tax_amount; |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
36 |
|
$pre_compound_total = array_sum( $taxes ); |
184
|
|
|
|
185
|
|
|
// Compound taxes. |
186
|
36 |
|
foreach ( $rates as $key => $rate ) { |
187
|
36 |
|
if ( 'no' === $rate['compound'] ) { |
188
|
36 |
|
continue; |
189
|
|
|
} |
190
|
1 |
|
$the_price_inc_tax = $price + ( $pre_compound_total ); |
191
|
1 |
|
$tax_amount = $the_price_inc_tax * ( $rate['rate'] / 100 ); |
192
|
1 |
|
$tax_amount = apply_filters( 'woocommerce_price_ex_tax_amount', $tax_amount, $key, $rate, $price, $the_price_inc_tax, $pre_compound_total ); // ADVANCED: Allow third parties to modify this rate. |
193
|
|
|
|
194
|
1 |
View Code Duplication |
if ( ! isset( $taxes[ $key ] ) ) { |
195
|
1 |
|
$taxes[ $key ] = $tax_amount; |
196
|
|
|
} else { |
197
|
|
|
$taxes[ $key ] += $tax_amount; |
198
|
|
|
} |
199
|
|
|
|
200
|
1 |
|
$pre_compound_total = array_sum( $taxes ); |
201
|
|
|
} |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Round all taxes to precision (4DP) before passing them back. Note, this is not the same rounding |
206
|
|
|
* as in the cart calculation class which, depending on settings, will round to 2DP when calculating |
207
|
|
|
* final totals. Also unlike that class, this rounds .5 up for all cases. |
208
|
|
|
*/ |
209
|
92 |
|
$taxes = array_map( array( __CLASS__, 'round' ), $taxes ); |
210
|
|
|
|
211
|
92 |
|
return $taxes; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Searches for all matching country/state/postcode tax rates. |
216
|
|
|
* |
217
|
|
|
* @param array $args Args that determine the rate to find. |
218
|
|
|
* @return array |
219
|
|
|
*/ |
220
|
102 |
|
public static function find_rates( $args = array() ) { |
221
|
102 |
|
$args = wp_parse_args( |
222
|
102 |
|
$args, |
223
|
|
|
array( |
224
|
102 |
|
'country' => '', |
225
|
|
|
'state' => '', |
226
|
|
|
'city' => '', |
227
|
|
|
'postcode' => '', |
228
|
|
|
'tax_class' => '', |
229
|
|
|
) |
230
|
|
|
); |
231
|
|
|
|
232
|
102 |
|
$country = $args['country']; |
233
|
102 |
|
$state = $args['state']; |
234
|
102 |
|
$city = $args['city']; |
235
|
102 |
|
$postcode = wc_normalize_postcode( wc_clean( $args['postcode'] ) ); |
|
|
|
|
236
|
102 |
|
$tax_class = $args['tax_class']; |
237
|
|
|
|
238
|
102 |
|
if ( ! $country ) { |
239
|
|
|
return array(); |
240
|
|
|
} |
241
|
|
|
|
242
|
102 |
|
$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 ) ); |
243
|
102 |
|
$matched_tax_rates = wp_cache_get( $cache_key, 'taxes' ); |
244
|
|
|
|
245
|
102 |
|
if ( false === $matched_tax_rates ) { |
246
|
102 |
|
$matched_tax_rates = self::get_matched_tax_rates( $country, $state, $postcode, $city, $tax_class ); |
247
|
102 |
|
wp_cache_set( $cache_key, $matched_tax_rates, 'taxes' ); |
248
|
|
|
} |
249
|
|
|
|
250
|
102 |
|
return apply_filters( 'woocommerce_find_rates', $matched_tax_rates, $args ); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Searches for all matching country/state/postcode tax rates. |
255
|
|
|
* |
256
|
|
|
* @param array $args Args that determine the rate to find. |
257
|
|
|
* @return array |
258
|
|
|
*/ |
259
|
69 |
|
public static function find_shipping_rates( $args = array() ) { |
260
|
69 |
|
$rates = self::find_rates( $args ); |
261
|
69 |
|
$shipping_rates = array(); |
262
|
|
|
|
263
|
69 |
|
if ( is_array( $rates ) ) { |
264
|
69 |
|
foreach ( $rates as $key => $rate ) { |
265
|
13 |
|
if ( 'yes' === $rate['shipping'] ) { |
266
|
12 |
|
$shipping_rates[ $key ] = $rate; |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
69 |
|
return $shipping_rates; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Does the sort comparison. Compares (in this order): |
276
|
|
|
* - Priority |
277
|
|
|
* - Country |
278
|
|
|
* - State |
279
|
|
|
* - Number of postcodes |
280
|
|
|
* - Number of cities |
281
|
|
|
* - ID |
282
|
|
|
* |
283
|
|
|
* @param object $rate1 First rate to compare. |
284
|
|
|
* @param object $rate2 Second rate to compare. |
285
|
|
|
* @return int |
286
|
|
|
*/ |
287
|
11 |
|
private static function sort_rates_callback( $rate1, $rate2 ) { |
|
|
|
|
288
|
11 |
|
if ( $rate1->tax_rate_priority !== $rate2->tax_rate_priority ) { |
289
|
4 |
|
return $rate1->tax_rate_priority < $rate2->tax_rate_priority ? -1 : 1; // ASC. |
290
|
|
|
} |
291
|
|
|
|
292
|
7 |
View Code Duplication |
if ( $rate1->tax_rate_country !== $rate2->tax_rate_country ) { |
293
|
3 |
|
if ( '' === $rate1->tax_rate_country ) { |
294
|
3 |
|
return 1; |
295
|
|
|
} |
296
|
|
|
if ( '' === $rate2->tax_rate_country ) { |
297
|
|
|
return -1; |
298
|
|
|
} |
299
|
|
|
return strcmp( $rate1->tax_rate_country, $rate2->tax_rate_country ) > 0 ? 1 : -1; |
300
|
|
|
} |
301
|
|
|
|
302
|
4 |
View Code Duplication |
if ( $rate1->tax_rate_state !== $rate2->tax_rate_state ) { |
303
|
|
|
if ( '' === $rate1->tax_rate_state ) { |
304
|
|
|
return 1; |
305
|
|
|
} |
306
|
|
|
if ( '' === $rate2->tax_rate_state ) { |
307
|
|
|
return -1; |
308
|
|
|
} |
309
|
|
|
return strcmp( $rate1->tax_rate_state, $rate2->tax_rate_state ) > 0 ? 1 : -1; |
310
|
|
|
} |
311
|
|
|
|
312
|
4 |
View Code Duplication |
if ( isset( $rate1->postcode_count, $rate2->postcode_count ) && $rate1->postcode_count !== $rate2->postcode_count ) { |
313
|
|
|
return $rate1->postcode_count < $rate2->postcode_count ? 1 : -1; |
314
|
|
|
} |
315
|
|
|
|
316
|
4 |
View Code Duplication |
if ( isset( $rate1->city_count, $rate2->city_count ) && $rate1->city_count !== $rate2->city_count ) { |
317
|
|
|
return $rate1->city_count < $rate2->city_count ? 1 : -1; |
318
|
|
|
} |
319
|
|
|
|
320
|
4 |
|
return $rate1->tax_rate_id < $rate2->tax_rate_id ? -1 : 1; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* Logical sort order for tax rates based on the following in order of priority. |
325
|
|
|
* |
326
|
|
|
* @param array $rates Rates to be sorted. |
327
|
|
|
* @return array |
328
|
|
|
*/ |
329
|
102 |
|
private static function sort_rates( $rates ) { |
330
|
102 |
|
uasort( $rates, __CLASS__ . '::sort_rates_callback' ); |
331
|
102 |
|
$i = 0; |
332
|
102 |
|
foreach ( $rates as $key => $rate ) { |
333
|
46 |
|
$rates[ $key ]->tax_rate_order = $i++; |
334
|
|
|
} |
335
|
102 |
|
return $rates; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Loop through a set of tax rates and get the matching rates (1 per priority). |
340
|
|
|
* |
341
|
|
|
* @param string $country Country code to match against. |
342
|
|
|
* @param string $state State code to match against. |
343
|
|
|
* @param string $postcode Postcode to match against. |
344
|
|
|
* @param string $city City to match against. |
345
|
|
|
* @param string $tax_class Tax class to match against. |
346
|
|
|
* @return array |
347
|
|
|
*/ |
348
|
102 |
|
private static function get_matched_tax_rates( $country, $state, $postcode, $city, $tax_class ) { |
349
|
|
|
global $wpdb; |
350
|
|
|
|
351
|
|
|
// Query criteria - these will be ANDed. |
352
|
102 |
|
$criteria = array(); |
353
|
102 |
|
$criteria[] = $wpdb->prepare( "tax_rate_country IN ( %s, '' )", strtoupper( $country ) ); |
354
|
102 |
|
$criteria[] = $wpdb->prepare( "tax_rate_state IN ( %s, '' )", strtoupper( $state ) ); |
355
|
102 |
|
$criteria[] = $wpdb->prepare( 'tax_rate_class = %s', sanitize_title( $tax_class ) ); |
356
|
|
|
|
357
|
|
|
// Pre-query postcode ranges for PHP based matching. |
358
|
102 |
|
$postcode_search = wc_get_wildcard_postcodes( $postcode, $country ); |
359
|
102 |
|
$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 '%...%';" ); |
360
|
|
|
|
361
|
102 |
|
if ( $postcode_ranges ) { |
362
|
|
|
$matches = wc_postcode_location_matcher( $postcode, $postcode_ranges, 'tax_rate_id', 'location_code', $country ); |
363
|
|
|
if ( ! empty( $matches ) ) { |
364
|
|
|
foreach ( $matches as $matched_postcodes ) { |
365
|
|
|
$postcode_search = array_merge( $postcode_search, $matched_postcodes ); |
366
|
|
|
} |
367
|
|
|
} |
368
|
|
|
} |
369
|
|
|
|
370
|
102 |
|
$postcode_search = array_unique( $postcode_search ); |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* Location matching criteria - ORed |
374
|
|
|
* Needs to match: |
375
|
|
|
* - rates with no postcodes and cities |
376
|
|
|
* - rates with a matching postcode and city |
377
|
|
|
* - rates with matching postcode, no city |
378
|
|
|
* - rates with matching city, no postcode |
379
|
|
|
*/ |
380
|
102 |
|
$locations_criteria = array(); |
381
|
102 |
|
$locations_criteria[] = 'locations.location_type IS NULL'; |
382
|
102 |
|
$locations_criteria[] = " |
383
|
102 |
|
locations.location_type = 'postcode' AND locations.location_code IN ('" . implode( "','", array_map( 'esc_sql', $postcode_search ) ) . "') |
384
|
|
|
AND ( |
385
|
102 |
|
( locations2.location_type = 'city' AND locations2.location_code = '" . esc_sql( strtoupper( $city ) ) . "' ) |
386
|
|
|
OR NOT EXISTS ( |
387
|
102 |
|
SELECT sub.tax_rate_id FROM {$wpdb->prefix}woocommerce_tax_rate_locations as sub |
388
|
|
|
WHERE sub.location_type = 'city' |
389
|
|
|
AND sub.tax_rate_id = tax_rates.tax_rate_id |
390
|
|
|
) |
391
|
|
|
) |
392
|
|
|
"; |
393
|
102 |
|
$locations_criteria[] = " |
394
|
102 |
|
locations.location_type = 'city' AND locations.location_code = '" . esc_sql( strtoupper( $city ) ) . "' |
395
|
|
|
AND NOT EXISTS ( |
396
|
102 |
|
SELECT sub.tax_rate_id FROM {$wpdb->prefix}woocommerce_tax_rate_locations as sub |
397
|
|
|
WHERE sub.location_type = 'postcode' |
398
|
|
|
AND sub.tax_rate_id = tax_rates.tax_rate_id |
399
|
|
|
) |
400
|
|
|
"; |
401
|
|
|
|
402
|
102 |
|
$criteria[] = '( ( ' . implode( ' ) OR ( ', $locations_criteria ) . ' ) )'; |
403
|
|
|
|
404
|
102 |
|
$criteria_string = implode( ' AND ', $criteria ); |
405
|
|
|
|
406
|
|
|
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared |
407
|
102 |
|
$found_rates = $wpdb->get_results( |
408
|
|
|
" |
409
|
|
|
SELECT tax_rates.*, COUNT( locations.location_id ) as postcode_count, COUNT( locations2.location_id ) as city_count |
410
|
102 |
|
FROM {$wpdb->prefix}woocommerce_tax_rates as tax_rates |
411
|
102 |
|
LEFT OUTER JOIN {$wpdb->prefix}woocommerce_tax_rate_locations as locations ON tax_rates.tax_rate_id = locations.tax_rate_id |
412
|
102 |
|
LEFT OUTER JOIN {$wpdb->prefix}woocommerce_tax_rate_locations as locations2 ON tax_rates.tax_rate_id = locations2.tax_rate_id |
413
|
102 |
|
WHERE 1=1 AND {$criteria_string} |
414
|
|
|
GROUP BY tax_rates.tax_rate_id |
415
|
|
|
ORDER BY tax_rates.tax_rate_priority |
416
|
|
|
" |
417
|
|
|
); |
418
|
|
|
// phpcs:enable |
419
|
|
|
|
420
|
102 |
|
$found_rates = self::sort_rates( $found_rates ); |
421
|
102 |
|
$matched_tax_rates = array(); |
422
|
102 |
|
$found_priority = array(); |
423
|
|
|
|
424
|
102 |
|
foreach ( $found_rates as $found_rate ) { |
425
|
46 |
|
if ( in_array( $found_rate->tax_rate_priority, $found_priority, true ) ) { |
426
|
7 |
|
continue; |
427
|
|
|
} |
428
|
|
|
|
429
|
46 |
|
$matched_tax_rates[ $found_rate->tax_rate_id ] = array( |
430
|
46 |
|
'rate' => (float) $found_rate->tax_rate, |
431
|
46 |
|
'label' => $found_rate->tax_rate_name, |
432
|
46 |
|
'shipping' => $found_rate->tax_rate_shipping ? 'yes' : 'no', |
433
|
46 |
|
'compound' => $found_rate->tax_rate_compound ? 'yes' : 'no', |
434
|
|
|
); |
435
|
|
|
|
436
|
46 |
|
$found_priority[] = $found_rate->tax_rate_priority; |
437
|
|
|
} |
438
|
|
|
|
439
|
102 |
|
return apply_filters( 'woocommerce_matched_tax_rates', $matched_tax_rates, $country, $state, $postcode, $city, $tax_class ); |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Get the customer tax location based on their status and the current page. |
444
|
|
|
* |
445
|
|
|
* Used by get_rates(), get_shipping_rates(). |
446
|
|
|
* |
447
|
|
|
* @param string $tax_class string Optional, passed to the filter for advanced tax setups. |
448
|
|
|
* @param object $customer Override the customer object to get their location. |
449
|
|
|
* @return array |
450
|
|
|
*/ |
451
|
91 |
|
public static function get_tax_location( $tax_class = '', $customer = null ) { |
452
|
91 |
|
$location = array(); |
453
|
|
|
|
454
|
91 |
|
if ( is_null( $customer ) && WC()->customer ) { |
455
|
76 |
|
$customer = WC()->customer; |
456
|
|
|
} |
457
|
|
|
|
458
|
91 |
|
if ( ! empty( $customer ) ) { |
459
|
91 |
|
$location = $customer->get_taxable_address(); |
460
|
|
|
} elseif ( wc_prices_include_tax() || 'base' === get_option( 'woocommerce_default_customer_address' ) || 'base' === get_option( 'woocommerce_tax_based_on' ) ) { |
461
|
|
|
$location = array( |
462
|
|
|
WC()->countries->get_base_country(), |
463
|
|
|
WC()->countries->get_base_state(), |
464
|
|
|
WC()->countries->get_base_postcode(), |
465
|
|
|
WC()->countries->get_base_city(), |
466
|
|
|
); |
467
|
|
|
} |
468
|
|
|
|
469
|
91 |
|
return apply_filters( 'woocommerce_get_tax_location', $location, $tax_class, $customer ); |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
/** |
473
|
|
|
* Get's an array of matching rates for a tax class. |
474
|
|
|
* |
475
|
|
|
* @param string $tax_class Tax class to get rates for. |
476
|
|
|
* @param object $customer Override the customer object to get their location. |
477
|
|
|
* @return array |
478
|
|
|
*/ |
479
|
36 |
|
public static function get_rates( $tax_class = '', $customer = null ) { |
480
|
36 |
|
$tax_class = sanitize_title( $tax_class ); |
481
|
36 |
|
$location = self::get_tax_location( $tax_class, $customer ); |
482
|
36 |
|
$matched_tax_rates = array(); |
483
|
|
|
|
484
|
36 |
|
if ( count( $location ) === 4 ) { |
485
|
36 |
|
list( $country, $state, $postcode, $city ) = $location; |
486
|
|
|
|
487
|
36 |
|
$matched_tax_rates = self::find_rates( |
488
|
|
|
array( |
489
|
36 |
|
'country' => $country, |
490
|
36 |
|
'state' => $state, |
491
|
36 |
|
'postcode' => $postcode, |
492
|
36 |
|
'city' => $city, |
493
|
36 |
|
'tax_class' => $tax_class, |
494
|
|
|
) |
495
|
|
|
); |
496
|
|
|
} |
497
|
|
|
|
498
|
36 |
|
return apply_filters( 'woocommerce_matched_rates', $matched_tax_rates, $tax_class ); |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/** |
502
|
|
|
* Get's an array of matching rates for the shop's base country. |
503
|
|
|
* |
504
|
|
|
* @param string $tax_class Tax Class. |
505
|
|
|
* @return array |
506
|
|
|
*/ |
507
|
11 |
|
public static function get_base_tax_rates( $tax_class = '' ) { |
508
|
11 |
|
return apply_filters( |
509
|
11 |
|
'woocommerce_base_tax_rates', |
510
|
11 |
|
self::find_rates( |
511
|
|
|
array( |
512
|
11 |
|
'country' => WC()->countries->get_base_country(), |
513
|
11 |
|
'state' => WC()->countries->get_base_state(), |
514
|
11 |
|
'postcode' => WC()->countries->get_base_postcode(), |
515
|
11 |
|
'city' => WC()->countries->get_base_city(), |
516
|
11 |
|
'tax_class' => $tax_class, |
517
|
|
|
) |
518
|
|
|
), |
519
|
11 |
|
$tax_class |
520
|
|
|
); |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
/** |
524
|
|
|
* Alias for get_base_tax_rates(). |
525
|
|
|
* |
526
|
|
|
* @deprecated 2.3 |
527
|
|
|
* @param string $tax_class Tax Class. |
528
|
|
|
* @return array |
529
|
|
|
*/ |
530
|
|
|
public static function get_shop_base_rate( $tax_class = '' ) { |
531
|
|
|
return self::get_base_tax_rates( $tax_class ); |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* Gets an array of matching shipping tax rates for a given class. |
536
|
|
|
* |
537
|
|
|
* @param string $tax_class Tax class to get rates for. |
538
|
|
|
* @param object $customer Override the customer object to get their location. |
539
|
|
|
* @return mixed |
540
|
|
|
*/ |
541
|
65 |
|
public static function get_shipping_tax_rates( $tax_class = null, $customer = null ) { |
542
|
|
|
// See if we have an explicitly set shipping tax class. |
543
|
65 |
|
$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' ); |
544
|
|
|
|
545
|
65 |
|
if ( 'inherit' !== $shipping_tax_class ) { |
546
|
|
|
$tax_class = $shipping_tax_class; |
547
|
|
|
} |
548
|
|
|
|
549
|
65 |
|
$location = self::get_tax_location( $tax_class, $customer ); |
550
|
65 |
|
$matched_tax_rates = array(); |
551
|
|
|
|
552
|
65 |
|
if ( 4 === count( $location ) ) { |
553
|
65 |
|
list( $country, $state, $postcode, $city ) = $location; |
554
|
|
|
|
555
|
65 |
|
if ( ! is_null( $tax_class ) ) { |
556
|
|
|
// This will be per item shipping. |
557
|
|
|
$matched_tax_rates = self::find_shipping_rates( |
558
|
|
|
array( |
559
|
|
|
'country' => $country, |
560
|
|
|
'state' => $state, |
561
|
|
|
'postcode' => $postcode, |
562
|
|
|
'city' => $city, |
563
|
|
|
'tax_class' => $tax_class, |
564
|
|
|
) |
565
|
|
|
); |
566
|
|
|
|
567
|
65 |
|
} elseif ( WC()->cart->get_cart() ) { |
568
|
|
|
|
569
|
|
|
// This will be per order shipping - loop through the order and find the highest tax class rate. |
570
|
6 |
|
$cart_tax_classes = WC()->cart->get_cart_item_tax_classes_for_shipping(); |
571
|
|
|
|
572
|
|
|
// No tax classes = no taxable items. |
573
|
6 |
|
if ( empty( $cart_tax_classes ) ) { |
574
|
|
|
return array(); |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
// 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. |
578
|
6 |
|
if ( count( $cart_tax_classes ) > 1 && ! in_array( '', $cart_tax_classes, true ) ) { |
579
|
|
|
$tax_classes = self::get_tax_class_slugs(); |
580
|
|
|
|
581
|
|
|
foreach ( $tax_classes as $tax_class ) { |
582
|
|
|
if ( in_array( $tax_class, $cart_tax_classes, true ) ) { |
583
|
|
|
$matched_tax_rates = self::find_shipping_rates( |
584
|
|
|
array( |
585
|
|
|
'country' => $country, |
586
|
|
|
'state' => $state, |
587
|
|
|
'postcode' => $postcode, |
588
|
|
|
'city' => $city, |
589
|
|
|
'tax_class' => $tax_class, |
590
|
|
|
) |
591
|
|
|
); |
592
|
|
|
break; |
593
|
|
|
} |
594
|
|
|
} |
595
|
6 |
|
} elseif ( 1 === count( $cart_tax_classes ) ) { |
596
|
|
|
// If a single tax class is found, use it. |
597
|
6 |
|
$matched_tax_rates = self::find_shipping_rates( |
598
|
|
|
array( |
599
|
6 |
|
'country' => $country, |
600
|
6 |
|
'state' => $state, |
601
|
6 |
|
'postcode' => $postcode, |
602
|
6 |
|
'city' => $city, |
603
|
6 |
|
'tax_class' => $cart_tax_classes[0], |
604
|
|
|
) |
605
|
|
|
); |
606
|
|
|
} |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
// Get standard rate if no taxes were found. |
610
|
65 |
|
if ( ! count( $matched_tax_rates ) ) { |
611
|
59 |
|
$matched_tax_rates = self::find_shipping_rates( |
612
|
|
|
array( |
613
|
59 |
|
'country' => $country, |
614
|
59 |
|
'state' => $state, |
615
|
59 |
|
'postcode' => $postcode, |
616
|
59 |
|
'city' => $city, |
617
|
|
|
) |
618
|
|
|
); |
619
|
|
|
} |
620
|
|
|
} |
621
|
|
|
|
622
|
65 |
|
return $matched_tax_rates; |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
/** |
626
|
|
|
* Return true/false depending on if a rate is a compound rate. |
627
|
|
|
* |
628
|
|
|
* @param mixed $key_or_rate Tax rate ID, or the db row itself in object format. |
629
|
|
|
* @return bool |
630
|
|
|
*/ |
631
|
12 |
|
public static function is_compound( $key_or_rate ) { |
632
|
|
|
global $wpdb; |
633
|
|
|
|
634
|
12 |
View Code Duplication |
if ( is_object( $key_or_rate ) ) { |
635
|
10 |
|
$key = $key_or_rate->tax_rate_id; |
636
|
10 |
|
$compound = $key_or_rate->tax_rate_compound; |
637
|
|
|
} else { |
638
|
3 |
|
$key = $key_or_rate; |
639
|
3 |
|
$compound = (bool) $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_compound FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) ); |
640
|
|
|
} |
641
|
|
|
|
642
|
12 |
|
return (bool) apply_filters( 'woocommerce_rate_compound', $compound, $key ); |
643
|
|
|
} |
644
|
|
|
|
645
|
|
|
/** |
646
|
|
|
* Return a given rates label. |
647
|
|
|
* |
648
|
|
|
* @param mixed $key_or_rate Tax rate ID, or the db row itself in object format. |
649
|
|
|
* @return string |
650
|
|
|
*/ |
651
|
12 |
|
public static function get_rate_label( $key_or_rate ) { |
652
|
|
|
global $wpdb; |
653
|
|
|
|
654
|
12 |
View Code Duplication |
if ( is_object( $key_or_rate ) ) { |
655
|
10 |
|
$key = $key_or_rate->tax_rate_id; |
656
|
10 |
|
$rate_name = $key_or_rate->tax_rate_name; |
657
|
|
|
} else { |
658
|
3 |
|
$key = $key_or_rate; |
659
|
3 |
|
$rate_name = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_name FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) ); |
660
|
|
|
} |
661
|
|
|
|
662
|
12 |
|
if ( ! $rate_name ) { |
663
|
1 |
|
$rate_name = WC()->countries->tax_or_vat(); |
664
|
|
|
} |
665
|
|
|
|
666
|
12 |
|
return apply_filters( 'woocommerce_rate_label', $rate_name, $key ); |
667
|
|
|
} |
668
|
|
|
|
669
|
|
|
/** |
670
|
|
|
* Return a given rates percent. |
671
|
|
|
* |
672
|
|
|
* @param mixed $key_or_rate Tax rate ID, or the db row itself in object format. |
673
|
|
|
* @return string |
674
|
|
|
*/ |
675
|
1 |
|
public static function get_rate_percent( $key_or_rate ) { |
676
|
1 |
|
$rate_percent_value = self::get_rate_percent_value( $key_or_rate ); |
677
|
1 |
|
$tax_rate_id = is_object( $key_or_rate ) ? $key_or_rate->tax_rate_id : $key_or_rate; |
678
|
1 |
|
return apply_filters( 'woocommerce_rate_percent', $rate_percent_value . '%', $tax_rate_id ); |
679
|
|
|
} |
680
|
|
|
|
681
|
|
|
/** |
682
|
|
|
* Return a given rates percent. |
683
|
|
|
* |
684
|
|
|
* @param mixed $key_or_rate Tax rate ID, or the db row itself in object format. |
685
|
|
|
* @return float |
686
|
|
|
*/ |
687
|
11 |
|
public static function get_rate_percent_value( $key_or_rate ) { |
688
|
|
|
global $wpdb; |
689
|
|
|
|
690
|
11 |
View Code Duplication |
if ( is_object( $key_or_rate ) ) { |
691
|
10 |
|
$tax_rate = $key_or_rate->tax_rate; |
692
|
|
|
} else { |
693
|
2 |
|
$key = $key_or_rate; |
694
|
2 |
|
$tax_rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) ); |
695
|
|
|
} |
696
|
|
|
|
697
|
11 |
|
return floatval( $tax_rate ); |
698
|
|
|
} |
699
|
|
|
|
700
|
|
|
|
701
|
|
|
/** |
702
|
|
|
* Get a rates code. Code is made up of COUNTRY-STATE-NAME-Priority. E.g GB-VAT-1, US-AL-TAX-1. |
703
|
|
|
* |
704
|
|
|
* @param mixed $key_or_rate Tax rate ID, or the db row itself in object format. |
705
|
|
|
* @return string |
706
|
|
|
*/ |
707
|
12 |
|
public static function get_rate_code( $key_or_rate ) { |
708
|
|
|
global $wpdb; |
709
|
|
|
|
710
|
12 |
View Code Duplication |
if ( is_object( $key_or_rate ) ) { |
711
|
10 |
|
$key = $key_or_rate->tax_rate_id; |
712
|
10 |
|
$rate = $key_or_rate; |
713
|
|
|
} else { |
714
|
3 |
|
$key = $key_or_rate; |
715
|
3 |
|
$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 ) ); |
716
|
|
|
} |
717
|
|
|
|
718
|
12 |
|
$code_string = ''; |
719
|
|
|
|
720
|
12 |
|
if ( null !== $rate ) { |
721
|
12 |
|
$code = array(); |
722
|
12 |
|
$code[] = $rate->tax_rate_country; |
723
|
12 |
|
$code[] = $rate->tax_rate_state; |
724
|
12 |
|
$code[] = $rate->tax_rate_name ? $rate->tax_rate_name : 'TAX'; |
725
|
12 |
|
$code[] = absint( $rate->tax_rate_priority ); |
726
|
12 |
|
$code_string = strtoupper( implode( '-', array_filter( $code ) ) ); |
727
|
|
|
} |
728
|
|
|
|
729
|
12 |
|
return apply_filters( 'woocommerce_rate_code', $code_string, $key ); |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
/** |
733
|
|
|
* Sums a set of taxes to form a single total. Values are pre-rounded to precision from 3.6.0. |
734
|
|
|
* |
735
|
|
|
* @param array $taxes Array of taxes. |
736
|
|
|
* @return float |
737
|
|
|
*/ |
738
|
1 |
|
public static function get_tax_total( $taxes ) { |
739
|
1 |
|
return array_sum( $taxes ); |
740
|
|
|
} |
741
|
|
|
|
742
|
|
|
/** |
743
|
|
|
* Gets all tax rate classes from the database. |
744
|
|
|
* |
745
|
|
|
* @since 3.7.0 |
746
|
|
|
* @return array Array of tax class objects consisting of tax_rate_class_id, name, and slug. |
747
|
|
|
*/ |
748
|
265 |
|
protected static function get_tax_rate_classes() { |
749
|
|
|
global $wpdb; |
750
|
|
|
|
751
|
265 |
|
$cache_key = 'tax-rate-classes'; |
752
|
265 |
|
$tax_rate_classes = wp_cache_get( $cache_key, 'taxes' ); |
753
|
|
|
|
754
|
265 |
|
if ( ! is_array( $tax_rate_classes ) ) { |
755
|
264 |
|
$tax_rate_classes = $wpdb->get_results( |
756
|
|
|
" |
757
|
264 |
|
SELECT * FROM {$wpdb->wc_tax_rate_classes} ORDER BY name; |
758
|
|
|
" |
759
|
|
|
); |
760
|
264 |
|
wp_cache_set( $cache_key, $tax_rate_classes, 'taxes' ); |
761
|
|
|
} |
762
|
|
|
|
763
|
265 |
|
return $tax_rate_classes; |
764
|
|
|
} |
765
|
|
|
|
766
|
|
|
/** |
767
|
|
|
* Get store tax class names. |
768
|
|
|
* |
769
|
|
|
* @return array Array of class names ("Reduced rate", "Zero rate", etc). |
770
|
|
|
*/ |
771
|
15 |
|
public static function get_tax_classes() { |
772
|
15 |
|
return wp_list_pluck( self::get_tax_rate_classes(), 'name' ); |
773
|
|
|
} |
774
|
|
|
|
775
|
|
|
/** |
776
|
|
|
* Get store tax classes as slugs. |
777
|
|
|
* |
778
|
|
|
* @since 3.0.0 |
779
|
|
|
* @return array Array of class slugs ("reduced-rate", "zero-rate", etc). |
780
|
|
|
*/ |
781
|
265 |
|
public static function get_tax_class_slugs() { |
782
|
265 |
|
return wp_list_pluck( self::get_tax_rate_classes(), 'slug' ); |
783
|
|
|
} |
784
|
|
|
|
785
|
|
|
/** |
786
|
|
|
* Create a new tax class. |
787
|
|
|
* |
788
|
|
|
* @since 3.7.0 |
789
|
|
|
* @param string $name Name of the tax class to add. |
790
|
|
|
* @param string $slug (optional) Slug of the tax class to add. Defaults to sanitized name. |
791
|
|
|
* @return WP_Error|array Returns name and slug (array) if the tax class is created, or WP_Error if something went wrong. |
792
|
|
|
*/ |
793
|
2 |
|
public static function create_tax_class( $name, $slug = '' ) { |
794
|
|
|
global $wpdb; |
795
|
|
|
|
796
|
2 |
|
if ( empty( $name ) ) { |
797
|
|
|
return new WP_Error( 'tax_class_invalid_name', __( 'Tax class requires a valid name', 'woocommerce' ) ); |
798
|
|
|
} |
799
|
|
|
|
800
|
2 |
|
$existing = self::get_tax_classes(); |
801
|
2 |
|
$existing_slugs = self::get_tax_class_slugs(); |
802
|
|
|
|
803
|
2 |
|
if ( in_array( $name, $existing, true ) ) { |
804
|
2 |
|
return new WP_Error( 'tax_class_exists', __( 'Tax class already exists', 'woocommerce' ) ); |
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
if ( ! $slug ) { |
808
|
|
|
$slug = sanitize_title( $name ); |
809
|
|
|
} |
810
|
|
|
|
811
|
|
|
if ( in_array( $slug, $existing_slugs, true ) ) { |
812
|
|
|
return new WP_Error( 'tax_class_slug_exists', __( 'Tax class slug already exists', 'woocommerce' ) ); |
813
|
|
|
} |
814
|
|
|
|
815
|
|
|
$insert = $wpdb->insert( |
816
|
|
|
$wpdb->wc_tax_rate_classes, |
817
|
|
|
array( |
818
|
|
|
'name' => $name, |
819
|
|
|
'slug' => $slug, |
820
|
|
|
) |
821
|
|
|
); |
822
|
|
|
|
823
|
|
|
if ( is_wp_error( $insert ) ) { |
824
|
|
|
return new WP_Error( 'tax_class_insert_error', $insert->get_error_message() ); |
825
|
|
|
} |
826
|
|
|
|
827
|
|
|
wp_cache_delete( 'tax-rate-classes', 'taxes' ); |
828
|
|
|
|
829
|
|
|
return array( |
830
|
|
|
'name' => $name, |
831
|
|
|
'slug' => $slug, |
832
|
|
|
); |
833
|
|
|
} |
834
|
|
|
|
835
|
|
|
/** |
836
|
|
|
* Get an existing tax class. |
837
|
|
|
* |
838
|
|
|
* @since 3.7.0 |
839
|
|
|
* @param string $field Field to get by. Valid values are id, name, or slug. |
840
|
|
|
* @param string|int $item Item to get. |
841
|
|
|
* @return array|bool Returns the tax class as an array. False if not found. |
842
|
|
|
*/ |
843
|
|
|
public static function get_tax_class_by( $field, $item ) { |
844
|
|
View Code Duplication |
if ( ! in_array( $field, array( 'id', 'name', 'slug' ), true ) ) { |
845
|
|
|
return new WP_Error( 'invalid_field', __( 'Invalid field', 'woocommerce' ) ); |
846
|
|
|
} |
847
|
|
|
|
848
|
|
|
if ( 'id' === $field ) { |
849
|
|
|
$field = 'tax_rate_class_id'; |
850
|
|
|
} |
851
|
|
|
|
852
|
|
|
$matches = wp_list_filter( |
853
|
|
|
self::get_tax_rate_classes(), |
854
|
|
|
array( |
855
|
|
|
$field => $item, |
856
|
|
|
) |
857
|
|
|
); |
858
|
|
|
|
859
|
|
|
if ( ! $matches ) { |
860
|
|
|
return false; |
861
|
|
|
} |
862
|
|
|
|
863
|
|
|
$tax_class = current( $matches ); |
864
|
|
|
|
865
|
|
|
return array( |
866
|
|
|
'name' => $tax_class->name, |
867
|
|
|
'slug' => $tax_class->slug, |
868
|
|
|
); |
869
|
|
|
} |
870
|
|
|
|
871
|
|
|
/** |
872
|
|
|
* Delete an existing tax class. |
873
|
|
|
* |
874
|
|
|
* @since 3.7.0 |
875
|
|
|
* @param string $field Field to delete by. Valid values are id, name, or slug. |
876
|
|
|
* @param string|int $item Item to delete. |
877
|
|
|
* @return WP_Error|bool Returns true if deleted successfully, false if nothing was deleted, or WP_Error if there is an invalid request. |
878
|
|
|
*/ |
879
|
|
|
public static function delete_tax_class_by( $field, $item ) { |
880
|
|
|
global $wpdb; |
881
|
|
|
|
882
|
|
View Code Duplication |
if ( ! in_array( $field, array( 'id', 'name', 'slug' ), true ) ) { |
883
|
|
|
return new WP_Error( 'invalid_field', __( 'Invalid field', 'woocommerce' ) ); |
884
|
|
|
} |
885
|
|
|
|
886
|
|
|
$tax_class = self::get_tax_class_by( $field, $item ); |
887
|
|
|
|
888
|
|
|
if ( ! $tax_class ) { |
889
|
|
|
return new WP_Error( 'invalid_tax_class', __( 'Invalid tax class', 'woocommerce' ) ); |
890
|
|
|
} |
891
|
|
|
|
892
|
|
|
if ( 'id' === $field ) { |
893
|
|
|
$field = 'tax_rate_class_id'; |
894
|
|
|
} |
895
|
|
|
|
896
|
|
|
$delete = $wpdb->delete( |
897
|
|
|
$wpdb->wc_tax_rate_classes, |
898
|
|
|
array( |
899
|
|
|
$field => $item, |
900
|
|
|
) |
901
|
|
|
); |
902
|
|
|
|
903
|
|
|
if ( $delete ) { |
904
|
|
|
// Delete associated tax rates. |
905
|
|
|
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_class = %s;", $tax_class['slug'] ) ); |
906
|
|
|
$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;" ); |
907
|
|
|
} |
908
|
|
|
|
909
|
|
|
wp_cache_delete( 'tax-rate-classes', 'taxes' ); |
910
|
|
|
WC_Cache_Helper::incr_cache_prefix( 'taxes' ); |
911
|
|
|
|
912
|
|
|
return (bool) $delete; |
913
|
|
|
} |
914
|
|
|
|
915
|
|
|
/** |
916
|
|
|
* Format the city. |
917
|
|
|
* |
918
|
|
|
* @param string $city Value to format. |
919
|
|
|
* @return string |
920
|
|
|
*/ |
921
|
2 |
|
private static function format_tax_rate_city( $city ) { |
922
|
2 |
|
return strtoupper( trim( $city ) ); |
923
|
|
|
} |
924
|
|
|
|
925
|
|
|
/** |
926
|
|
|
* Format the state. |
927
|
|
|
* |
928
|
|
|
* @param string $state Value to format. |
929
|
|
|
* @return string |
930
|
|
|
*/ |
931
|
84 |
|
private static function format_tax_rate_state( $state ) { |
|
|
|
|
932
|
84 |
|
$state = strtoupper( $state ); |
933
|
84 |
|
return ( '*' === $state ) ? '' : $state; |
934
|
|
|
} |
935
|
|
|
|
936
|
|
|
/** |
937
|
|
|
* Format the country. |
938
|
|
|
* |
939
|
|
|
* @param string $country Value to format. |
940
|
|
|
* @return string |
941
|
|
|
*/ |
942
|
84 |
|
private static function format_tax_rate_country( $country ) { |
|
|
|
|
943
|
84 |
|
$country = strtoupper( $country ); |
944
|
84 |
|
return ( '*' === $country ) ? '' : $country; |
945
|
|
|
} |
946
|
|
|
|
947
|
|
|
/** |
948
|
|
|
* Format the tax rate name. |
949
|
|
|
* |
950
|
|
|
* @param string $name Value to format. |
951
|
|
|
* @return string |
952
|
|
|
*/ |
953
|
84 |
|
private static function format_tax_rate_name( $name ) { |
|
|
|
|
954
|
84 |
|
return $name ? $name : __( 'Tax', 'woocommerce' ); |
955
|
|
|
} |
956
|
|
|
|
957
|
|
|
/** |
958
|
|
|
* Format the rate. |
959
|
|
|
* |
960
|
|
|
* @param float $rate Value to format. |
961
|
|
|
* @return string |
962
|
|
|
*/ |
963
|
84 |
|
private static function format_tax_rate( $rate ) { |
|
|
|
|
964
|
84 |
|
return number_format( (float) $rate, 4, '.', '' ); |
965
|
|
|
} |
966
|
|
|
|
967
|
|
|
/** |
968
|
|
|
* Format the priority. |
969
|
|
|
* |
970
|
|
|
* @param string $priority Value to format. |
971
|
|
|
* @return int |
972
|
|
|
*/ |
973
|
84 |
|
private static function format_tax_rate_priority( $priority ) { |
|
|
|
|
974
|
84 |
|
return absint( $priority ); |
975
|
|
|
} |
976
|
|
|
|
977
|
|
|
/** |
978
|
|
|
* Format the class. |
979
|
|
|
* |
980
|
|
|
* @param string $class Value to format. |
981
|
|
|
* @return string |
982
|
|
|
*/ |
983
|
84 |
|
public static function format_tax_rate_class( $class ) { |
984
|
84 |
|
$class = sanitize_title( $class ); |
985
|
84 |
|
$classes = self::get_tax_class_slugs(); |
986
|
84 |
|
if ( ! in_array( $class, $classes, true ) ) { |
987
|
84 |
|
$class = ''; |
988
|
|
|
} |
989
|
84 |
|
return ( 'standard' === $class ) ? '' : $class; |
990
|
|
|
} |
991
|
|
|
|
992
|
|
|
/** |
993
|
|
|
* Prepare and format tax rate for DB insertion. |
994
|
|
|
* |
995
|
|
|
* @param array $tax_rate Tax rate to format. |
996
|
|
|
* @return array |
997
|
|
|
*/ |
998
|
84 |
|
private static function prepare_tax_rate( $tax_rate ) { |
999
|
84 |
|
foreach ( $tax_rate as $key => $value ) { |
1000
|
84 |
|
if ( method_exists( __CLASS__, 'format_' . $key ) ) { |
1001
|
84 |
|
if ( 'tax_rate_state' === $key ) { |
1002
|
84 |
|
$tax_rate[ $key ] = call_user_func( array( __CLASS__, 'format_' . $key ), sanitize_key( $value ) ); |
1003
|
|
|
} else { |
1004
|
84 |
|
$tax_rate[ $key ] = call_user_func( array( __CLASS__, 'format_' . $key ), $value ); |
1005
|
|
|
} |
1006
|
|
|
} |
1007
|
|
|
} |
1008
|
84 |
|
return $tax_rate; |
1009
|
|
|
} |
1010
|
|
|
|
1011
|
|
|
/** |
1012
|
|
|
* Insert a new tax rate. |
1013
|
|
|
* |
1014
|
|
|
* Internal use only. |
1015
|
|
|
* |
1016
|
|
|
* @since 2.3.0 |
1017
|
|
|
* |
1018
|
|
|
* @param array $tax_rate Tax rate to insert. |
1019
|
|
|
* @return int tax rate id |
1020
|
|
|
*/ |
1021
|
84 |
View Code Duplication |
public static function _insert_tax_rate( $tax_rate ) { |
|
|
|
|
1022
|
|
|
global $wpdb; |
1023
|
|
|
|
1024
|
84 |
|
$wpdb->insert( $wpdb->prefix . 'woocommerce_tax_rates', self::prepare_tax_rate( $tax_rate ) ); |
1025
|
|
|
|
1026
|
84 |
|
$tax_rate_id = $wpdb->insert_id; |
1027
|
|
|
|
1028
|
84 |
|
WC_Cache_Helper::incr_cache_prefix( 'taxes' ); |
1029
|
|
|
|
1030
|
84 |
|
do_action( 'woocommerce_tax_rate_added', $tax_rate_id, $tax_rate ); |
1031
|
|
|
|
1032
|
84 |
|
return $tax_rate_id; |
1033
|
|
|
} |
1034
|
|
|
|
1035
|
|
|
/** |
1036
|
|
|
* Get tax rate. |
1037
|
|
|
* |
1038
|
|
|
* Internal use only. |
1039
|
|
|
* |
1040
|
|
|
* @since 2.5.0 |
1041
|
|
|
* |
1042
|
|
|
* @param int $tax_rate_id Tax rate ID. |
1043
|
|
|
* @param string $output_type Type of output. |
1044
|
|
|
* @return array|object |
1045
|
|
|
*/ |
1046
|
10 |
|
public static function _get_tax_rate( $tax_rate_id, $output_type = ARRAY_A ) { |
1047
|
|
|
global $wpdb; |
1048
|
|
|
|
1049
|
10 |
|
return $wpdb->get_row( |
1050
|
10 |
|
$wpdb->prepare( |
1051
|
10 |
|
" |
1052
|
|
|
SELECT * |
1053
|
10 |
|
FROM {$wpdb->prefix}woocommerce_tax_rates |
1054
|
|
|
WHERE tax_rate_id = %d |
1055
|
|
|
", |
1056
|
|
|
$tax_rate_id |
1057
|
|
|
), |
1058
|
|
|
$output_type |
1059
|
|
|
); |
1060
|
|
|
} |
1061
|
|
|
|
1062
|
|
|
/** |
1063
|
|
|
* Update a tax rate. |
1064
|
|
|
* |
1065
|
|
|
* Internal use only. |
1066
|
|
|
* |
1067
|
|
|
* @since 2.3.0 |
1068
|
|
|
* |
1069
|
|
|
* @param int $tax_rate_id Tax rate to update. |
1070
|
|
|
* @param array $tax_rate Tax rate values. |
1071
|
|
|
*/ |
1072
|
1 |
View Code Duplication |
public static function _update_tax_rate( $tax_rate_id, $tax_rate ) { |
|
|
|
|
1073
|
|
|
global $wpdb; |
1074
|
|
|
|
1075
|
1 |
|
$tax_rate_id = absint( $tax_rate_id ); |
1076
|
|
|
|
1077
|
1 |
|
$wpdb->update( |
1078
|
1 |
|
$wpdb->prefix . 'woocommerce_tax_rates', |
1079
|
1 |
|
self::prepare_tax_rate( $tax_rate ), |
1080
|
|
|
array( |
1081
|
1 |
|
'tax_rate_id' => $tax_rate_id, |
1082
|
|
|
) |
1083
|
|
|
); |
1084
|
|
|
|
1085
|
1 |
|
WC_Cache_Helper::incr_cache_prefix( 'taxes' ); |
1086
|
|
|
|
1087
|
1 |
|
do_action( 'woocommerce_tax_rate_updated', $tax_rate_id, $tax_rate ); |
1088
|
|
|
} |
1089
|
|
|
|
1090
|
|
|
/** |
1091
|
|
|
* Delete a tax rate from the database. |
1092
|
|
|
* |
1093
|
|
|
* Internal use only. |
1094
|
|
|
* |
1095
|
|
|
* @since 2.3.0 |
1096
|
|
|
* @param int $tax_rate_id Tax rate to delete. |
1097
|
|
|
*/ |
1098
|
4 |
|
public static function _delete_tax_rate( $tax_rate_id ) { |
1099
|
|
|
global $wpdb; |
1100
|
|
|
|
1101
|
4 |
|
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE tax_rate_id = %d;", $tax_rate_id ) ); |
1102
|
4 |
|
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $tax_rate_id ) ); |
1103
|
|
|
|
1104
|
4 |
|
WC_Cache_Helper::incr_cache_prefix( 'taxes' ); |
1105
|
|
|
|
1106
|
4 |
|
do_action( 'woocommerce_tax_rate_deleted', $tax_rate_id ); |
1107
|
|
|
} |
1108
|
|
|
|
1109
|
|
|
/** |
1110
|
|
|
* Update postcodes for a tax rate in the DB. |
1111
|
|
|
* |
1112
|
|
|
* Internal use only. |
1113
|
|
|
* |
1114
|
|
|
* @since 2.3.0 |
1115
|
|
|
* |
1116
|
|
|
* @param int $tax_rate_id Tax rate to update. |
1117
|
|
|
* @param string $postcodes String of postcodes separated by ; characters. |
1118
|
|
|
*/ |
1119
|
2 |
|
public static function _update_tax_rate_postcodes( $tax_rate_id, $postcodes ) { |
1120
|
2 |
|
if ( ! is_array( $postcodes ) ) { |
1121
|
2 |
|
$postcodes = explode( ';', $postcodes ); |
1122
|
|
|
} |
1123
|
|
|
// No normalization - postcodes are matched against both normal and formatted versions to support wildcards. |
1124
|
2 |
|
foreach ( $postcodes as $key => $postcode ) { |
1125
|
2 |
|
$postcodes[ $key ] = strtoupper( trim( str_replace( chr( 226 ) . chr( 128 ) . chr( 166 ), '...', $postcode ) ) ); |
1126
|
|
|
} |
1127
|
2 |
|
self::update_tax_rate_locations( $tax_rate_id, array_diff( array_filter( $postcodes ), array( '*' ) ), 'postcode' ); |
1128
|
|
|
} |
1129
|
|
|
|
1130
|
|
|
/** |
1131
|
|
|
* Update cities for a tax rate in the DB. |
1132
|
|
|
* |
1133
|
|
|
* Internal use only. |
1134
|
|
|
* |
1135
|
|
|
* @since 2.3.0 |
1136
|
|
|
* |
1137
|
|
|
* @param int $tax_rate_id Tax rate to update. |
1138
|
|
|
* @param string $cities Cities to set. |
1139
|
|
|
*/ |
1140
|
2 |
|
public static function _update_tax_rate_cities( $tax_rate_id, $cities ) { |
1141
|
2 |
|
if ( ! is_array( $cities ) ) { |
1142
|
2 |
|
$cities = explode( ';', $cities ); |
1143
|
|
|
} |
1144
|
2 |
|
$cities = array_filter( array_diff( array_map( array( __CLASS__, 'format_tax_rate_city' ), $cities ), array( '*' ) ) ); |
1145
|
|
|
|
1146
|
2 |
|
self::update_tax_rate_locations( $tax_rate_id, $cities, 'city' ); |
1147
|
|
|
} |
1148
|
|
|
|
1149
|
|
|
/** |
1150
|
|
|
* Updates locations (postcode and city). |
1151
|
|
|
* |
1152
|
|
|
* Internal use only. |
1153
|
|
|
* |
1154
|
|
|
* @since 2.3.0 |
1155
|
|
|
* |
1156
|
|
|
* @param int $tax_rate_id Tax rate ID to update. |
1157
|
|
|
* @param array $values Values to set. |
1158
|
|
|
* @param string $type Location type. |
1159
|
|
|
*/ |
1160
|
3 |
|
private static function update_tax_rate_locations( $tax_rate_id, $values, $type ) { |
1161
|
|
|
global $wpdb; |
1162
|
|
|
|
1163
|
3 |
|
$tax_rate_id = absint( $tax_rate_id ); |
1164
|
|
|
|
1165
|
3 |
|
$wpdb->query( |
1166
|
3 |
|
$wpdb->prepare( |
1167
|
3 |
|
"DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE tax_rate_id = %d AND location_type = %s;", |
1168
|
|
|
$tax_rate_id, |
1169
|
|
|
$type |
1170
|
|
|
) |
1171
|
|
|
); |
1172
|
|
|
|
1173
|
3 |
|
if ( count( $values ) > 0 ) { |
1174
|
3 |
|
$sql = "( '" . implode( "', $tax_rate_id, '" . esc_sql( $type ) . "' ),( '", array_map( 'esc_sql', $values ) ) . "', $tax_rate_id, '" . esc_sql( $type ) . "' )"; |
1175
|
|
|
|
1176
|
3 |
|
$wpdb->query( "INSERT INTO {$wpdb->prefix}woocommerce_tax_rate_locations ( location_code, tax_rate_id, location_type ) VALUES $sql;" ); // @codingStandardsIgnoreLine. |
1177
|
|
|
} |
1178
|
|
|
|
1179
|
3 |
|
WC_Cache_Helper::incr_cache_prefix( 'taxes' ); |
1180
|
|
|
} |
1181
|
|
|
|
1182
|
|
|
/** |
1183
|
|
|
* Used by admin settings page. |
1184
|
|
|
* |
1185
|
|
|
* @param string $tax_class Tax class slug. |
1186
|
|
|
* |
1187
|
|
|
* @return array|null|object |
1188
|
|
|
*/ |
1189
|
|
|
public static function get_rates_for_tax_class( $tax_class ) { |
1190
|
|
|
global $wpdb; |
1191
|
|
|
|
1192
|
|
|
// Get all the rates and locations. Snagging all at once should significantly cut down on the number of queries. |
1193
|
|
|
$rates = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}woocommerce_tax_rates` WHERE `tax_rate_class` = %s;", sanitize_title( $tax_class ) ) ); |
1194
|
|
|
$locations = $wpdb->get_results( "SELECT * FROM `{$wpdb->prefix}woocommerce_tax_rate_locations`" ); |
1195
|
|
|
|
1196
|
|
|
if ( ! empty( $rates ) ) { |
1197
|
|
|
// Set the rates keys equal to their ids. |
1198
|
|
|
$rates = array_combine( wp_list_pluck( $rates, 'tax_rate_id' ), $rates ); |
1199
|
|
|
} |
1200
|
|
|
|
1201
|
|
|
// Drop the locations into the rates array. |
1202
|
|
|
foreach ( $locations as $location ) { |
1203
|
|
|
// Don't set them for unexistent rates. |
1204
|
|
|
if ( ! isset( $rates[ $location->tax_rate_id ] ) ) { |
1205
|
|
|
continue; |
1206
|
|
|
} |
1207
|
|
|
// If the rate exists, initialize the array before appending to it. |
1208
|
|
|
if ( ! isset( $rates[ $location->tax_rate_id ]->{$location->location_type} ) ) { |
1209
|
|
|
$rates[ $location->tax_rate_id ]->{$location->location_type} = array(); |
1210
|
|
|
} |
1211
|
|
|
$rates[ $location->tax_rate_id ]->{$location->location_type}[] = $location->location_code; |
1212
|
|
|
} |
1213
|
|
|
|
1214
|
|
|
foreach ( $rates as $rate_id => $rate ) { |
1215
|
|
|
$rates[ $rate_id ]->postcode_count = isset( $rates[ $rate_id ]->postcode ) ? count( $rates[ $rate_id ]->postcode ) : 0; |
1216
|
|
|
$rates[ $rate_id ]->city_count = isset( $rates[ $rate_id ]->city ) ? count( $rates[ $rate_id ]->city ) : 0; |
1217
|
|
|
} |
1218
|
|
|
|
1219
|
|
|
$rates = self::sort_rates( $rates ); |
1220
|
|
|
|
1221
|
|
|
return $rates; |
1222
|
|
|
} |
1223
|
|
|
} |
1224
|
|
|
WC_Tax::init(); |
1225
|
|
|
|
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.