PluginProbe ʕ •ᴥ•ʔ
TaxCloud for WooCommerce / 8.4.11
TaxCloud for WooCommerce v8.4.11
8.4.11 8.4.10 8.4.9 trunk 6.0.11 6.0.12 6.0.13 6.0.14 6.1.0 6.1.1 6.1.2 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.3.0 6.3.1 6.3.10 6.3.11 6.3.12 6.3.13 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.3.8 6.3.9 7.0.0 7.0.1 7.0.10 7.0.11 7.0.12 7.0.13 7.0.2 7.0.3 7.0.4 7.0.5 7.0.6 7.0.7 7.0.8 7.0.9 8.0.0 8.0.1 8.0.10 8.0.11 8.0.12 8.0.13 8.0.14 8.0.15 8.0.16 8.0.17 8.0.2 8.0.3 8.0.4 8.0.5 8.0.6 8.0.7 8.0.8 8.0.9 8.1.0 8.1.1 8.2.0 8.2.1 8.2.2 8.2.3 8.2.4 8.3.0 8.3.1 8.3.2 8.3.3 8.3.4 8.3.5 8.3.6 8.3.7 8.3.8 8.4.0 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.4.6 8.4.7 8.4.8
simple-sales-tax / includes / sst-update-functions.php
simple-sales-tax / includes Last commit date
abstracts 3 months ago admin 3 months ago frontend 1 month ago integrations 1 month ago v3 3 months ago vendor 6 days ago views 8 months ago class-simplesalestax.php 6 days ago class-sst-addresses.php 1 month ago class-sst-ajax.php 3 months ago class-sst-assets.php 6 months ago class-sst-blocks-integration.php 1 month ago class-sst-blocks.php 1 year ago class-sst-certificates.php 6 days ago class-sst-install.php 6 months ago class-sst-logger.php 5 months ago class-sst-marketplaces.php 6 months ago class-sst-order-controller.php 3 months ago class-sst-order.php 1 month ago class-sst-origin-address.php 8 months ago class-sst-product.php 3 months ago class-sst-rate-limit.php 5 months ago class-sst-settings.php 3 months ago class-sst-shipping.php 3 years ago class-sst-taxcloud-v3-api.php 3 months ago class-sst-taxcloud-v3.php 3 months ago class-sst-tic.php 2 years ago class-sst-updater.php 3 years ago sst-compatibility-functions.php 4 months ago sst-functions.php 6 days ago sst-message-functions.php 3 years ago sst-update-functions.php 11 months ago
sst-update-functions.php
1487 lines
1 <?php
2 /**
3 * Update functions.
4 *
5 * Callbacks invoked by the SST updater.
6 *
7 * @author Simple Sales Tax
8 * @package SST
9 * @since 5.0
10 */
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly.
14 }
15
16 /**
17 * In 2.6, we eliminated the ability to manually disable shipping tax. This
18 * function deletes the related option.
19 *
20 * @since 5.0
21 */
22 function sst_update_26_remove_shipping_taxable_option() {
23 delete_option( 'wootax_shipping_taxable' );
24
25 return false;
26 }
27
28 /**
29 * In 3.8, we started allowing multiple business addresses. This function
30 * migrates existing address data to work with the new address system.
31 *
32 * @since 5.0
33 */
34 function sst_update_38_update_addresses() {
35 // Set new address array.
36 $address = new TaxCloud\Address(
37 get_option( 'wootax_address1' ),
38 get_option( 'wootax_address2' ),
39 get_option( 'wootax_city' ),
40 get_option( 'wootax_state' ),
41 get_option( 'wootax_zip5' ),
42 get_option( 'wootax_zip4' )
43 );
44
45 SST_Settings::set( 'addresses', array( wp_json_encode( $address ) ) );
46
47 // Delete old options.
48 delete_option( 'wootax_address1' );
49 delete_option( 'wootax_address2' );
50 delete_option( 'wootax_state' );
51 delete_option( 'wootax_city' );
52 delete_option( 'wootax_zip5' );
53 delete_option( 'wootax_zip4' );
54
55 return false;
56 }
57
58 /**
59 * In version 4.2, we started using the WooCommerce settings API to manage plugin
60 * settings. This function migrates existing settings so they can work with the
61 * API.
62 *
63 * @since 5.0
64 */
65 function sst_update_42_migrate_settings() {
66 global $wpdb;
67
68 $options = array(
69 'wootax_tc_id',
70 'wootax_tc_key',
71 'wootax_usps_id',
72 'wootax_show_exempt',
73 'wootax_exemption_text',
74 'wootax_company_name',
75 'wootax_show_zero_tax',
76 'wootax_tax_based_on',
77 'wootax_addresses',
78 'wootax_default_address',
79 );
80
81 // Get old options.
82 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
83 $existing = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name IN ( " . implode( ',', $options ) . ' );');
84
85 // Migrate.
86 $new_options = get_option( 'woocommerce_wootax_settings', array() );
87
88 foreach ( $existing as $old_option ) {
89 $new_options[ $old_option->option_name ] = maybe_unserialize( $old_option->option_value );
90 }
91
92 update_option( 'woocommerce_wootax_settings', $new_options );
93
94 // Delete old options.
95 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
96 $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name IN ( " . implode( ',', $options ) . ' );' );
97
98 return false;
99 }
100
101 /**
102 * In version 4.2, we eliminated the "wootax_order" post type and started
103 * storing tax data as order metadata. This function transfer all metadata
104 * associated with wootax_order posts to the corresponding WooCommerce order.
105 *
106 * Unlike its predecessor, this function DOES NOT reformat existing cart_taxes,
107 * lookup_data, and mapping arrays. This means that the refund/capture/lookup
108 * routines must detect and handle legacy orders.
109 *
110 * @since 5.0
111 */
112 function sst_update_42_migrate_order_data() {
113 global $wpdb;
114
115 /**
116 * Associate all existing metadata for wootax_order posts with the
117 * corresponding WooCommerce order.
118 */
119 $meta_keys = array(
120 '_wootax_tax_total',
121 '_wootax_shipping_tax_total',
122 '_wootax_captured',
123 '_wootax_refunded',
124 '_wootax_customer_id',
125 '_wootax_tax_item_id',
126 '_wootax_exemption_applied',
127 '_wootax_lookup_data',
128 '_wootax_cart_taxes',
129 );
130
131 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
132 $wpdb->query( "UPDATE {$wpdb->postmeta} wt, (SELECT * FROM {$wpdb->postmeta} WHERE meta_key = '_wootax_wc_order_id' AND meta_key <> 0) wc SET wt.post_id = wc.meta_value WHERE wt.post_id = wc.post_id AND wt.meta_key <> 0 AND wt.meta_key IN (" . implode( ',', $meta_keys ) . ');' );
133
134 /**
135 * Process WooCommerce orders. Add new order and item metadata introduced
136 * in 4.2.
137 */
138 $orders = $wpdb->get_results(
139 "
140 SELECT p.ID as wt_oid, pm.meta_value AS wc_oid
141 FROM {$wpdb->posts} p, {$wpdb->postmeta} pm
142 WHERE p.ID = pm.post_id
143 AND pm.meta_key = '_wootax_wc_order_id';
144 "
145 );
146
147 foreach ( $orders as $order ) {
148
149 $lookup_data = get_post_meta( $order->wt_oid, '_wootax_lookup_data', true );
150 $cart_taxes = get_post_meta( $order->wt_oid, '_wootax_cart_taxes', true );
151
152 // No need to update order if lookup_data not set.
153 if ( ! is_array( $lookup_data ) ) {
154 continue;
155 }
156
157 foreach ( $lookup_data as $location_key => $items ) {
158 // Skip cart_id/order_id.
159 if ( ! is_array( $items ) ) {
160 continue;
161 }
162
163 foreach ( $items as $index => $item ) {
164 // Get sales tax for item.
165 if ( isset( $cart_taxes[ $location_key ][ $index ] ) ) {
166 $tax_amount = $cart_taxes[ $location_key ][ $index ];
167 } else {
168 $tax_amount = 0;
169 }
170
171 // Update item metadata.
172 $item_id = $item['ItemID'];
173 $item_type = sst_update_42_get_item_type( $item_id );
174
175 switch ( $item_type ) {
176 case 'product':
177 if ( 'product_variation' === get_post_type( $item_id ) ) {
178 $meta_key = '_variation_id';
179 } else {
180 $meta_key = '_product_id';
181 }
182
183 $cart_item_id = $wpdb->get_var(
184 $wpdb->prepare(
185 "
186 SELECT order_item_id
187 FROM {$wpdb->prefix}woocommerce_order_itemmeta
188 WHERE meta_key = %s
189 AND meta_value = %s
190 AND order_id = %d
191 ",
192 $meta_key,
193 $item_id,
194 $order->wc_oid
195 )
196 );
197
198 if ( ! is_null( $cart_item_id ) ) {
199 wc_update_order_item_meta( $cart_item_id, '_wootax_index', $index );
200 wc_update_order_item_meta( $cart_item_id, '_wootax_tax_amount', $tax_amount );
201 wc_update_order_item_meta( $cart_item_id, '_wootax_location_id', $location_key );
202 }
203 break;
204 case 'shipping':
205 if ( version_compare( WC_VERSION, '2.2', '<' ) ) {
206 update_post_meta( $order->wc_oid, '_wootax_first_found', $location_key );
207 update_post_meta( $order->wc_oid, '_wootax_shipping_index', $index );
208 } else {
209 // NOTE: This assumes one shipping method per order.
210 $shipping_id = $wpdb->get_var(
211 $wpdb->prepare(
212 "
213 SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items
214 WHERE order_item_type = 'shipping'
215 AND order_id = %d
216 LIMIT 1;
217 ",
218 $order->wc_oid
219 )
220 );
221
222 if ( ! is_null( $shipping_id ) ) {
223 wc_update_order_item_meta( $shipping_id, '_wootax_index', $index );
224 wc_update_order_item_meta( $shipping_id, '_wootax_tax_amount', $tax_amount );
225 wc_update_order_item_meta( $shipping_id, '_wootax_location_id', $location_key );
226 }
227 }
228 break;
229 case 'fee':
230 $fee_id = $wpdb->get_var(
231 $wpdb->prepare(
232 "
233 SELECT order_item_id
234 FROM {$wpdb->prefix}woocommerce_order_items
235 WHERE order_item_name = %s
236 AND order_item_type = 'fee'
237 AND order_id = %d
238 ",
239 $item_id,
240 $order->wc_oid
241 )
242 );
243
244 if ( ! is_null( $fee_id ) ) {
245 wc_update_order_item_meta( $fee_id, '_wootax_index', $index );
246 wc_update_order_item_meta( $fee_id, '_wootax_tax_amount', $tax_amount );
247 wc_update_order_item_meta( $fee_id, '_wootax_location_id', $location_key );
248 }
249 break;
250 }
251 }
252 }
253 }
254
255 // Remove all wootax_order posts and the corresponding metadata.
256 $wpdb->query(
257 "
258 DELETE FROM {$wpdb->posts} p, {$wpdb->postmeta} pm
259 WHERE p.ID = pm.post_id
260 AND p.post_type = 'wootax_order'
261 "
262 );
263
264 return false;
265 }
266
267 /**
268 * Helper for 4.2 update: Get item type given item ID.
269 *
270 * @param int $item_id Item ID.
271 *
272 * @return string "shipping," "cart," or "fee"
273 * @since 5.0
274 */
275 function sst_update_42_get_item_type( $item_id ) {
276 global $wpdb;
277
278 if ( 99999 === (int) $item_id ) {
279 return 'shipping';
280 } elseif ( in_array( get_post_type( $item_id ), array( 'product', 'product_variation' ), true ) ) {
281 return 'product';
282 } else {
283 return 'fee';
284 }
285
286 return false;
287 }
288
289 /**
290 * After version 4.5, license keys were no longer required. Remove the license
291 * key option.
292 *
293 * @since 5.0
294 */
295 function sst_update_45_remove_license_option() {
296 delete_option( 'wootax_license_key' );
297
298 return false;
299 }
300
301 /**
302 * In 5.0, we added support for multiple default origin addresses. This function
303 * migrates existing address data to work with the new system.
304 *
305 * @since 5.0
306 */
307 function sst_update_50_origin_addresses() {
308 $default_address = SST_Settings::get( 'default_address' );
309 $addresses = SST_Settings::get( 'addresses' );
310
311 foreach ( $addresses as $key => $address ) {
312 try {
313 $new_address = new SST_Origin_Address(
314 $key,
315 (int) $key === (int) $default_address,
316 isset( $address['address_1'] ) ? $address['address_1'] : '',
317 isset( $address['address_2'] ) ? $address['address_2'] : '',
318 isset( $address['city'] ) ? $address['city'] : '',
319 isset( $address['state'] ) ? $address['state'] : '',
320 isset( $address['zip5'] ) ? $address['zip5'] : '',
321 isset( $address['zip4'] ) ? $address['zip4'] : ''
322 );
323
324 $addresses[ $key ] = wp_json_encode( $new_address );
325 } catch ( Exception $ex ) {
326 // Address was invalid -- not much we can do.
327 unset( $addresses[ $key ] );
328 }
329 }
330
331 SST_Settings::set( 'addresses', $addresses );
332 SST_Settings::set( 'default_address', null );
333
334 return false;
335 }
336
337 /**
338 * Prior to 5.0, default category TICs were stored as WordPress options. After 5.0,
339 * category TICs are stored as term metadata.
340 *
341 * @since 5.0
342 */
343 function sst_update_50_category_tics() {
344 $terms = get_terms(
345 array(
346 'taxonomy' => 'product_cat',
347 'hide_empty' => false,
348 )
349 );
350
351 foreach ( $terms as $term ) {
352 $tic = get_option( 'tic_' . $term->term_id );
353
354 if ( ! empty( $tic ) ) {
355 if ( is_array( $tic ) ) {
356 $tic = $tic['tic'];
357 }
358
359 update_term_meta( $term->term_id, 'tic', $tic );
360
361 delete_option( 'tic_' . $term->term_id );
362 }
363 }
364
365 return false;
366 }
367
368 /**
369 * Before 5.0, we used a "lookup_data" data structure to store information about
370 * the tax lookups for an order. The structure consisted of a two-dimensional array
371 * of CartItems indexed by origin address key. One lookup was sent for each of
372 * the entries in "lookup_data," and the CartID/OrderID sent to TaxCloud were stored
373 * in a separate "taxcloud_ids" array, also indexed by origin address key.
374 *
375 * In addition to the above, each order had two other data structures: an "identifiers"
376 * array and a "mapping" array. The identifiers array was used to deal with the fact
377 * that different item identifiers were sent to TaxCloud during checkout and after. It
378 * mapped universally available item IDs (product/variation IDs for line items,
379 * sanitized name for fees, SHIPPING for shipping methods) to the IDs sent to TaxCloud
380 * during checkout, and was meant to ensure that orders could be captured immediately
381 * after checkout without an additional lookup.
382 *
383 * The mapping array was generated for each entry in lookup_data before a lookup and
384 * mapped tuples (origin address key, CartItem index) to an internal item ID (or, during
385 * checkout, an array with keys 'type', 'index', 'id' and 'key').
386 *
387 * Each order also had two boolean flags 'captured' and 'refunded' associated with
388 * it. The 'captured' flag was set after the order was captured in TaxCloud, and the
389 * 'refunded' flag was set when a partial or full refund was issued.
390 *
391 * In 5.0, the "lookup_data" and "mapping_array" data structures were merged into a
392 * simplified "packages" data structure. In addition, the "identifiers" data structure
393 * was eliminated. The boolean flags 'captured' and 'refunded' have also been merged
394 * into a single 'status' field. This function updates all existing orders to use the
395 * new data structures.
396 *
397 * @throws Exception When order processing fails.
398 */
399 function sst_update_50_order_data() {
400 global $wpdb;
401
402 /* Get next batch of orders to process */
403 $orders = $wpdb->get_results(
404 "
405 SELECT p.ID AS ID
406 FROM {$wpdb->posts} p
407 WHERE p.post_type = 'shop_order'
408 AND NOT EXISTS (
409 SELECT meta_id
410 FROM {$wpdb->postmeta} pm
411 WHERE pm.post_id = p.ID
412 AND pm.meta_key = '_wootax_db_version'
413 AND pm.meta_value = '5.0'
414 )
415 ORDER BY p.ID DESC
416 LIMIT 50
417 "
418 );
419
420 /* Define variables used within the loop */
421 $logger = new WC_Logger();
422 $woo_3_0 = version_compare( WC_VERSION, '3.0', '>=' );
423 $based_on = SST_Settings::get( 'tax_based_on' );
424
425 foreach ( $orders as $order ) {
426
427 $_order = new SST_Order( $order->ID );
428
429 try {
430 $destination = $_order->get_destination_address();
431
432 if ( is_null( $destination ) ) {
433 throw new Exception( 'No destination address is available for order ' . $order->ID . '. Skipping.' );
434 }
435
436 /*
437 * Exemption certificates previously stored under key 'exemption_applied'.
438 * In 5.0, we move them to key 'exempt_cert' and store them in a different
439 * format.
440 */
441 $old_certificate = $_order->get_meta( 'exemption_applied' );
442
443 if ( is_array( $old_certificate ) && isset( $old_certificate['CertificateID'] ) ) {
444 $new_certificate = new TaxCloud\ExemptionCertificateBase(
445 $old_certificate['CertificateID']
446 );
447
448 $_order->set_certificate( $new_certificate );
449 }
450
451 /*
452 * Actions we take from here will depend on the order status (pending,
453 * captured, refunded).
454 */
455 $captured = $_order->get_meta( 'captured' );
456 $refunded = $_order->get_meta( 'refunded' );
457
458 if ( ! $captured && ! $refunded ) { /* Pending */
459
460 /*
461 * If order is not actually pending, update the status, but don't
462 * recalculate the taxes. Orders with a status other than pending
463 * were very likely placed before SST was installed.
464 */
465 if ( in_array( $_order->get_status(), array( 'pending', 'processing', 'on-hold' ), true ) ) {
466 $_order->calculate_taxes();
467 $_order->calculate_totals( false );
468 }
469
470 $_order->update_meta( 'status', 'pending' );
471 } elseif ( $captured && ! $refunded ) { /* Captured */
472
473 $taxcloud_ids = $_order->get_meta( 'taxcloud_ids' );
474 $identifiers = $_order->get_meta( 'identifiers' );
475
476 if ( ! is_array( $taxcloud_ids ) || empty( $taxcloud_ids ) ) {
477 throw new Exception( 'TaxCloud IDs not available for order ' . $order->ID . '. Skipping.' );
478 }
479
480 /* Build map from address keys to items */
481 $mappings = array();
482
483 foreach ( $_order->get_items( array( 'line_item', 'fee', 'shipping' ) ) as $item_id => $item ) {
484 $location_id = wc_get_order_item_meta( $item_id, '_wootax_location_id' );
485
486 if ( empty( $location_id ) ) { /* Very old order (pre 4.2) */
487 continue;
488 }
489
490 if ( ! isset( $mappings[ $location_id ] ) ) {
491 $mappings[ $location_id ] = array();
492 }
493
494 $mappings[ $location_id ][ $item_id ] = $item;
495 }
496
497 /* For each address key, create one or more new packages. */
498 $packages = array();
499
500 foreach ( $mappings as $address_key => $items ) {
501 /*
502 * Create a base package with just the cart id, order id, and
503 * addresses set. We will copy this package to create others.
504 */
505 $base_package = sst_create_package();
506
507 $base_package['cart_id'] = $taxcloud_ids[ $address_key ]['cart_id'];
508 $base_package['order_id'] = $taxcloud_ids[ $address_key ]['order_id'];
509 $base_package['origin'] = SST_Addresses::to_address(
510 SST_Addresses::get_address( $address_key )
511 );
512 $base_package['destination'] = $destination;
513
514 /*
515 * Create a package for every shipping method that falls under
516 * this address key.
517 */
518 $new_packages = array();
519
520 foreach ( $items as $item_id => $item ) {
521 if ( 'shipping' !== $item['type'] ) {
522 continue;
523 }
524
525 $new_package = $base_package;
526
527 /* Set shipping method */
528 $method_id = wc_get_order_item_meta( $item_id, 'method_id' );
529 $total = wc_get_order_item_meta( $item_id, $woo_3_0 ? 'total' : 'cost' );
530
531 $new_package['shipping'] = new WC_Shipping_Rate( $item_id, '', $total, array(), $method_id );
532
533 /* Add cart item and map entry for shipping */
534 $new_package['contents'][] = new TaxCloud\CartItem(
535 count( $new_package['contents'] ),
536 isset( $identifiers[ SST_SHIPPING_ITEM ] ) ? $identifiers[ SST_SHIPPING_ITEM ] : $item_id,
537 sst_get_shipping_tic( $method_id ),
538 $total,
539 1
540 );
541 $new_package['map'][] = array(
542 'type' => 'shipping',
543 'id' => SST_SHIPPING_ITEM,
544 'cart_id' => $item_id,
545 );
546
547 $new_packages[] = $new_package;
548 unset( $items[ $item_id ] );
549 }
550
551 /*
552 * Add all fees and line items to the first package. If no
553 * packages were created, create one.
554 */
555 if ( empty( $new_packages ) ) {
556 $new_packages[] = $base_package;
557 }
558
559 foreach ( $items as $item_id => $item ) {
560 if ( 'fee' === $item['type'] ) {
561 $taxcloud_id = sanitize_title(
562 empty( $item['name'] ) ? __( 'Fee', 'simple-sales-tax' ) : $item['name']
563 );
564
565 $new_packages[0]['contents'][] = new TaxCloud\CartItem(
566 count( $new_packages[0]['contents'] ),
567 isset( $identifiers[ $taxcloud_id ] ) ? $identifiers[ $taxcloud_id ] : $item_id,
568 apply_filters( 'wootax_fee_tic', SST_DEFAULT_FEE_TIC ),
569 $item['line_total'],
570 1
571 );
572 $new_packages[0]['map'][] = array(
573 'type' => 'fee',
574 'id' => $taxcloud_id,
575 'cart_id' => $item_id,
576 );
577 } else {
578 $taxcloud_id = $item['variation_id'] ? $item['variation_id'] : $item['product_id'];
579
580 $new_packages[0]['contents'][] = new TaxCloud\CartItem(
581 count( $new_packages[0]['contents'] ),
582 isset( $identifiers[ $taxcloud_id ] ) ? $identifiers[ $taxcloud_id ] : $item_id,
583 SST_Product::get_tic( $item['product_id'], $item['variation_id'] ),
584 'item-price' === $based_on ? $item['line_subtotal'] / $item['qty'] : $item['line_subtotal'],
585 'item-price' === $based_on ? $item['qty'] : 1
586 );
587 $new_packages[0]['map'][] = array(
588 'type' => 'line_item',
589 'id' => $taxcloud_id,
590 'cart_id' => $item_id,
591 );
592 }
593 }
594
595 $packages = array_merge( $packages, $new_packages );
596 }
597
598 /*
599 * Generate lookup request for each package (cartItems is the only
600 * required field).
601 */
602 foreach ( $packages as &$package ) {
603 $package['request'] = new TaxCloud\Request\Lookup(
604 SST_Settings::get( 'tc_id' ),
605 SST_Settings::get( 'tc_key' ),
606 $_order->get_user_id(),
607 $package['cart_id'],
608 $package['contents'],
609 $package['origin'],
610 $package['destination']
611 );
612 }
613
614 $_order->set_packages( $packages );
615 $_order->update_meta( 'status', 'captured' );
616 } elseif ( $refunded ) { /* Refunded */
617
618 $_order->update_meta( 'status', 'refunded' );
619 }
620 } catch ( Exception $ex ) {
621 $logger->add( 'sst_db_updates', $ex->getMessage() );
622 } finally {
623 $_order->update_meta( 'db_version', '5.0' );
624 $_order->save();
625 }
626 }
627
628 /*
629 * If more orders need processing, keep this function in the background
630 * processing queue.
631 */
632 if ( 50 === count( $orders ) ) {
633 return 'sst_update_50_order_data';
634 }
635
636 return false;
637 }
638
639 /**
640 * Starting with 5.9, TICs are stored in a WordPress transient. The sst_tics
641 * table is no longer needed and can be safely dropped.
642 */
643 function sst_update_59_tic_table() {
644 global $wpdb;
645
646 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}sst_tics" );
647 }
648
649 /**
650 * Fixes the duplicate transaction bug introduced in v6.0.5.
651 */
652 function sst_update_606_fix_duplicate_transactions() {
653 $batch_size = 25;
654 $args = array(
655 'type' => 'shop_order',
656 'return' => 'ids',
657 'limit' => $batch_size,
658 'wootax_version' => '6.0.5',
659 'wootax_status' => 'captured',
660 );
661
662 $order_ids = wc_get_orders( $args );
663
664 $api_id = SST_Settings::get( 'tc_id' );
665 $api_key = SST_Settings::get( 'tc_key' );
666
667 foreach ( $order_ids as $order_id ) {
668 $order = new SST_Order( $order_id );
669 $old_packages = $order->get_packages();
670 $order_packages = $order->create_packages();
671 $new_packages = array();
672 $removed = array();
673
674 foreach ( $old_packages as $key => $package ) {
675 $matching_package = _sst_update_606_find_matching_package( $package, $order_packages );
676
677 if ( $matching_package ) {
678 $new_packages[] = $package;
679 } else {
680 $package_order_id = sprintf( '%d_%s', $order_id, $key );
681 $cart_items = $package['request']->getCartItems();
682
683 // Try to return the extraneous package.
684 try {
685 $request = new TaxCloud\Request\Returned(
686 $api_id,
687 $api_key,
688 $package_order_id,
689 $cart_items,
690 gmdate( 'c' )
691 );
692
693 TaxCloud()->Returned( $request );
694 } catch ( Exception $ex ) {
695 wc_get_logger()->debug(
696 sprintf(
697 /* translators: 1 - WooCommerce order ID, 2 - Error message */
698 __( 'Failed to refund extraneous package for order #%1$d: %2$s.', 'simple-sales-tax' ),
699 $order_id,
700 $ex->getMessage()
701 ),
702 array( 'source' => 'sst_db_updates' )
703 );
704 }
705
706 $removed[] = $package;
707 }
708 }
709
710 if ( count( $removed ) > 0 ) {
711 $order->update_meta( 'removed_packages', $removed );
712 }
713
714 $order->set_packages( $new_packages );
715 $order->update_meta( 'db_version', '6.0.6' );
716
717 $order->save();
718 }
719
720 if ( count( $order_ids ) === $batch_size ) {
721 // More orders remain to be processed.
722 return 'sst_update_606_fix_duplicate_transactions';
723 }
724
725 return false;
726 }
727
728 /**
729 * Helper to find a shipping package matching the given package.
730 *
731 * Packages match if they have the same contents, fees, shipping method, origin and destination addresses,
732 * and exemption certificate.
733 *
734 * @param array $needle Package to find a match for.
735 * @param array $haystack Packages to search for matches.
736 *
737 * @return bool|array False if no matching package is found or matching package otherwise.
738 */
739 function _sst_update_606_find_matching_package( $needle, $haystack ) {
740 $needle = _sst_update_606_normalize_package( $needle );
741 $needle_hash = md5( wp_json_encode( $needle ) );
742
743 foreach ( $haystack as $package ) {
744 $normalized_package = _sst_update_606_normalize_package( $package );
745 $package_hash = md5( wp_json_encode( $normalized_package ) );
746
747 if ( $needle_hash === $package_hash ) {
748 return $package;
749 }
750 }
751
752 return false;
753 }
754
755 /**
756 * Normalizes packages to ensure that the hashes of two identical packages are the same.
757 *
758 * @param array $package Package to normalize.
759 *
760 * @return array
761 */
762 function _sst_update_606_normalize_package( $package ) {
763 $new_package = array();
764
765 // Items.
766 $contents = array();
767
768 foreach ( $package['contents'] as $item ) {
769 $contents[] = array(
770 'product_id' => (int) $item['product_id'],
771 'variation_id' => (int) $item['variation_id'],
772 'quantity' => (int) $item['quantity'],
773 'line_total' => (float) $item['line_total'],
774 'line_subtotal' => (float) $item['line_subtotal'],
775 );
776 }
777
778 $new_package['contents'] = wp_list_sort( $contents, 'product_id' );
779
780 // Fees.
781 $fees = array();
782
783 foreach ( $package['fees'] as $fee ) {
784 $fees[] = array(
785 'id' => $fee->id,
786 'amount' => $fee->amount,
787 );
788 }
789
790 $new_package['fees'] = wp_list_sort( $fees, 'id' );
791
792 // Shipping.
793 $shipping = null;
794
795 if ( ! empty( $package['shipping'] ) ) {
796 $shipping = array(
797 'method_id' => $package['shipping']->method_id,
798 'cost' => $package['shipping']->cost,
799 );
800 }
801
802 $new_package['shipping'] = $shipping;
803
804 // Origin and destination addresses.
805 $new_package['origin'] = _sst_update_606_get_address( $package['origin'] );
806 $new_package['destination'] = _sst_update_606_get_address( $package['destination'] );
807
808 // Certificate - use ID for comparison.
809 $new_package['certificate'] = $package['certificate'];
810
811 if ( ! empty( $new_package['certificate'] ) ) {
812 $new_package['certificate'] = $new_package['certificate']->getCertificateID();
813 }
814
815 return $new_package;
816 }
817
818 /**
819 * Gets a TaxCloud Address as an array.
820 *
821 * @param TaxCloud\Address $address Address to format as array.
822 *
823 * @return array|null Address, or NULL if $address is null.
824 */
825 function _sst_update_606_get_address( $address ) {
826 if ( is_null( $address ) ) {
827 return null;
828 }
829
830 return array(
831 'address_1' => $address->getAddress1(),
832 'address_2' => $address->getAddress2(),
833 'city' => $address->getCity(),
834 'state' => $address->getState(),
835 'zip' => $address->getZip(),
836 );
837 }
838
839 /**
840 * Import shipping origin addresses from TaxCloud when updating to 6.2.
841 */
842 function sst_update_620_import_origin_addresses() {
843 $taxcloud_addresses = SST_Addresses::get_origin_addresses( true );
844 $saved_addresses = SST_Settings::get( 'addresses', array() );
845
846 if ( empty( $saved_addresses ) ) {
847 // Easy case: Just save the new addresses
848 SST_Settings::set( 'addresses', array_map( 'json_encode', $taxcloud_addresses ) );
849 return false;
850 }
851
852 // Reset Default flag for all addresses. May be incorrect.
853 foreach ( $taxcloud_addresses as $taxcloud_address ) {
854 $taxcloud_address->setDefault( false );
855 }
856
857 // Set Default flag for addresses as appropriate.
858 $address_id_map = array();
859 $mismatched_addresses = array();
860
861 foreach ( $saved_addresses as $old_index => $saved_address ) {
862 $saved_address = json_decode( $saved_address, true );
863 $normalized_address = _sst_update_620_normalize_address( $saved_address['Address1'] );
864 $matching_address = null;
865
866 foreach ( $taxcloud_addresses as $new_index => $taxcloud_address ) {
867 if ( _sst_update_620_normalize_address( $taxcloud_address->getAddress1() ) === $normalized_address ) {
868 $matching_address = $taxcloud_address;
869 $address_id_map[ $old_index ] = $taxcloud_address->getID();
870 break;
871 }
872 }
873
874 if ( $matching_address ) {
875 $matching_address->setDefault( $saved_address['Default'] );
876 } else {
877 $mismatched_addresses[] = $saved_address;
878 }
879 }
880
881 // Abort if an address mismatch was detected.
882 if ( ! empty( $mismatched_addresses ) ) {
883 SST_Settings::set( 'address_mismatch', true );
884
885 if ( ! class_exists( 'WC_Admin_Notices' ) ) {
886 require WC()->plugin_path() . '/admin/class-wc-admin-notices.php';
887 }
888
889 WC_Admin_Notices::add_custom_notice(
890 'sst_address_mismatch',
891 _sst_update_620_get_address_mismatch_notice( $mismatched_addresses )
892 );
893
894 return false;
895 }
896
897 SST_Settings::set( 'address_mismatch', false );
898 SST_Settings::set( 'addresses', array_map( 'json_encode', $taxcloud_addresses ) );
899
900 // Save the address ID map so we can use it to update product origin addresses
901 update_option( '_sst_update_620_address_id_map', $address_id_map );
902
903 // This will queue the sst_update_620_fix_product_origin_addresses task
904 return 'sst_update_620_fix_product_origin_addresses';
905 }
906
907 /**
908 * Get the markup for the address mismatch notice.
909 *
910 * @param array $mismatched_addresses Addresses not found in TaxCloud.
911 *
912 * @return string
913 */
914 function _sst_update_620_get_address_mismatch_notice( $mismatched_addresses ) {
915 $address_list_html = '<ul class="sst-address-list">';
916
917 foreach ( $mismatched_addresses as $address ) {
918 $address_list_html .= sprintf(
919 '<li>%s, %s, %s %s</li>',
920 $address['Address1'],
921 $address['City'],
922 $address['State'],
923 $address['Zip5']
924 );
925 }
926
927 $address_list_html .= '</ul>';
928
929 // translators: %1$s is the dismiss link URL. %2$s is the HTML list of missing addresses.
930 return sprintf(
931 __( '<strong>IMPORTANT: Your TaxCloud Locations are out of sync.</strong> One or more of the addresses from your TaxCloud for WooCommerce settings are not registered as Locations in your TaxCloud account. Please add all of the addresses listed below on the <a href="https://app.taxcloud.com/go/locations" target="_blank">Locations</a> page in your TaxCloud account.', 'simple-sales-tax' ),
932 esc_url( 'https://app.taxcloud.com/go/locations' )
933 );
934 }
935
936 /**
937 * Helper function to normalize a street address. Replaces street suffixes
938 * like Avenue and Road with their abbreviations and converts everything to
939 * uppercase.
940 *
941 * Abbreviations taken from https://pe.usps.com/text/pub28/28apc_002.htm
942 *
943 * @param string $address A street address like 123 Easy Street.
944 *
945 * @return string Normalized street address.
946 */
947 function _sst_update_620_normalize_address( $address ) {
948 $address = strtoupper( $address );
949 $replacements = array(
950 'ALLEE' => 'ALY',
951 'ALLEY' => 'ALY',
952 'ALLY' => 'ALY',
953 'ANEX' => 'ANX',
954 'ANNEX' => 'ANX',
955 'ANNX' => 'ANX',
956 'ARCADE' => 'ARC',
957 'AV' => 'AVE',
958 'AVEN' => 'AVE',
959 'AVENU' => 'AVE',
960 'AVENUE' => 'AVE',
961 'AVN' => 'AVE',
962 'AVNUE' => 'AVE',
963 'BAYOO' => 'BYU',
964 'BAYOU' => 'BYU',
965 'BEACH' => 'BCH',
966 'BEND' => 'BND',
967 'BLUF' => 'BLF',
968 'BLUFF' => 'BLF',
969 'BOT' => 'BTM',
970 'BOTTM' => 'BTM',
971 'BOTTOM' => 'BTM',
972 'BOUL' => 'BLVD',
973 'BOULEVARD' => 'BLVD',
974 'BOULV' => 'BLVD',
975 'BRNCH' => 'BR',
976 'BRANCH' => 'BR',
977 'BRDGE' => 'BRG',
978 'BRIDGE' => 'BRG',
979 'BROOK' => 'BRK',
980 'BROOKS' => 'BRKS',
981 'BURG' => 'BG',
982 'BURGS' => 'BGS',
983 'BYPA' => 'BYP',
984 'BYPAS' => 'BYP',
985 'BYPASS' => 'BYP',
986 'BYPS' => 'BYP',
987 'CAMP' => 'CP',
988 'CMP' => 'CP',
989 'CANYN' => 'CYN',
990 'CANYON' => 'CYN',
991 'CNYN' => 'CYN',
992 'CAPE' => 'CPE',
993 'CAUSEWAY' => 'CSWY',
994 'CAUSWA' => 'CSWY',
995 'CEN' => 'CTR',
996 'CENT' => 'CTR',
997 'CENTER' => 'CTR',
998 'CENTR' => 'CTR',
999 'CENTRE' => 'CTR',
1000 'CNTER' => 'CTR',
1001 'CNTR' => 'CTR',
1002 'CENTERS' => 'CTRS',
1003 'CIRC' => 'CIR',
1004 'CIRCL' => 'CIR',
1005 'CIRCLE' => 'CIR',
1006 'CRCL' => 'CIR',
1007 'CRCLE' => 'CIR',
1008 'CIRCLES' => 'CIRS',
1009 'CLIFF' => 'CLF',
1010 'CLIFFS' => 'CLFS',
1011 'CLUB' => 'CLB',
1012 'COMMON' => 'CMN',
1013 'COMMONS' => 'CMNS',
1014 'CORNER' => 'COR',
1015 'CORNERS' => 'CORS',
1016 'COURSE' => 'CRSE',
1017 'COURT' => 'CT',
1018 'COURTS' => 'CTS',
1019 'COVE' => 'CV',
1020 'COVES' => 'CVS',
1021 'CREEK' => 'CRK',
1022 'CRESCENT' => 'CRES',
1023 'CRSENT' => 'CRES',
1024 'CRSNT' => 'CRES',
1025 'CREST' => 'CRST',
1026 'CROSSING' => 'XING',
1027 'CRSSNG' => 'XING',
1028 'CROSSROAD' => 'XRD',
1029 'CROSSROADS' => 'XRDS',
1030 'CURVE' => 'CURV',
1031 'DALE' => 'DL',
1032 'DAM' => 'DM',
1033 'DIV' => 'DV',
1034 'DIVIDE' => 'DV',
1035 'DVD' => 'DV',
1036 'DRIV' => 'DR',
1037 'DRIVE' => 'DR',
1038 'DRV' => 'DR',
1039 'DRIVES' => 'DRS',
1040 'ESTATE' => 'EST',
1041 'ESTATES' => 'ESTS',
1042 'EXP' => 'EXPY',
1043 'EXPR' => 'EXPY',
1044 'EXPRESS' => 'EXPY',
1045 'EXPRESSWAY' => 'EXPY',
1046 'EXPW' => 'EXPY',
1047 'EXTENSION' => 'EXT',
1048 'EXTN' => 'EXT',
1049 'EXTNSN' => 'EXT',
1050 'EXTENSIONS' => 'EXTS',
1051 'FALLS' => 'FLS',
1052 'FERRY' => 'FRY',
1053 'FRRY' => 'FRY',
1054 'FIELD' => 'FLD',
1055 'FIELDS' => 'FLDS',
1056 'FLAT' => 'FLT',
1057 'FLATS' => 'FLTS',
1058 'FORD' => 'FRD',
1059 'FORDS' => 'FRDS',
1060 'FOREST' => 'FRST',
1061 'FORESTS' => 'FRST',
1062 'FORG' => 'FRG',
1063 'FORGE' => 'FRG',
1064 'FORGES' => 'FRGS',
1065 'FORK' => 'FRK',
1066 'FORKS' => 'FRKS',
1067 'FORT' => 'FT',
1068 'FRT' => 'FT',
1069 'FREEWAY' => 'FWY',
1070 'FREEWY' => 'FWY',
1071 'FRWAY' => 'FWY',
1072 'FRWY' => 'FWY',
1073 'GARDEN' => 'GDN',
1074 'GARDN' => 'GDN',
1075 'GRDEN' => 'GDN',
1076 'GRDN' => 'GDN',
1077 'GARDENS' => 'GDNS',
1078 'GRDNS' => 'GDNS',
1079 'GATEWAY' => 'GTWY',
1080 'GATEWY' => 'GTWY',
1081 'GATWAY' => 'GTWY',
1082 'GTWAY' => 'GTWY',
1083 'GLEN' => 'GLN',
1084 'GLENS' => 'GLNS',
1085 'GREEN' => 'GRN',
1086 'GREENS' => 'GRNS',
1087 'GROV' => 'GRV',
1088 'GROVE' => 'GRV',
1089 'GROVES' => 'GRVS',
1090 'HARB' => 'HBR',
1091 'HARBOR' => 'HBR',
1092 'HARBR' => 'HBR',
1093 'HRBOR' => 'HBR',
1094 'HARBORS' => 'HBRS',
1095 'HAVEN' => 'HVN',
1096 'HEIGHTS' => 'HTS',
1097 'HT' => 'HTS',
1098 'HIGHWAY' => 'HWY',
1099 'HIGHWY' => 'HWY',
1100 'HIWAY' => 'HWY',
1101 'HIWY' => 'HWY',
1102 'HWAY' => 'HWY',
1103 'HILL' => 'HL',
1104 'HILLS' => 'HLS',
1105 'HLLW' => 'HOLW',
1106 'HOLLOW' => 'HOLW',
1107 'HOLLOWS' => 'HOLW',
1108 'HOLWS' => 'HOLW',
1109 'INLET' => 'INLT',
1110 'ISLAND' => 'IS',
1111 'ISLND' => 'IS',
1112 'ISLANDS' => 'ISS',
1113 'ISLNDS' => 'ISS',
1114 'ISLES' => 'ISLE',
1115 'JCTION' => 'JCT',
1116 'JCTN' => 'JCT',
1117 'JUNCTION' => 'JCT',
1118 'JUNCTN' => 'JCT',
1119 'JUNCTON' => 'JCT',
1120 'JCTNS' => 'JCTS',
1121 'JUNCTIONS' => 'JCTS',
1122 'KEY' => 'KY',
1123 'KEYS' => 'KYS',
1124 'KNOL' => 'KNL',
1125 'KNOLL' => 'KNL',
1126 'KNOLLS' => 'KNLS',
1127 'LAKE' => 'LK',
1128 'LAKES' => 'LKS',
1129 'LANDING' => 'LNDG',
1130 'LNDNG' => 'LNDG',
1131 'LANE' => 'LN',
1132 'LIGHT' => 'LGT',
1133 'LIGHTS' => 'LGTS',
1134 'LOAF' => 'LF',
1135 'LOCK' => 'LCK',
1136 'LOCKS' => 'LCKS',
1137 'LDGE' => 'LDG',
1138 'LODG' => 'LDG',
1139 'LODGE' => 'LDG',
1140 'LOOPS' => 'LOOP',
1141 'MANOR' => 'MNR',
1142 'MANORS' => 'MNRS',
1143 'MDW' => 'MDWS',
1144 'MEADOWS' => 'MDWS',
1145 'MEDOWS' => 'MDWS',
1146 'MILL' => 'ML',
1147 'MILLS' => 'MLS',
1148 'MISSION' => 'MSN',
1149 'MISSN' => 'MSN',
1150 'MSSN' => 'MSN',
1151 'MOTORWAY' => 'MTWY',
1152 'MOUNT' => 'MT',
1153 'MNT' => 'MT',
1154 'MNTAIN' => 'MTN',
1155 'MNTN' => 'MTN',
1156 'MOUNTAIN' => 'MTN',
1157 'MOUNTIN' => 'MTN',
1158 'MTIN' => 'MTN',
1159 'MNTNS' => 'MTNS',
1160 'MOUNTAINS' => 'MTNS',
1161 'NECK' => 'NCK',
1162 'ORCHARD' => 'ORCH',
1163 'ORCHRD' => 'ORCH',
1164 'OVL' => 'OVAL',
1165 'OVERPASS' => 'OPAS',
1166 'PRK' => 'PARK',
1167 'PARKS' => 'PARK',
1168 'PARKWAY' => 'PKWY',
1169 'PARKWY' => 'PKWY',
1170 'PKWAY' => 'PKWY',
1171 'PKY' => 'PKWY',
1172 'PARKWAYS' => 'PKWY',
1173 'PKWYS' => 'PKWY',
1174 'PASSAGE' => 'PSGE',
1175 'PATHS' => 'PATH',
1176 'PIKES' => 'PIKE',
1177 'PINE' => 'PNE',
1178 'PINES' => 'PNES',
1179 'PLACE' => 'PL',
1180 'PLAIN' => 'PLN',
1181 'PLAINS' => 'PLNS',
1182 'PLAZA' => 'PLZ',
1183 'PLZA' => 'PLZ',
1184 'POINT' => 'PT',
1185 'POINTS' => 'PTS',
1186 'PORT' => 'PRT',
1187 'PORTS' => 'PRTS',
1188 'PRAIRIE' => 'PR',
1189 'PRR' => 'PR',
1190 'RAD' => 'RADL',
1191 'RADIAL' => 'RADL',
1192 'RADIEL' => 'RADL',
1193 'RANCH' => 'RNCH',
1194 'RANCHES' => 'RNCH',
1195 'RNCHS' => 'RNCH',
1196 'RAPID' => 'RPD',
1197 'RAPIDS' => 'RPDS',
1198 'REST' => 'RST',
1199 'RDGE' => 'RDG',
1200 'RIDGE' => 'RDG',
1201 'RIDGES' => 'RDGS',
1202 'RIVER' => 'RIV',
1203 'RVR' => 'RIV',
1204 'RIVR' => 'RIV',
1205 'ROAD' => 'RD',
1206 'ROADS' => 'RDS',
1207 'ROUTE' => 'RTE',
1208 'SHOAL' => 'SHL',
1209 'SHOALS' => 'SHLS',
1210 'SHOAR' => 'SHR',
1211 'SHORE' => 'SHR',
1212 'SHOARS' => 'SHRS',
1213 'SHORES' => 'SHRS',
1214 'SKYWAY' => 'SKWY',
1215 'SPNG' => 'SPG',
1216 'SPRING' => 'SPG',
1217 'SPRNG' => 'SPG',
1218 'SPNGS' => 'SPGS',
1219 'SPRINGS' => 'SPGS',
1220 'SPRNGS' => 'SPGS',
1221 'SPURS' => 'SPUR',
1222 'SQR' => 'SQ',
1223 'SQRE' => 'SQ',
1224 'SQU' => 'SQ',
1225 'SQUARE' => 'SQ',
1226 'SQRS' => 'SQS',
1227 'SQUARES' => 'SQS',
1228 'STATION' => 'STA',
1229 'STATN' => 'STA',
1230 'STN' => 'STA',
1231 'STRAV' => 'STRA',
1232 'STRAVEN' => 'STRA',
1233 'STRAVENUE' => 'STRA',
1234 'STRAVN' => 'STRA',
1235 'STRVN' => 'STRA',
1236 'STRVNUE' => 'STRA',
1237 'STREAM' => 'STRM',
1238 'STREME' => 'STREME',
1239 'STREET' => 'ST',
1240 'STRT' => 'ST',
1241 'STR' => 'ST',
1242 'STREETS' => 'STS',
1243 'SUMIT' => 'SMT',
1244 'SUMITT' => 'SMT',
1245 'SUMMIT' => 'SMT',
1246 'TERR' => 'TER',
1247 'TERRACE' => 'TER',
1248 'THROUGHWAY' => 'TRWY',
1249 'TRACE' => 'TRCE',
1250 'TRACES' => 'TRCE',
1251 'TRACK' => 'TRAK',
1252 'TRACKS' => 'TRAK',
1253 'TRK' => 'TRAK',
1254 'TRKS' => 'TRAK',
1255 'TRAFFICWAY' => 'TRFY',
1256 'TRAIL' => 'TRL',
1257 'TRAILS' => 'TRL',
1258 'TRLS' => 'TRL',
1259 'TRAILER' => 'TRLR',
1260 'TRLRS' => 'TRLR',
1261 'TUNEL' => 'TUNL',
1262 'TUNLS' => 'TUNL',
1263 'TUNNEL' => 'TUNL',
1264 'TUNNELS' => 'TUNL',
1265 'TUNNL' => 'TUNL',
1266 'TRNPK' => 'TPKE',
1267 'TURNPIKE' => 'TPKE',
1268 'TURNPK' => 'TPKE',
1269 'UNDERPASS' => 'UPAS',
1270 'UNION' => 'UN',
1271 'UNIONS' => 'UNS',
1272 'VALLEY' => 'VLY',
1273 'VALLY' => 'VLY',
1274 'VLLY' => 'VLY',
1275 'VALLEYS' => 'VLYS',
1276 'VDCT' => 'VIA',
1277 'VIADCT' => 'VIA',
1278 'VIADUCT' => 'VIA',
1279 'VIEW' => 'VW',
1280 'VIEWS' => 'VWS',
1281 'VILL' => 'VLG',
1282 'VILLAG' => 'VLG',
1283 'VILLAGE' => 'VLG',
1284 'VILLG' => 'VLG',
1285 'VILLIAGE' => 'VLG',
1286 'VILLAGES' => 'VLGS',
1287 'VILLE' => 'VL',
1288 'VIST' => 'VIS',
1289 'VISTA' => 'VIS',
1290 'VST' => 'VIS',
1291 'VSTA' => 'VIS',
1292 'WALKS' => 'WALK',
1293 'WY' => 'WAY',
1294 'WELL' => 'WL',
1295 'WELLS' => 'WLS',
1296 'NORTH' => 'N',
1297 'NORTHEAST' => 'NE',
1298 'NORTHWEST' => 'NW',
1299 'EAST' => 'E',
1300 'SOUTH' => 'SW',
1301 'SOUTHEAST' => 'SE',
1302 'SOUTHWEST' => 'SW',
1303 'WEST' => 'W',
1304 );
1305
1306 $find = array();
1307 $replace = array();
1308 foreach ( $replacements as $str_to_replace => $replacement ) {
1309 $find[] = " {$str_to_replace} ";
1310 $replace[] = " {$replacement} ";
1311 }
1312
1313 // Add space after address so finding word boundaries is easier
1314 $address .= ' ';
1315
1316 return trim( str_replace( $find, $replace, $address ) );
1317 }
1318
1319 /**
1320 * Update the saved origin addresses for each product after importing the
1321 * addresses from the user's TaxCloud account.
1322 */
1323 function sst_update_620_fix_product_origin_addresses() {
1324 global $wpdb;
1325
1326 $product_ids = $wpdb->get_col(
1327 "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_wootax_origin_addresses' AND meta_value != ''"
1328 );
1329
1330 if ( empty( $product_ids ) ) {
1331 // Nothing to process. Return false to bail.
1332 return false;
1333 }
1334
1335 // Kick off batch processing of products.
1336 update_option( '_sst_update_620_products_list', $product_ids );
1337 update_option( '_sst_update_620_batch_offset', 0 );
1338
1339 return 'sst_update_620_fix_product_origin_addresses_batch';
1340 }
1341
1342 /**
1343 * Updates the origin addresses for a batch of 100 products.
1344 *
1345 * We used to save the index of the product origin addresses, now we save
1346 * TaxCloud Location IDs.
1347 */
1348 function sst_update_620_fix_product_origin_addresses_batch() {
1349 $address_id_map = get_option( '_sst_update_620_address_id_map', array() );
1350
1351 // If we don't have the address ID map we can't do anything, so bail.
1352 if ( ! $address_id_map ) {
1353 delete_option( '_sst_update_620_products_list' );
1354 delete_option( '_sst_update_620_batch_offset' );
1355
1356 return false;
1357 }
1358
1359 $all_product_ids = get_option( '_sst_update_620_products_list', array() );
1360 $offset = get_option( '_sst_update_620_batch_offset', 0 );
1361 $batch_size = 100;
1362 $batch_product_ids = array_slice( $all_product_ids, $offset, $batch_size );
1363
1364 foreach ( $batch_product_ids as $product_id ) {
1365 $saved_origin_addresses = get_post_meta( $product_id, '_wootax_origin_addresses', true );
1366 $new_origin_addresses = array();
1367 foreach ( $saved_origin_addresses as $address_index ) {
1368 if ( isset( $address_id_map[ $address_index ] ) ) {
1369 $new_origin_addresses[] = $address_id_map[ $address_index ];
1370 }
1371 }
1372 update_post_meta( $product_id, '_wootax_origin_addresses', $new_origin_addresses );
1373 update_post_meta( $product_id, '_wootax_origin_addresses_pre_62', $saved_origin_addresses );
1374 }
1375
1376 if ( count( $batch_product_ids ) < $batch_size ) {
1377 // Reached end. Clean up.
1378 delete_option( '_sst_update_620_products_list' );
1379 delete_option( '_sst_update_620_batch_offset' );
1380
1381 return false;
1382 }
1383
1384 // Continue processing next batch.
1385 update_option( '_sst_update_620_batch_offset', $offset + $batch_size );
1386
1387 return 'sst_update_620_fix_product_origin_addresses_batch';
1388 }
1389
1390 /**
1391 * Delete the _wootax_package_cache meta key on upgrade to 7.0.
1392 */
1393 function sst_update_700_delete_package_cache() {
1394 global $wpdb;
1395
1396 $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key = '_wootax_package_cache'" );
1397 }
1398
1399 /**
1400 * Delete null _wootax_exempt_cert meta values on upgrade to 7.0.
1401 */
1402 function sst_update_700_delete_null_certificates() {
1403 global $wpdb;
1404
1405 $null_value = 's:2:"N;";';
1406
1407 $wpdb->query(
1408 $wpdb->prepare(
1409 "DELETE FROM {$wpdb->postmeta} WHERE meta_key = '_wootax_exempt_cert' AND meta_value = %s",
1410 $null_value
1411 )
1412 );
1413 }
1414
1415 /**
1416 * Replaces serialized TaxCloud\ExemptionCertificateBase objects
1417 * with string certificate IDs in the `exempt_cert` meta key on
1418 * upgrade to 7.0.
1419 */
1420 function sst_update_700_migrate_certificates() {
1421 $batch_size = 100;
1422 $args = array(
1423 'type' => 'shop_order',
1424 'return' => 'ids',
1425 'limit' => $batch_size,
1426 'meta_key' => '_wootax_exempt_cert',
1427 'meta_value' => 'ExemptionCertificateBase',
1428 'meta_compare' => 'LIKE',
1429 );
1430
1431 $order_ids = wc_get_orders( $args );
1432
1433 foreach ( $order_ids as $order_id ) {
1434 $order = new SST_Order( $order_id );
1435 $certificate = $order->get_meta( 'exempt_cert' );
1436
1437 if ( is_a( $certificate, 'TaxCloud\ExemptionCertificateBase' ) ) {
1438 $order->set_certificate_id( $certificate->getCertificateID() );
1439 $order->save();
1440 }
1441 }
1442
1443 if ( count( $order_ids ) === $batch_size ) {
1444 // More orders remain to process.
1445 return 'sst_update_700_migrate_certificates';
1446 }
1447
1448 return false;
1449 }
1450
1451 /**
1452 * Compresses saved order packages on upgrade to 7.0.
1453 */
1454 function sst_update_700_compress_packages() {
1455 $batch_size = 100;
1456 $args = array(
1457 'type' => 'shop_order',
1458 'return' => 'ids',
1459 'limit' => $batch_size,
1460 'meta_key' => '_wootax_db_version',
1461 'meta_value' => '7.0.0',
1462 'meta_compare' => '!=',
1463 );
1464
1465 $order_ids = wc_get_orders( $args );
1466
1467 foreach ( $order_ids as $order_id ) {
1468 $order = new SST_Order( $order_id );
1469 $packages = $order->get_meta( 'packages' );
1470 $new_packages = array_map(
1471 array( $order, 'compress_package_data' ),
1472 $packages
1473 );
1474
1475 $order->set_packages( $new_packages );
1476 $order->update_meta( 'db_version', '7.0.0' );
1477 $order->save();
1478 }
1479
1480 if ( count( $order_ids ) === $batch_size ) {
1481 // More orders remain to process.
1482 return 'sst_update_700_compress_packages';
1483 }
1484
1485 return false;
1486 }
1487