Plugin Directory

Changeset 3471597


Ignore:
Timestamp:
02/28/2026 11:49:09 AM (5 weeks ago)
Author:
transdirect
Message:

Update to version 8.0.2 from GitHub

Location:
transdirect-shipping
Files:
20 edited
1 copied

Legend:

Unmodified
Added
Removed
  • transdirect-shipping/tags/8.0.2/frontend/TransdirectShippingPublic.php

    r3347335 r3471597  
    107107        wp_enqueue_script('typeahead', 'https://cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.11.1/typeahead.bundle.min.js', array('jquery'), null, true);
    108108
    109         wp_enqueue_script('transdirect', plugin_dir_url(__FILE__) . 'js/transdirect.js', ['wp-data', 'jquery', 'wp-element'],null, true);
     109        wp_enqueue_script('transdirect', plugin_dir_url(__FILE__) . 'js/transdirect.js', ['wp-data', 'jquery', 'wp-element'], TRANSDIRECT_SHIPPING_VERSION, true);
     110
     111        $defaultShippingType = isset($apiDetails->street_type) ? $apiDetails->street_type : 'residential';
     112        if (WC()->session) {
     113            $sessionType = WC()->session->get('td_shipping_type');
     114            if (!empty($sessionType)) {
     115                $defaultShippingType = $sessionType;
     116            }
     117        }
    110118
    111119        wp_localize_script(
     
    113121            'transdirectData',
    114122            [
    115                 'locationsUrl' => plugin_dir_url(__FILE__) . 'includes/locations.php',
    116                 'mode'        => $apiDetails,
     123                'locationsUrl'        => plugin_dir_url(__FILE__) . 'includes/locations.php',
     124                'mode'                => $apiDetails,
     125                'ajaxUrl'             => admin_url('admin-ajax.php'),
     126                'defaultShippingType' => $defaultShippingType,
    117127            ]
    118128        );       
  • transdirect-shipping/tags/8.0.2/frontend/css/transdirect-shipping-public.css

    r3347335 r3471597  
    2424}
    2525
     26#td-shipping-type-wrap {
     27    padding: 0;
     28    margin-bottom: 4px;
     29}
     30
     31#td-shipping-type-radios {
     32    display: flex;
     33    align-items: baseline;
     34    gap: 20px;
     35    padding: 8px 0;
     36    font-size: 16px;
     37    line-height: 1.4;
     38}
     39
     40#td-shipping-type-radios .td-shipping-type-label {
     41    font-weight: 600;
     42    margin-right: 4px;
     43}
     44
     45#td-shipping-type-radios label {
     46    display: inline-flex;
     47    align-items: baseline;
     48    gap: 6px;
     49    cursor: pointer;
     50    font-weight: 400;
     51}
     52
     53#td-shipping-type-radios input[type="radio"] {
     54    margin: 0;
     55    cursor: pointer;
     56    position: relative;
     57    top: 1px;
     58}
     59
     60.wc-block-components-shipping-rates-control.td-rates-loading {
     61    opacity: 0.4;
     62    pointer-events: none;
     63    transition: opacity 0.2s ease;
     64}
     65
     66.td-shipping-type-row td {
     67    padding: 0.5em 0 !important;
     68}
     69
     70.td-shipping-type-row #td-shipping-type-radios {
     71    padding: 0;
     72    margin: 0;
     73}
     74
    2675#autocomplete-div, #autocomplete-div-postcode {
    27     background:#FFFFFF;
     76    background: #FFFFFF;
    2877    border: 1px solid #EDEDED;
    29     border-radius: 3px 3px 3px 3px;
     78    border-radius: 0 0 3px 3px;
     79    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    3080    display: none;
    3181    height: auto;
    3282    max-height: 150px;
    33     margin: -5px 0 0 1px;
    3483    overflow-y: auto;
    3584    padding: 5px;
    3685    position: absolute;
    37     width: 189px;
    38     z-index: 99;
    39     width: 100%;
     86    z-index: 999999;
     87    box-sizing: border-box;
     88    line-height: 1.4;
     89    white-space: normal;
     90    font-size: 14px;
    4091}
    4192
  • transdirect-shipping/tags/8.0.2/frontend/js/transdirect.js

    r3347335 r3471597  
    11jQuery(document).ready(function () {
    22    if (transdirectData.mode.mode === 'full_mode') {
    3         function insertAutocompleteBox() {
    4             const $cityInput = jQuery('#shipping-city');
     3        var latestRequestNumber = 0;
    54
    6             if ($cityInput.length && !jQuery('#autocomplete-div').length) {
    7                 const $autocompleteBox = jQuery('<div id="autocomplete-div"></div>');
     5        function buildRadioHTML() {
     6            var current = transdirectData.defaultShippingType || 'residential';
     7            return '<div class="wc-block-components-shipping-rates-control" id="td-shipping-type-wrap">' +
     8                '<div id="td-shipping-type-radios">' +
     9                '<span class="td-shipping-type-label">Shipping Type</span>' +
     10                '<label><input type="radio" name="td_shipping_type" value="residential"' +
     11                (current === 'residential' ? ' checked' : '') + '> Residential</label>' +
     12                '<label><input type="radio" name="td_shipping_type" value="business"' +
     13                (current === 'business' ? ' checked' : '') + '> Commercial</label>' +
     14                '</div></div>';
     15        }
    816
    9                 jQuery($cityInput).parent().append($autocompleteBox);
    10 
    11                 const offset = $cityInput.offset();
    12                 const height = $cityInput.outerHeight();
    13 
    14                 $autocompleteBox.css({
    15                     top: offset.top + height,
    16                     left: offset.left,
    17                     width: $cityInput.outerWidth()
    18                 });
     17        function insertElements() {
     18            if (jQuery('#shipping-city').length && !jQuery('#autocomplete-div').length) {
     19                jQuery('body').append('<div id="autocomplete-div"></div>');
    1920            }
    2021
    21             const $postcodeInput = jQuery('#shipping-postcode');
     22            if (jQuery('#shipping-postcode').length && !jQuery('#autocomplete-div-postcode').length) {
     23                jQuery('body').append('<div id="autocomplete-div-postcode"></div>');
     24            }
    2225
    23             if ($postcodeInput.length && !jQuery('#autocomplete-div-postcode').length) {
    24                 const $autocompleteBox = jQuery('<div id="autocomplete-div-postcode"></div>');
     26            var $ratesContainer = jQuery('.wc-block-components-shipping-rates-control').not('#td-shipping-type-wrap');
     27            if ($ratesContainer.length && !jQuery('#td-shipping-type-wrap').length) {
     28                $ratesContainer.before(buildRadioHTML());
     29            }
     30        }
    2531
    26                 jQuery($postcodeInput).parent().append($autocompleteBox);
    27 
    28                 const offset = $postcodeInput.offset();
    29                 const height = $postcodeInput.outerHeight();
    30 
    31                 $autocompleteBox.css({
    32                     top: offset.top + height,
     32        function positionDropdown(inputSelector, autoSelector) {
     33            var $input = jQuery(inputSelector);
     34            var $auto = jQuery('#' + autoSelector);
     35            if ($input.length && $auto.length) {
     36                var offset = $input.offset();
     37                $auto.css({
     38                    top: offset.top + $input.outerHeight(),
    3339                    left: offset.left,
    34                     width: $postcodeInput.outerWidth()
     40                    width: $input.outerWidth()
    3541                });
    3642            }
    3743        }
    3844
    39         insertAutocompleteBox();
    40         var latestRequestNumber = 0;
     45        insertElements();
    4146
    42         const observer = new MutationObserver(insertAutocompleteBox);
     47        var observer = new MutationObserver(insertElements);
    4348        observer.observe(document.body, { childList: true, subtree: true });
    4449
     
    5156        });
    5257
     58        var shippingTypeXhr = null;
     59        var shippingTypeCartXhr = null;
     60
     61        jQuery('body').on('change', 'input[name="td_shipping_type"]', function () {
     62            var newType = this.value;
     63            var $rates = jQuery('.wc-block-components-shipping-rates-control').not('#td-shipping-type-wrap');
     64
     65            if (shippingTypeXhr) { shippingTypeXhr.abort(); }
     66            if (shippingTypeCartXhr) { shippingTypeCartXhr.abort(); }
     67
     68            $rates.addClass('td-rates-loading');
     69
     70            shippingTypeXhr = jQuery.post(transdirectData.ajaxUrl, {
     71                action: 'td_set_shipping_type',
     72                shipping_type: newType
     73            }, function () {
     74                shippingTypeXhr = null;
     75
     76                var address = {
     77                    city: jQuery('#shipping-city').val() || '',
     78                    postcode: jQuery('#shipping-postcode').val() || '',
     79                    country: jQuery('#shipping-country').val() || 'AU',
     80                    state: jQuery('#shipping-state').val() || ''
     81                };
     82
     83                if (address.postcode && address.city) {
     84                    var apiBase = (typeof wpApiSettings !== 'undefined' && wpApiSettings.root)
     85                        ? wpApiSettings.root : '/wp-json/';
     86                    var nonce = (typeof wcBlocksMiddlewareConfig !== 'undefined')
     87                        ? wcBlocksMiddlewareConfig.storeApiNonce : '';
     88
     89                    shippingTypeCartXhr = jQuery.ajax({
     90                        url: apiBase + 'wc/store/v1/cart/update-customer',
     91                        method: 'POST',
     92                        contentType: 'application/json',
     93                        data: JSON.stringify({ shipping_address: address }),
     94                        beforeSend: function (xhr) {
     95                            if (nonce) { xhr.setRequestHeader('Nonce', nonce); }
     96                            if (typeof wpApiSettings !== 'undefined' && wpApiSettings.nonce) {
     97                                xhr.setRequestHeader('X-WP-Nonce', wpApiSettings.nonce);
     98                            }
     99                        },
     100                        success: function (response) {
     101                            shippingTypeCartXhr = null;
     102                            if (typeof wp !== 'undefined' && wp.data && wp.data.dispatch) {
     103                                wp.data.dispatch('wc/store/cart').receiveCart(response);
     104                            }
     105                            $rates.removeClass('td-rates-loading');
     106                        },
     107                        error: function () {
     108                            shippingTypeCartXhr = null;
     109                            $rates.removeClass('td-rates-loading');
     110                        }
     111                    });
     112                } else {
     113                    $rates.removeClass('td-rates-loading');
     114                }
     115                jQuery(document.body).trigger('update_checkout');
     116                jQuery(document.body).trigger('update_cart');
     117            });
     118        });
     119
    53120        function td_autocomplete(td_selector, auto_selector) {
    54121            var key_val = jQuery(td_selector).val();
    55             var position = jQuery(td_selector).position();
    56122            var html = '';
    57123
     
    72138                    var main_content = '<ul id="auto_complete">' + html + '</ul>';
    73139                    jQuery("#loading-div").hide();
    74                     jQuery("#" + auto_selector).show();
    75                     jQuery("#" + auto_selector).html(main_content);
    76                     jQuery("#" + auto_selector).css('left', position.left);
    77                     jQuery("#" + auto_selector).css('top', parseInt(position.top) + 55);
     140                    positionDropdown(td_selector, auto_selector);
     141                    jQuery("#" + auto_selector).html(main_content).show();
    78142                } else {
    79143                    html = html + '<li>No Results Found</li>';
    80144                    var main_content = '<ul id="auto_complete">' + html + '</ul>';
    81 
    82                     jQuery("#" + auto_selector).show();
    83                     jQuery("#" + auto_selector).html(main_content);
    84                     jQuery("#" + auto_selector).css('left', position.left);
    85                     jQuery("#" + auto_selector).css('top', parseInt(position.top) + 55);
    86                     jQuery("#" + auto_selector).css('overflow-y', 'hidden');
    87 
     145                    positionDropdown(td_selector, auto_selector);
     146                    jQuery("#" + auto_selector).html(main_content).css('overflow-y', 'hidden').show();
    88147                }
    89148            });
     
    93152
    94153function get_value(postcode, locality, txt_selector, div_selector) {
    95     const updateCity = (postcode, city) => {
    96         if (
    97             wp.data &&
    98             wp.data.dispatch &&
    99             typeof wp.data.dispatch("wc/store/cart").receiveCart === "function"
    100         ) {
    101             if (typeof wp !== 'undefined' && wp.apiFetch) {
    102                 wp.apiFetch.use(wp.apiFetch.createNonceMiddleware(wpApiSettings.nonce));
    103             }
    104             wp.data.dispatch("wc/store/cart").receiveCart({
    105                 city: city,
     154    if (wp.data && wp.data.dispatch) {
     155        wp.data.dispatch('wc/store/cart').setShippingAddress({
     156            city: locality,
     157            postcode: postcode,
     158            country: 'AU',
     159        });
     160        wp.data.dispatch('wc/store/cart').updateCustomerData({
     161            shipping_address: {
     162                city: locality,
    106163                postcode: postcode,
    107164                country: 'AU',
    108             });
    109             wp.data.dispatch('wc/store/cart').setShippingAddress({
    110                 city: city,
    111                 postcode: postcode,
    112                 country: 'AU',
    113             });
    114             wp.data.dispatch('wc/store/cart').updateCustomerData({
    115                 shipping_address: {
    116                     city: city,
    117                     postcode: postcode,
    118                     country: 'AU',
    119                 }
    120             });
    121         }
    122         jQuery(document.body).trigger("update_checkout");
    123         jQuery(document.body).trigger("update_cart");
    124     };
     165            }
     166        });
     167    }
     168    jQuery(document.body).trigger("update_checkout");
     169    jQuery(document.body).trigger("update_cart");
    125170
    126     updateCity(postcode, locality);
    127171    jQuery("#autocomplete-div").html('');
    128172    jQuery("#autocomplete-div").hide();
  • transdirect-shipping/tags/8.0.2/functions.php

    r3347335 r3471597  
    1010    return strtolower(str_replace(' ', '_', $string));
    1111}
     12
     13/**
     14 * Detect whether WooCommerce High-Performance Order Storage (HPOS) is active.
     15 */
     16function td_is_hpos_enabled()
     17{
     18    if (
     19        function_exists('wc_get_container') &&
     20        class_exists('\Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController')
     21    ) {
     22        $controller = wc_get_container()->get(
     23            \Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController::class
     24        );
     25        return $controller->custom_orders_table_usage_is_enabled();
     26    }
     27    return false;
     28}
  • transdirect-shipping/tags/8.0.2/includes/OrderSync.php

    r3356427 r3471597  
    241241        if ((isset($apiDetails->enable_sync) && $apiDetails->enable_sync == 'on') || (isset($apiDetails->sync_all_order) && $apiDetails->sync_all_order == 'on') || (isset($apiDetails->mode) && $apiDetails->mode == 'no_display_mode')) {
    242242
    243             $filters = [
    244                 'post_status' => ['draft', 'pending', 'publish'],
    245                 'post_type' => ['shop_order', 'shop_order_placehold'],
    246                 'posts_per_page' => 20,
    247                 'meta_query' => [
    248                     [
    249                         'key' => 'synced',
    250                         'value' => '0'
    251                     ]
    252                 ]
    253             ];
    254 
    255             $post_query = new \WP_Query($filters);
    256            
    257             if ($post_query->have_posts()) {
    258                 while ($post_query->have_posts()) : $post_query->the_post();
    259                     $order = new \WC_Order($post_query->post->ID);
    260                     $order_id = $order->get_id();
    261                     $booking_id = $order->get_meta('transdirect_booking_id');
    262                    
    263                     if ($booking_id && (isset($apiDetails->enable_sync) && $apiDetails->enable_sync == 'on')) {
    264                         $response = $this->td_sync_order($order, $booking_id, true);
    265                         if (isset($response->id) && $response->id != '' && get_post_meta($order_id, 'synced', true) == '0') {
    266                             update_post_meta($order_id, 'synced', '1');
    267                         }
    268                     } else if (!$booking_id && (isset($apiDetails->sync_all_order) && $apiDetails->sync_all_order == 'on') || $apiDetails->mode == 'no_display_mode') {
    269                         $response = $this->td_sync_all_orders($order);
    270                         if (isset($response->id) && $response->id != '' && get_post_meta($order_id, 'synced', true) == '0') {
    271                             update_post_meta($order_id, 'synced', '1');
    272                         }
    273                     }
    274                 endwhile;
    275                 wp_reset_query();
     243            if (td_is_hpos_enabled()) {
     244                $orders = wc_get_orders([
     245                    'limit'      => 20,
     246                    'status'     => array_keys(wc_get_order_statuses()),
     247                    'meta_query' => [
     248                        [
     249                            'key'   => 'synced',
     250                            'value' => '0',
     251                        ]
     252                    ],
     253                ]);
     254
     255                foreach ($orders as $order) {
     256                    $this->td_process_order_sync($order, $apiDetails);
     257                }
     258            } else {
     259                $filters = [
     260                    'post_status'    => ['draft', 'pending', 'publish'],
     261                    'post_type'      => ['shop_order', 'shop_order_placehold'],
     262                    'posts_per_page' => 20,
     263                    'meta_query'     => [
     264                        [
     265                            'key'   => 'synced',
     266                            'value' => '0',
     267                        ]
     268                    ],
     269                ];
     270
     271                $post_query = new \WP_Query($filters);
     272
     273                if ($post_query->have_posts()) {
     274                    while ($post_query->have_posts()) : $post_query->the_post();
     275                        $order = new \WC_Order($post_query->post->ID);
     276                        $this->td_process_order_sync($order, $apiDetails);
     277                    endwhile;
     278                    wp_reset_query();
     279                }
     280            }
     281        }
     282    }
     283
     284    /**
     285     * Process sync for a single order (shared by HPOS and legacy paths).
     286     */
     287    private function td_process_order_sync($order, $apiDetails)
     288    {
     289        $order_id   = $order->get_id();
     290        $booking_id = $order->get_meta('transdirect_booking_id');
     291
     292        if ($booking_id && (isset($apiDetails->enable_sync) && $apiDetails->enable_sync == 'on')) {
     293            $response = $this->td_sync_order($order, $booking_id, true);
     294            if (isset($response->id) && $response->id != '' && $order->get_meta('synced') === '0') {
     295                $order->update_meta_data('synced', '1');
     296                $order->save();
     297            }
     298        } else if (!$booking_id && ((isset($apiDetails->sync_all_order) && $apiDetails->sync_all_order == 'on') || $apiDetails->mode == 'no_display_mode')) {
     299            $response = $this->td_sync_all_orders($order);
     300            if (isset($response->id) && $response->id != '' && $order->get_meta('synced') === '0') {
     301                $order->update_meta_data('synced', '1');
     302                $order->save();
    276303            }
    277304        }
     
    299326    public function td_start_cron()
    300327    {
    301         global $wpdb;
    302 
    303         $filters = [
    304             'post_status' => ['draft', 'pending', 'publish'],
    305             'post_type' => ['shop_order', 'shop_order_placehold'],
    306             'posts_per_page' => -1,
    307             'meta_query' => [
    308                 [
    309                     'key' => 'synced',
    310                     'value' => '0'
    311                 ]
    312             ]
    313         ];
    314 
    315         $post_query = new \WP_Query($filters);
    316 
    317         if (!$post_query->have_posts()) {
     328        if (td_is_hpos_enabled()) {
     329            $unsynced = wc_get_orders([
     330                'limit'      => 1,
     331                'status'     => array_keys(wc_get_order_statuses()),
     332                'return'     => 'ids',
     333                'meta_query' => [
     334                    [
     335                        'key'   => 'synced',
     336                        'value' => '0',
     337                    ]
     338                ],
     339            ]);
     340
     341            if (empty($unsynced)) {
     342                $all_orders = wc_get_orders([
     343                    'limit'  => -1,
     344                    'status' => array_keys(wc_get_order_statuses()),
     345                ]);
     346
     347                foreach ($all_orders as $order) {
     348                    if ($order->get_meta('synced') === '') {
     349                        $order->update_meta_data('synced', '0');
     350                        $order->save();
     351                    }
     352                }
     353            }
     354        } else {
    318355            $filters = [
    319                 'post_status' => ['draft', 'pending', 'publish'],
    320                 'post_type' => ['shop_order', 'shop_order_placehold'],
    321                 'posts_per_page' => -1
     356                'post_status'    => ['draft', 'pending', 'publish'],
     357                'post_type'      => ['shop_order', 'shop_order_placehold'],
     358                'posts_per_page' => -1,
     359                'meta_query'     => [
     360                    [
     361                        'key'   => 'synced',
     362                        'value' => '0',
     363                    ]
     364                ],
    322365            ];
    323366
    324             $i = 0;
    325             $loop = new \WP_Query($filters);
    326 
    327             while ($loop->have_posts()) {
    328                 $loop->the_post();
    329                 $order = new \WC_Order($loop->post->ID);
    330                 $orderId = get_post_meta($order->get_id(), 'synced', true);
    331 
    332                 if ($orderId == '') {
    333                     add_post_meta($order->get_id(), 'synced', '0');
     367            $post_query = new \WP_Query($filters);
     368
     369            if (!$post_query->have_posts()) {
     370                $filters = [
     371                    'post_status'    => ['draft', 'pending', 'publish'],
     372                    'post_type'      => ['shop_order', 'shop_order_placehold'],
     373                    'posts_per_page' => -1,
     374                ];
     375
     376                $loop = new \WP_Query($filters);
     377
     378                while ($loop->have_posts()) {
     379                    $loop->the_post();
     380                    $order = new \WC_Order($loop->post->ID);
     381
     382                    if (get_post_meta($order->get_id(), 'synced', true) === '') {
     383                        add_post_meta($order->get_id(), 'synced', '0');
     384                    }
    334385                }
    335386            }
  • transdirect-shipping/tags/8.0.2/includes/TransdirectShipping.php

    r3356427 r3471597  
    142142        $this->loader->add_action('enqueue_block_assets', $plugin_public, 'enqueue_scripts');
    143143        $this->loader->add_action('woocommerce_store_api_checkout_order_processed', $this, 'before_create_order');
     144        $this->loader->add_action(
     145            'woocommerce_checkout_create_order',
     146            $this,
     147            'before_create_order',
     148            10,
     149            2
     150        );
    144151        $this->loader->add_action('wp_footer', $this, 'override_shipping_options_text', 100);
    145         $this->loader->add_action('woocommerce_init', $this, 'custom_address_type_field');
     152        $this->loader->add_filter('woocommerce_cart_shipping_packages', $this, 'add_shipping_type_to_package');
     153        $this->loader->add_action('woocommerce_review_order_before_shipping', $this, 'render_shipping_type_radios');
     154        $this->loader->add_action('wp_ajax_td_set_shipping_type', $this, 'handle_shipping_type_ajax');
     155        $this->loader->add_action('wp_ajax_nopriv_td_set_shipping_type', $this, 'handle_shipping_type_ajax');
     156        $this->loader->add_action('woocommerce_init', $this, 'init_shipping_type_session');
    146157    }
    147158
     
    249260        $is_td_order = false;
    250261        $api_array = [];
    251         foreach (json_decode($cached_data)->quotes as $service => $quote) {
     262        $decoded_data = $cached_data ? json_decode($cached_data) : null;
     263        $quotes = isset($decoded_data->quotes) ? (array) $decoded_data->quotes : [];
     264        foreach ($quotes as $service => $quote) {
    252265            if($selected_shipping === humanizeCamelString($service)) {
    253266                $is_td_order = true;
     
    263276
    264277    /**
    265     *
    266     * Add shipping type field to checkout shipping and billing address form.
    267     * @access public
    268     *
    269     */
    270     public function custom_address_type_field() {
    271         $api_details = $this->td_get_api_details(true);
    272         if(isset($api_details->mode)) {
    273             $displayMode = $api_details->mode;
    274             if($displayMode === 'full_mode') {
    275                 woocommerce_register_additional_checkout_field(
    276                     array(
    277                         'id'            => 'namespace/shipping-type',
    278                         'label'         => 'Shipping Type',
    279                         'optionalLabel' => 'Shipping Type (optional)',
    280                         'location'      => 'address',
    281                         'required'      => true,
    282                         'type'          => 'select',
    283                         'options'       => array(
    284                             array(
    285                                 'value' => 'residential',
    286                                 'label' => 'Residential',
    287                             ),
    288                             array(
    289                                 'value' => 'business',
    290                                 'label' => 'Commercial',
    291                             ),
    292                         ),
    293                         'attributes'    => array(
    294                             'aria-label' => 'Select Shipping Type',
    295                         ),
    296                     )
    297                 );
     278     * Set the default shipping type in the WC session on first visit.
     279     */
     280    public function init_shipping_type_session() {
     281        if (!WC()->session) {
     282            return;
     283        }
     284
     285        $current = WC()->session->get('td_shipping_type');
     286        if (empty($current)) {
     287            $api_details = $this->td_get_api_details();
     288            $default_type = isset($api_details->street_type) ? $api_details->street_type : 'residential';
     289            WC()->session->set('td_shipping_type', $default_type);
     290        }
     291    }
     292
     293    /**
     294     * Render shipping type radio buttons for Classic Checkout
     295     * (injected via woocommerce_review_order_before_shipping hook).
     296     */
     297    public function render_shipping_type_radios() {
     298        $api_details = $this->td_get_api_details();
     299        if (!isset($api_details->mode) || $api_details->mode !== 'full_mode') {
     300            return;
     301        }
     302
     303        $current = WC()->session ? WC()->session->get('td_shipping_type', 'residential') : 'residential';
     304        ?>
     305        <tr class="td-shipping-type-row">
     306            <th>Shipping Type</th>
     307            <td>
     308                <div id="td-shipping-type-radios">
     309                    <label>
     310                        <input type="radio" name="td_shipping_type" value="residential" <?php checked($current, 'residential'); ?>>
     311                        Residential
     312                    </label>
     313                    <label>
     314                        <input type="radio" name="td_shipping_type" value="business" <?php checked($current, 'business'); ?>>
     315                        Commercial
     316                    </label>
     317                </div>
     318            </td>
     319        </tr>
     320        <?php
     321    }
     322
     323    /**
     324     * AJAX handler to save the selected shipping type to WC session.
     325     */
     326    public function handle_shipping_type_ajax() {
     327        $type = isset($_POST['shipping_type']) ? sanitize_text_field($_POST['shipping_type']) : '';
     328        if (!empty($type) && WC()->session) {
     329            WC()->session->set('td_shipping_type', $type);
     330
     331            $pkg_count = 1;
     332            if (WC()->cart) {
     333                $packages = WC()->cart->get_shipping_packages();
     334                if (!empty($packages)) {
     335                    $pkg_count = count($packages);
     336                }
    298337            }
    299         }
     338            for ($i = 0; $i < $pkg_count; $i++) {
     339                WC()->session->set('shipping_for_package_' . $i, false);
     340            }
     341
     342            WC()->session->save_data();
     343
     344            wp_send_json_success();
     345        }
     346        wp_send_json_error();
     347    }
     348
     349    /**
     350     * Include shipping type in the shipping package so it becomes part of
     351     * WooCommerce's package hash. This forces rate recalculation when the
     352     * customer changes shipping type without changing their address.
     353     */
     354    public function add_shipping_type_to_package($packages) {
     355        $shipping_type = WC()->session ? WC()->session->get('td_shipping_type', 'residential') : 'residential';
     356
     357        foreach ($packages as &$package) {
     358            $package['td_shipping_type'] = $shipping_type;
     359        }
     360
     361        return $packages;
    300362    }
    301363}
  • transdirect-shipping/tags/8.0.2/includes/TransdirectShippingTrait.php

    r3356427 r3471597  
    171171                return $m->id === $this->shipping_method_id;
    172172            });
    173             return reset($td_method)->instance_settings ?? '';
     173            $first = reset($td_method);
     174            return $first ? ($first->instance_settings ?? '') : '';
    174175        }, $methods);
    175176       
     
    177178            return !empty($selected);
    178179        });
    179         return (object) reset($filtered);
     180        $result = reset($filtered);
     181        return (object) ($result ?: []);
    180182    }
    181183}
  • transdirect-shipping/tags/8.0.2/includes/WCTransdirectShipping.php

    r3347335 r3471597  
    131131    public function calculate_shipping($package = array())
    132132    {
    133         $batch_request_body = file_get_contents('php://input');
    134         $request_params = json_decode($batch_request_body, true);
    135         $shipping_type = "residential";
    136133        $apiDetails = $this->td_get_api_details(true);
     134        $default_type = isset($apiDetails->street_type) ? $apiDetails->street_type : 'residential';
     135        $shipping_type = WC()->session ? WC()->session->get('td_shipping_type', $default_type) : $default_type;
    137136
    138137        if(isset($apiDetails->mode) && $apiDetails->mode != 'no_display_mode') {
    139             if (isset($request_params['requests']) && is_array($request_params['requests'])) {
    140                 $shipping_data = $request_params['requests'][0]['data']['shipping_address'] ?? [];
    141                 if (isset($shipping_data['namespace/shipping-type'])) {
    142                     $shipping_type = sanitize_text_field($shipping_data['namespace/shipping-type']) ?? "residential";
    143                 }
    144             } elseif (is_user_logged_in()) {
    145                 $user_id = get_current_user_id();
    146                 $shipping_type_meta = get_user_meta($user_id, '_wc_shipping/namespace/shipping-type', true);
    147                 if (!empty($shipping_type_meta)) {
    148                     $shipping_type = sanitize_text_field($shipping_type_meta);
    149                 }
    150             }
    151138
    152139            $data = $this->get_quote($shipping_type);
     
    179166    private function get_quote($shipping_type)
    180167    {
     168        if (!function_exists('WC') || !WC()->cart) {
     169            return null;
     170        }
    181171        $get_shipping_packages = WC()->cart->get_shipping_packages();
    182172        $shipping = reset($get_shipping_packages);
     
    230220
    231221        $response = $this->send('POST', 'bookings/v4', $this->api_key, $params, true);
     222        $response = json_decode($response, true);
     223        $quotes = $response['quotes'] ?? [];
     224
     225        $quotes_array = [];
     226        foreach ($quotes as $carrier => $data) {
     227            $quotes_array[$carrier] = $data;
     228        }
     229
     230        uasort($quotes_array, function($a, $b) {
     231            return $a['total'] <=> $b['total'];
     232        });
     233
     234        $response['quotes'] = $quotes_array;
     235        $response = json_encode($response);
    232236        set_transient($hash_key, $request_hash, 5 * MINUTE_IN_SECONDS);
    233237        set_transient($response_key, $response, 5 * MINUTE_IN_SECONDS);
  • transdirect-shipping/tags/8.0.2/readme.txt

    r3347335 r3471597  
    44Requires at least: 4.2
    55Tested up to: 6.8.2
     6Stable tag: 8.0.2
     7WC requires at least: 7.1
     8WC tested up to: 9.6
    69License: GPLv3
    710License URI: https://www.gnu.org/licenses/gpl-3.0.html
  • transdirect-shipping/tags/8.0.2/transdirect-shipping.php

    r3356427 r3471597  
    99 *
    1010 * @link              https://transdirect.com.au/
    11  * @since             8.0.1
     11 * @since             8.0.2
    1212 * @package           Transdirect_Shipping
    1313 *
     
    1616 * Plugin URI:        https://www.transdirect.com.au/e-commerce/woo-commerce/
    1717 * Description:       This plugin allows you to calculate shipping as per your delivery location.
    18  * Version:           8.0.1
     18 * Version:           8.0.2
    1919 * Author:            Transdirect
    2020 * Author URI:        https://transdirect.com.au/
     
    2323 * Text Domain:       transdirect-shipping
    2424 * Domain Path:       /languages
     25 * WC requires at least: 7.1
     26 * WC tested up to:      10.5
    2527 */
    2628
     
    3335require_once 'functions.php';
    3436
     37add_action('before_woocommerce_init', function () {
     38    if (class_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil')) {
     39        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true);
     40    }
     41});
     42
    3543/**
    3644 * Currently plugin version.
    37  * Start at version 8.0.1 and use SemVer - https://semver.org
     45 * Start at version 8.0.2 and use SemVer - https://semver.org
    3846 * Rename this for your plugin and update it as you release new versions.
    3947 */
    40 define('TRANSDIRECT_SHIPPING_VERSION', '8.0.1');
     48define('TRANSDIRECT_SHIPPING_VERSION', '8.0.2');
    4149
    4250/**
     
    5967 * @return void
    6068 */
    61 function td_run_at_activation()
    62 {
    63     $filters = [
    64         'post_status' => 'any',
    65         'post_type' => 'shop_order',
    66         'posts_per_page' => -1
    67     ];
    68 
    69     $i = 0;
    70     $loop = new WP_Query($filters);
    71 
    72     while ($loop->have_posts()) {
    73         $loop->the_post();
    74         $order = new WC_Order($loop->post->ID);
    75         $orderId = get_post_meta($order->id, 'synced', true);
    76         if ($orderId == '') {
    77             add_post_meta($order->id, 'synced', '0');
     69register_activation_hook(__FILE__, function () {
     70    update_option('td_run_sync_on_activation', true);
     71});
     72
     73add_action('plugins_loaded', function () {
     74    if (!class_exists('WooCommerce')) {
     75        return;
     76    }
     77
     78    if (get_option('td_run_sync_on_activation')) {
     79        td_run_at_activation_safe();
     80        delete_option('td_run_sync_on_activation');
     81    }
     82});
     83
     84function td_run_at_activation_safe() {
     85    $is_hpos_enabled = td_is_hpos_enabled();
     86    $orders = [];
     87    if ($is_hpos_enabled) {
     88        $orders = wc_get_orders([
     89            'limit'  => -1,
     90            'return' => 'ids',
     91            'status' => array_keys(wc_get_order_statuses()),
     92        ]);
     93    } else {
     94        $loop = new WP_Query([
     95            'post_type'      => 'shop_order',
     96            'post_status'    => 'any',
     97            'posts_per_page' => -1,
     98            'fields'         => 'ids',
     99        ]);
     100        $orders = $loop->posts;
     101    }
     102
     103    $total_orders = count($orders);
     104    $batch_size = 100;
     105    $count = 0;
     106
     107    for ($offset = 0; $offset < $total_orders; $offset += $batch_size) {
     108        $batch = array_slice($orders, $offset, $batch_size);
     109        foreach ($batch as $order_id) {
     110            $synced = get_post_meta($order_id, 'synced', true);
     111            if ($synced == '') {
     112                add_post_meta($order_id, 'synced', '0');
     113                $count++;
     114            }
    78115        }
    79116    }
    80117}
    81 register_activation_hook(__FILE__, 'td_run_at_activation');
    82118
    83119/**
     
    137173}
    138174
    139 register_deactivation_hook(__FILE__, 'td_cronstarter_deactivate');
    140 
    141175add_action('td_cron_order_sync', 'td_my_repeat_function');
    142176
     
    175209}
    176210
    177 
    178 /**
    179 *
    180 * Hook add action to make cron work in background when wordpress is load.
    181 *
    182 */
    183 add_action('admin_init', 'td_cronstarter_activation');
    184 add_action('wp', 'td_cronstarter_activation');
    185 
    186 /**
    187 *
    188 * This will start the and activate cron job every 5 minutes.
    189 * @access public
    190 *
    191 */
    192 function td_cronstarter_activation() {
    193     if (!get_transient( 'timeout_for_30_min' )) {
    194         set_transient( 'timeout_for_30_min', 'cron_activation_timeout', 0.5 * HOUR_IN_SECONDS );
    195         $startCron = new OrderSync();
    196         $startCron->td_start_cron();
    197         $startProductCron = new ProductSync();
    198         $startProductCron->td_start_product_cron();
    199     }
    200 }
    201211
    202212/**
  • transdirect-shipping/trunk/frontend/TransdirectShippingPublic.php

    r3347335 r3471597  
    107107        wp_enqueue_script('typeahead', 'https://cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.11.1/typeahead.bundle.min.js', array('jquery'), null, true);
    108108
    109         wp_enqueue_script('transdirect', plugin_dir_url(__FILE__) . 'js/transdirect.js', ['wp-data', 'jquery', 'wp-element'],null, true);
     109        wp_enqueue_script('transdirect', plugin_dir_url(__FILE__) . 'js/transdirect.js', ['wp-data', 'jquery', 'wp-element'], TRANSDIRECT_SHIPPING_VERSION, true);
     110
     111        $defaultShippingType = isset($apiDetails->street_type) ? $apiDetails->street_type : 'residential';
     112        if (WC()->session) {
     113            $sessionType = WC()->session->get('td_shipping_type');
     114            if (!empty($sessionType)) {
     115                $defaultShippingType = $sessionType;
     116            }
     117        }
    110118
    111119        wp_localize_script(
     
    113121            'transdirectData',
    114122            [
    115                 'locationsUrl' => plugin_dir_url(__FILE__) . 'includes/locations.php',
    116                 'mode'        => $apiDetails,
     123                'locationsUrl'        => plugin_dir_url(__FILE__) . 'includes/locations.php',
     124                'mode'                => $apiDetails,
     125                'ajaxUrl'             => admin_url('admin-ajax.php'),
     126                'defaultShippingType' => $defaultShippingType,
    117127            ]
    118128        );       
  • transdirect-shipping/trunk/frontend/css/transdirect-shipping-public.css

    r3347335 r3471597  
    2424}
    2525
     26#td-shipping-type-wrap {
     27    padding: 0;
     28    margin-bottom: 4px;
     29}
     30
     31#td-shipping-type-radios {
     32    display: flex;
     33    align-items: baseline;
     34    gap: 20px;
     35    padding: 8px 0;
     36    font-size: 16px;
     37    line-height: 1.4;
     38}
     39
     40#td-shipping-type-radios .td-shipping-type-label {
     41    font-weight: 600;
     42    margin-right: 4px;
     43}
     44
     45#td-shipping-type-radios label {
     46    display: inline-flex;
     47    align-items: baseline;
     48    gap: 6px;
     49    cursor: pointer;
     50    font-weight: 400;
     51}
     52
     53#td-shipping-type-radios input[type="radio"] {
     54    margin: 0;
     55    cursor: pointer;
     56    position: relative;
     57    top: 1px;
     58}
     59
     60.wc-block-components-shipping-rates-control.td-rates-loading {
     61    opacity: 0.4;
     62    pointer-events: none;
     63    transition: opacity 0.2s ease;
     64}
     65
     66.td-shipping-type-row td {
     67    padding: 0.5em 0 !important;
     68}
     69
     70.td-shipping-type-row #td-shipping-type-radios {
     71    padding: 0;
     72    margin: 0;
     73}
     74
    2675#autocomplete-div, #autocomplete-div-postcode {
    27     background:#FFFFFF;
     76    background: #FFFFFF;
    2877    border: 1px solid #EDEDED;
    29     border-radius: 3px 3px 3px 3px;
     78    border-radius: 0 0 3px 3px;
     79    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    3080    display: none;
    3181    height: auto;
    3282    max-height: 150px;
    33     margin: -5px 0 0 1px;
    3483    overflow-y: auto;
    3584    padding: 5px;
    3685    position: absolute;
    37     width: 189px;
    38     z-index: 99;
    39     width: 100%;
     86    z-index: 999999;
     87    box-sizing: border-box;
     88    line-height: 1.4;
     89    white-space: normal;
     90    font-size: 14px;
    4091}
    4192
  • transdirect-shipping/trunk/frontend/js/transdirect.js

    r3347335 r3471597  
    11jQuery(document).ready(function () {
    22    if (transdirectData.mode.mode === 'full_mode') {
    3         function insertAutocompleteBox() {
    4             const $cityInput = jQuery('#shipping-city');
     3        var latestRequestNumber = 0;
    54
    6             if ($cityInput.length && !jQuery('#autocomplete-div').length) {
    7                 const $autocompleteBox = jQuery('<div id="autocomplete-div"></div>');
     5        function buildRadioHTML() {
     6            var current = transdirectData.defaultShippingType || 'residential';
     7            return '<div class="wc-block-components-shipping-rates-control" id="td-shipping-type-wrap">' +
     8                '<div id="td-shipping-type-radios">' +
     9                '<span class="td-shipping-type-label">Shipping Type</span>' +
     10                '<label><input type="radio" name="td_shipping_type" value="residential"' +
     11                (current === 'residential' ? ' checked' : '') + '> Residential</label>' +
     12                '<label><input type="radio" name="td_shipping_type" value="business"' +
     13                (current === 'business' ? ' checked' : '') + '> Commercial</label>' +
     14                '</div></div>';
     15        }
    816
    9                 jQuery($cityInput).parent().append($autocompleteBox);
    10 
    11                 const offset = $cityInput.offset();
    12                 const height = $cityInput.outerHeight();
    13 
    14                 $autocompleteBox.css({
    15                     top: offset.top + height,
    16                     left: offset.left,
    17                     width: $cityInput.outerWidth()
    18                 });
     17        function insertElements() {
     18            if (jQuery('#shipping-city').length && !jQuery('#autocomplete-div').length) {
     19                jQuery('body').append('<div id="autocomplete-div"></div>');
    1920            }
    2021
    21             const $postcodeInput = jQuery('#shipping-postcode');
     22            if (jQuery('#shipping-postcode').length && !jQuery('#autocomplete-div-postcode').length) {
     23                jQuery('body').append('<div id="autocomplete-div-postcode"></div>');
     24            }
    2225
    23             if ($postcodeInput.length && !jQuery('#autocomplete-div-postcode').length) {
    24                 const $autocompleteBox = jQuery('<div id="autocomplete-div-postcode"></div>');
     26            var $ratesContainer = jQuery('.wc-block-components-shipping-rates-control').not('#td-shipping-type-wrap');
     27            if ($ratesContainer.length && !jQuery('#td-shipping-type-wrap').length) {
     28                $ratesContainer.before(buildRadioHTML());
     29            }
     30        }
    2531
    26                 jQuery($postcodeInput).parent().append($autocompleteBox);
    27 
    28                 const offset = $postcodeInput.offset();
    29                 const height = $postcodeInput.outerHeight();
    30 
    31                 $autocompleteBox.css({
    32                     top: offset.top + height,
     32        function positionDropdown(inputSelector, autoSelector) {
     33            var $input = jQuery(inputSelector);
     34            var $auto = jQuery('#' + autoSelector);
     35            if ($input.length && $auto.length) {
     36                var offset = $input.offset();
     37                $auto.css({
     38                    top: offset.top + $input.outerHeight(),
    3339                    left: offset.left,
    34                     width: $postcodeInput.outerWidth()
     40                    width: $input.outerWidth()
    3541                });
    3642            }
    3743        }
    3844
    39         insertAutocompleteBox();
    40         var latestRequestNumber = 0;
     45        insertElements();
    4146
    42         const observer = new MutationObserver(insertAutocompleteBox);
     47        var observer = new MutationObserver(insertElements);
    4348        observer.observe(document.body, { childList: true, subtree: true });
    4449
     
    5156        });
    5257
     58        var shippingTypeXhr = null;
     59        var shippingTypeCartXhr = null;
     60
     61        jQuery('body').on('change', 'input[name="td_shipping_type"]', function () {
     62            var newType = this.value;
     63            var $rates = jQuery('.wc-block-components-shipping-rates-control').not('#td-shipping-type-wrap');
     64
     65            if (shippingTypeXhr) { shippingTypeXhr.abort(); }
     66            if (shippingTypeCartXhr) { shippingTypeCartXhr.abort(); }
     67
     68            $rates.addClass('td-rates-loading');
     69
     70            shippingTypeXhr = jQuery.post(transdirectData.ajaxUrl, {
     71                action: 'td_set_shipping_type',
     72                shipping_type: newType
     73            }, function () {
     74                shippingTypeXhr = null;
     75
     76                var address = {
     77                    city: jQuery('#shipping-city').val() || '',
     78                    postcode: jQuery('#shipping-postcode').val() || '',
     79                    country: jQuery('#shipping-country').val() || 'AU',
     80                    state: jQuery('#shipping-state').val() || ''
     81                };
     82
     83                if (address.postcode && address.city) {
     84                    var apiBase = (typeof wpApiSettings !== 'undefined' && wpApiSettings.root)
     85                        ? wpApiSettings.root : '/wp-json/';
     86                    var nonce = (typeof wcBlocksMiddlewareConfig !== 'undefined')
     87                        ? wcBlocksMiddlewareConfig.storeApiNonce : '';
     88
     89                    shippingTypeCartXhr = jQuery.ajax({
     90                        url: apiBase + 'wc/store/v1/cart/update-customer',
     91                        method: 'POST',
     92                        contentType: 'application/json',
     93                        data: JSON.stringify({ shipping_address: address }),
     94                        beforeSend: function (xhr) {
     95                            if (nonce) { xhr.setRequestHeader('Nonce', nonce); }
     96                            if (typeof wpApiSettings !== 'undefined' && wpApiSettings.nonce) {
     97                                xhr.setRequestHeader('X-WP-Nonce', wpApiSettings.nonce);
     98                            }
     99                        },
     100                        success: function (response) {
     101                            shippingTypeCartXhr = null;
     102                            if (typeof wp !== 'undefined' && wp.data && wp.data.dispatch) {
     103                                wp.data.dispatch('wc/store/cart').receiveCart(response);
     104                            }
     105                            $rates.removeClass('td-rates-loading');
     106                        },
     107                        error: function () {
     108                            shippingTypeCartXhr = null;
     109                            $rates.removeClass('td-rates-loading');
     110                        }
     111                    });
     112                } else {
     113                    $rates.removeClass('td-rates-loading');
     114                }
     115                jQuery(document.body).trigger('update_checkout');
     116                jQuery(document.body).trigger('update_cart');
     117            });
     118        });
     119
    53120        function td_autocomplete(td_selector, auto_selector) {
    54121            var key_val = jQuery(td_selector).val();
    55             var position = jQuery(td_selector).position();
    56122            var html = '';
    57123
     
    72138                    var main_content = '<ul id="auto_complete">' + html + '</ul>';
    73139                    jQuery("#loading-div").hide();
    74                     jQuery("#" + auto_selector).show();
    75                     jQuery("#" + auto_selector).html(main_content);
    76                     jQuery("#" + auto_selector).css('left', position.left);
    77                     jQuery("#" + auto_selector).css('top', parseInt(position.top) + 55);
     140                    positionDropdown(td_selector, auto_selector);
     141                    jQuery("#" + auto_selector).html(main_content).show();
    78142                } else {
    79143                    html = html + '<li>No Results Found</li>';
    80144                    var main_content = '<ul id="auto_complete">' + html + '</ul>';
    81 
    82                     jQuery("#" + auto_selector).show();
    83                     jQuery("#" + auto_selector).html(main_content);
    84                     jQuery("#" + auto_selector).css('left', position.left);
    85                     jQuery("#" + auto_selector).css('top', parseInt(position.top) + 55);
    86                     jQuery("#" + auto_selector).css('overflow-y', 'hidden');
    87 
     145                    positionDropdown(td_selector, auto_selector);
     146                    jQuery("#" + auto_selector).html(main_content).css('overflow-y', 'hidden').show();
    88147                }
    89148            });
     
    93152
    94153function get_value(postcode, locality, txt_selector, div_selector) {
    95     const updateCity = (postcode, city) => {
    96         if (
    97             wp.data &&
    98             wp.data.dispatch &&
    99             typeof wp.data.dispatch("wc/store/cart").receiveCart === "function"
    100         ) {
    101             if (typeof wp !== 'undefined' && wp.apiFetch) {
    102                 wp.apiFetch.use(wp.apiFetch.createNonceMiddleware(wpApiSettings.nonce));
    103             }
    104             wp.data.dispatch("wc/store/cart").receiveCart({
    105                 city: city,
     154    if (wp.data && wp.data.dispatch) {
     155        wp.data.dispatch('wc/store/cart').setShippingAddress({
     156            city: locality,
     157            postcode: postcode,
     158            country: 'AU',
     159        });
     160        wp.data.dispatch('wc/store/cart').updateCustomerData({
     161            shipping_address: {
     162                city: locality,
    106163                postcode: postcode,
    107164                country: 'AU',
    108             });
    109             wp.data.dispatch('wc/store/cart').setShippingAddress({
    110                 city: city,
    111                 postcode: postcode,
    112                 country: 'AU',
    113             });
    114             wp.data.dispatch('wc/store/cart').updateCustomerData({
    115                 shipping_address: {
    116                     city: city,
    117                     postcode: postcode,
    118                     country: 'AU',
    119                 }
    120             });
    121         }
    122         jQuery(document.body).trigger("update_checkout");
    123         jQuery(document.body).trigger("update_cart");
    124     };
     165            }
     166        });
     167    }
     168    jQuery(document.body).trigger("update_checkout");
     169    jQuery(document.body).trigger("update_cart");
    125170
    126     updateCity(postcode, locality);
    127171    jQuery("#autocomplete-div").html('');
    128172    jQuery("#autocomplete-div").hide();
  • transdirect-shipping/trunk/functions.php

    r3347335 r3471597  
    1010    return strtolower(str_replace(' ', '_', $string));
    1111}
     12
     13/**
     14 * Detect whether WooCommerce High-Performance Order Storage (HPOS) is active.
     15 */
     16function td_is_hpos_enabled()
     17{
     18    if (
     19        function_exists('wc_get_container') &&
     20        class_exists('\Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController')
     21    ) {
     22        $controller = wc_get_container()->get(
     23            \Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController::class
     24        );
     25        return $controller->custom_orders_table_usage_is_enabled();
     26    }
     27    return false;
     28}
  • transdirect-shipping/trunk/includes/OrderSync.php

    r3356427 r3471597  
    241241        if ((isset($apiDetails->enable_sync) && $apiDetails->enable_sync == 'on') || (isset($apiDetails->sync_all_order) && $apiDetails->sync_all_order == 'on') || (isset($apiDetails->mode) && $apiDetails->mode == 'no_display_mode')) {
    242242
    243             $filters = [
    244                 'post_status' => ['draft', 'pending', 'publish'],
    245                 'post_type' => ['shop_order', 'shop_order_placehold'],
    246                 'posts_per_page' => 20,
    247                 'meta_query' => [
    248                     [
    249                         'key' => 'synced',
    250                         'value' => '0'
    251                     ]
    252                 ]
    253             ];
    254 
    255             $post_query = new \WP_Query($filters);
    256            
    257             if ($post_query->have_posts()) {
    258                 while ($post_query->have_posts()) : $post_query->the_post();
    259                     $order = new \WC_Order($post_query->post->ID);
    260                     $order_id = $order->get_id();
    261                     $booking_id = $order->get_meta('transdirect_booking_id');
    262                    
    263                     if ($booking_id && (isset($apiDetails->enable_sync) && $apiDetails->enable_sync == 'on')) {
    264                         $response = $this->td_sync_order($order, $booking_id, true);
    265                         if (isset($response->id) && $response->id != '' && get_post_meta($order_id, 'synced', true) == '0') {
    266                             update_post_meta($order_id, 'synced', '1');
    267                         }
    268                     } else if (!$booking_id && (isset($apiDetails->sync_all_order) && $apiDetails->sync_all_order == 'on') || $apiDetails->mode == 'no_display_mode') {
    269                         $response = $this->td_sync_all_orders($order);
    270                         if (isset($response->id) && $response->id != '' && get_post_meta($order_id, 'synced', true) == '0') {
    271                             update_post_meta($order_id, 'synced', '1');
    272                         }
    273                     }
    274                 endwhile;
    275                 wp_reset_query();
     243            if (td_is_hpos_enabled()) {
     244                $orders = wc_get_orders([
     245                    'limit'      => 20,
     246                    'status'     => array_keys(wc_get_order_statuses()),
     247                    'meta_query' => [
     248                        [
     249                            'key'   => 'synced',
     250                            'value' => '0',
     251                        ]
     252                    ],
     253                ]);
     254
     255                foreach ($orders as $order) {
     256                    $this->td_process_order_sync($order, $apiDetails);
     257                }
     258            } else {
     259                $filters = [
     260                    'post_status'    => ['draft', 'pending', 'publish'],
     261                    'post_type'      => ['shop_order', 'shop_order_placehold'],
     262                    'posts_per_page' => 20,
     263                    'meta_query'     => [
     264                        [
     265                            'key'   => 'synced',
     266                            'value' => '0',
     267                        ]
     268                    ],
     269                ];
     270
     271                $post_query = new \WP_Query($filters);
     272
     273                if ($post_query->have_posts()) {
     274                    while ($post_query->have_posts()) : $post_query->the_post();
     275                        $order = new \WC_Order($post_query->post->ID);
     276                        $this->td_process_order_sync($order, $apiDetails);
     277                    endwhile;
     278                    wp_reset_query();
     279                }
     280            }
     281        }
     282    }
     283
     284    /**
     285     * Process sync for a single order (shared by HPOS and legacy paths).
     286     */
     287    private function td_process_order_sync($order, $apiDetails)
     288    {
     289        $order_id   = $order->get_id();
     290        $booking_id = $order->get_meta('transdirect_booking_id');
     291
     292        if ($booking_id && (isset($apiDetails->enable_sync) && $apiDetails->enable_sync == 'on')) {
     293            $response = $this->td_sync_order($order, $booking_id, true);
     294            if (isset($response->id) && $response->id != '' && $order->get_meta('synced') === '0') {
     295                $order->update_meta_data('synced', '1');
     296                $order->save();
     297            }
     298        } else if (!$booking_id && ((isset($apiDetails->sync_all_order) && $apiDetails->sync_all_order == 'on') || $apiDetails->mode == 'no_display_mode')) {
     299            $response = $this->td_sync_all_orders($order);
     300            if (isset($response->id) && $response->id != '' && $order->get_meta('synced') === '0') {
     301                $order->update_meta_data('synced', '1');
     302                $order->save();
    276303            }
    277304        }
     
    299326    public function td_start_cron()
    300327    {
    301         global $wpdb;
    302 
    303         $filters = [
    304             'post_status' => ['draft', 'pending', 'publish'],
    305             'post_type' => ['shop_order', 'shop_order_placehold'],
    306             'posts_per_page' => -1,
    307             'meta_query' => [
    308                 [
    309                     'key' => 'synced',
    310                     'value' => '0'
    311                 ]
    312             ]
    313         ];
    314 
    315         $post_query = new \WP_Query($filters);
    316 
    317         if (!$post_query->have_posts()) {
     328        if (td_is_hpos_enabled()) {
     329            $unsynced = wc_get_orders([
     330                'limit'      => 1,
     331                'status'     => array_keys(wc_get_order_statuses()),
     332                'return'     => 'ids',
     333                'meta_query' => [
     334                    [
     335                        'key'   => 'synced',
     336                        'value' => '0',
     337                    ]
     338                ],
     339            ]);
     340
     341            if (empty($unsynced)) {
     342                $all_orders = wc_get_orders([
     343                    'limit'  => -1,
     344                    'status' => array_keys(wc_get_order_statuses()),
     345                ]);
     346
     347                foreach ($all_orders as $order) {
     348                    if ($order->get_meta('synced') === '') {
     349                        $order->update_meta_data('synced', '0');
     350                        $order->save();
     351                    }
     352                }
     353            }
     354        } else {
    318355            $filters = [
    319                 'post_status' => ['draft', 'pending', 'publish'],
    320                 'post_type' => ['shop_order', 'shop_order_placehold'],
    321                 'posts_per_page' => -1
     356                'post_status'    => ['draft', 'pending', 'publish'],
     357                'post_type'      => ['shop_order', 'shop_order_placehold'],
     358                'posts_per_page' => -1,
     359                'meta_query'     => [
     360                    [
     361                        'key'   => 'synced',
     362                        'value' => '0',
     363                    ]
     364                ],
    322365            ];
    323366
    324             $i = 0;
    325             $loop = new \WP_Query($filters);
    326 
    327             while ($loop->have_posts()) {
    328                 $loop->the_post();
    329                 $order = new \WC_Order($loop->post->ID);
    330                 $orderId = get_post_meta($order->get_id(), 'synced', true);
    331 
    332                 if ($orderId == '') {
    333                     add_post_meta($order->get_id(), 'synced', '0');
     367            $post_query = new \WP_Query($filters);
     368
     369            if (!$post_query->have_posts()) {
     370                $filters = [
     371                    'post_status'    => ['draft', 'pending', 'publish'],
     372                    'post_type'      => ['shop_order', 'shop_order_placehold'],
     373                    'posts_per_page' => -1,
     374                ];
     375
     376                $loop = new \WP_Query($filters);
     377
     378                while ($loop->have_posts()) {
     379                    $loop->the_post();
     380                    $order = new \WC_Order($loop->post->ID);
     381
     382                    if (get_post_meta($order->get_id(), 'synced', true) === '') {
     383                        add_post_meta($order->get_id(), 'synced', '0');
     384                    }
    334385                }
    335386            }
  • transdirect-shipping/trunk/includes/TransdirectShipping.php

    r3356427 r3471597  
    142142        $this->loader->add_action('enqueue_block_assets', $plugin_public, 'enqueue_scripts');
    143143        $this->loader->add_action('woocommerce_store_api_checkout_order_processed', $this, 'before_create_order');
     144        $this->loader->add_action(
     145            'woocommerce_checkout_create_order',
     146            $this,
     147            'before_create_order',
     148            10,
     149            2
     150        );
    144151        $this->loader->add_action('wp_footer', $this, 'override_shipping_options_text', 100);
    145         $this->loader->add_action('woocommerce_init', $this, 'custom_address_type_field');
     152        $this->loader->add_filter('woocommerce_cart_shipping_packages', $this, 'add_shipping_type_to_package');
     153        $this->loader->add_action('woocommerce_review_order_before_shipping', $this, 'render_shipping_type_radios');
     154        $this->loader->add_action('wp_ajax_td_set_shipping_type', $this, 'handle_shipping_type_ajax');
     155        $this->loader->add_action('wp_ajax_nopriv_td_set_shipping_type', $this, 'handle_shipping_type_ajax');
     156        $this->loader->add_action('woocommerce_init', $this, 'init_shipping_type_session');
    146157    }
    147158
     
    249260        $is_td_order = false;
    250261        $api_array = [];
    251         foreach (json_decode($cached_data)->quotes as $service => $quote) {
     262        $decoded_data = $cached_data ? json_decode($cached_data) : null;
     263        $quotes = isset($decoded_data->quotes) ? (array) $decoded_data->quotes : [];
     264        foreach ($quotes as $service => $quote) {
    252265            if($selected_shipping === humanizeCamelString($service)) {
    253266                $is_td_order = true;
     
    263276
    264277    /**
    265     *
    266     * Add shipping type field to checkout shipping and billing address form.
    267     * @access public
    268     *
    269     */
    270     public function custom_address_type_field() {
    271         $api_details = $this->td_get_api_details(true);
    272         if(isset($api_details->mode)) {
    273             $displayMode = $api_details->mode;
    274             if($displayMode === 'full_mode') {
    275                 woocommerce_register_additional_checkout_field(
    276                     array(
    277                         'id'            => 'namespace/shipping-type',
    278                         'label'         => 'Shipping Type',
    279                         'optionalLabel' => 'Shipping Type (optional)',
    280                         'location'      => 'address',
    281                         'required'      => true,
    282                         'type'          => 'select',
    283                         'options'       => array(
    284                             array(
    285                                 'value' => 'residential',
    286                                 'label' => 'Residential',
    287                             ),
    288                             array(
    289                                 'value' => 'business',
    290                                 'label' => 'Commercial',
    291                             ),
    292                         ),
    293                         'attributes'    => array(
    294                             'aria-label' => 'Select Shipping Type',
    295                         ),
    296                     )
    297                 );
     278     * Set the default shipping type in the WC session on first visit.
     279     */
     280    public function init_shipping_type_session() {
     281        if (!WC()->session) {
     282            return;
     283        }
     284
     285        $current = WC()->session->get('td_shipping_type');
     286        if (empty($current)) {
     287            $api_details = $this->td_get_api_details();
     288            $default_type = isset($api_details->street_type) ? $api_details->street_type : 'residential';
     289            WC()->session->set('td_shipping_type', $default_type);
     290        }
     291    }
     292
     293    /**
     294     * Render shipping type radio buttons for Classic Checkout
     295     * (injected via woocommerce_review_order_before_shipping hook).
     296     */
     297    public function render_shipping_type_radios() {
     298        $api_details = $this->td_get_api_details();
     299        if (!isset($api_details->mode) || $api_details->mode !== 'full_mode') {
     300            return;
     301        }
     302
     303        $current = WC()->session ? WC()->session->get('td_shipping_type', 'residential') : 'residential';
     304        ?>
     305        <tr class="td-shipping-type-row">
     306            <th>Shipping Type</th>
     307            <td>
     308                <div id="td-shipping-type-radios">
     309                    <label>
     310                        <input type="radio" name="td_shipping_type" value="residential" <?php checked($current, 'residential'); ?>>
     311                        Residential
     312                    </label>
     313                    <label>
     314                        <input type="radio" name="td_shipping_type" value="business" <?php checked($current, 'business'); ?>>
     315                        Commercial
     316                    </label>
     317                </div>
     318            </td>
     319        </tr>
     320        <?php
     321    }
     322
     323    /**
     324     * AJAX handler to save the selected shipping type to WC session.
     325     */
     326    public function handle_shipping_type_ajax() {
     327        $type = isset($_POST['shipping_type']) ? sanitize_text_field($_POST['shipping_type']) : '';
     328        if (!empty($type) && WC()->session) {
     329            WC()->session->set('td_shipping_type', $type);
     330
     331            $pkg_count = 1;
     332            if (WC()->cart) {
     333                $packages = WC()->cart->get_shipping_packages();
     334                if (!empty($packages)) {
     335                    $pkg_count = count($packages);
     336                }
    298337            }
    299         }
     338            for ($i = 0; $i < $pkg_count; $i++) {
     339                WC()->session->set('shipping_for_package_' . $i, false);
     340            }
     341
     342            WC()->session->save_data();
     343
     344            wp_send_json_success();
     345        }
     346        wp_send_json_error();
     347    }
     348
     349    /**
     350     * Include shipping type in the shipping package so it becomes part of
     351     * WooCommerce's package hash. This forces rate recalculation when the
     352     * customer changes shipping type without changing their address.
     353     */
     354    public function add_shipping_type_to_package($packages) {
     355        $shipping_type = WC()->session ? WC()->session->get('td_shipping_type', 'residential') : 'residential';
     356
     357        foreach ($packages as &$package) {
     358            $package['td_shipping_type'] = $shipping_type;
     359        }
     360
     361        return $packages;
    300362    }
    301363}
  • transdirect-shipping/trunk/includes/TransdirectShippingTrait.php

    r3356427 r3471597  
    171171                return $m->id === $this->shipping_method_id;
    172172            });
    173             return reset($td_method)->instance_settings ?? '';
     173            $first = reset($td_method);
     174            return $first ? ($first->instance_settings ?? '') : '';
    174175        }, $methods);
    175176       
     
    177178            return !empty($selected);
    178179        });
    179         return (object) reset($filtered);
     180        $result = reset($filtered);
     181        return (object) ($result ?: []);
    180182    }
    181183}
  • transdirect-shipping/trunk/includes/WCTransdirectShipping.php

    r3347335 r3471597  
    131131    public function calculate_shipping($package = array())
    132132    {
    133         $batch_request_body = file_get_contents('php://input');
    134         $request_params = json_decode($batch_request_body, true);
    135         $shipping_type = "residential";
    136133        $apiDetails = $this->td_get_api_details(true);
     134        $default_type = isset($apiDetails->street_type) ? $apiDetails->street_type : 'residential';
     135        $shipping_type = WC()->session ? WC()->session->get('td_shipping_type', $default_type) : $default_type;
    137136
    138137        if(isset($apiDetails->mode) && $apiDetails->mode != 'no_display_mode') {
    139             if (isset($request_params['requests']) && is_array($request_params['requests'])) {
    140                 $shipping_data = $request_params['requests'][0]['data']['shipping_address'] ?? [];
    141                 if (isset($shipping_data['namespace/shipping-type'])) {
    142                     $shipping_type = sanitize_text_field($shipping_data['namespace/shipping-type']) ?? "residential";
    143                 }
    144             } elseif (is_user_logged_in()) {
    145                 $user_id = get_current_user_id();
    146                 $shipping_type_meta = get_user_meta($user_id, '_wc_shipping/namespace/shipping-type', true);
    147                 if (!empty($shipping_type_meta)) {
    148                     $shipping_type = sanitize_text_field($shipping_type_meta);
    149                 }
    150             }
    151138
    152139            $data = $this->get_quote($shipping_type);
     
    179166    private function get_quote($shipping_type)
    180167    {
     168        if (!function_exists('WC') || !WC()->cart) {
     169            return null;
     170        }
    181171        $get_shipping_packages = WC()->cart->get_shipping_packages();
    182172        $shipping = reset($get_shipping_packages);
     
    230220
    231221        $response = $this->send('POST', 'bookings/v4', $this->api_key, $params, true);
     222        $response = json_decode($response, true);
     223        $quotes = $response['quotes'] ?? [];
     224
     225        $quotes_array = [];
     226        foreach ($quotes as $carrier => $data) {
     227            $quotes_array[$carrier] = $data;
     228        }
     229
     230        uasort($quotes_array, function($a, $b) {
     231            return $a['total'] <=> $b['total'];
     232        });
     233
     234        $response['quotes'] = $quotes_array;
     235        $response = json_encode($response);
    232236        set_transient($hash_key, $request_hash, 5 * MINUTE_IN_SECONDS);
    233237        set_transient($response_key, $response, 5 * MINUTE_IN_SECONDS);
  • transdirect-shipping/trunk/readme.txt

    r3347335 r3471597  
    44Requires at least: 4.2
    55Tested up to: 6.8.2
     6Stable tag: 8.0.2
     7WC requires at least: 7.1
     8WC tested up to: 9.6
    69License: GPLv3
    710License URI: https://www.gnu.org/licenses/gpl-3.0.html
  • transdirect-shipping/trunk/transdirect-shipping.php

    r3356427 r3471597  
    99 *
    1010 * @link              https://transdirect.com.au/
    11  * @since             8.0.1
     11 * @since             8.0.2
    1212 * @package           Transdirect_Shipping
    1313 *
     
    1616 * Plugin URI:        https://www.transdirect.com.au/e-commerce/woo-commerce/
    1717 * Description:       This plugin allows you to calculate shipping as per your delivery location.
    18  * Version:           8.0.1
     18 * Version:           8.0.2
    1919 * Author:            Transdirect
    2020 * Author URI:        https://transdirect.com.au/
     
    2323 * Text Domain:       transdirect-shipping
    2424 * Domain Path:       /languages
     25 * WC requires at least: 7.1
     26 * WC tested up to:      10.5
    2527 */
    2628
     
    3335require_once 'functions.php';
    3436
     37add_action('before_woocommerce_init', function () {
     38    if (class_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil')) {
     39        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true);
     40    }
     41});
     42
    3543/**
    3644 * Currently plugin version.
    37  * Start at version 8.0.1 and use SemVer - https://semver.org
     45 * Start at version 8.0.2 and use SemVer - https://semver.org
    3846 * Rename this for your plugin and update it as you release new versions.
    3947 */
    40 define('TRANSDIRECT_SHIPPING_VERSION', '8.0.1');
     48define('TRANSDIRECT_SHIPPING_VERSION', '8.0.2');
    4149
    4250/**
     
    5967 * @return void
    6068 */
    61 function td_run_at_activation()
    62 {
    63     $filters = [
    64         'post_status' => 'any',
    65         'post_type' => 'shop_order',
    66         'posts_per_page' => -1
    67     ];
    68 
    69     $i = 0;
    70     $loop = new WP_Query($filters);
    71 
    72     while ($loop->have_posts()) {
    73         $loop->the_post();
    74         $order = new WC_Order($loop->post->ID);
    75         $orderId = get_post_meta($order->id, 'synced', true);
    76         if ($orderId == '') {
    77             add_post_meta($order->id, 'synced', '0');
     69register_activation_hook(__FILE__, function () {
     70    update_option('td_run_sync_on_activation', true);
     71});
     72
     73add_action('plugins_loaded', function () {
     74    if (!class_exists('WooCommerce')) {
     75        return;
     76    }
     77
     78    if (get_option('td_run_sync_on_activation')) {
     79        td_run_at_activation_safe();
     80        delete_option('td_run_sync_on_activation');
     81    }
     82});
     83
     84function td_run_at_activation_safe() {
     85    $is_hpos_enabled = td_is_hpos_enabled();
     86    $orders = [];
     87    if ($is_hpos_enabled) {
     88        $orders = wc_get_orders([
     89            'limit'  => -1,
     90            'return' => 'ids',
     91            'status' => array_keys(wc_get_order_statuses()),
     92        ]);
     93    } else {
     94        $loop = new WP_Query([
     95            'post_type'      => 'shop_order',
     96            'post_status'    => 'any',
     97            'posts_per_page' => -1,
     98            'fields'         => 'ids',
     99        ]);
     100        $orders = $loop->posts;
     101    }
     102
     103    $total_orders = count($orders);
     104    $batch_size = 100;
     105    $count = 0;
     106
     107    for ($offset = 0; $offset < $total_orders; $offset += $batch_size) {
     108        $batch = array_slice($orders, $offset, $batch_size);
     109        foreach ($batch as $order_id) {
     110            $synced = get_post_meta($order_id, 'synced', true);
     111            if ($synced == '') {
     112                add_post_meta($order_id, 'synced', '0');
     113                $count++;
     114            }
    78115        }
    79116    }
    80117}
    81 register_activation_hook(__FILE__, 'td_run_at_activation');
    82118
    83119/**
     
    137173}
    138174
    139 register_deactivation_hook(__FILE__, 'td_cronstarter_deactivate');
    140 
    141175add_action('td_cron_order_sync', 'td_my_repeat_function');
    142176
     
    175209}
    176210
    177 
    178 /**
    179 *
    180 * Hook add action to make cron work in background when wordpress is load.
    181 *
    182 */
    183 add_action('admin_init', 'td_cronstarter_activation');
    184 add_action('wp', 'td_cronstarter_activation');
    185 
    186 /**
    187 *
    188 * This will start the and activate cron job every 5 minutes.
    189 * @access public
    190 *
    191 */
    192 function td_cronstarter_activation() {
    193     if (!get_transient( 'timeout_for_30_min' )) {
    194         set_transient( 'timeout_for_30_min', 'cron_activation_timeout', 0.5 * HOUR_IN_SECONDS );
    195         $startCron = new OrderSync();
    196         $startCron->td_start_cron();
    197         $startProductCron = new ProductSync();
    198         $startProductCron->td_start_product_cron();
    199     }
    200 }
    201211
    202212/**
Note: See TracChangeset for help on using the changeset viewer.