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 |