Plugin Directory

Changeset 3452263


Ignore:
Timestamp:
02/02/2026 04:36:50 PM (8 weeks ago)
Author:
IQComputing
Message:

Update to version 1.2.0 from GitHub

Location:
live-rates-for-shipstation
Files:
26 added
10 deleted
26 edited
1 copied

Legend:

Unmodified
Added
Removed
  • live-rates-for-shipstation/tags/1.2.0/README.md

    r3407166 r3452263  
    4242
    4343This is a free plugin entirely volunteer run. While we will ensure that the plugin is up-to-date for any security issues or breakfixes, we cannot commit to supporting any customizations to the plugin or feature requests, but welcome these questions and requests through the [WordPress Support Forums](https://wordpress.org/support/plugin/live-rates-for-shipstation/) for our team to consider in a future release of the plugin. We recommend using the WordPress Forums for all inquiries.
     44
     45## Filter Hooks
     46
     47- `iqlrss/cache/cart_rates`
     48- `iqlrss/cache/shipstation`
     49- `iqlrss/cache/shipstation_expires`
     50- `iqlrss/shipping/calculator_object`
     51- `iqlrss/shipping/packages`
     52- `iqlrss/zone/package_presets`
     53- `iqlrss/zone/settings`
  • live-rates-for-shipstation/tags/1.2.0/_autoload.php

    r3442676 r3452263  
    2121    ) );
    2222
     23    /* Vroom! */
     24    if( 'driver' === $class_path ) {
     25        $file_path  = wp_normalize_path( sprintf( '%s/%s',
     26            rtrim( plugin_dir_path( __FILE__ ), '\\/' ),
     27            'live-rates-for-shipstation.php'
     28        ) );
     29    }
     30
    2331    if( file_exists( $file_path ) ) {
    2432        require_once $file_path;
  • live-rates-for-shipstation/tags/1.2.0/_stallation.php

    r3407166 r3452263  
    2020
    2121    /**
    22      * Unintsall Plugin
     22     * Uninstall Plugin
    2323     */
    2424    public static function uninstall() {
  • live-rates-for-shipstation/tags/1.2.0/changelog.txt

    r3442676 r3452263  
    22
    33This is a brief text document keeping track of changes to the plugin. For a full history, see the Github Repository.
     4
     5= 1.2.0 =
     6
     7Relase Date: Eventually.
     8
     9* Overview
     10    * Shipping Calculations moved to it's own Class for better PHPUnit testing.
     11        * See unit-tests branch; work-in-progress.
     12    * ShipStation Warehouses are now supported Globally and per Shipping Zone.
     13        * Defaults to WooCommerce Store. Global overrides Store. Zone overrides Global.
     14    * ShipStation Packages are now integrated into Custom Packages.
     15    * Multiple new filter hooks added to manage caching, warehouses, and Shipping Calculator object.
     16
     17* Code Updates
     18    * Filter Hook `iqlrss/cache/shipstation`
     19        * Boolean to disable caching for API requests.
     20    * Filter Hook `iqlrss/cache/shipstation_expires`
     21        * Integer seconds for how long to cache package rates for a customer.
     22        * 1 Week is the default.
     23    * Filter Hook `iqlrss/cache/cart_rates`
     24        * Boolean to diable caching at a cart level.
     25        * This is not recommended to disable since WC (sometimes) makes multiple calls to the cart per page load, but the option is available if you have a usecase.
     26    * Fitler Hook `iqlrss/shipping/calculator_object`
     27        * Expects a \IQLRSS\Core\Classes\Shipping_Calculator Object.
     28        * Must inherit the IQLRSS Calculator to be valid.
     29        * The shipping method calculations had an overhaul and broken into multiple methods of the new Shipping Calculator object.
     30        * This is for PHPUnit testing and to make it easier for developers to override specific calculator functionality for their usecases.
    431
    532= 1.1.2 =
     
    1542= 1.1.1 =
    1643
    17 Relase Date: December 04, 2025
     44Release Date: December 04, 2025
    1845
    1946* Overview
     
    2350= 1.1.0 =
    2451
    25 Relase Date: December 01, 2025
     52Release Date: December 01, 2025
    2653
    2754* Overview
     
    5885= 1.0.8 =
    5986
    60 Relase Date: October 10, 2025
     87Release Date: October 10, 2025
    6188
    6289* Overview
     
    74101        * Ugh, the Block Editor + WooCommerce makes _multiple_ async requests to shipping calculations which would re-trigger things unnecessarily.
    75102        * The new methodology uses the WC()->session to automatically return the known rates if the cart has not changed.
    76         * This is a noticable increase in speed when browsing the shop when your cart hasn't necessarily changed.
    77     * Caching layer also prevents multiple / duplciate API requests being logged which is nice.
     103        * This is a noticeable increase in speed when browsing the shop when your cart hasn't necessarily changed.
     104    * Caching layer also prevents multiple / duplicate API requests being logged which is nice.
    78105
    79106* Code Updates
     
    83110= 1.0.7 =
    84111
    85 Relase Date: October 08, 2025
     112Release Date: October 08, 2025
    86113
    87114* Overview
     
    89116        * This denotes what items got what rates, the adjustments, where the adjustments come from.
    90117    * Patches issue on Shipping Zone where WP_Error was treated as an Exception
    91     * Starts to noramlize code for carrier_id and carrier_code.
     118    * Starts to normalize code for carrier_id and carrier_code.
    92119        * Doing this makes it easier to integrate the v1 API.
    93120        * The Carrier ID is the ShipStation `se-` code.
     
    102129= 1.0.6 =
    103130
    104 Relase Date: September 22, 2025
     131Release Date: September 22, 2025
    105132
    106133* Overview
     
    109136= 1.0.5 =
    110137
    111 Relase Date: September 16, 2025
     138Release Date: September 16, 2025
    112139
    113140* Overview
     
    135162= 1.0.4 =
    136163
    137 Relase Date: September 15, 2025
     164Release Date: September 15, 2025
    138165
    139166* Overview
     
    153180= 1.0.3 =
    154181
    155 Relase Date: August 05, 2025
     182Release Date: August 05, 2025
    156183
    157184* Overview
     
    165192= 1.0.2 =
    166193
    167 Relase Date: August 04, 2025
     194Release Date: August 04, 2025
    168195
    169196* Overview
     
    177204= 1.0.1 =
    178205
    179 Relase Date: August 01, 2025
     206Release Date: August 01, 2025
    180207
    181208* Overview
  • live-rates-for-shipstation/tags/1.2.0/core/api/shipstation.php

    r3407166 r3452263  
    22/**
    33 * ShipStation API Helper
     4 *
     5 * @link https://docs.shipstation.com/openapi
    46 *
    57 * Carrier ID   : se-*
     
    1012 */
    1113namespace IQLRSS\Core\Api;
     14use \IQLRSS\Core\Traits;
    1215
    1316if( ! defined( 'ABSPATH' ) ) {
     
    1821
    1922    /**
     23     * Inherit logger traits
     24     */
     25    use Traits\Logger;
     26
     27
     28    /**
    2029     * Skip cache check
    2130     *
     
    6776        $this->prefix   = \IQLRSS\Driver::get( 'slug' );
    6877        $this->key      = \IQLRSS\Driver::get_ss_opt( 'api_key', '' );
    69         $this->skip_cache = (boolean)$skip_cache;
    70         $this->cache_time = defined( 'WEEK_IN_SECONDS' ) ? WEEK_IN_SECONDS : 604800;
     78
     79
     80        /**
     81         * Skip caching for the API.
     82         *
     83         * @hook filter
     84         *
     85         * @param Bolean FALSE
     86         *
     87         * @return Boolean
     88         */
     89        $this->skip_cache = (boolean)apply_filters( 'iqlrss/cache/shipstation', $skip_cache, $this );
     90
     91
     92        /**
     93         * Allow filtering the cache time.
     94         *
     95         * @see https://codex.wordpress.org/Easier_Expression_of_Time_Constants
     96         *
     97         * @hook filter
     98         *
     99         * @param Integer $cache_time - Week in seconds.
     100         *
     101         * @return Boolean
     102         */
     103        $cache_time = apply_filters( 'iqlrss/cache/shipstation_expires', $this->cache_time, $this );
     104        $this->cache_time = ( is_numeric( $cache_time ) ) ? absint( $cache_time ) : $this->cache_time;
    71105
    72106    }
     
    101135        // Return Early - Something went wrong getting carriers.
    102136        } else if( ! isset( $carriers[ $carrier_code ] ) ) {
    103             return $this->log( new \WP_Error( 404, esc_html__( 'Could not find carrier information.', 'live-rates-for-shipstation' ) ) );
     137            return $this->log( new \WP_Error( 404, esc_html__( 'Could not find carrier information.', 'live-rates-for-shipstation' ) ), 'warning' );
    104138        }
    105139
     
    233267     * @todo Look into `delivery_days` field. UPS has, is it carrier consistent?
    234268     *
    235      * @param Array $est_opts
    236      *
    237      * @return Array|WP_Error
    238      */
    239     public function get_shipping_estimates( $est_opts ) {
    240 
    241         $body = $this->make_request( 'post', 'rates/estimate', $est_opts );
     269     * @link https://docs.shipstation.com/openapi/rates/calculate_rates
     270     * @link https://docs.shipstation.com/openapi/rates/estimate_rates
     271     *
     272     * @param Array $api_args - See ShipStation API docs for required fields.
     273     *
     274     * @return Array|WP_Error
     275     */
     276    public function get_shipping_estimates( $api_args ) {
     277
     278        $body = $this->make_request( 'post', 'rates/estimate', $api_args );
    242279
    243280        // Return Early - API Request error - see logs.
     
    284321
    285322    /**
    286      * Create a new Shipment
    287      *
    288      * @param Array $args
    289      *
    290      * @return Array $data
    291      */
    292     public function create_shipments( $args ) {
    293 
    294         $body = $this->make_request( 'post', 'shipments', array( 'shipments' => $args ) );
     323     * Return a single warehouse as a flat array of key value pairs.
     324     *
     325     * @param String $warehouse_id - Shipstation specific reference code.
     326     *
     327     * @return Array|WP_Error
     328     */
     329    public function get_warehouse( $warehouse_id ) {
     330
     331        $warehouses = $this->get_warehouses();
     332        if( is_wp_error( $warehouses ) || empty( $warehouses ) ) {
     333            return $warehouses;
     334        }
     335
     336        return ( isset( $warehouses[ $warehouse_id ] ) ) ? $warehouses[ $warehouse_id ] : array();
     337
     338    }
     339
     340
     341    /**
     342     * Return an array of Warehouses.
     343     *
     344     * @link https://docs.shipstation.com/openapi/warehouses/list_warehouses
     345     *
     346     * @return Array|WP_Error
     347     */
     348    public function get_warehouses() {
     349
     350        $trans_key  = $this->prefix_key( 'warehouses' );
     351        $warehouses = get_transient( $trans_key );
     352
     353        if( empty( $warehouses ) || $this->skip_cache ) {
     354
     355            $body = $this->make_request( 'get', 'warehouses' );
     356
     357            // Return Early - API Request error - see logs.
     358            if( is_wp_error( $body ) ) {
     359                return $body;
     360            }
     361
     362            // Return Early - No Warehouses to work with.
     363            if( empty( $body['warehouses'] ) ) {
     364                return array();
     365            }
     366
     367            // We do need most the Warehouse data, but not all.
     368            $warehouses = array();
     369            foreach( $body['warehouses'] as $warehouse_data ) {
     370
     371                $warehouse = array_intersect_key( $warehouse_data, array_flip( array(
     372                    'warehouse_id',
     373                    'is_default',
     374                    'name',
     375                    'origin_address',
     376                    'return_address',
     377                ) ) );
     378
     379                if( $warehouse['is_default'] ) {
     380                    $warehouse['name'] .= ' (' . esc_html__( 'ShipStation Default', 'live-rates-for-shipstation' ) . ')';
     381                }
     382
     383                $warehouses[ $warehouse['warehouse_id'] ] = $warehouse;
     384
     385            }
     386
     387            // Cache Warehouse data.
     388            if( ! empty( $warehouses ) ) {
     389                set_transient( $trans_key, $warehouses, $this->cache_time );
     390            }
     391        }
     392
     393        return $warehouses;
     394
     395    }
     396
     397
     398    /**
     399     * Return a single package by ID.
     400     *
     401     * @param String $package_id - Shipstation specific reference code.
     402     *
     403     * @return Array|WP_Error
     404     */
     405    public function get_package( $package_id ) {
     406
     407        $packages = $this->get_packages();
     408        if( is_wp_error( $packages ) || empty( $packages ) ) {
     409            return $packages;
     410        }
     411
     412        return ( isset( $packages[ $package_id ] ) ) ? $packages[ $package_id ] : array();
     413
     414    }
     415
     416
     417    /**
     418     * Return an array of Packages
     419     *
     420     * @link https://docs.shipstation.com/openapi/package_types/list_package_types
     421     *
     422     * @return Array|WP_Error
     423     */
     424    public function get_packages() {
     425
     426        $trans_key  = $this->prefix_key( 'packages' );
     427        $packages = get_transient( $trans_key );
     428
     429        if( empty( $packages ) || $this->skip_cache ) {
     430
     431            $body = $this->make_request( 'get', 'packages' );
     432
     433            // Return Early - API Request error - see logs.
     434            if( is_wp_error( $body ) ) {
     435                return $body;
     436            }
     437
     438            // Return Early - No Custom Packages to work with.
     439            if( empty( $body['packages'] ) ) {
     440                return array();
     441            }
     442
     443            // We do need most the Package data, just id, name, dimensions - ezpz.
     444            $packages = array();
     445            foreach( $body['packages'] as $package_data ) {
     446
     447                $package = array_intersect_key( $package_data, array_flip( array(
     448                    'package_id',
     449                    'name',
     450                    'dimensions',
     451                ) ) );
     452
     453                $packages[ $package['package_id'] ] = $package;
     454
     455            }
     456
     457            // Cache Warehouse data.
     458            if( ! empty( $packages ) ) {
     459                set_transient( $trans_key, $packages, $this->cache_time );
     460            }
     461        }
     462
     463        return $packages;
     464
     465    }
     466
     467
     468    /**
     469     * Purchase a shipping label by a carrier.
     470     *
     471     * @link https://docs.shipstation.com/openapi/labels/create_label
     472     *
     473     * @param Array $api_args - See ShipStation API docs for required fields.
     474     * @param Boolean $test_label - Whether or not to create a test label.
     475     *
     476     * @return Array|WP_Error
     477     */
     478    public function purchase_shipping_label( $api_args, $test_label = false ) {
     479
     480        if( $test_label ) $api_args['test_label'] = true;
     481        $body = $this->make_request( 'post', 'labels', $api_args );
    295482
    296483        // Return Early - API Request error - see logs.
     
    299486        }
    300487
    301         /**
    302          * API returns no errors but also doesn't do anything in ShipStation.
    303          */
    304         $data = $body;
     488        $data = array();
    305489
    306490        return $data;
     
    309493
    310494
    311 
    312495    /**
    313496     * Create Shipments from given WC_Orders.
     
    317500     * @return Array|WP_Error
    318501     */
    319     public function create_shipments_from_wc_orders( $wc_orders ) {
     502    public function shipment_args_from_wc_orders( $wc_orders ) {
    320503
    321504        $data = array();
     
    325508
    326509        $shipments = array();
    327         foreach( $wc_orders as $wc_order ) {
     510        foreach( (array)$wc_orders as $wc_order ) {
    328511
    329512            // Skip
     
    337520            $order_items     = $wc_order->get_items();
    338521            $order_item_ship = $wc_order->get_items( 'shipping' );
    339             $order_item_ship = ( ! empty( $order_item_ship ) ) ? $order_item_ship[ array_key_first( $order_item_ship ) ] : null;
    340 
     522            $shipmentItem = ( ! empty( $order_item_ship ) ) ? $order_item_ship[ array_key_first( $order_item_ship ) ] : null;
     523
     524            // Return Early - No shipping item found.
     525            if( null === $shipmentItem ) {
     526                return new \WP_Error( 400, esc_html__( 'No shipping item found in order.', 'live-rates-for-shipstation' ) );
     527            }
     528
     529            // Shipment args I'll eventually need
     530            $others = array(
     531                'hold_until_date'   => 'Y-m-d UTC', // ShipStation will hold the shipment until this date. Maybe leave blank, but could impact preorder items?
     532                'ship_by_date'      => 'Y-m-d UTC', // Purely informational - useful for store owners collecting additional data.
     533                'ship_date'         => 'Y-m-d UTC', // The date the shipment is actually shipped. Defaults to current date.
     534                'requested_shipment_service' => 'carrier_service_code', // ex. ups_ground
     535                'warehouse_id'      => 'se-*', // ShipStation warehouse association: WC_Order Label Override > Shipping Zone > Global. We can skip ship_from if we have this.
     536                'return_to'         => array(), // Return to address info - need settings for this.
     537                'advanced_options'  => array(), // Advanced options - might need settings for this.
     538            );
     539
     540            // Default ShipStation Shipment Array
    341541            $shipment = array(
    342                 'validate_address'  => 'no_validation',
    343                 'carrier_id'        => $order_item_ship->get_meta( '_iqlrss_carrier_id', true ),
    344                 'store_id'          => \IQLRSS\Driver::get_ss_opt( 'store_id' ),
     542                'external_order_id' => $wc_order->get_id(),
     543                'order_source_code'=> 'woocommerce',
     544                'carrier_id'        => $shipmentItem->get_meta( '_iqlrss_carrier_id', true ),
     545                'service_code'      => $shipmentItem->get_meta( '_iqlrss_service_code', true ),
    345546                'shipping_paid'     => array(
    346547                    'currency'  => $wc_order->get_currency(),
     
    377578            );
    378579
    379             $shipment['items'] = array();
     580            // Add Packages
     581            // $packages = $shipmentItem->get_meta( 'boxes', true );
     582            // if( ! empty( $packages ) && is_array( $packages ) ) {
     583            //  foreach( $packages as $package ) {
     584
     585            //      $shipment['packages'][] = array(
     586            //          'package_code'  => $ship_package['packageCode'],
     587            //          'package_name'  => $ship_package['packageName'],
     588            //          'weight'        => array(
     589            //              'value' => $ship_package['weight']['value'],
     590            //              'unit'  => $ship_package['weight']['unit'],
     591            //          ),
     592            //          'dimensions' => array(
     593            //              'length'    => $ship_package['dimensions']['length'],
     594            //              'width'     => $ship_package['dimensions']['width'],
     595            //              'height'    => $ship_package['dimensions']['height'],
     596            //              'unit'      => $ship_package['dimensions']['unit'],
     597            //          ),
     598            //      );
     599
     600            //  }
     601            // }
     602
     603            // Add Order Items
    380604            foreach( $shipstation_order_arr['items'] as $ship_item ) {
    381605
     
    434658        }
    435659
    436         return $this->create_shipments( $shipments );
    437 
    438660    }
    439661
     
    506728        // Return Early - API encountered an error.
    507729        if( is_wp_error( $request ) ) {
    508             return $this->log( $request );
     730            return $this->log( $request, 'error' );
    509731        } else if( 200 != $code || ! is_array( $body ) ) {
    510732
     
    524746            }
    525747
    526             return $this->log( new \WP_Error( $err_code, $err_msg ) );
     748            return $this->log( new \WP_Error( $err_code, $err_msg ), 'error' );
    527749        }
    528750
    529751        // Log API Request Result
    530752        /* translators: %s is the API endpoint (example: carriers/rates). */
    531         $this->log( sprintf( esc_html__( 'ShipStation API Request to %s', 'live-rates-for-shipstation' ), $endpoint ), 'info', array(
     753        $this->log( sprintf( esc_html__( 'ShipStation API Request to %s', 'live-rates-for-shipstation' ), $endpoint ), 'debug', array(
    532754            'args'      => $args,
    533755            'code'      => $code,
     
    575797    }
    576798
    577 
    578     /**
    579      * Log error in WooCommerce
    580      * Passthru method - log what's given and give it back.
    581      *
    582      * @param Mixed $error      - String or WP_Error
    583      * @param String $level     - WooCommerce level (debug|info|notice|warning|error|critical|alert|emergency)
    584      * @param Array $context
    585      *
    586      * @return Mixed - Return the error back.
    587      */
    588     protected function log( $error, $level = 'debug', $context = array() ) {
    589 
    590         if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', 0, true ) ) {
    591             return $error;
    592         }
    593 
    594         if( is_wp_error( $error ) ) {
    595             $error_msg = sprintf( '(%s) %s', $error->get_error_code(), $error->get_error_message() );
    596         } else {
    597             $error_msg = $error;
    598         }
    599 
    600         if( class_exists( '\WC_Logger' ) ) {
    601 
    602             if( null === $this->logger ) {
    603                 $this->logger = \wc_get_logger();
    604             }
    605 
    606             /**
    607              * The WC_Logger does not handle double quotes well.
    608              * This will conver double quotes to faux: " -> ''
    609              */
    610             array_walk_recursive( $context, function( &$val ) {
    611                 $val = ( is_string( $val ) ) ? str_replace( '"', "''", $val ) : $val;
    612             } );
    613 
    614             $this->logger->log( $level, $error_msg, array_merge( $context, array( 'source' => 'live-rates-for-shipstation' ) ) );
    615 
    616         }
    617 
    618         return $error;
    619 
    620     }
    621 
    622799}
  • live-rates-for-shipstation/tags/1.2.0/core/api/shipstationv1.php

    r3407166 r3452263  
    146146        // Return Early - Something went wrong getting carriers.
    147147        } else if( ! isset( $carriers[ $carrier_code ] ) ) {
    148             return $this->log( new \WP_Error( 404, esc_html__( '[v1] Could not find carrier information.', 'live-rates-for-shipstation' ) ) );
     148            return $this->log( new \WP_Error( 404, esc_html__( '[v1] Could not find carrier information.', 'live-rates-for-shipstation' ) ), 'warning' );
    149149        }
    150150
     
    527527        } );
    528528
    529         // Return Early - Skip the log but o
     529        // Return Early - No orders to work with.
    530530        if( empty( $orders ) ) {
    531             return $this->log( new \WP_Error( 400, esc_html__( '[v1] Empty Orders. Data may be missing orderNumber or orderKey.', 'live-rates-for-shipstation' ) ), 'warning', array(
     531            return $this->log( new \WP_Error( 400, esc_html__( '[v1] Empty Orders. Data may be missing orderNumber or orderKey.', 'live-rates-for-shipstation' ) ), 'error', array(
    532532                'orders' => $order_arr,
    533533            ) );
     
    654654        // Log API Request Result
    655655        /* translators: %s is the API endpoint (example: carriers/rates). */
    656         $this->log( sprintf( esc_html__( '[v1] ShipStation API Request to %s', 'live-rates-for-shipstation' ), $endpoint ), 'info', array(
     656        $this->log( sprintf( esc_html__( '[v1] ShipStation API Request to %s', 'live-rates-for-shipstation' ), $endpoint ), 'debug', array(
    657657            'args'      => $args,
    658658            'code'      => $code,
    659             'reponse'   => $body,
     659            'response'  => $body,
    660660        ) );
    661661
     
    692692     */
    693693    public function get_shipping_estimates( $est_opts ) {
    694         return $this->log( new \WP_Error( 400, esc_html__( 'Live Rates for ShipStation v1 API Class does not support this endpoint. Use the v2 API Class: \IQLRSS\Core\Api\Shipstation', 'live-rates-for-shipstation' ) ) );
     694        return $this->log( new \WP_Error( 400, esc_html__( 'Live Rates for ShipStation v1 API Class does not support this endpoint. Use the v2 API Class: \IQLRSS\Core\Api\Shipstation', 'live-rates-for-shipstation' ) ), 'notice' );
    695695    }
    696696
  • live-rates-for-shipstation/tags/1.2.0/core/assets/js/admin.js

    r3407166 r3452263  
    1919    } );
    2020}
     21
     22
     23/**
     24 * Edit Order Module Settings
     25 * @import editOrderSettings
     26 */
     27if( document.querySelector( 'form#order' ) ) {
     28    import( './edit-order/_main.js' ).then( ( Module ) => {
     29        new Module.editOrderSettings();
     30    } );
     31}
  • live-rates-for-shipstation/tags/1.2.0/core/assets/js/shipping-zones/_main.js

    r3407166 r3452263  
    44 * Not really meant to be used as an object but more for
    55 * encapsulation and organization.
     6 *
     7 * Manages the the possible initialization of custom boxes.
     8 * Manages the show/hide functionality of price adjustments.
    69 *
    710 * @global {Object} iqlrss - Localized object of saved values.
  • live-rates-for-shipstation/tags/1.2.0/core/settings-shipstation.php

    r3411187 r3452263  
    167167    public function enqueue_admin_assets() {
    168168
    169         global $wp_scripts;
    170 
    171169        if( ! $this->maybe_enqueue( 'admin' ) ) {
    172170            return;
     
    175173        wp_enqueue_style( \IQLRSS\Driver::plugin_prefix( 'admin', '-' ) );
    176174        wp_enqueue_script_module( \IQLRSS\Driver::plugin_prefix( 'admin', '-' ) );
    177 
    178         // if( current_user_can( 'list_users' ) ) {
    179         //  $foo = wp_script_modules();
    180         //  printf( '<pre>%s</pre>', print_r( $foo, 1 ) );
    181         //  die( 'end' );
    182         // }
    183175
    184176    }
     
    211203
    212204        global $wpdb;
     205
    213206
    214207        /**
     
    297290            '' => esc_html__( 'ShipStation carriers may still be loading...', 'live-rates-for-shipstation' ),
    298291        );
     292        $warehouses = array(
     293            '' => '(' . esc_html__( 'Website Store Address', 'live-rates-for-shipstation' ) . ')',
     294        );
    299295        $appended_fields = array();
    300296
    301297        if( ! empty( \IQLRSS\Driver::get_ss_opt( 'api_key' ) ) ) {
    302298
    303             $carrier_desc = esc_html__( 'Select which ShipStation carriers you would like to see live shipping rates from.', 'live-rates-for-shipstation' );
    304             $response = ( new Api\Shipstation() )->get_carriers();
    305 
     299            $api = new Api\Shipstation();
     300
     301            // Grab Warehouse options
     302            $api_warehouses = $api->get_warehouses();
     303            if( is_a( $api_warehouses, 'WP_Error' ) ) {
     304                $warehouses = array( '' => $api_warehouses->get_error_message() );
     305            } else if( is_array( $api_warehouses ) && ! empty( $api_warehouses ) ) {
     306                $warehouses = array_merge( $warehouses, array_combine(
     307                    array_keys( $api_warehouses ),
     308                    array_column( $api_warehouses, 'name' ),
     309                ) );
     310            }
     311
     312            // Grab Carrier options
    306313            $carriers = array();
    307             if( is_a( $response, 'WP_Error' ) ) {
    308                 $carriers[''] = $response->get_error_message();
    309             } else if( is_array( $response ) ) {
    310                 foreach( $response as $carrier ) {
     314            $api_carriers = $api->get_carriers();
     315            if( is_a( $api_carriers, 'WP_Error' ) ) {
     316                $carriers[''] = $api_carriers->get_error_message();
     317            } else if( is_array( $api_carriers ) && ! empty( $api_carriers ) ) {
     318                foreach( $api_carriers as $carrier ) {
    311319                    $carriers[ $carrier['carrier_id'] ] = $carrier['name'];
    312320                }
    313321            }
    314322
    315         } else {
    316             $carrier_desc = esc_html__( 'Please set and verify your ShipStation API key. Then, click the Save button at the bottom of this page.', 'live-rates-for-shipstation' );
    317323        }
    318324
     
    340346                    'class'         => 'chosen_select',
    341347                    'options'       => $carriers,
    342                     'description'   => $carrier_desc,
     348                    'description'   => ( function() {
     349                        if( ! empty( \IQLRSS\Driver::get_ss_opt( 'api_key' ) ) ) {
     350                            return esc_html__( 'Select which ShipStation carriers you would like to see live shipping rates from.', 'live-rates-for-shipstation' );
     351                        }
     352                        return esc_html__( 'Please set and verify your ShipStation API key. Then, click the Save button at the bottom of this page.', 'live-rates-for-shipstation' );
     353                    } )(),
    343354                    'desc_tip'      => esc_html__( 'Services from selected carriers will be available when setting up Shipping Zones.', 'live-rates-for-shipstation' ),
     355                    'default'       => '',
     356                );
     357
     358                $appended_fields[ \IQLRSS\Driver::plugin_prefix( 'global_warehouse' ) ] = array(
     359                    'title'         => esc_html__( 'Shipping From', 'live-rates-for-shipstation' ),
     360                    'type'          => 'select',
     361                    'options'       => $warehouses,
     362                    'description'   => ( function() {
     363                        if( ! empty( \IQLRSS\Driver::get_ss_opt( 'api_key' ) ) ) {
     364                            return esc_html__( 'Select to ship from a different location than what is set as your WooCommerce website default location.', 'live-rates-for-shipstation' );
     365                        }
     366                        return esc_html__( 'Please set and verify your ShipStation API key. Then, click the Save button at the bottom of this page.', 'live-rates-for-shipstation' );
     367                    } )(),
     368                    'desc_tip'      => esc_html__( 'This can be overridden per Shipping Zone.', 'live-rates-for-shipstation' ),
    344369                    'default'       => '',
    345370                );
     
    503528            $enqueue = ( $enqueue || ( isset( $_GET, $_GET['section'] ) && 'shipstation' == $_GET['section'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    504529
     530            // Edit Order page
     531            $enqueue = ( $enqueue || ( isset( $_GET, $_GET['page'], $_GET['id'] ) && 'wc-orders' == $_GET['page'] && ! empty( $_GET['id'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     532
    505533            // Overprotective WooCommerce settings page check
    506             $enqueue = ( $enqueue && 'woocommerce_page_wc-settings' == $screen_id );
     534            $enqueue = ( $enqueue && in_array( $screen_id, array( 'woocommerce_page_wc-orders', 'woocommerce_page_wc-settings' ) ) );
    507535        }
    508536        return $enqueue;
  • live-rates-for-shipstation/tags/1.2.0/core/shipping-method-shipstation.php

    r3442676 r3452263  
    22/**
    33 * ShipStation Live Shipping Rates Method
    4  *
    5  * @todo Consider moving Shipping Calculations into it's own class.
    64 *
    75 * @link https://www.fedex.com/en-us/shipping/one-rate.html
     
    1614 */
    1715namespace IQLRSS\Core;
     16use \IQLRSS\Core\Traits;
    1817
    1918if( ! defined( 'ABSPATH' ) ) {
     
    2625
    2726    /**
     27     * Inherit logger traits
     28     */
     29    use Traits\Logger;
     30
     31
     32    /**
    2833     * Plugin prefix used to namespace data keys.
    2934     *
     
    3136     */
    3237    protected $plugin_prefix;
    33 
    34 
    35     /**
    36      * Array of store specific settings.
    37      *
    38      * @var Array
    39      */
    40     protected $store_data = array(
    41         'weight_unit'   => '',
    42         'dim_unit'      => '', // Dimension
    43     );
    4438
    4539
     
    9488        }
    9589
    96         // Set the store unit term and associate it with ShipStations term.
    97         $this->store_data = array(
    98             'weight_unit'   => get_option( 'woocommerce_weight_unit', $this->store_data['weight_unit'] ),
    99             'dim_unit'      => get_option( 'woocommerce_dimension_unit', $this->store_data['dim_unit'] ),
    100         );
    101 
    10290        /**
    10391         * Init shipping methods.
     
    189177    /**
    190178     * Increase the HTTP Request Timeout
    191      * Sometimes ShipStation takes awhile to responde with rates.
     179     * Sometimes ShipStation takes awhile to respond with rates.
    192180     * Presumably, the more services enabled, the longer it takes.
    193181     *
     
    409397    protected function init_instance_form_fields() {
    410398
     399        $store_warehouse_label = '(' . esc_html__( 'Website Store Address', 'live-rates-for-shipstation' ) . ')';
     400        $warehouses = array( '' => $store_warehouse_label );
     401
     402        if( ! empty( \IQLRSS\Driver::get_ss_opt( 'api_key' ) ) ) {
     403
     404            $og_warehouse = \IQLRSS\Driver::get_ss_opt( 'global_warehouse' );
     405            $api = new Api\Shipstation();
     406
     407            // Grab Warehouse options
     408            $api_warehouses = $api->get_warehouses();
     409            if( is_a( $api_warehouses, 'WP_Error' ) ) {
     410                $warehouses = array( '' => $api_warehouses->get_error_message() );
     411            } else if( is_array( $api_warehouses ) && ! empty( $api_warehouses ) ) {
     412                $warehouses = array_merge( $warehouses, array_combine(
     413                    array_keys( $api_warehouses ),
     414                    array_column( $api_warehouses, 'name' ),
     415                ) );
     416            }
     417
     418            // Move the global warehouse to the top.
     419            if( ! empty( $og_warehouse ) && isset( $warehouses[ $og_warehouse ] ) ) {
     420                $tmp_houses = array_diff_key( $warehouses, array( '' => '' ) );
     421                $warehouses = array_merge( array(
     422                    ''              => $warehouses[ $og_warehouse ] . ' (' . esc_html__( 'Store Global', 'live-rates-for-shipstation' ) . ')',
     423                    '_woo_default'  => $store_warehouse_label,
     424                ), $tmp_houses );
     425            }
     426        }
     427
    411428        $settings = array(
    412429            'title' => array(
     
    416433                'default'       => esc_html__( 'ShipStation Rates', 'live-rates-for-shipstation' ),
    417434                'desc_tip'      => true,
     435            ),
     436            'warehouse' => array(
     437                'title'         => esc_html__( 'Shipping Location', 'live-rates-for-shipstation' ),
     438                'type'          => 'select',
     439                'options'       => $warehouses,
     440                'description'   => esc_html__( 'Select to ship from a different location than the default.', 'live-rates-for-shipstation' ),
    418441            ),
    419442            'minweight' => array(
     
    453476        /**
    454477         * Allow filtering the Shipping Zone settings
    455          * 
     478         *
    456479         * @hook filter
    457          * 
     480         *
    458481         * @param Array $settings
    459482         * @param \IQLRSS\Core\Shipping_Method_Shipstation $this
    460          * 
     483         *
    461484         * @return Array $settings
    462485         */
     
    481504
    482505        ob_start();
    483             include 'assets/views/customboxes-table.php';
     506            include \IQLRSS\Driver::get_asset_path( 'views/shipping-zone/customboxes-table.php' );
    484507        return ob_get_clean();
    485508
     
    590613
    591614        ob_start();
    592             include 'assets/views/services-table.php';
     615            include \IQLRSS\Driver::get_asset_path( 'views/shipping-zone/services-table.php' );
    593616        return ob_get_clean();
    594617
     
    660683                }
    661684
     685
    662686                /**
    663687                 * We don't want to array_filter() since
     
    703727        }
    704728
    705         $enabled_services = $this->get_enabled_services();
    706         if( empty( $enabled_services ) ) {
    707             $this->log( esc_html__( 'No enabled carrier services found. Please enable carrier services within the shipping zone.', 'live-rates-for-shipstation' ) );
    708             return;
    709         }
    710 
    711         $saved_carriers = array_keys( $enabled_services );
    712         if( ! empty( $saved_carriers ) && ! empty( $this->carriers ) ) {
    713             $saved_carriers = array_values( array_intersect( $saved_carriers, $this->carriers ) );
    714         }
    715 
    716         $global_adjustment      = floatval( \IQLRSS\Driver::get_ss_opt( 'global_adjustment', 0 ) );
    717         $global_adjustment_type = \IQLRSS\Driver::get_ss_opt( 'global_adjustment_type','' );
    718         $global_adjustment_type = ( empty( $global_adjustment_type ) && ! empty( $global_adjustment ) ) ? 'percentage' : $global_adjustment_type;
    719 
    720         $packing_type = $this->get_option( 'packing', 'individual' );
    721         $request = array(
    722             'from_country_code'  => WC()->countries->get_base_country(),
    723             'from_postal_code'   => WC()->countries->get_base_postcode(),
    724             'from_city_locality' => WC()->countries->get_base_city(),
    725             'from_state_province'=> WC()->countries->get_base_state(),
    726 
    727             'to_country_code'   => $packages['destination']['country'],
    728             'to_postal_code'    => $packages['destination']['postcode'],
    729             'to_city_locality'  => $packages['destination']['city'],
    730             'to_state_province' => $packages['destination']['state'],
    731 
    732             'address_residential_indicator' => 'unknown',
    733         );
    734 
    735         $item_requests = array();
    736         $callback = sprintf( 'group_requestsby_%s', str_replace( '-', '_', $packing_type ) );
    737         if( method_exists( $this, $callback ) ) {
    738             $item_requests = call_user_func( array( $this, $callback ), $packages['contents'] );
    739         }
     729        // Grab the calculator to be filtered.
     730        $calculator = new Classes\Shipping_Calculator( $packages, array(
     731            'shipping_method' => $this,
     732        ) );
    740733
    741734
    742735        /**
    743          * Allow filtering the packages before requesting estimates.
    744          *
    745          * The returned array should follow this format:
    746          * Multi-dimensional Array
    747          *
    748          * $item_requests = Array( Array(
    749          * ~ Required Fields:
    750          *      '_name' => '$productID|$productName', - This format makes it easy to show the Shop Manager what's packed into the box.
    751          *      'dimensions' => array(
    752          *          'length => 123,
    753          *          'width' => 123,
    754          *          'height' => 123,
    755          *          'unit' => 'inch', - ShipStation expects a specific string. See \IQLRSS\Core\Api\Shipstation::convert_unit_term( $unit )
    756          *      ),
    757          *      'weight' => array(
    758          *          'value' => 123,
    759          *          'unit' => 'pound',  - ShipStation expects a specific string. See \IQLRSS\Core\Api\Shipstation::convert_unit_term( $unit )
    760          *      ),
    761          *
    762          * ~ Entirely optional, but the system will try to read them if available.
    763          *      'packed' => Array( '$productID|$productName', '$productID|$productName' ),
    764          *      'price'  => 123,
    765          *      'nickname' => 'String' - Displayed to the Shop Owner on the Edit Order page.
    766          *      'box_weight' => 123,
    767          *      'box_max_weight'=> 123,
    768          *      'package_code' => 'ups_ground',
    769          *      'carrier_code' => 'ups', - Carrier Code should match what ShipStation expects. I.E. fedex_walleted. This is to group packages with carriers for discounts.
    770          * ) )
    771          *
     736         * Allow overriding the Shipping Calculator object.
     737         * Must inherit IQLRSS\Core\Classes\Shipping_Calculator
     738         *
    772739         * @hook filter
    773          * 
    774          * @param Array $item_requests - Array of Package dimensions that the API will use to get rates on. Multidimensional Array.
    775          * @param Array $packages - The cart contents. See $packages['contents'] for items. 
     740         *
     741         * @param \IQLRSS\Core\Classes\Shipping_Calculator $calculator
     742         * @param Array $packages - The cart contents. See $packages['contents'] for items.
    776743         * @param \IQLRSS\Core\Shipping_Method_Shipstation $this
    777          * 
     744         *
    778745         * @return Array $settings
    779746         */
    780         $filtered_requests = apply_filters( 'iqlrss/shipping/packages', $item_requests, $packages, $packing_type, $this );
    781 
    782         // IF the hash doesn't match what was given to the filter, note it in the logs so the store owner will know.
    783         $item_req_hash      = ( ! empty( $item_requests ) ) ? md5( maybe_serialize( $item_requests ) ) : '';
    784         $filtered_req_hash  = ( ! empty( $filtered_requests ) ) ? md5( maybe_serialize( $filtered_requests ) ) : '';
    785         if( $item_req_hash !== $filtered_req_hash ) {
    786             $this->log( esc_html__( 'The Shipping packages were modified by a 3rd party using the `iqlrss/shipping/packages` filter hook.', 'live-rates-for-shipstation' ), 'notice' );
    787         }
    788 
    789         /**
    790          * We have to return reates per package.
    791          * The /rates/estimate endpoint requires less info
    792          * and /rates endpoint is way slower.
    793          */
    794         $rates = array();
    795         foreach( $filtered_requests as $item_id => $req ) {
    796 
    797             // Create the API request combining the package (weight, dimensions), general request data, and the carrier info.
    798             $api_request = array_merge(
    799                 $req,       // Package (weight, dimensions)
    800                 $request,   // General info like to/from address
    801                 array(      // Saved carrier ids
    802                     'carrier_ids' => $saved_carriers,
    803                 )
    804             );
    805 
    806             // Ping the ShipStation API to get rates per Carrier.
    807             // Continue - Something went wrong, should be logged on the API side.
    808             $available_rates = $this->shipStationApi->get_shipping_estimates( $api_request );
    809 
    810             if( is_wp_error( $available_rates ) || empty( $available_rates ) ) {
    811                 continue;
     747        $maybe_calc = apply_filters( 'iqlrss/shipping/calculator_object', $calculator, $packages, $this );
     748        if( is_object( $maybe_calc ) && $maybe_calc !== $calculator ) {
     749
     750            // Override calculator object and log it's change.
     751            if( is_subclass_of( $maybe_calc, '\IQLRSS\Core\Classes\Shipping_Calculator' ) ) {
     752
     753                $calculator = $maybe_calc;
     754                $this->log( sprintf( '%s [%s]',
     755                    esc_html__( 'Shipping Calculations Object overridden.', 'live-rates-for-shipstation' ),
     756                    get_class( $maybe_calc )
     757                ), 'notice' );
     758
     759            // Something went wrong, log that too.
     760            } else {
     761
     762                $this->log( sprintf( '%s [%s]',
     763                    esc_html__( 'Shipping Calculations Object override failed. Class may not inherit "\IQLRSS\Core\Classes\Shipping_Calculator".', 'live-rates-for-shipstation' ),
     764                    get_class( $maybe_calc ),
     765                    'warning'
     766                ) );
     767
    812768            }
    813 
    814             // Loop the found rates and setup the WooCommerce rates array for each.
    815             foreach( $available_rates as $shiprate ) {
    816 
    817                 if( ! isset( $enabled_services[ $shiprate['carrier_id'] ][ $shiprate['code'] ] ) ) {
    818                     continue;
    819                 }
    820 
    821                 $ratehash    = md5( sprintf( '%s%s', $shiprate['code'], $shiprate['carrier_id'] ) );
    822                 $service_arr = $enabled_services[ $shiprate['carrier_id'] ][ $shiprate['code'] ];
    823                 $cost        = floatval( $shiprate['cost'] );
    824                 $rate_name   = ( isset( $req['_name'] ) ) ? $req['_name'] : '';
    825                 $rate_name   = ( empty( $rate_name ) && isset( $req['nickname'] ) ) ? $req['nickname'] : $rate_name;
    826                 $ratemeta    = array(
    827                     '_name'=> $rate_name, // Item products(ID|Name) or box nickname.
    828                     'rate' => $cost,
    829                 );
    830 
    831                 // Apply service upcharge
    832                 if( isset( $service_arr['adjustment'] ) ) {
    833 
    834                     /**
    835                      * Adjustment type could be '' to skip global adjustment.
    836                      * Defaults to percentage for v1.03 backwards compatibility.
    837                      */
    838                     $adjustment      = floatval( $service_arr['adjustment'] );
    839                     $adjustment_type = ( isset( $service_arr['adjustment_type'] ) ) ? $service_arr['adjustment_type'] : 'percentage';
    840 
    841                     if( ! empty( $adjustment_type ) && $adjustment > 0 ) {
    842 
    843                         $adjustment_cost = ( 'percentage' == $adjustment_type ) ? ( $cost * ( floatval( $adjustment ) / 100 ) ) : floatval( $adjustment );
    844                         $ratemeta['adjustment'] = array(
    845                             'type' => $adjustment_type,
    846                             'rate' => $adjustment,
    847                             'cost' => $adjustment_cost,
    848                             'global'=> false,
    849                         );
    850                         $cost += $adjustment_cost;
    851 
    852                     }
    853 
    854                 } else if( ! empty( $global_adjustment_type ) && $global_adjustment > 0 ) {
    855 
    856                     $adjustment_cost = ( 'percentage' == $global_adjustment_type ) ? ( $cost * ( floatval( $global_adjustment ) / 100 ) ) : floatval( $global_adjustment );
    857                     $ratemeta['adjustment'] = array(
    858                         'type' => $global_adjustment_type,
    859                         'rate' => $global_adjustment,
    860                         'cost' => $adjustment_cost,
    861                         'global'=> true,
    862                     );
    863                     $cost += $adjustment_cost;
    864 
    865                 }
    866 
    867                 // Loop and add any other shipment amounts.
    868                 if( ! empty( $shiprate['other_costs'] ) ) {
    869 
    870                     $ratemeta['other_costs'] = array();
    871                     foreach( $shiprate['other_costs'] as $slug => $cost_arr ) {
    872 
    873                         if( empty( $cost_arr['amount'] ) ) continue;
    874                         $cost += floatval( $cost_arr['amount'] );
    875                         $ratemeta['other_costs'][ $slug ] = $cost_arr['amount'];
    876 
    877                     }
    878                 }
    879 
    880                 // Maybe a package price
    881                 if( 'wc-box-packer' == $packing_type && isset( $req['price'] ) && ! empty( $req['price'] ) ) {
    882                     $cost += floatval( $req['price'] );
    883                     $ratemeta['other_costs']['box_price'] = $req['price'];
    884                 }
    885 
    886                 // Maybe apply per item.
    887                 if( 'individual' == $packing_type ) {
    888                     $cost *= $packages['contents'][ $item_id ]['quantity'];
    889                     $ratemeta['qty'] = $packages['contents'][ $item_id ]['quantity'];
    890                 }
    891 
    892                 // Set rate or append the estimated item ship cost.
    893                 if( ! isset( $rates[ $ratehash ] ) ) {
    894 
    895                     $rates[ $ratehash ] = array(
    896                         'id'        => $ratehash,
    897                         'label'     => ( ! empty( $service_arr['nickname'] ) ) ? $service_arr['nickname'] : $shiprate['name'],
    898                         'package'   => $packages,
    899                         'cost'      => array( $cost ),
    900                         'meta_data' => array(
    901                             'carrier'   => $shiprate['carrier_name'],
    902                             'service'   => $shiprate['name'],
    903                             'rates'     => array(),
    904                             'boxes'     => array(),
    905 
    906                             // Private metadata fields must be excluded via filter way above.
    907                             "_{$this->plugin_prefix}_carrier_id"    => $shiprate['carrier_id'],
    908                             "_{$this->plugin_prefix}_carrier_code"  => $shiprate['carrier_code'],
    909                             "_{$this->plugin_prefix}_service_code"  => $shiprate['code'],
    910                         ),
    911                     );
    912 
    913                 } else {
    914                     $rates[ $ratehash ]['cost'][] = $cost;
    915                 }
    916 
    917                 // Merge item rates
    918                 $rates[ $ratehash ]['meta_data']['rates'] = array_merge(
    919                     $rates[ $ratehash ]['meta_data']['rates'],
    920                     array( $ratemeta ),
    921                 );
    922 
    923                 // Merge item boxes
    924                 $rates[ $ratehash ]['meta_data']['boxes'] = array_merge(
    925                     $rates[ $ratehash ]['meta_data']['boxes'],
    926                     array( $req ),
    927                 );
    928 
     769        }
     770
     771        // Get and Add Rates - EZPZ
     772        if( $rates = $calculator->get_rates() ) {
     773            foreach( $rates as $rate ) {
     774                $this->add_rate( $rate );
    929775            }
    930 
    931         }
    932 
    933         $single_lowest       = \IQLRSS\Driver::get_ss_opt( 'return_lowest', 'no' );
    934         $single_lowest_label = \IQLRSS\Driver::get_ss_opt( 'return_lowest_label', '' );
    935 
    936         // Add all shipping rates, let the user decide.
    937         if( 'no' == $single_lowest || empty( $single_lowest ) ) {
    938 
    939             foreach( $rates as $rate_arr ) {
    940 
    941                 // If more than 1 rate, add the cheapest.
    942                 if( count( $rate_arr['cost'] ) > 1 ) {
    943                     usort( $rate_arr['cost'], fn( $r1, $r2 ) => ( (float)$r1 < (float)$r2 ) ? -1 : 1 );
    944                     $rate_arr['cost'] = (array)array_shift( $rate_arr['cost'] );
    945                 }
    946 
    947                 // WooCommerce skips serialized data when outputting order item meta, this is a workaround.
    948                 // See hooks above for formatting.
    949                 $rate_arr['meta_data']['rates'] = wp_json_encode( $rate_arr['meta_data']['rates'] );
    950                 $rate_arr['meta_data']['boxes'] = wp_json_encode( $rate_arr['meta_data']['boxes'] );
    951 
    952                 $this->add_rate( $rate_arr );
    953             }
    954 
    955         // Find the single lowest shipping rate
    956         } else if( 'yes' == $single_lowest ) {
    957 
    958             $lowest = 0;
    959             $lowest_service = array_key_first( $rates );
    960             foreach( $rates as $service_id => $rate_arr ) {
    961 
    962                 $total = array_sum( $rate_arr['cost'] );
    963                 if( 0 == $lowest || $total < $lowest ) {
    964                     $lowest = $total;
    965                     $lowest_service = $service_id;
    966                 }
    967             }
    968 
    969             if( ! empty( $single_lowest_label ) ) {
    970                 $rates[ $lowest_service ]['label'] = $single_lowest_label;
    971             }
    972 
    973             // WooCommerce skips serialized data when outputting order item meta, this is a workaround.
    974             // See hooks above for formatting.
    975             $rates[ $lowest_service ]['meta_data']['rates'] = wp_json_encode( $rates[ $lowest_service ]['meta_data']['rates'] );
    976             $rates[ $lowest_service ]['meta_data']['boxes'] = wp_json_encode( $rates[ $lowest_service ]['meta_data']['boxes'] );
    977 
    978             $this->add_rate( $rates[ $lowest_service ] );
    979 
    980776        }
    981777
     
    994790
    995791    /**
    996      * Return an array of API requests which would be for individual products.
    997      *
    998      * @param Array $items
    999      *
    1000      * @return Array $requests
    1001      */
    1002     public function group_requestsby_individual( $items ) {
    1003 
    1004         $item_requests  = array();
    1005         $default_weight = $this->get_option( 'minweight', '' );
    1006 
    1007         foreach( $items as $item_id => $item ) {
    1008 
    1009             // Continue - No shipping needed for product.
    1010             if( ! $item['data']->needs_shipping() ) {
    1011                 continue;
    1012             }
    1013 
    1014             $request = array(
    1015                 '_name' => sprintf( '%s|%s',
    1016                     $item['data']->get_id(),
    1017                     $item['data']->get_name(),
    1018                 ),
    1019                 'weight' => ( ! empty( $item['data']->get_weight() ) ) ? $item['data']->get_weight() : $default_weight,
    1020             );
    1021             $physicals = array_filter( array(
    1022                 'length'    => $item['data']->get_length(),
    1023                 'width'     => $item['data']->get_width(),
    1024                 'height'    => $item['data']->get_height(),
    1025             ) );
    1026 
    1027             // Return Early - Product missing one of the 4 key dimensions.
    1028             if( count( $physicals ) < 3 || empty( $request['weight'] ) ) {
    1029                 $this->log( sprintf(
    1030 
    1031                     /* translators: %1$d is the Product ID. %2$s is the Product Dimensions separated by a comma. */
    1032                     esc_html__( 'Product ID #%1$d missing (%2$s) dimensions. Weight is a minimum requirement. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    1033                     $item['product_id'],
    1034                     implode( ', ', array_diff_key( array(
    1035                         'length'    => 'length',
    1036                         'width'     => 'width',
    1037                         'height'    => 'height',
    1038                         'weight'    => 'weight',
    1039                     ), $physicals + array( 'weight' => $request['weight'] ) ) )
    1040                 ) );
    1041 
    1042                 return array();
    1043             }
    1044 
    1045             // Set rate request dimensions.
    1046             sort( $physicals );
    1047             if( 3 == count( $physicals ) ) {
    1048                 $request['dimensions'] = array(
    1049                     'length'    => round( wc_get_dimension( $physicals[2], $this->store_data['dim_unit'] ), 2 ),
    1050                     'width'     => round( wc_get_dimension( $physicals[1], $this->store_data['dim_unit'] ), 2 ),
    1051                     'height'    => round( wc_get_dimension( $physicals[0], $this->store_data['dim_unit'] ), 2 ),
    1052                     'unit'      => $this->shipStationApi->convert_unit_term( $this->store_data['dim_unit'] ),
    1053                 );
    1054             }
    1055 
    1056             // Set rate request weight.
    1057             if( ! empty( $request['weight'] ) ) {
    1058                 $request['weight'] = array(
    1059                     'value' => (float)round( wc_get_weight( $request['weight'], $this->store_data['weight_unit'] ), 2 ),
    1060                     'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    1061                 );
    1062             }
    1063 
    1064             $item_requests[ $item_id ] = $request;
    1065 
    1066         }
    1067 
    1068         return $item_requests;
    1069 
    1070     }
    1071 
    1072 
    1073     /**
    1074      * One Big Box
    1075      * Group all the products by weight and get rates by total weight.
    1076      *
    1077      * @param Array $items
    1078      *
    1079      * @return Array $requests
    1080      */
    1081     public function group_requestsby_onebox( $items ) {
    1082 
    1083         $default_weight = $this->get_option( 'minweight', 0 );
    1084         $subtype        = $this->get_option( 'packing_sub', 'weightonly' );
    1085         $dimensions = array(
    1086             'running' => array_combine( array( 'length', 'width', 'height', 'weight' ), array_fill( 0, 4, 0 ) ),
    1087             'largest' => array_combine( array( 'length', 'width', 'height', 'weight' ), array_fill( 0, 4, 0 ) ),
    1088         );
    1089 
    1090         foreach( $items as $item_id => $item ) {
    1091 
    1092             // Continue - No shipping needed for product.
    1093             if( ! $item['data']->needs_shipping() ) {
    1094                 continue;
    1095             }
    1096 
    1097             $request = array(
    1098                 '_name' => sprintf( '%s|%s',
    1099                     $item['data']->get_id(),
    1100                     $item['data']->get_name(),
    1101                 ),
    1102                 'weight' => ( ! empty( $item['data']->get_weight() ) ) ? $item['data']->get_weight() : $default_weight,
    1103             );
    1104 
    1105             // Return Early - Missing minimum requirement: weight.
    1106             if( empty( $request['weight'] ) ) {
    1107 
    1108                 $this->log( sprintf(
    1109 
    1110                     /* translators: %1$d is the Product ID. */
    1111                     esc_html__( 'Product ID #%1$d missing weight. Shipping Zone weight fallback could not be used. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    1112                     $item['product_id']
    1113                 ) );
    1114 
    1115                 return array();
    1116 
    1117             }
    1118 
    1119             $dimensions['running']['weight'] = $dimensions['running']['weight'] + ( floatval( $request['weight'] ) * $item['quantity'] );
    1120             $dimensions['running']['height'] = $dimensions['running']['height'] + ( floatval( $item['data']->get_height() ) * $item['quantity'] );
    1121             $dimensions['largest'] = array(
    1122                 'length'    => ( $dimensions['largest']['length'] < $item['data']->get_length() ) ? $item['data']->get_length() : $dimensions['largest']['length'],
    1123                 'width'     => ( $dimensions['largest']['width'] < $item['data']->get_width() )   ? $item['data']->get_width()  : $dimensions['largest']['width'],
    1124                 'height'    => ( $dimensions['largest']['height'] < $item['data']->get_height() ) ? $item['data']->get_height() : $dimensions['largest']['height'],
    1125                 'weight'    => ( $dimensions['largest']['weight'] < $request['weight'] )          ? $request['weight']          : $dimensions['largest']['weight'],
    1126             );
    1127 
    1128         }
    1129 
    1130         // Return Early - Rates by total weight.
    1131         if( 'weightonly' == $subtype ) {
    1132 
    1133             return array( array(
    1134                 'weight' => array(
    1135                     'value' => (float)round( wc_get_weight( $dimensions['running']['weight'], $this->store_data['weight_unit'] ), 2 ),
    1136                     'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    1137                 ),
    1138             ) );
    1139 
    1140         }
    1141 
    1142         $physicals = array_filter( array(
    1143             'length'    => $dimensions['largest']['length'],
    1144             'width'     => $dimensions['largest']['width'],
    1145             'height'    => $dimensions['running']['height'],
    1146             'weight'    => $dimensions['running']['weight'],
    1147         ) );
    1148 
    1149         // Return Early - Error - Missing dimensions to work with.
    1150         if( $physicals < 4 ) {
    1151 
    1152             $this->log( sprintf(
    1153 
    1154                 /* translators: %1$d is the Product ID. %2$s is the Product Dimensions separated by a comma. */
    1155                 esc_html__( 'OneBox rate requestion missing dimensions (%1$s). Weight is a minimum requirement. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    1156                 implode( ', ', array_diff_key( array(
    1157                     'length'    => 'length',
    1158                     'width'     => 'width',
    1159                     'height'    => 'height',
    1160                     'weight'    => 'weight',
    1161                 ), $physicals ) )
    1162             ) );
    1163 
    1164             return array();
    1165 
    1166         }
    1167 
    1168         // Default - Stacked Verticially
    1169         return array( array(
    1170             'weight' => array(
    1171                 'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    1172                 'value' => (float)round( wc_get_weight( $physicals['weight'], $this->store_data['weight_unit'] ), 2 ),
    1173             ),
    1174             'dimensions' => array(
    1175                 'unit'      => $this->shipStationApi->convert_unit_term( $this->store_data['dim_unit'] ),
    1176 
    1177                 // Largest
    1178                 'length'    => round( wc_get_dimension( $physicals['length'], $this->store_data['dim_unit'] ), 2 ),
    1179                 'width'     => round( wc_get_dimension( $physicals['width'], $this->store_data['dim_unit'] ), 2 ),
    1180 
    1181                 // Running
    1182                 'height'    => round( wc_get_dimension( $physicals['height'], $this->store_data['dim_unit'] ), 2 ),
    1183             ),
    1184         ) );
    1185 
    1186     }
    1187 
    1188 
    1189     /**
    1190      * Return an array of API requests for custom packed boxes.
    1191      * Shoutout to Mike Jolly & Co.
    1192      *
    1193      * @param Array $items
    1194      *
    1195      * @return Array $requests
    1196      */
    1197     public function group_requestsby_wc_box_packer( $items ) {
    1198 
    1199         $item_requests  = array();
    1200         $boxes          = $this->get_option( 'customboxes', array() );
    1201         $default_weight = $this->get_option( 'minweight', '' );
    1202 
    1203         /* Return Early - No custom boxes found. */
    1204         if( empty( $boxes ) ) {
    1205             $this->log( esc_html__( 'Custom Boxes selected, but no boxes found. Items packed individually', 'live-rates-for-shipstation' ), 'warning' );
    1206             return $this->group_requestsby_individual( $items );
    1207         }
    1208 
    1209         if( ! class_exists( '\IQRLSS\WC_Box_Packer\WC_Boxpack' ) ) {
    1210             include_once 'wc-box-packer/class-wc-boxpack.php';
    1211         }
    1212 
    1213         // Setup the WC_Boxpack boxes based on user submitted custom boxes.
    1214         $wc_boxpack = new WC_Box_Packer\WC_Boxpack();
    1215         foreach( $boxes as $box ) {
    1216             if( empty( $box['active'] ) ) continue;
    1217             $wc_boxpack->add_box( $box );
    1218         }
    1219 
    1220         // Loop the items, grabs their dimensions, and assocaite them with WC_Boxpack for future packing.
    1221         foreach( $items as $item_id => $item ) {
    1222             if( ! $item['data']->needs_shipping() ) continue;
    1223 
    1224             $weight = ( ! empty( $item['data']->get_weight() ) ) ? $item['data']->get_weight() : $default_weight;
    1225             $data   = array(
    1226                 'weight' => (float)round( wc_get_weight( $weight, $this->store_data['weight_unit'] ), 2 ),
    1227             );
    1228             $physicals = array_filter( array(
    1229                 'length'    => $item['data']->get_length(),
    1230                 'width'     => $item['data']->get_width(),
    1231                 'height'    => $item['data']->get_height(),
    1232             ) );
    1233 
    1234             // Return Early - Product missing one of the 4 key dimensions.
    1235             if( count( $physicals ) < 3 && empty( $data['weight'] ) ) {
    1236                 $this->log( sprintf(
    1237 
    1238                     /* translators: %1$d is the Product ID. %2$s is the Product Dimensions separated by a comma. */
    1239                     esc_html__( 'Product ID #%1$d missing (%2$s) dimensions and no weight found. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    1240                     $item['product_id'],
    1241                     implode( ', ', array_diff_key( array(
    1242                         'width'     => 'width',
    1243                         'height'    => 'height',
    1244                         'length'    => 'length',
    1245                     ), $physicals ) )
    1246                 ) );
    1247                 return array();
    1248             }
    1249 
    1250             sort( $physicals );
    1251             $data = array(
    1252                 'length'    => round( wc_get_dimension( $physicals[2], $this->store_data['dim_unit'] ), 2 ),
    1253                 'width'     => round( wc_get_dimension( $physicals[1], $this->store_data['dim_unit'] ), 2 ),
    1254                 'height'    => round( wc_get_dimension( $physicals[0], $this->store_data['dim_unit'] ), 2 ),
    1255                 'weight'    => round( wc_get_weight( $data['weight'], $this->store_data['weight_unit'] ), 2 ),
    1256             );
    1257 
    1258             // Pack Products
    1259             for( $i = 0; $i < $item['quantity']; $i++ ) {
    1260                 $wc_boxpack->add_item(
    1261                     $data['length'],
    1262                     $data['width'],
    1263                     $data['height'],
    1264                     $data['weight'],
    1265                     $item['data']->get_price(),
    1266                     array(
    1267                         '_name' => sprintf( '%s|%s',
    1268                             $item['data']->get_id(),
    1269                             $item['data']->get_name(),
    1270                         ),
    1271                     ),
    1272                 );
    1273             }
    1274         }
    1275 
    1276         // Pack it up, missions over.
    1277         $wc_boxpack->pack();
    1278         $wc_box_packages = $wc_boxpack->get_packages();
    1279         $box_log = array();
    1280 
    1281         // Delivery!
    1282         foreach( $wc_box_packages as $key => $package ) {
    1283 
    1284             $packed_items = ( is_array( $package->packed ) ) ? array_map( function( $item ) { return $item->meta['_name']; }, $package->packed ) : array();
    1285             $item_requests[] = array(
    1286                 'weight' => array(
    1287                     'value' => round( $package->weight, 2 ),
    1288                     'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    1289                 ),
    1290                 'dimensions' => array(
    1291                     'length'    => round( $package->length, 2 ),
    1292                     'width'     => round( $package->width, 2 ),
    1293                     'height'    => round( $package->height, 2 ),
    1294                     'unit'      => $this->shipStationApi->convert_unit_term( $this->store_data['dim_unit'] ),
    1295                 ),
    1296                 'packed' => $packed_items,
    1297                 'price'  => ( ! empty( $package->data ) ) ? $package->data['price'] : 0,
    1298                 'nickname'      => ( ! empty( $package->data ) ) ? $package->data['nickname'] : '',
    1299                 'box_weight'    => ( ! empty( $package->data ) ) ? $package->data['weight'] : 0,
    1300                 'box_max_weight'=> ( ! empty( $package->data ) ) ? $package->data['weight_max'] : 0,
    1301                 'package_code'  => ( ! empty( $package->data ) ) ? $package->data['preset'] : '',
    1302                 'carrier_code'  => ( ! empty( $package->data ) ) ? $package->data['carrier_code'] : '',
    1303             );
    1304 
    1305             $box_log[] = array(
    1306                 'is_packed'      => boolval( empty( $package->unpacked ) ),
    1307                 'item_count'     => count( $package->packed ),
    1308                 'items'          => $packed_items,
    1309                 'box_dimensions' => sprintf( '%s x %s x %s | %s | %s', $package->length, $package->width, $package->height, $package->weight, $package->volume ),
    1310                 'box_dim_key'    => sprintf( '%s x %s x %s | %s | %s',
    1311                     esc_html__( 'Length', 'live-rates-for-shipstation' ),
    1312                     esc_html__( 'Width', 'live-rates-for-shipstation' ),
    1313                     esc_html__( 'Height', 'live-rates-for-shipstation' ),
    1314                     esc_html__( 'Weight', 'live-rates-for-shipstation' ),
    1315                     esc_html__( 'Volume', 'live-rates-for-shipstation' ),
    1316                 ),
    1317                 'max_volume' => floatval( $package->width * $package->height * $package->length ),
    1318                 'data' => ( ! empty( $package->data ) ) ? $package->data : array(),
    1319             );
    1320 
    1321         }
    1322 
    1323         if( ! empty( $box_log ) ) {
    1324             $this->log( esc_html__( 'Custom Boxes Packed', 'live-rates-for-shipstation' ), 'info', $box_log );
    1325         }
    1326 
    1327         return $item_requests;
    1328 
    1329     }
    1330 
    1331 
    1332     /**
    1333792     * Set the rates based on cached packages.
    1334793     *
     
    1343802    protected function check_packages_rate_cache( $packages ) {
    1344803
     804
     805        /**
     806         * Maybe skip cart caches.
     807         * Do note that WooCommerce makes multiple calls to the cart / calculations.
     808         * Disabling this may result in many more API calls than expected.
     809         *
     810         * @hook filter
     811         *
     812         * @param Bolean TRUE
     813         *
     814         * @return Boolean
     815         */
     816        // Return Early - Filter Skips Cache.
     817        if( true !== apply_filters( 'iqlrss/cache/cart_rates', true ) ) return;
     818
    1345819        $session    = WC()->session->get( $this->plugin_prefix . '_packages', array() );
    1346820        $cleartime  = get_transient( \IQLRSS\Driver::plugin_prefix( 'wcs_timeout' ) );
     
    1359833        $size = count( $packages );
    1360834        for( $i = 0; $i < $size; $i++ ) {
    1361 
    1362835            $cache = WC()->session->get( 'shipping_for_package_' . $i, false );
    1363             if( empty( $cache ) || ! is_array( $cache ) ) {
    1364                 break;
    1365             }
     836            if( empty( $cache ) || ! is_array( $cache ) ) break;
    1366837            $this->rates = array_merge( $cache['rates'], $this->rates );
    1367 
    1368838        }
    1369839
     
    1469939
    1470940    /**
    1471      * Return an m-array of enabled services grouped by carrier key.
    1472      *
    1473      * @return Array
    1474      */
    1475     public function get_enabled_services() {
    1476 
    1477         $enabled = array();
    1478         $saved_services = $this->get_option( 'services', array() );
    1479         if( empty( $saved_services ) ) return $enabled;
    1480 
    1481         foreach( $saved_services as $c => $sa ) {
    1482             foreach( $sa as $sk => $s ) {
    1483                 if( ! isset( $s['enabled'] ) || ! $s['enabled'] ) continue;
    1484                 $enabled[ $c ][ $sk ] = $s;
    1485             }
    1486         }
    1487 
    1488         return $enabled;
    1489 
    1490     }
    1491 
    1492 
    1493     /**
    1494941     * Convert a WooCommerce unit to a ShipStation unit.
    1495      * 
     942     *
    1496943     * @param String $unit
    1497      * 
     944     *
    1498945     * @return String $new_unit
    1499946     */
     
    1515962        }
    1516963
    1517         $global_carriers= $this->shipStationApi->get_carriers();
    1518         $carrier_codes  = wp_list_pluck( $global_carriers, 'carrier_code' );
    1519         $carrier_codes  = array_intersect_key( $carrier_codes, array_flip( $this->carriers ) );
     964        $global_carriers  = $this->shipStationApi->get_carriers();
     965        $carrier_codes    = wp_list_pluck( $global_carriers, 'carrier_code' );
     966        $carrier_codes    = array_intersect_key( $carrier_codes, array_flip( $this->carriers ) );
     967        $carrier_packages = array();
    1520968
    1521969        $data = array(
     
    1534982        );
    1535983
     984        // Append ShipStation Packages
     985        $sspackages = $this->shipStationApi->get_packages();
     986        if( ! is_wp_error( $sspackages ) && ! empty( $sspackages ) ) {
     987
     988            $carrier_packages['shipstation'] = array(
     989                'label'     => esc_html__( 'ShipStation' ),
     990                'packages'  => array(),
     991            );
     992
     993            foreach( $sspackages as $package ) {
     994                $carrier_packages['shipstation']['packages'][] = array(
     995                    'label'         => $package['name'],
     996                    'code'          => $package['package_id'],
     997                    'length'        => $package['dimensions']['length'],
     998                    'width'         => $package['dimensions']['width'],
     999                    'height'        => $package['dimensions']['height'],
     1000                    'weight_max'    => '',
     1001                    'carrier_code'  => '',
     1002                );
     1003            }
     1004        }
     1005
    15361006        // Append Translated Labels
    1537         $carrier_packages = array();
    15381007        foreach( $data as $carrier_code => &$carriers ) {
    15391008
     
    16401109    }
    16411110
    1642 
    1643     /**
    1644      * Log error in WooCommerce
    1645      * Passthru method - log what's given and give it back.
    1646      * Could make a good Trait
    1647      *
    1648      * @param Mixed $error      - String or WP_Error
    1649      * @param String $level     - WooCommerce level (debug|info|notice|warning|error|critical|alert|emergency)
    1650      * @param Array $context
    1651      *
    1652      * @return Mixed - Return the error back.
    1653      */
    1654     protected function log( $error, $level = 'debug', $context = array() ) {
    1655 
    1656         if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', 0, true ) ) {
    1657             return $error;
    1658         }
    1659 
    1660         if( is_wp_error( $error ) ) {
    1661             $error_msg = sprintf( '(%s) %s', $error->get_error_code(), $error->get_error_message() );
    1662         } else {
    1663             $error_msg = $error;
    1664         }
    1665 
    1666         if( class_exists( '\WC_Logger' ) ) {
    1667 
    1668             if( null === $this->logger ) {
    1669                 $this->logger = \wc_get_logger();
    1670             }
    1671 
    1672             $this->logger->log( $level, $error_msg, array_merge( $context, array( 'source' => 'live-rates-for-shipstation' ) ) );
    1673 
    1674         }
    1675 
    1676         return $error;
    1677 
    1678     }
    1679 
    16801111}
  • live-rates-for-shipstation/tags/1.2.0/core/traits/logger.php

    r3442676 r3452263  
    2222     * @return Mixed - Return the error back.
    2323     */
    24     protected function log( $error, $level = 'debug', $context = array() ) {
     24    protected function log( $error, $level = 'info', $context = array() ) {
    2525
    2626        if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', 0, true ) ) {
     
    3737        if( class_exists( '\WC_Logger' ) ) {
    3838
     39            /**
     40             * The WC_Logger does not handle double quotes well.
     41             * This will convert double quotes to faux: " -> ''
     42             */
     43            array_walk_recursive( $context, function( &$val ) {
     44                $val = ( is_string( $val ) ) ? str_replace( '"', "''", $val ) : $val;
     45            } );
     46
    3947            $logger = \wc_get_logger();
    4048            $logger->log( $level, $error_msg, array_merge( $context, array( 'source' => 'live-rates-for-shipstation' ) ) );
  • live-rates-for-shipstation/tags/1.2.0/live-rates-for-shipstation.php

    r3442676 r3452263  
    44 * Plugin URI: https://iqcomputing.com/contact/
    55 * Description: ShipStation shipping method with live rates.
    6  * Version: 1.1.2
    7  * Requries at least: 6.2
     6 * Version: 1.2.0
     7 * Requires at least: 6.2
    88 * Author: IQComputing
    99 * Author URI: https://iqcomputing.com/
     
    2626     * @var String
    2727     */
    28     protected static $version = '1.1.2';
     28    protected static $version = '1.2.0';
    2929
    3030
     
    229229        Core\Rest_Router::initialize();
    230230        Core\Settings_Shipstation::initialize();
     231        Core\Admin_Edit_Order::initialize();
     232
    231233    }
    232234
     
    235237
    236238/**
    237  * Class Autoloader
    238  *
    239  * @param String $class
     239 * Autoload and Drive!
    240240 */
    241 spl_autoload_register( function( $class ) {
    242 
    243     if( false === strpos( $class, __NAMESPACE__ . '\\' ) ) {
    244         return $class;
    245     }
    246 
    247     $class_path = str_replace( __NAMESPACE__ . '\\', '', $class );
    248     $class_path = str_replace( '_', '-', strtolower( $class_path ) );
    249     $class_path = str_replace( '\\', '/', $class_path );
    250     $file_path  = wp_normalize_path( sprintf( '%s/%s',
    251         rtrim( plugin_dir_path( __FILE__ ), '\\/' ),
    252         $class_path . '.php'
    253     ) );
    254 
    255     if( file_exists( $file_path ) ) {
    256         require_once $file_path;
    257     }
    258 
    259 } );
    260241require_once rtrim( __DIR__, '\\/' ) . '/_autoload.php';
    261242add_action( 'plugins_loaded', array( '\IQLRSS\Driver', 'drive' ), 8 );
  • live-rates-for-shipstation/tags/1.2.0/readme.txt

    r3442676 r3452263  
    44Requires at least: 5.9
    55Tested up to: 6.8
    6 Stable tag: 1.1.2
     6Stable tag: 1.2.0
    77License: GPLv3 or later
    88License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    5151== Changelog ==
    5252
     53= 1.2.0 (2026-02-02) =
     54* Adds Warehouse Support (Global and Zone based).
     55* Adds ShipStation Packages into Custom Packages on a Shipping Zone.
     56* Moves shipping calculations into a dedicated class with filer hook to override.
     57* New `iqlrss/cache/shipstation` filter hook.
     58* New `iqlrss/cache/shipstation_expires` filter hook.
     59* New `iqlrss/cache/cart_rates` filter hook.
     60* New `iqlrss/shipping/calculator_object` filter hook.
     61
    5362= 1.1.2 (2026-01-19) =
    5463* Patched an issue where rate caching would not account for a destination change.
     
    6069= 1.1.1 (2025-12-04) =
    6170* Fixed JS conflict with WordPress 6.9 (nice!)
    62 
    63 = 1.1.0 (2025-12-01) =
    64 * Redux the Custom Packaging screen and options.
    65 * Packing option for Weight Only.
    66 * Packing option for Stacked Vertically.
    67 * Packing option for default product weight.
    68 * Custom Package Presets from UPS, FedEx, and USPS.
    69 * New filter hook for Shipping Zone Settings `iqlrss/zone/settings`. Useful for managing Product Packing options.
    70 * New filter hook for Shipping Zone Settings `iqlrss/zone/package_presets`. Useful for managing Custom Package presets.
    71 * New filter hook for Shipping Estimates `iqlrss/shipping/packages`. Useful for modifying what gets sent to ShipStation API for retrieving shipping estimates.
  • live-rates-for-shipstation/trunk/README.md

    r3407166 r3452263  
    4242
    4343This is a free plugin entirely volunteer run. While we will ensure that the plugin is up-to-date for any security issues or breakfixes, we cannot commit to supporting any customizations to the plugin or feature requests, but welcome these questions and requests through the [WordPress Support Forums](https://wordpress.org/support/plugin/live-rates-for-shipstation/) for our team to consider in a future release of the plugin. We recommend using the WordPress Forums for all inquiries.
     44
     45## Filter Hooks
     46
     47- `iqlrss/cache/cart_rates`
     48- `iqlrss/cache/shipstation`
     49- `iqlrss/cache/shipstation_expires`
     50- `iqlrss/shipping/calculator_object`
     51- `iqlrss/shipping/packages`
     52- `iqlrss/zone/package_presets`
     53- `iqlrss/zone/settings`
  • live-rates-for-shipstation/trunk/_autoload.php

    r3442676 r3452263  
    2121    ) );
    2222
     23    /* Vroom! */
     24    if( 'driver' === $class_path ) {
     25        $file_path  = wp_normalize_path( sprintf( '%s/%s',
     26            rtrim( plugin_dir_path( __FILE__ ), '\\/' ),
     27            'live-rates-for-shipstation.php'
     28        ) );
     29    }
     30
    2331    if( file_exists( $file_path ) ) {
    2432        require_once $file_path;
  • live-rates-for-shipstation/trunk/_stallation.php

    r3407166 r3452263  
    2020
    2121    /**
    22      * Unintsall Plugin
     22     * Uninstall Plugin
    2323     */
    2424    public static function uninstall() {
  • live-rates-for-shipstation/trunk/changelog.txt

    r3442676 r3452263  
    22
    33This is a brief text document keeping track of changes to the plugin. For a full history, see the Github Repository.
     4
     5= 1.2.0 =
     6
     7Relase Date: Eventually.
     8
     9* Overview
     10    * Shipping Calculations moved to it's own Class for better PHPUnit testing.
     11        * See unit-tests branch; work-in-progress.
     12    * ShipStation Warehouses are now supported Globally and per Shipping Zone.
     13        * Defaults to WooCommerce Store. Global overrides Store. Zone overrides Global.
     14    * ShipStation Packages are now integrated into Custom Packages.
     15    * Multiple new filter hooks added to manage caching, warehouses, and Shipping Calculator object.
     16
     17* Code Updates
     18    * Filter Hook `iqlrss/cache/shipstation`
     19        * Boolean to disable caching for API requests.
     20    * Filter Hook `iqlrss/cache/shipstation_expires`
     21        * Integer seconds for how long to cache package rates for a customer.
     22        * 1 Week is the default.
     23    * Filter Hook `iqlrss/cache/cart_rates`
     24        * Boolean to diable caching at a cart level.
     25        * This is not recommended to disable since WC (sometimes) makes multiple calls to the cart per page load, but the option is available if you have a usecase.
     26    * Fitler Hook `iqlrss/shipping/calculator_object`
     27        * Expects a \IQLRSS\Core\Classes\Shipping_Calculator Object.
     28        * Must inherit the IQLRSS Calculator to be valid.
     29        * The shipping method calculations had an overhaul and broken into multiple methods of the new Shipping Calculator object.
     30        * This is for PHPUnit testing and to make it easier for developers to override specific calculator functionality for their usecases.
    431
    532= 1.1.2 =
     
    1542= 1.1.1 =
    1643
    17 Relase Date: December 04, 2025
     44Release Date: December 04, 2025
    1845
    1946* Overview
     
    2350= 1.1.0 =
    2451
    25 Relase Date: December 01, 2025
     52Release Date: December 01, 2025
    2653
    2754* Overview
     
    5885= 1.0.8 =
    5986
    60 Relase Date: October 10, 2025
     87Release Date: October 10, 2025
    6188
    6289* Overview
     
    74101        * Ugh, the Block Editor + WooCommerce makes _multiple_ async requests to shipping calculations which would re-trigger things unnecessarily.
    75102        * The new methodology uses the WC()->session to automatically return the known rates if the cart has not changed.
    76         * This is a noticable increase in speed when browsing the shop when your cart hasn't necessarily changed.
    77     * Caching layer also prevents multiple / duplciate API requests being logged which is nice.
     103        * This is a noticeable increase in speed when browsing the shop when your cart hasn't necessarily changed.
     104    * Caching layer also prevents multiple / duplicate API requests being logged which is nice.
    78105
    79106* Code Updates
     
    83110= 1.0.7 =
    84111
    85 Relase Date: October 08, 2025
     112Release Date: October 08, 2025
    86113
    87114* Overview
     
    89116        * This denotes what items got what rates, the adjustments, where the adjustments come from.
    90117    * Patches issue on Shipping Zone where WP_Error was treated as an Exception
    91     * Starts to noramlize code for carrier_id and carrier_code.
     118    * Starts to normalize code for carrier_id and carrier_code.
    92119        * Doing this makes it easier to integrate the v1 API.
    93120        * The Carrier ID is the ShipStation `se-` code.
     
    102129= 1.0.6 =
    103130
    104 Relase Date: September 22, 2025
     131Release Date: September 22, 2025
    105132
    106133* Overview
     
    109136= 1.0.5 =
    110137
    111 Relase Date: September 16, 2025
     138Release Date: September 16, 2025
    112139
    113140* Overview
     
    135162= 1.0.4 =
    136163
    137 Relase Date: September 15, 2025
     164Release Date: September 15, 2025
    138165
    139166* Overview
     
    153180= 1.0.3 =
    154181
    155 Relase Date: August 05, 2025
     182Release Date: August 05, 2025
    156183
    157184* Overview
     
    165192= 1.0.2 =
    166193
    167 Relase Date: August 04, 2025
     194Release Date: August 04, 2025
    168195
    169196* Overview
     
    177204= 1.0.1 =
    178205
    179 Relase Date: August 01, 2025
     206Release Date: August 01, 2025
    180207
    181208* Overview
  • live-rates-for-shipstation/trunk/core/api/shipstation.php

    r3407166 r3452263  
    22/**
    33 * ShipStation API Helper
     4 *
     5 * @link https://docs.shipstation.com/openapi
    46 *
    57 * Carrier ID   : se-*
     
    1012 */
    1113namespace IQLRSS\Core\Api;
     14use \IQLRSS\Core\Traits;
    1215
    1316if( ! defined( 'ABSPATH' ) ) {
     
    1821
    1922    /**
     23     * Inherit logger traits
     24     */
     25    use Traits\Logger;
     26
     27
     28    /**
    2029     * Skip cache check
    2130     *
     
    6776        $this->prefix   = \IQLRSS\Driver::get( 'slug' );
    6877        $this->key      = \IQLRSS\Driver::get_ss_opt( 'api_key', '' );
    69         $this->skip_cache = (boolean)$skip_cache;
    70         $this->cache_time = defined( 'WEEK_IN_SECONDS' ) ? WEEK_IN_SECONDS : 604800;
     78
     79
     80        /**
     81         * Skip caching for the API.
     82         *
     83         * @hook filter
     84         *
     85         * @param Bolean FALSE
     86         *
     87         * @return Boolean
     88         */
     89        $this->skip_cache = (boolean)apply_filters( 'iqlrss/cache/shipstation', $skip_cache, $this );
     90
     91
     92        /**
     93         * Allow filtering the cache time.
     94         *
     95         * @see https://codex.wordpress.org/Easier_Expression_of_Time_Constants
     96         *
     97         * @hook filter
     98         *
     99         * @param Integer $cache_time - Week in seconds.
     100         *
     101         * @return Boolean
     102         */
     103        $cache_time = apply_filters( 'iqlrss/cache/shipstation_expires', $this->cache_time, $this );
     104        $this->cache_time = ( is_numeric( $cache_time ) ) ? absint( $cache_time ) : $this->cache_time;
    71105
    72106    }
     
    101135        // Return Early - Something went wrong getting carriers.
    102136        } else if( ! isset( $carriers[ $carrier_code ] ) ) {
    103             return $this->log( new \WP_Error( 404, esc_html__( 'Could not find carrier information.', 'live-rates-for-shipstation' ) ) );
     137            return $this->log( new \WP_Error( 404, esc_html__( 'Could not find carrier information.', 'live-rates-for-shipstation' ) ), 'warning' );
    104138        }
    105139
     
    233267     * @todo Look into `delivery_days` field. UPS has, is it carrier consistent?
    234268     *
    235      * @param Array $est_opts
    236      *
    237      * @return Array|WP_Error
    238      */
    239     public function get_shipping_estimates( $est_opts ) {
    240 
    241         $body = $this->make_request( 'post', 'rates/estimate', $est_opts );
     269     * @link https://docs.shipstation.com/openapi/rates/calculate_rates
     270     * @link https://docs.shipstation.com/openapi/rates/estimate_rates
     271     *
     272     * @param Array $api_args - See ShipStation API docs for required fields.
     273     *
     274     * @return Array|WP_Error
     275     */
     276    public function get_shipping_estimates( $api_args ) {
     277
     278        $body = $this->make_request( 'post', 'rates/estimate', $api_args );
    242279
    243280        // Return Early - API Request error - see logs.
     
    284321
    285322    /**
    286      * Create a new Shipment
    287      *
    288      * @param Array $args
    289      *
    290      * @return Array $data
    291      */
    292     public function create_shipments( $args ) {
    293 
    294         $body = $this->make_request( 'post', 'shipments', array( 'shipments' => $args ) );
     323     * Return a single warehouse as a flat array of key value pairs.
     324     *
     325     * @param String $warehouse_id - Shipstation specific reference code.
     326     *
     327     * @return Array|WP_Error
     328     */
     329    public function get_warehouse( $warehouse_id ) {
     330
     331        $warehouses = $this->get_warehouses();
     332        if( is_wp_error( $warehouses ) || empty( $warehouses ) ) {
     333            return $warehouses;
     334        }
     335
     336        return ( isset( $warehouses[ $warehouse_id ] ) ) ? $warehouses[ $warehouse_id ] : array();
     337
     338    }
     339
     340
     341    /**
     342     * Return an array of Warehouses.
     343     *
     344     * @link https://docs.shipstation.com/openapi/warehouses/list_warehouses
     345     *
     346     * @return Array|WP_Error
     347     */
     348    public function get_warehouses() {
     349
     350        $trans_key  = $this->prefix_key( 'warehouses' );
     351        $warehouses = get_transient( $trans_key );
     352
     353        if( empty( $warehouses ) || $this->skip_cache ) {
     354
     355            $body = $this->make_request( 'get', 'warehouses' );
     356
     357            // Return Early - API Request error - see logs.
     358            if( is_wp_error( $body ) ) {
     359                return $body;
     360            }
     361
     362            // Return Early - No Warehouses to work with.
     363            if( empty( $body['warehouses'] ) ) {
     364                return array();
     365            }
     366
     367            // We do need most the Warehouse data, but not all.
     368            $warehouses = array();
     369            foreach( $body['warehouses'] as $warehouse_data ) {
     370
     371                $warehouse = array_intersect_key( $warehouse_data, array_flip( array(
     372                    'warehouse_id',
     373                    'is_default',
     374                    'name',
     375                    'origin_address',
     376                    'return_address',
     377                ) ) );
     378
     379                if( $warehouse['is_default'] ) {
     380                    $warehouse['name'] .= ' (' . esc_html__( 'ShipStation Default', 'live-rates-for-shipstation' ) . ')';
     381                }
     382
     383                $warehouses[ $warehouse['warehouse_id'] ] = $warehouse;
     384
     385            }
     386
     387            // Cache Warehouse data.
     388            if( ! empty( $warehouses ) ) {
     389                set_transient( $trans_key, $warehouses, $this->cache_time );
     390            }
     391        }
     392
     393        return $warehouses;
     394
     395    }
     396
     397
     398    /**
     399     * Return a single package by ID.
     400     *
     401     * @param String $package_id - Shipstation specific reference code.
     402     *
     403     * @return Array|WP_Error
     404     */
     405    public function get_package( $package_id ) {
     406
     407        $packages = $this->get_packages();
     408        if( is_wp_error( $packages ) || empty( $packages ) ) {
     409            return $packages;
     410        }
     411
     412        return ( isset( $packages[ $package_id ] ) ) ? $packages[ $package_id ] : array();
     413
     414    }
     415
     416
     417    /**
     418     * Return an array of Packages
     419     *
     420     * @link https://docs.shipstation.com/openapi/package_types/list_package_types
     421     *
     422     * @return Array|WP_Error
     423     */
     424    public function get_packages() {
     425
     426        $trans_key  = $this->prefix_key( 'packages' );
     427        $packages = get_transient( $trans_key );
     428
     429        if( empty( $packages ) || $this->skip_cache ) {
     430
     431            $body = $this->make_request( 'get', 'packages' );
     432
     433            // Return Early - API Request error - see logs.
     434            if( is_wp_error( $body ) ) {
     435                return $body;
     436            }
     437
     438            // Return Early - No Custom Packages to work with.
     439            if( empty( $body['packages'] ) ) {
     440                return array();
     441            }
     442
     443            // We do need most the Package data, just id, name, dimensions - ezpz.
     444            $packages = array();
     445            foreach( $body['packages'] as $package_data ) {
     446
     447                $package = array_intersect_key( $package_data, array_flip( array(
     448                    'package_id',
     449                    'name',
     450                    'dimensions',
     451                ) ) );
     452
     453                $packages[ $package['package_id'] ] = $package;
     454
     455            }
     456
     457            // Cache Warehouse data.
     458            if( ! empty( $packages ) ) {
     459                set_transient( $trans_key, $packages, $this->cache_time );
     460            }
     461        }
     462
     463        return $packages;
     464
     465    }
     466
     467
     468    /**
     469     * Purchase a shipping label by a carrier.
     470     *
     471     * @link https://docs.shipstation.com/openapi/labels/create_label
     472     *
     473     * @param Array $api_args - See ShipStation API docs for required fields.
     474     * @param Boolean $test_label - Whether or not to create a test label.
     475     *
     476     * @return Array|WP_Error
     477     */
     478    public function purchase_shipping_label( $api_args, $test_label = false ) {
     479
     480        if( $test_label ) $api_args['test_label'] = true;
     481        $body = $this->make_request( 'post', 'labels', $api_args );
    295482
    296483        // Return Early - API Request error - see logs.
     
    299486        }
    300487
    301         /**
    302          * API returns no errors but also doesn't do anything in ShipStation.
    303          */
    304         $data = $body;
     488        $data = array();
    305489
    306490        return $data;
     
    309493
    310494
    311 
    312495    /**
    313496     * Create Shipments from given WC_Orders.
     
    317500     * @return Array|WP_Error
    318501     */
    319     public function create_shipments_from_wc_orders( $wc_orders ) {
     502    public function shipment_args_from_wc_orders( $wc_orders ) {
    320503
    321504        $data = array();
     
    325508
    326509        $shipments = array();
    327         foreach( $wc_orders as $wc_order ) {
     510        foreach( (array)$wc_orders as $wc_order ) {
    328511
    329512            // Skip
     
    337520            $order_items     = $wc_order->get_items();
    338521            $order_item_ship = $wc_order->get_items( 'shipping' );
    339             $order_item_ship = ( ! empty( $order_item_ship ) ) ? $order_item_ship[ array_key_first( $order_item_ship ) ] : null;
    340 
     522            $shipmentItem = ( ! empty( $order_item_ship ) ) ? $order_item_ship[ array_key_first( $order_item_ship ) ] : null;
     523
     524            // Return Early - No shipping item found.
     525            if( null === $shipmentItem ) {
     526                return new \WP_Error( 400, esc_html__( 'No shipping item found in order.', 'live-rates-for-shipstation' ) );
     527            }
     528
     529            // Shipment args I'll eventually need
     530            $others = array(
     531                'hold_until_date'   => 'Y-m-d UTC', // ShipStation will hold the shipment until this date. Maybe leave blank, but could impact preorder items?
     532                'ship_by_date'      => 'Y-m-d UTC', // Purely informational - useful for store owners collecting additional data.
     533                'ship_date'         => 'Y-m-d UTC', // The date the shipment is actually shipped. Defaults to current date.
     534                'requested_shipment_service' => 'carrier_service_code', // ex. ups_ground
     535                'warehouse_id'      => 'se-*', // ShipStation warehouse association: WC_Order Label Override > Shipping Zone > Global. We can skip ship_from if we have this.
     536                'return_to'         => array(), // Return to address info - need settings for this.
     537                'advanced_options'  => array(), // Advanced options - might need settings for this.
     538            );
     539
     540            // Default ShipStation Shipment Array
    341541            $shipment = array(
    342                 'validate_address'  => 'no_validation',
    343                 'carrier_id'        => $order_item_ship->get_meta( '_iqlrss_carrier_id', true ),
    344                 'store_id'          => \IQLRSS\Driver::get_ss_opt( 'store_id' ),
     542                'external_order_id' => $wc_order->get_id(),
     543                'order_source_code'=> 'woocommerce',
     544                'carrier_id'        => $shipmentItem->get_meta( '_iqlrss_carrier_id', true ),
     545                'service_code'      => $shipmentItem->get_meta( '_iqlrss_service_code', true ),
    345546                'shipping_paid'     => array(
    346547                    'currency'  => $wc_order->get_currency(),
     
    377578            );
    378579
    379             $shipment['items'] = array();
     580            // Add Packages
     581            // $packages = $shipmentItem->get_meta( 'boxes', true );
     582            // if( ! empty( $packages ) && is_array( $packages ) ) {
     583            //  foreach( $packages as $package ) {
     584
     585            //      $shipment['packages'][] = array(
     586            //          'package_code'  => $ship_package['packageCode'],
     587            //          'package_name'  => $ship_package['packageName'],
     588            //          'weight'        => array(
     589            //              'value' => $ship_package['weight']['value'],
     590            //              'unit'  => $ship_package['weight']['unit'],
     591            //          ),
     592            //          'dimensions' => array(
     593            //              'length'    => $ship_package['dimensions']['length'],
     594            //              'width'     => $ship_package['dimensions']['width'],
     595            //              'height'    => $ship_package['dimensions']['height'],
     596            //              'unit'      => $ship_package['dimensions']['unit'],
     597            //          ),
     598            //      );
     599
     600            //  }
     601            // }
     602
     603            // Add Order Items
    380604            foreach( $shipstation_order_arr['items'] as $ship_item ) {
    381605
     
    434658        }
    435659
    436         return $this->create_shipments( $shipments );
    437 
    438660    }
    439661
     
    506728        // Return Early - API encountered an error.
    507729        if( is_wp_error( $request ) ) {
    508             return $this->log( $request );
     730            return $this->log( $request, 'error' );
    509731        } else if( 200 != $code || ! is_array( $body ) ) {
    510732
     
    524746            }
    525747
    526             return $this->log( new \WP_Error( $err_code, $err_msg ) );
     748            return $this->log( new \WP_Error( $err_code, $err_msg ), 'error' );
    527749        }
    528750
    529751        // Log API Request Result
    530752        /* translators: %s is the API endpoint (example: carriers/rates). */
    531         $this->log( sprintf( esc_html__( 'ShipStation API Request to %s', 'live-rates-for-shipstation' ), $endpoint ), 'info', array(
     753        $this->log( sprintf( esc_html__( 'ShipStation API Request to %s', 'live-rates-for-shipstation' ), $endpoint ), 'debug', array(
    532754            'args'      => $args,
    533755            'code'      => $code,
     
    575797    }
    576798
    577 
    578     /**
    579      * Log error in WooCommerce
    580      * Passthru method - log what's given and give it back.
    581      *
    582      * @param Mixed $error      - String or WP_Error
    583      * @param String $level     - WooCommerce level (debug|info|notice|warning|error|critical|alert|emergency)
    584      * @param Array $context
    585      *
    586      * @return Mixed - Return the error back.
    587      */
    588     protected function log( $error, $level = 'debug', $context = array() ) {
    589 
    590         if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', 0, true ) ) {
    591             return $error;
    592         }
    593 
    594         if( is_wp_error( $error ) ) {
    595             $error_msg = sprintf( '(%s) %s', $error->get_error_code(), $error->get_error_message() );
    596         } else {
    597             $error_msg = $error;
    598         }
    599 
    600         if( class_exists( '\WC_Logger' ) ) {
    601 
    602             if( null === $this->logger ) {
    603                 $this->logger = \wc_get_logger();
    604             }
    605 
    606             /**
    607              * The WC_Logger does not handle double quotes well.
    608              * This will conver double quotes to faux: " -> ''
    609              */
    610             array_walk_recursive( $context, function( &$val ) {
    611                 $val = ( is_string( $val ) ) ? str_replace( '"', "''", $val ) : $val;
    612             } );
    613 
    614             $this->logger->log( $level, $error_msg, array_merge( $context, array( 'source' => 'live-rates-for-shipstation' ) ) );
    615 
    616         }
    617 
    618         return $error;
    619 
    620     }
    621 
    622799}
  • live-rates-for-shipstation/trunk/core/api/shipstationv1.php

    r3407166 r3452263  
    146146        // Return Early - Something went wrong getting carriers.
    147147        } else if( ! isset( $carriers[ $carrier_code ] ) ) {
    148             return $this->log( new \WP_Error( 404, esc_html__( '[v1] Could not find carrier information.', 'live-rates-for-shipstation' ) ) );
     148            return $this->log( new \WP_Error( 404, esc_html__( '[v1] Could not find carrier information.', 'live-rates-for-shipstation' ) ), 'warning' );
    149149        }
    150150
     
    527527        } );
    528528
    529         // Return Early - Skip the log but o
     529        // Return Early - No orders to work with.
    530530        if( empty( $orders ) ) {
    531             return $this->log( new \WP_Error( 400, esc_html__( '[v1] Empty Orders. Data may be missing orderNumber or orderKey.', 'live-rates-for-shipstation' ) ), 'warning', array(
     531            return $this->log( new \WP_Error( 400, esc_html__( '[v1] Empty Orders. Data may be missing orderNumber or orderKey.', 'live-rates-for-shipstation' ) ), 'error', array(
    532532                'orders' => $order_arr,
    533533            ) );
     
    654654        // Log API Request Result
    655655        /* translators: %s is the API endpoint (example: carriers/rates). */
    656         $this->log( sprintf( esc_html__( '[v1] ShipStation API Request to %s', 'live-rates-for-shipstation' ), $endpoint ), 'info', array(
     656        $this->log( sprintf( esc_html__( '[v1] ShipStation API Request to %s', 'live-rates-for-shipstation' ), $endpoint ), 'debug', array(
    657657            'args'      => $args,
    658658            'code'      => $code,
    659             'reponse'   => $body,
     659            'response'  => $body,
    660660        ) );
    661661
     
    692692     */
    693693    public function get_shipping_estimates( $est_opts ) {
    694         return $this->log( new \WP_Error( 400, esc_html__( 'Live Rates for ShipStation v1 API Class does not support this endpoint. Use the v2 API Class: \IQLRSS\Core\Api\Shipstation', 'live-rates-for-shipstation' ) ) );
     694        return $this->log( new \WP_Error( 400, esc_html__( 'Live Rates for ShipStation v1 API Class does not support this endpoint. Use the v2 API Class: \IQLRSS\Core\Api\Shipstation', 'live-rates-for-shipstation' ) ), 'notice' );
    695695    }
    696696
  • live-rates-for-shipstation/trunk/core/assets/js/admin.js

    r3407166 r3452263  
    1919    } );
    2020}
     21
     22
     23/**
     24 * Edit Order Module Settings
     25 * @import editOrderSettings
     26 */
     27if( document.querySelector( 'form#order' ) ) {
     28    import( './edit-order/_main.js' ).then( ( Module ) => {
     29        new Module.editOrderSettings();
     30    } );
     31}
  • live-rates-for-shipstation/trunk/core/assets/js/shipping-zones/_main.js

    r3407166 r3452263  
    44 * Not really meant to be used as an object but more for
    55 * encapsulation and organization.
     6 *
     7 * Manages the the possible initialization of custom boxes.
     8 * Manages the show/hide functionality of price adjustments.
    69 *
    710 * @global {Object} iqlrss - Localized object of saved values.
  • live-rates-for-shipstation/trunk/core/settings-shipstation.php

    r3411187 r3452263  
    167167    public function enqueue_admin_assets() {
    168168
    169         global $wp_scripts;
    170 
    171169        if( ! $this->maybe_enqueue( 'admin' ) ) {
    172170            return;
     
    175173        wp_enqueue_style( \IQLRSS\Driver::plugin_prefix( 'admin', '-' ) );
    176174        wp_enqueue_script_module( \IQLRSS\Driver::plugin_prefix( 'admin', '-' ) );
    177 
    178         // if( current_user_can( 'list_users' ) ) {
    179         //  $foo = wp_script_modules();
    180         //  printf( '<pre>%s</pre>', print_r( $foo, 1 ) );
    181         //  die( 'end' );
    182         // }
    183175
    184176    }
     
    211203
    212204        global $wpdb;
     205
    213206
    214207        /**
     
    297290            '' => esc_html__( 'ShipStation carriers may still be loading...', 'live-rates-for-shipstation' ),
    298291        );
     292        $warehouses = array(
     293            '' => '(' . esc_html__( 'Website Store Address', 'live-rates-for-shipstation' ) . ')',
     294        );
    299295        $appended_fields = array();
    300296
    301297        if( ! empty( \IQLRSS\Driver::get_ss_opt( 'api_key' ) ) ) {
    302298
    303             $carrier_desc = esc_html__( 'Select which ShipStation carriers you would like to see live shipping rates from.', 'live-rates-for-shipstation' );
    304             $response = ( new Api\Shipstation() )->get_carriers();
    305 
     299            $api = new Api\Shipstation();
     300
     301            // Grab Warehouse options
     302            $api_warehouses = $api->get_warehouses();
     303            if( is_a( $api_warehouses, 'WP_Error' ) ) {
     304                $warehouses = array( '' => $api_warehouses->get_error_message() );
     305            } else if( is_array( $api_warehouses ) && ! empty( $api_warehouses ) ) {
     306                $warehouses = array_merge( $warehouses, array_combine(
     307                    array_keys( $api_warehouses ),
     308                    array_column( $api_warehouses, 'name' ),
     309                ) );
     310            }
     311
     312            // Grab Carrier options
    306313            $carriers = array();
    307             if( is_a( $response, 'WP_Error' ) ) {
    308                 $carriers[''] = $response->get_error_message();
    309             } else if( is_array( $response ) ) {
    310                 foreach( $response as $carrier ) {
     314            $api_carriers = $api->get_carriers();
     315            if( is_a( $api_carriers, 'WP_Error' ) ) {
     316                $carriers[''] = $api_carriers->get_error_message();
     317            } else if( is_array( $api_carriers ) && ! empty( $api_carriers ) ) {
     318                foreach( $api_carriers as $carrier ) {
    311319                    $carriers[ $carrier['carrier_id'] ] = $carrier['name'];
    312320                }
    313321            }
    314322
    315         } else {
    316             $carrier_desc = esc_html__( 'Please set and verify your ShipStation API key. Then, click the Save button at the bottom of this page.', 'live-rates-for-shipstation' );
    317323        }
    318324
     
    340346                    'class'         => 'chosen_select',
    341347                    'options'       => $carriers,
    342                     'description'   => $carrier_desc,
     348                    'description'   => ( function() {
     349                        if( ! empty( \IQLRSS\Driver::get_ss_opt( 'api_key' ) ) ) {
     350                            return esc_html__( 'Select which ShipStation carriers you would like to see live shipping rates from.', 'live-rates-for-shipstation' );
     351                        }
     352                        return esc_html__( 'Please set and verify your ShipStation API key. Then, click the Save button at the bottom of this page.', 'live-rates-for-shipstation' );
     353                    } )(),
    343354                    'desc_tip'      => esc_html__( 'Services from selected carriers will be available when setting up Shipping Zones.', 'live-rates-for-shipstation' ),
     355                    'default'       => '',
     356                );
     357
     358                $appended_fields[ \IQLRSS\Driver::plugin_prefix( 'global_warehouse' ) ] = array(
     359                    'title'         => esc_html__( 'Shipping From', 'live-rates-for-shipstation' ),
     360                    'type'          => 'select',
     361                    'options'       => $warehouses,
     362                    'description'   => ( function() {
     363                        if( ! empty( \IQLRSS\Driver::get_ss_opt( 'api_key' ) ) ) {
     364                            return esc_html__( 'Select to ship from a different location than what is set as your WooCommerce website default location.', 'live-rates-for-shipstation' );
     365                        }
     366                        return esc_html__( 'Please set and verify your ShipStation API key. Then, click the Save button at the bottom of this page.', 'live-rates-for-shipstation' );
     367                    } )(),
     368                    'desc_tip'      => esc_html__( 'This can be overridden per Shipping Zone.', 'live-rates-for-shipstation' ),
    344369                    'default'       => '',
    345370                );
     
    503528            $enqueue = ( $enqueue || ( isset( $_GET, $_GET['section'] ) && 'shipstation' == $_GET['section'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    504529
     530            // Edit Order page
     531            $enqueue = ( $enqueue || ( isset( $_GET, $_GET['page'], $_GET['id'] ) && 'wc-orders' == $_GET['page'] && ! empty( $_GET['id'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     532
    505533            // Overprotective WooCommerce settings page check
    506             $enqueue = ( $enqueue && 'woocommerce_page_wc-settings' == $screen_id );
     534            $enqueue = ( $enqueue && in_array( $screen_id, array( 'woocommerce_page_wc-orders', 'woocommerce_page_wc-settings' ) ) );
    507535        }
    508536        return $enqueue;
  • live-rates-for-shipstation/trunk/core/shipping-method-shipstation.php

    r3442676 r3452263  
    22/**
    33 * ShipStation Live Shipping Rates Method
    4  *
    5  * @todo Consider moving Shipping Calculations into it's own class.
    64 *
    75 * @link https://www.fedex.com/en-us/shipping/one-rate.html
     
    1614 */
    1715namespace IQLRSS\Core;
     16use \IQLRSS\Core\Traits;
    1817
    1918if( ! defined( 'ABSPATH' ) ) {
     
    2625
    2726    /**
     27     * Inherit logger traits
     28     */
     29    use Traits\Logger;
     30
     31
     32    /**
    2833     * Plugin prefix used to namespace data keys.
    2934     *
     
    3136     */
    3237    protected $plugin_prefix;
    33 
    34 
    35     /**
    36      * Array of store specific settings.
    37      *
    38      * @var Array
    39      */
    40     protected $store_data = array(
    41         'weight_unit'   => '',
    42         'dim_unit'      => '', // Dimension
    43     );
    4438
    4539
     
    9488        }
    9589
    96         // Set the store unit term and associate it with ShipStations term.
    97         $this->store_data = array(
    98             'weight_unit'   => get_option( 'woocommerce_weight_unit', $this->store_data['weight_unit'] ),
    99             'dim_unit'      => get_option( 'woocommerce_dimension_unit', $this->store_data['dim_unit'] ),
    100         );
    101 
    10290        /**
    10391         * Init shipping methods.
     
    189177    /**
    190178     * Increase the HTTP Request Timeout
    191      * Sometimes ShipStation takes awhile to responde with rates.
     179     * Sometimes ShipStation takes awhile to respond with rates.
    192180     * Presumably, the more services enabled, the longer it takes.
    193181     *
     
    409397    protected function init_instance_form_fields() {
    410398
     399        $store_warehouse_label = '(' . esc_html__( 'Website Store Address', 'live-rates-for-shipstation' ) . ')';
     400        $warehouses = array( '' => $store_warehouse_label );
     401
     402        if( ! empty( \IQLRSS\Driver::get_ss_opt( 'api_key' ) ) ) {
     403
     404            $og_warehouse = \IQLRSS\Driver::get_ss_opt( 'global_warehouse' );
     405            $api = new Api\Shipstation();
     406
     407            // Grab Warehouse options
     408            $api_warehouses = $api->get_warehouses();
     409            if( is_a( $api_warehouses, 'WP_Error' ) ) {
     410                $warehouses = array( '' => $api_warehouses->get_error_message() );
     411            } else if( is_array( $api_warehouses ) && ! empty( $api_warehouses ) ) {
     412                $warehouses = array_merge( $warehouses, array_combine(
     413                    array_keys( $api_warehouses ),
     414                    array_column( $api_warehouses, 'name' ),
     415                ) );
     416            }
     417
     418            // Move the global warehouse to the top.
     419            if( ! empty( $og_warehouse ) && isset( $warehouses[ $og_warehouse ] ) ) {
     420                $tmp_houses = array_diff_key( $warehouses, array( '' => '' ) );
     421                $warehouses = array_merge( array(
     422                    ''              => $warehouses[ $og_warehouse ] . ' (' . esc_html__( 'Store Global', 'live-rates-for-shipstation' ) . ')',
     423                    '_woo_default'  => $store_warehouse_label,
     424                ), $tmp_houses );
     425            }
     426        }
     427
    411428        $settings = array(
    412429            'title' => array(
     
    416433                'default'       => esc_html__( 'ShipStation Rates', 'live-rates-for-shipstation' ),
    417434                'desc_tip'      => true,
     435            ),
     436            'warehouse' => array(
     437                'title'         => esc_html__( 'Shipping Location', 'live-rates-for-shipstation' ),
     438                'type'          => 'select',
     439                'options'       => $warehouses,
     440                'description'   => esc_html__( 'Select to ship from a different location than the default.', 'live-rates-for-shipstation' ),
    418441            ),
    419442            'minweight' => array(
     
    453476        /**
    454477         * Allow filtering the Shipping Zone settings
    455          * 
     478         *
    456479         * @hook filter
    457          * 
     480         *
    458481         * @param Array $settings
    459482         * @param \IQLRSS\Core\Shipping_Method_Shipstation $this
    460          * 
     483         *
    461484         * @return Array $settings
    462485         */
     
    481504
    482505        ob_start();
    483             include 'assets/views/customboxes-table.php';
     506            include \IQLRSS\Driver::get_asset_path( 'views/shipping-zone/customboxes-table.php' );
    484507        return ob_get_clean();
    485508
     
    590613
    591614        ob_start();
    592             include 'assets/views/services-table.php';
     615            include \IQLRSS\Driver::get_asset_path( 'views/shipping-zone/services-table.php' );
    593616        return ob_get_clean();
    594617
     
    660683                }
    661684
     685
    662686                /**
    663687                 * We don't want to array_filter() since
     
    703727        }
    704728
    705         $enabled_services = $this->get_enabled_services();
    706         if( empty( $enabled_services ) ) {
    707             $this->log( esc_html__( 'No enabled carrier services found. Please enable carrier services within the shipping zone.', 'live-rates-for-shipstation' ) );
    708             return;
    709         }
    710 
    711         $saved_carriers = array_keys( $enabled_services );
    712         if( ! empty( $saved_carriers ) && ! empty( $this->carriers ) ) {
    713             $saved_carriers = array_values( array_intersect( $saved_carriers, $this->carriers ) );
    714         }
    715 
    716         $global_adjustment      = floatval( \IQLRSS\Driver::get_ss_opt( 'global_adjustment', 0 ) );
    717         $global_adjustment_type = \IQLRSS\Driver::get_ss_opt( 'global_adjustment_type','' );
    718         $global_adjustment_type = ( empty( $global_adjustment_type ) && ! empty( $global_adjustment ) ) ? 'percentage' : $global_adjustment_type;
    719 
    720         $packing_type = $this->get_option( 'packing', 'individual' );
    721         $request = array(
    722             'from_country_code'  => WC()->countries->get_base_country(),
    723             'from_postal_code'   => WC()->countries->get_base_postcode(),
    724             'from_city_locality' => WC()->countries->get_base_city(),
    725             'from_state_province'=> WC()->countries->get_base_state(),
    726 
    727             'to_country_code'   => $packages['destination']['country'],
    728             'to_postal_code'    => $packages['destination']['postcode'],
    729             'to_city_locality'  => $packages['destination']['city'],
    730             'to_state_province' => $packages['destination']['state'],
    731 
    732             'address_residential_indicator' => 'unknown',
    733         );
    734 
    735         $item_requests = array();
    736         $callback = sprintf( 'group_requestsby_%s', str_replace( '-', '_', $packing_type ) );
    737         if( method_exists( $this, $callback ) ) {
    738             $item_requests = call_user_func( array( $this, $callback ), $packages['contents'] );
    739         }
     729        // Grab the calculator to be filtered.
     730        $calculator = new Classes\Shipping_Calculator( $packages, array(
     731            'shipping_method' => $this,
     732        ) );
    740733
    741734
    742735        /**
    743          * Allow filtering the packages before requesting estimates.
    744          *
    745          * The returned array should follow this format:
    746          * Multi-dimensional Array
    747          *
    748          * $item_requests = Array( Array(
    749          * ~ Required Fields:
    750          *      '_name' => '$productID|$productName', - This format makes it easy to show the Shop Manager what's packed into the box.
    751          *      'dimensions' => array(
    752          *          'length => 123,
    753          *          'width' => 123,
    754          *          'height' => 123,
    755          *          'unit' => 'inch', - ShipStation expects a specific string. See \IQLRSS\Core\Api\Shipstation::convert_unit_term( $unit )
    756          *      ),
    757          *      'weight' => array(
    758          *          'value' => 123,
    759          *          'unit' => 'pound',  - ShipStation expects a specific string. See \IQLRSS\Core\Api\Shipstation::convert_unit_term( $unit )
    760          *      ),
    761          *
    762          * ~ Entirely optional, but the system will try to read them if available.
    763          *      'packed' => Array( '$productID|$productName', '$productID|$productName' ),
    764          *      'price'  => 123,
    765          *      'nickname' => 'String' - Displayed to the Shop Owner on the Edit Order page.
    766          *      'box_weight' => 123,
    767          *      'box_max_weight'=> 123,
    768          *      'package_code' => 'ups_ground',
    769          *      'carrier_code' => 'ups', - Carrier Code should match what ShipStation expects. I.E. fedex_walleted. This is to group packages with carriers for discounts.
    770          * ) )
    771          *
     736         * Allow overriding the Shipping Calculator object.
     737         * Must inherit IQLRSS\Core\Classes\Shipping_Calculator
     738         *
    772739         * @hook filter
    773          * 
    774          * @param Array $item_requests - Array of Package dimensions that the API will use to get rates on. Multidimensional Array.
    775          * @param Array $packages - The cart contents. See $packages['contents'] for items. 
     740         *
     741         * @param \IQLRSS\Core\Classes\Shipping_Calculator $calculator
     742         * @param Array $packages - The cart contents. See $packages['contents'] for items.
    776743         * @param \IQLRSS\Core\Shipping_Method_Shipstation $this
    777          * 
     744         *
    778745         * @return Array $settings
    779746         */
    780         $filtered_requests = apply_filters( 'iqlrss/shipping/packages', $item_requests, $packages, $packing_type, $this );
    781 
    782         // IF the hash doesn't match what was given to the filter, note it in the logs so the store owner will know.
    783         $item_req_hash      = ( ! empty( $item_requests ) ) ? md5( maybe_serialize( $item_requests ) ) : '';
    784         $filtered_req_hash  = ( ! empty( $filtered_requests ) ) ? md5( maybe_serialize( $filtered_requests ) ) : '';
    785         if( $item_req_hash !== $filtered_req_hash ) {
    786             $this->log( esc_html__( 'The Shipping packages were modified by a 3rd party using the `iqlrss/shipping/packages` filter hook.', 'live-rates-for-shipstation' ), 'notice' );
    787         }
    788 
    789         /**
    790          * We have to return reates per package.
    791          * The /rates/estimate endpoint requires less info
    792          * and /rates endpoint is way slower.
    793          */
    794         $rates = array();
    795         foreach( $filtered_requests as $item_id => $req ) {
    796 
    797             // Create the API request combining the package (weight, dimensions), general request data, and the carrier info.
    798             $api_request = array_merge(
    799                 $req,       // Package (weight, dimensions)
    800                 $request,   // General info like to/from address
    801                 array(      // Saved carrier ids
    802                     'carrier_ids' => $saved_carriers,
    803                 )
    804             );
    805 
    806             // Ping the ShipStation API to get rates per Carrier.
    807             // Continue - Something went wrong, should be logged on the API side.
    808             $available_rates = $this->shipStationApi->get_shipping_estimates( $api_request );
    809 
    810             if( is_wp_error( $available_rates ) || empty( $available_rates ) ) {
    811                 continue;
     747        $maybe_calc = apply_filters( 'iqlrss/shipping/calculator_object', $calculator, $packages, $this );
     748        if( is_object( $maybe_calc ) && $maybe_calc !== $calculator ) {
     749
     750            // Override calculator object and log it's change.
     751            if( is_subclass_of( $maybe_calc, '\IQLRSS\Core\Classes\Shipping_Calculator' ) ) {
     752
     753                $calculator = $maybe_calc;
     754                $this->log( sprintf( '%s [%s]',
     755                    esc_html__( 'Shipping Calculations Object overridden.', 'live-rates-for-shipstation' ),
     756                    get_class( $maybe_calc )
     757                ), 'notice' );
     758
     759            // Something went wrong, log that too.
     760            } else {
     761
     762                $this->log( sprintf( '%s [%s]',
     763                    esc_html__( 'Shipping Calculations Object override failed. Class may not inherit "\IQLRSS\Core\Classes\Shipping_Calculator".', 'live-rates-for-shipstation' ),
     764                    get_class( $maybe_calc ),
     765                    'warning'
     766                ) );
     767
    812768            }
    813 
    814             // Loop the found rates and setup the WooCommerce rates array for each.
    815             foreach( $available_rates as $shiprate ) {
    816 
    817                 if( ! isset( $enabled_services[ $shiprate['carrier_id'] ][ $shiprate['code'] ] ) ) {
    818                     continue;
    819                 }
    820 
    821                 $ratehash    = md5( sprintf( '%s%s', $shiprate['code'], $shiprate['carrier_id'] ) );
    822                 $service_arr = $enabled_services[ $shiprate['carrier_id'] ][ $shiprate['code'] ];
    823                 $cost        = floatval( $shiprate['cost'] );
    824                 $rate_name   = ( isset( $req['_name'] ) ) ? $req['_name'] : '';
    825                 $rate_name   = ( empty( $rate_name ) && isset( $req['nickname'] ) ) ? $req['nickname'] : $rate_name;
    826                 $ratemeta    = array(
    827                     '_name'=> $rate_name, // Item products(ID|Name) or box nickname.
    828                     'rate' => $cost,
    829                 );
    830 
    831                 // Apply service upcharge
    832                 if( isset( $service_arr['adjustment'] ) ) {
    833 
    834                     /**
    835                      * Adjustment type could be '' to skip global adjustment.
    836                      * Defaults to percentage for v1.03 backwards compatibility.
    837                      */
    838                     $adjustment      = floatval( $service_arr['adjustment'] );
    839                     $adjustment_type = ( isset( $service_arr['adjustment_type'] ) ) ? $service_arr['adjustment_type'] : 'percentage';
    840 
    841                     if( ! empty( $adjustment_type ) && $adjustment > 0 ) {
    842 
    843                         $adjustment_cost = ( 'percentage' == $adjustment_type ) ? ( $cost * ( floatval( $adjustment ) / 100 ) ) : floatval( $adjustment );
    844                         $ratemeta['adjustment'] = array(
    845                             'type' => $adjustment_type,
    846                             'rate' => $adjustment,
    847                             'cost' => $adjustment_cost,
    848                             'global'=> false,
    849                         );
    850                         $cost += $adjustment_cost;
    851 
    852                     }
    853 
    854                 } else if( ! empty( $global_adjustment_type ) && $global_adjustment > 0 ) {
    855 
    856                     $adjustment_cost = ( 'percentage' == $global_adjustment_type ) ? ( $cost * ( floatval( $global_adjustment ) / 100 ) ) : floatval( $global_adjustment );
    857                     $ratemeta['adjustment'] = array(
    858                         'type' => $global_adjustment_type,
    859                         'rate' => $global_adjustment,
    860                         'cost' => $adjustment_cost,
    861                         'global'=> true,
    862                     );
    863                     $cost += $adjustment_cost;
    864 
    865                 }
    866 
    867                 // Loop and add any other shipment amounts.
    868                 if( ! empty( $shiprate['other_costs'] ) ) {
    869 
    870                     $ratemeta['other_costs'] = array();
    871                     foreach( $shiprate['other_costs'] as $slug => $cost_arr ) {
    872 
    873                         if( empty( $cost_arr['amount'] ) ) continue;
    874                         $cost += floatval( $cost_arr['amount'] );
    875                         $ratemeta['other_costs'][ $slug ] = $cost_arr['amount'];
    876 
    877                     }
    878                 }
    879 
    880                 // Maybe a package price
    881                 if( 'wc-box-packer' == $packing_type && isset( $req['price'] ) && ! empty( $req['price'] ) ) {
    882                     $cost += floatval( $req['price'] );
    883                     $ratemeta['other_costs']['box_price'] = $req['price'];
    884                 }
    885 
    886                 // Maybe apply per item.
    887                 if( 'individual' == $packing_type ) {
    888                     $cost *= $packages['contents'][ $item_id ]['quantity'];
    889                     $ratemeta['qty'] = $packages['contents'][ $item_id ]['quantity'];
    890                 }
    891 
    892                 // Set rate or append the estimated item ship cost.
    893                 if( ! isset( $rates[ $ratehash ] ) ) {
    894 
    895                     $rates[ $ratehash ] = array(
    896                         'id'        => $ratehash,
    897                         'label'     => ( ! empty( $service_arr['nickname'] ) ) ? $service_arr['nickname'] : $shiprate['name'],
    898                         'package'   => $packages,
    899                         'cost'      => array( $cost ),
    900                         'meta_data' => array(
    901                             'carrier'   => $shiprate['carrier_name'],
    902                             'service'   => $shiprate['name'],
    903                             'rates'     => array(),
    904                             'boxes'     => array(),
    905 
    906                             // Private metadata fields must be excluded via filter way above.
    907                             "_{$this->plugin_prefix}_carrier_id"    => $shiprate['carrier_id'],
    908                             "_{$this->plugin_prefix}_carrier_code"  => $shiprate['carrier_code'],
    909                             "_{$this->plugin_prefix}_service_code"  => $shiprate['code'],
    910                         ),
    911                     );
    912 
    913                 } else {
    914                     $rates[ $ratehash ]['cost'][] = $cost;
    915                 }
    916 
    917                 // Merge item rates
    918                 $rates[ $ratehash ]['meta_data']['rates'] = array_merge(
    919                     $rates[ $ratehash ]['meta_data']['rates'],
    920                     array( $ratemeta ),
    921                 );
    922 
    923                 // Merge item boxes
    924                 $rates[ $ratehash ]['meta_data']['boxes'] = array_merge(
    925                     $rates[ $ratehash ]['meta_data']['boxes'],
    926                     array( $req ),
    927                 );
    928 
     769        }
     770
     771        // Get and Add Rates - EZPZ
     772        if( $rates = $calculator->get_rates() ) {
     773            foreach( $rates as $rate ) {
     774                $this->add_rate( $rate );
    929775            }
    930 
    931         }
    932 
    933         $single_lowest       = \IQLRSS\Driver::get_ss_opt( 'return_lowest', 'no' );
    934         $single_lowest_label = \IQLRSS\Driver::get_ss_opt( 'return_lowest_label', '' );
    935 
    936         // Add all shipping rates, let the user decide.
    937         if( 'no' == $single_lowest || empty( $single_lowest ) ) {
    938 
    939             foreach( $rates as $rate_arr ) {
    940 
    941                 // If more than 1 rate, add the cheapest.
    942                 if( count( $rate_arr['cost'] ) > 1 ) {
    943                     usort( $rate_arr['cost'], fn( $r1, $r2 ) => ( (float)$r1 < (float)$r2 ) ? -1 : 1 );
    944                     $rate_arr['cost'] = (array)array_shift( $rate_arr['cost'] );
    945                 }
    946 
    947                 // WooCommerce skips serialized data when outputting order item meta, this is a workaround.
    948                 // See hooks above for formatting.
    949                 $rate_arr['meta_data']['rates'] = wp_json_encode( $rate_arr['meta_data']['rates'] );
    950                 $rate_arr['meta_data']['boxes'] = wp_json_encode( $rate_arr['meta_data']['boxes'] );
    951 
    952                 $this->add_rate( $rate_arr );
    953             }
    954 
    955         // Find the single lowest shipping rate
    956         } else if( 'yes' == $single_lowest ) {
    957 
    958             $lowest = 0;
    959             $lowest_service = array_key_first( $rates );
    960             foreach( $rates as $service_id => $rate_arr ) {
    961 
    962                 $total = array_sum( $rate_arr['cost'] );
    963                 if( 0 == $lowest || $total < $lowest ) {
    964                     $lowest = $total;
    965                     $lowest_service = $service_id;
    966                 }
    967             }
    968 
    969             if( ! empty( $single_lowest_label ) ) {
    970                 $rates[ $lowest_service ]['label'] = $single_lowest_label;
    971             }
    972 
    973             // WooCommerce skips serialized data when outputting order item meta, this is a workaround.
    974             // See hooks above for formatting.
    975             $rates[ $lowest_service ]['meta_data']['rates'] = wp_json_encode( $rates[ $lowest_service ]['meta_data']['rates'] );
    976             $rates[ $lowest_service ]['meta_data']['boxes'] = wp_json_encode( $rates[ $lowest_service ]['meta_data']['boxes'] );
    977 
    978             $this->add_rate( $rates[ $lowest_service ] );
    979 
    980776        }
    981777
     
    994790
    995791    /**
    996      * Return an array of API requests which would be for individual products.
    997      *
    998      * @param Array $items
    999      *
    1000      * @return Array $requests
    1001      */
    1002     public function group_requestsby_individual( $items ) {
    1003 
    1004         $item_requests  = array();
    1005         $default_weight = $this->get_option( 'minweight', '' );
    1006 
    1007         foreach( $items as $item_id => $item ) {
    1008 
    1009             // Continue - No shipping needed for product.
    1010             if( ! $item['data']->needs_shipping() ) {
    1011                 continue;
    1012             }
    1013 
    1014             $request = array(
    1015                 '_name' => sprintf( '%s|%s',
    1016                     $item['data']->get_id(),
    1017                     $item['data']->get_name(),
    1018                 ),
    1019                 'weight' => ( ! empty( $item['data']->get_weight() ) ) ? $item['data']->get_weight() : $default_weight,
    1020             );
    1021             $physicals = array_filter( array(
    1022                 'length'    => $item['data']->get_length(),
    1023                 'width'     => $item['data']->get_width(),
    1024                 'height'    => $item['data']->get_height(),
    1025             ) );
    1026 
    1027             // Return Early - Product missing one of the 4 key dimensions.
    1028             if( count( $physicals ) < 3 || empty( $request['weight'] ) ) {
    1029                 $this->log( sprintf(
    1030 
    1031                     /* translators: %1$d is the Product ID. %2$s is the Product Dimensions separated by a comma. */
    1032                     esc_html__( 'Product ID #%1$d missing (%2$s) dimensions. Weight is a minimum requirement. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    1033                     $item['product_id'],
    1034                     implode( ', ', array_diff_key( array(
    1035                         'length'    => 'length',
    1036                         'width'     => 'width',
    1037                         'height'    => 'height',
    1038                         'weight'    => 'weight',
    1039                     ), $physicals + array( 'weight' => $request['weight'] ) ) )
    1040                 ) );
    1041 
    1042                 return array();
    1043             }
    1044 
    1045             // Set rate request dimensions.
    1046             sort( $physicals );
    1047             if( 3 == count( $physicals ) ) {
    1048                 $request['dimensions'] = array(
    1049                     'length'    => round( wc_get_dimension( $physicals[2], $this->store_data['dim_unit'] ), 2 ),
    1050                     'width'     => round( wc_get_dimension( $physicals[1], $this->store_data['dim_unit'] ), 2 ),
    1051                     'height'    => round( wc_get_dimension( $physicals[0], $this->store_data['dim_unit'] ), 2 ),
    1052                     'unit'      => $this->shipStationApi->convert_unit_term( $this->store_data['dim_unit'] ),
    1053                 );
    1054             }
    1055 
    1056             // Set rate request weight.
    1057             if( ! empty( $request['weight'] ) ) {
    1058                 $request['weight'] = array(
    1059                     'value' => (float)round( wc_get_weight( $request['weight'], $this->store_data['weight_unit'] ), 2 ),
    1060                     'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    1061                 );
    1062             }
    1063 
    1064             $item_requests[ $item_id ] = $request;
    1065 
    1066         }
    1067 
    1068         return $item_requests;
    1069 
    1070     }
    1071 
    1072 
    1073     /**
    1074      * One Big Box
    1075      * Group all the products by weight and get rates by total weight.
    1076      *
    1077      * @param Array $items
    1078      *
    1079      * @return Array $requests
    1080      */
    1081     public function group_requestsby_onebox( $items ) {
    1082 
    1083         $default_weight = $this->get_option( 'minweight', 0 );
    1084         $subtype        = $this->get_option( 'packing_sub', 'weightonly' );
    1085         $dimensions = array(
    1086             'running' => array_combine( array( 'length', 'width', 'height', 'weight' ), array_fill( 0, 4, 0 ) ),
    1087             'largest' => array_combine( array( 'length', 'width', 'height', 'weight' ), array_fill( 0, 4, 0 ) ),
    1088         );
    1089 
    1090         foreach( $items as $item_id => $item ) {
    1091 
    1092             // Continue - No shipping needed for product.
    1093             if( ! $item['data']->needs_shipping() ) {
    1094                 continue;
    1095             }
    1096 
    1097             $request = array(
    1098                 '_name' => sprintf( '%s|%s',
    1099                     $item['data']->get_id(),
    1100                     $item['data']->get_name(),
    1101                 ),
    1102                 'weight' => ( ! empty( $item['data']->get_weight() ) ) ? $item['data']->get_weight() : $default_weight,
    1103             );
    1104 
    1105             // Return Early - Missing minimum requirement: weight.
    1106             if( empty( $request['weight'] ) ) {
    1107 
    1108                 $this->log( sprintf(
    1109 
    1110                     /* translators: %1$d is the Product ID. */
    1111                     esc_html__( 'Product ID #%1$d missing weight. Shipping Zone weight fallback could not be used. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    1112                     $item['product_id']
    1113                 ) );
    1114 
    1115                 return array();
    1116 
    1117             }
    1118 
    1119             $dimensions['running']['weight'] = $dimensions['running']['weight'] + ( floatval( $request['weight'] ) * $item['quantity'] );
    1120             $dimensions['running']['height'] = $dimensions['running']['height'] + ( floatval( $item['data']->get_height() ) * $item['quantity'] );
    1121             $dimensions['largest'] = array(
    1122                 'length'    => ( $dimensions['largest']['length'] < $item['data']->get_length() ) ? $item['data']->get_length() : $dimensions['largest']['length'],
    1123                 'width'     => ( $dimensions['largest']['width'] < $item['data']->get_width() )   ? $item['data']->get_width()  : $dimensions['largest']['width'],
    1124                 'height'    => ( $dimensions['largest']['height'] < $item['data']->get_height() ) ? $item['data']->get_height() : $dimensions['largest']['height'],
    1125                 'weight'    => ( $dimensions['largest']['weight'] < $request['weight'] )          ? $request['weight']          : $dimensions['largest']['weight'],
    1126             );
    1127 
    1128         }
    1129 
    1130         // Return Early - Rates by total weight.
    1131         if( 'weightonly' == $subtype ) {
    1132 
    1133             return array( array(
    1134                 'weight' => array(
    1135                     'value' => (float)round( wc_get_weight( $dimensions['running']['weight'], $this->store_data['weight_unit'] ), 2 ),
    1136                     'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    1137                 ),
    1138             ) );
    1139 
    1140         }
    1141 
    1142         $physicals = array_filter( array(
    1143             'length'    => $dimensions['largest']['length'],
    1144             'width'     => $dimensions['largest']['width'],
    1145             'height'    => $dimensions['running']['height'],
    1146             'weight'    => $dimensions['running']['weight'],
    1147         ) );
    1148 
    1149         // Return Early - Error - Missing dimensions to work with.
    1150         if( $physicals < 4 ) {
    1151 
    1152             $this->log( sprintf(
    1153 
    1154                 /* translators: %1$d is the Product ID. %2$s is the Product Dimensions separated by a comma. */
    1155                 esc_html__( 'OneBox rate requestion missing dimensions (%1$s). Weight is a minimum requirement. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    1156                 implode( ', ', array_diff_key( array(
    1157                     'length'    => 'length',
    1158                     'width'     => 'width',
    1159                     'height'    => 'height',
    1160                     'weight'    => 'weight',
    1161                 ), $physicals ) )
    1162             ) );
    1163 
    1164             return array();
    1165 
    1166         }
    1167 
    1168         // Default - Stacked Verticially
    1169         return array( array(
    1170             'weight' => array(
    1171                 'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    1172                 'value' => (float)round( wc_get_weight( $physicals['weight'], $this->store_data['weight_unit'] ), 2 ),
    1173             ),
    1174             'dimensions' => array(
    1175                 'unit'      => $this->shipStationApi->convert_unit_term( $this->store_data['dim_unit'] ),
    1176 
    1177                 // Largest
    1178                 'length'    => round( wc_get_dimension( $physicals['length'], $this->store_data['dim_unit'] ), 2 ),
    1179                 'width'     => round( wc_get_dimension( $physicals['width'], $this->store_data['dim_unit'] ), 2 ),
    1180 
    1181                 // Running
    1182                 'height'    => round( wc_get_dimension( $physicals['height'], $this->store_data['dim_unit'] ), 2 ),
    1183             ),
    1184         ) );
    1185 
    1186     }
    1187 
    1188 
    1189     /**
    1190      * Return an array of API requests for custom packed boxes.
    1191      * Shoutout to Mike Jolly & Co.
    1192      *
    1193      * @param Array $items
    1194      *
    1195      * @return Array $requests
    1196      */
    1197     public function group_requestsby_wc_box_packer( $items ) {
    1198 
    1199         $item_requests  = array();
    1200         $boxes          = $this->get_option( 'customboxes', array() );
    1201         $default_weight = $this->get_option( 'minweight', '' );
    1202 
    1203         /* Return Early - No custom boxes found. */
    1204         if( empty( $boxes ) ) {
    1205             $this->log( esc_html__( 'Custom Boxes selected, but no boxes found. Items packed individually', 'live-rates-for-shipstation' ), 'warning' );
    1206             return $this->group_requestsby_individual( $items );
    1207         }
    1208 
    1209         if( ! class_exists( '\IQRLSS\WC_Box_Packer\WC_Boxpack' ) ) {
    1210             include_once 'wc-box-packer/class-wc-boxpack.php';
    1211         }
    1212 
    1213         // Setup the WC_Boxpack boxes based on user submitted custom boxes.
    1214         $wc_boxpack = new WC_Box_Packer\WC_Boxpack();
    1215         foreach( $boxes as $box ) {
    1216             if( empty( $box['active'] ) ) continue;
    1217             $wc_boxpack->add_box( $box );
    1218         }
    1219 
    1220         // Loop the items, grabs their dimensions, and assocaite them with WC_Boxpack for future packing.
    1221         foreach( $items as $item_id => $item ) {
    1222             if( ! $item['data']->needs_shipping() ) continue;
    1223 
    1224             $weight = ( ! empty( $item['data']->get_weight() ) ) ? $item['data']->get_weight() : $default_weight;
    1225             $data   = array(
    1226                 'weight' => (float)round( wc_get_weight( $weight, $this->store_data['weight_unit'] ), 2 ),
    1227             );
    1228             $physicals = array_filter( array(
    1229                 'length'    => $item['data']->get_length(),
    1230                 'width'     => $item['data']->get_width(),
    1231                 'height'    => $item['data']->get_height(),
    1232             ) );
    1233 
    1234             // Return Early - Product missing one of the 4 key dimensions.
    1235             if( count( $physicals ) < 3 && empty( $data['weight'] ) ) {
    1236                 $this->log( sprintf(
    1237 
    1238                     /* translators: %1$d is the Product ID. %2$s is the Product Dimensions separated by a comma. */
    1239                     esc_html__( 'Product ID #%1$d missing (%2$s) dimensions and no weight found. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    1240                     $item['product_id'],
    1241                     implode( ', ', array_diff_key( array(
    1242                         'width'     => 'width',
    1243                         'height'    => 'height',
    1244                         'length'    => 'length',
    1245                     ), $physicals ) )
    1246                 ) );
    1247                 return array();
    1248             }
    1249 
    1250             sort( $physicals );
    1251             $data = array(
    1252                 'length'    => round( wc_get_dimension( $physicals[2], $this->store_data['dim_unit'] ), 2 ),
    1253                 'width'     => round( wc_get_dimension( $physicals[1], $this->store_data['dim_unit'] ), 2 ),
    1254                 'height'    => round( wc_get_dimension( $physicals[0], $this->store_data['dim_unit'] ), 2 ),
    1255                 'weight'    => round( wc_get_weight( $data['weight'], $this->store_data['weight_unit'] ), 2 ),
    1256             );
    1257 
    1258             // Pack Products
    1259             for( $i = 0; $i < $item['quantity']; $i++ ) {
    1260                 $wc_boxpack->add_item(
    1261                     $data['length'],
    1262                     $data['width'],
    1263                     $data['height'],
    1264                     $data['weight'],
    1265                     $item['data']->get_price(),
    1266                     array(
    1267                         '_name' => sprintf( '%s|%s',
    1268                             $item['data']->get_id(),
    1269                             $item['data']->get_name(),
    1270                         ),
    1271                     ),
    1272                 );
    1273             }
    1274         }
    1275 
    1276         // Pack it up, missions over.
    1277         $wc_boxpack->pack();
    1278         $wc_box_packages = $wc_boxpack->get_packages();
    1279         $box_log = array();
    1280 
    1281         // Delivery!
    1282         foreach( $wc_box_packages as $key => $package ) {
    1283 
    1284             $packed_items = ( is_array( $package->packed ) ) ? array_map( function( $item ) { return $item->meta['_name']; }, $package->packed ) : array();
    1285             $item_requests[] = array(
    1286                 'weight' => array(
    1287                     'value' => round( $package->weight, 2 ),
    1288                     'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    1289                 ),
    1290                 'dimensions' => array(
    1291                     'length'    => round( $package->length, 2 ),
    1292                     'width'     => round( $package->width, 2 ),
    1293                     'height'    => round( $package->height, 2 ),
    1294                     'unit'      => $this->shipStationApi->convert_unit_term( $this->store_data['dim_unit'] ),
    1295                 ),
    1296                 'packed' => $packed_items,
    1297                 'price'  => ( ! empty( $package->data ) ) ? $package->data['price'] : 0,
    1298                 'nickname'      => ( ! empty( $package->data ) ) ? $package->data['nickname'] : '',
    1299                 'box_weight'    => ( ! empty( $package->data ) ) ? $package->data['weight'] : 0,
    1300                 'box_max_weight'=> ( ! empty( $package->data ) ) ? $package->data['weight_max'] : 0,
    1301                 'package_code'  => ( ! empty( $package->data ) ) ? $package->data['preset'] : '',
    1302                 'carrier_code'  => ( ! empty( $package->data ) ) ? $package->data['carrier_code'] : '',
    1303             );
    1304 
    1305             $box_log[] = array(
    1306                 'is_packed'      => boolval( empty( $package->unpacked ) ),
    1307                 'item_count'     => count( $package->packed ),
    1308                 'items'          => $packed_items,
    1309                 'box_dimensions' => sprintf( '%s x %s x %s | %s | %s', $package->length, $package->width, $package->height, $package->weight, $package->volume ),
    1310                 'box_dim_key'    => sprintf( '%s x %s x %s | %s | %s',
    1311                     esc_html__( 'Length', 'live-rates-for-shipstation' ),
    1312                     esc_html__( 'Width', 'live-rates-for-shipstation' ),
    1313                     esc_html__( 'Height', 'live-rates-for-shipstation' ),
    1314                     esc_html__( 'Weight', 'live-rates-for-shipstation' ),
    1315                     esc_html__( 'Volume', 'live-rates-for-shipstation' ),
    1316                 ),
    1317                 'max_volume' => floatval( $package->width * $package->height * $package->length ),
    1318                 'data' => ( ! empty( $package->data ) ) ? $package->data : array(),
    1319             );
    1320 
    1321         }
    1322 
    1323         if( ! empty( $box_log ) ) {
    1324             $this->log( esc_html__( 'Custom Boxes Packed', 'live-rates-for-shipstation' ), 'info', $box_log );
    1325         }
    1326 
    1327         return $item_requests;
    1328 
    1329     }
    1330 
    1331 
    1332     /**
    1333792     * Set the rates based on cached packages.
    1334793     *
     
    1343802    protected function check_packages_rate_cache( $packages ) {
    1344803
     804
     805        /**
     806         * Maybe skip cart caches.
     807         * Do note that WooCommerce makes multiple calls to the cart / calculations.
     808         * Disabling this may result in many more API calls than expected.
     809         *
     810         * @hook filter
     811         *
     812         * @param Bolean TRUE
     813         *
     814         * @return Boolean
     815         */
     816        // Return Early - Filter Skips Cache.
     817        if( true !== apply_filters( 'iqlrss/cache/cart_rates', true ) ) return;
     818
    1345819        $session    = WC()->session->get( $this->plugin_prefix . '_packages', array() );
    1346820        $cleartime  = get_transient( \IQLRSS\Driver::plugin_prefix( 'wcs_timeout' ) );
     
    1359833        $size = count( $packages );
    1360834        for( $i = 0; $i < $size; $i++ ) {
    1361 
    1362835            $cache = WC()->session->get( 'shipping_for_package_' . $i, false );
    1363             if( empty( $cache ) || ! is_array( $cache ) ) {
    1364                 break;
    1365             }
     836            if( empty( $cache ) || ! is_array( $cache ) ) break;
    1366837            $this->rates = array_merge( $cache['rates'], $this->rates );
    1367 
    1368838        }
    1369839
     
    1469939
    1470940    /**
    1471      * Return an m-array of enabled services grouped by carrier key.
    1472      *
    1473      * @return Array
    1474      */
    1475     public function get_enabled_services() {
    1476 
    1477         $enabled = array();
    1478         $saved_services = $this->get_option( 'services', array() );
    1479         if( empty( $saved_services ) ) return $enabled;
    1480 
    1481         foreach( $saved_services as $c => $sa ) {
    1482             foreach( $sa as $sk => $s ) {
    1483                 if( ! isset( $s['enabled'] ) || ! $s['enabled'] ) continue;
    1484                 $enabled[ $c ][ $sk ] = $s;
    1485             }
    1486         }
    1487 
    1488         return $enabled;
    1489 
    1490     }
    1491 
    1492 
    1493     /**
    1494941     * Convert a WooCommerce unit to a ShipStation unit.
    1495      * 
     942     *
    1496943     * @param String $unit
    1497      * 
     944     *
    1498945     * @return String $new_unit
    1499946     */
     
    1515962        }
    1516963
    1517         $global_carriers= $this->shipStationApi->get_carriers();
    1518         $carrier_codes  = wp_list_pluck( $global_carriers, 'carrier_code' );
    1519         $carrier_codes  = array_intersect_key( $carrier_codes, array_flip( $this->carriers ) );
     964        $global_carriers  = $this->shipStationApi->get_carriers();
     965        $carrier_codes    = wp_list_pluck( $global_carriers, 'carrier_code' );
     966        $carrier_codes    = array_intersect_key( $carrier_codes, array_flip( $this->carriers ) );
     967        $carrier_packages = array();
    1520968
    1521969        $data = array(
     
    1534982        );
    1535983
     984        // Append ShipStation Packages
     985        $sspackages = $this->shipStationApi->get_packages();
     986        if( ! is_wp_error( $sspackages ) && ! empty( $sspackages ) ) {
     987
     988            $carrier_packages['shipstation'] = array(
     989                'label'     => esc_html__( 'ShipStation' ),
     990                'packages'  => array(),
     991            );
     992
     993            foreach( $sspackages as $package ) {
     994                $carrier_packages['shipstation']['packages'][] = array(
     995                    'label'         => $package['name'],
     996                    'code'          => $package['package_id'],
     997                    'length'        => $package['dimensions']['length'],
     998                    'width'         => $package['dimensions']['width'],
     999                    'height'        => $package['dimensions']['height'],
     1000                    'weight_max'    => '',
     1001                    'carrier_code'  => '',
     1002                );
     1003            }
     1004        }
     1005
    15361006        // Append Translated Labels
    1537         $carrier_packages = array();
    15381007        foreach( $data as $carrier_code => &$carriers ) {
    15391008
     
    16401109    }
    16411110
    1642 
    1643     /**
    1644      * Log error in WooCommerce
    1645      * Passthru method - log what's given and give it back.
    1646      * Could make a good Trait
    1647      *
    1648      * @param Mixed $error      - String or WP_Error
    1649      * @param String $level     - WooCommerce level (debug|info|notice|warning|error|critical|alert|emergency)
    1650      * @param Array $context
    1651      *
    1652      * @return Mixed - Return the error back.
    1653      */
    1654     protected function log( $error, $level = 'debug', $context = array() ) {
    1655 
    1656         if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', 0, true ) ) {
    1657             return $error;
    1658         }
    1659 
    1660         if( is_wp_error( $error ) ) {
    1661             $error_msg = sprintf( '(%s) %s', $error->get_error_code(), $error->get_error_message() );
    1662         } else {
    1663             $error_msg = $error;
    1664         }
    1665 
    1666         if( class_exists( '\WC_Logger' ) ) {
    1667 
    1668             if( null === $this->logger ) {
    1669                 $this->logger = \wc_get_logger();
    1670             }
    1671 
    1672             $this->logger->log( $level, $error_msg, array_merge( $context, array( 'source' => 'live-rates-for-shipstation' ) ) );
    1673 
    1674         }
    1675 
    1676         return $error;
    1677 
    1678     }
    1679 
    16801111}
  • live-rates-for-shipstation/trunk/core/traits/logger.php

    r3442676 r3452263  
    2222     * @return Mixed - Return the error back.
    2323     */
    24     protected function log( $error, $level = 'debug', $context = array() ) {
     24    protected function log( $error, $level = 'info', $context = array() ) {
    2525
    2626        if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', 0, true ) ) {
     
    3737        if( class_exists( '\WC_Logger' ) ) {
    3838
     39            /**
     40             * The WC_Logger does not handle double quotes well.
     41             * This will convert double quotes to faux: " -> ''
     42             */
     43            array_walk_recursive( $context, function( &$val ) {
     44                $val = ( is_string( $val ) ) ? str_replace( '"', "''", $val ) : $val;
     45            } );
     46
    3947            $logger = \wc_get_logger();
    4048            $logger->log( $level, $error_msg, array_merge( $context, array( 'source' => 'live-rates-for-shipstation' ) ) );
  • live-rates-for-shipstation/trunk/live-rates-for-shipstation.php

    r3442676 r3452263  
    44 * Plugin URI: https://iqcomputing.com/contact/
    55 * Description: ShipStation shipping method with live rates.
    6  * Version: 1.1.2
    7  * Requries at least: 6.2
     6 * Version: 1.2.0
     7 * Requires at least: 6.2
    88 * Author: IQComputing
    99 * Author URI: https://iqcomputing.com/
     
    2626     * @var String
    2727     */
    28     protected static $version = '1.1.2';
     28    protected static $version = '1.2.0';
    2929
    3030
     
    229229        Core\Rest_Router::initialize();
    230230        Core\Settings_Shipstation::initialize();
     231        Core\Admin_Edit_Order::initialize();
     232
    231233    }
    232234
     
    235237
    236238/**
    237  * Class Autoloader
    238  *
    239  * @param String $class
     239 * Autoload and Drive!
    240240 */
    241 spl_autoload_register( function( $class ) {
    242 
    243     if( false === strpos( $class, __NAMESPACE__ . '\\' ) ) {
    244         return $class;
    245     }
    246 
    247     $class_path = str_replace( __NAMESPACE__ . '\\', '', $class );
    248     $class_path = str_replace( '_', '-', strtolower( $class_path ) );
    249     $class_path = str_replace( '\\', '/', $class_path );
    250     $file_path  = wp_normalize_path( sprintf( '%s/%s',
    251         rtrim( plugin_dir_path( __FILE__ ), '\\/' ),
    252         $class_path . '.php'
    253     ) );
    254 
    255     if( file_exists( $file_path ) ) {
    256         require_once $file_path;
    257     }
    258 
    259 } );
    260241require_once rtrim( __DIR__, '\\/' ) . '/_autoload.php';
    261242add_action( 'plugins_loaded', array( '\IQLRSS\Driver', 'drive' ), 8 );
  • live-rates-for-shipstation/trunk/readme.txt

    r3442676 r3452263  
    44Requires at least: 5.9
    55Tested up to: 6.8
    6 Stable tag: 1.1.2
     6Stable tag: 1.2.0
    77License: GPLv3 or later
    88License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    5151== Changelog ==
    5252
     53= 1.2.0 (2026-02-02) =
     54* Adds Warehouse Support (Global and Zone based).
     55* Adds ShipStation Packages into Custom Packages on a Shipping Zone.
     56* Moves shipping calculations into a dedicated class with filer hook to override.
     57* New `iqlrss/cache/shipstation` filter hook.
     58* New `iqlrss/cache/shipstation_expires` filter hook.
     59* New `iqlrss/cache/cart_rates` filter hook.
     60* New `iqlrss/shipping/calculator_object` filter hook.
     61
    5362= 1.1.2 (2026-01-19) =
    5463* Patched an issue where rate caching would not account for a destination change.
     
    6069= 1.1.1 (2025-12-04) =
    6170* Fixed JS conflict with WordPress 6.9 (nice!)
    62 
    63 = 1.1.0 (2025-12-01) =
    64 * Redux the Custom Packaging screen and options.
    65 * Packing option for Weight Only.
    66 * Packing option for Stacked Vertically.
    67 * Packing option for default product weight.
    68 * Custom Package Presets from UPS, FedEx, and USPS.
    69 * New filter hook for Shipping Zone Settings `iqlrss/zone/settings`. Useful for managing Product Packing options.
    70 * New filter hook for Shipping Zone Settings `iqlrss/zone/package_presets`. Useful for managing Custom Package presets.
    71 * New filter hook for Shipping Estimates `iqlrss/shipping/packages`. Useful for modifying what gets sent to ShipStation API for retrieving shipping estimates.
Note: See TracChangeset for help on using the changeset viewer.