# routeapp/2.4.13/admin/class-routeapp-order-recover.php

Route ‑ Shipping Protection, version 2.4.13. 355 lines.

- Page: https://pluginprobe.com/plugins/routeapp/2.4.13/code/admin/class-routeapp-order-recover.php
- Raw: https://pluginprobe.com/plugins/routeapp/2.4.13/raw/admin/class-routeapp-order-recover.php
- Modified: 2026-08-10T14:17:54+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/routeapp/2.4.13/code/admin/class-routeapp-order-recover.php#L10-L20`.

```php
<?php
/**
 * Class Routeapp_Order_Recover file.
 *
 * @package Routeapp\Admin
 */

class Routeapp_Order_Recover {

    public function __construct() {
        // Ensure the AJAX actions are registered
        add_action('wp_ajax_routeapp_save_orders', array($this, 'save'));
        add_action('wp_ajax_routeapp_process_orders_batch', array($this, 'process_orders_batch'));

    }

    /**
     * Save function to handle the processing of orders based on specified date range and batch size.
     * This function processes orders in batches to avoid timeouts and server overload.
     * It checks if the necessary POST parameters are set, fetches the orders in batches,
     * processes each batch, and updates the order count accordingly.
     */
    public function save() {
        if ($this->isPostRequestValid()) {
            $recoverFrom = $_POST['routeapp_order_recover_from'];
            $recoverTo = $_POST['routeapp_order_recover_to'];

            // Get the total number of orders in the specified date range
            $orderCount = $this->getOrderCount($recoverFrom, $recoverTo);

            // Determine batch size based on the number of orders
            $batchSize = $this->determineBatchSize($orderCount);

            // Determine wait time based on batch size
            $waitTime = $this->determineWaitTimeParameter($batchSize);

            // Send back the initial response with the calculated values
            wp_send_json_success(array(
              'orderCount' => $orderCount,
              'batchSize' => $batchSize,
              'recoverFrom' => $recoverFrom,
              'recoverTo' => $recoverTo,
              'waitTime' => $waitTime,
            ));
        }
        else {
            wp_send_json_error(array(
              'message' => __('Error', 'routeapp'),
            ));
        }
        wp_die();
    }

    // New function to handle batch processing via AJAX
    public function process_orders_batch() {

        if ($this->isPostRequestValid()) {
            $recoverFrom = $_POST['routeapp_order_recover_from'];
            $recoverTo = $_POST['routeapp_order_recover_to'];
            $batchSize = $_POST['batchSize'];
            $offset = $_POST['offset'];

            $orders = $this->getOrdersBatch($batchSize, $offset, $recoverTo, $recoverFrom);

            if (!empty($orders)) {
                $process_result = $this->processOrders($orders);
                $response = array(
                  'processed' => count($orders),
                );
                if (!empty($process_result['tracking_warning'])) {
                    $response['tracking_warning'] = $process_result['tracking_warning'];
                }
                wp_send_json_success($response);
            } else {
                wp_send_json_error(array(
                  'message' => __('No more orders to process.', 'routeapp'),
                ));
            }
        }
        else {
            wp_send_json_error(array(
              'message' => __('Error', 'routeapp'),
            ));
        }
        wp_die();
    }

    /**
     * Validates the POST request to ensure required parameters are set.
     *
     * @return bool Returns true if the POST request contains 'routeapp_order_recover_from' and 'routeapp_order_recover_to'.
     */
    private function isPostRequestValid() {
        return isset($_POST) && isset($_POST['routeapp_order_recover_from']) && isset($_POST['routeapp_order_recover_to']);
    }


    /**
     * Fetches a batch of orders based on the specified batch size and offset.
     *
     * @param int $batchSize The number of orders to fetch in one batch.
     * @param int $offset The starting point for fetching orders.
     * @return array The fetched orders based on the specified arguments.
     */
    private function getOrdersBatch($batchSize, $offset, $recoverTo, $recoverFrom) {
        $args = array(
            'limit'        => (int) $batchSize,
            'offset'       => (int) $offset,
            'type'         => 'shop_order',
            'date_created' => $recoverFrom . '...' . $recoverTo,
        );

        return wc_get_orders( $args );
    }

    /**
     * Processes the fetched orders by either updating order post meta or performing a massive order save operation.
     * Optionally syncs tracking/shipments when the Sync tracking numbers checkbox is enabled.
     *
     * @param array $orders The fetched orders to be processed.
     * @return array{
     *     tracking_warning?: string
     * }
     */
    private function processOrders($orders) {
        $result = array();

        if (isset($_POST['routeapp_order_recover_reconcile_backend']) && $_POST['routeapp_order_recover_reconcile_backend']) {
            $this->updateOrderPostMeta($orders);
        } else {
            $this->massiveOrderSave($orders);
        }

        if (isset($_POST['routeapp_order_recover_sync_tracking']) && $_POST['routeapp_order_recover_sync_tracking']) {
            $tracking_warning = $this->syncOrderTrackings($orders);
            if ($tracking_warning) {
                $result['tracking_warning'] = $tracking_warning;
            }
        }

        return $result;
    }

    /**
     * Force-sync tracking numbers / shipments to Route for the given orders.
     * Clears local shipment sync flags first so providers re-check Route and
     * create missing shipments, then runs the original provider update paths.
     *
     * @param array $orders Orders to sync tracking for.
     * @return string|null Warning message when tracking sync could not run, otherwise null.
     */
    private function syncOrderTrackings($orders) {
        $routeapp = Routeapp_Cron_Schedules::get_route_public_instance();
        if (!$routeapp) {
            $warning = __( 'Tracking sync skipped: Route App instance is unavailable. Confirm the plugin is active and configured.', 'routeapp' );
            error_log( '[routeapp] order_recover_sync_tracking: ' . $warning );
            return $warning;
        }

        $shipment_tracking = new Routeapp_Shipment_Tracking();

        foreach ($orders as $order) {
            if (!$order) {
                continue;
            }

            $order_id = $order->get_id();

            try {
                $this->clearLocalShipmentSyncMeta($order);
                $shipment_tracking->update($order_id, $routeapp);
                $shipment_tracking->parse_order_notes($order_id, $routeapp);
            } catch (Exception $exception) {
                // Keep processing the rest of the batch if one order fails.
                $routeapp->routeapp_log($exception, array(
                    'endpoint' => 'order_recover_sync_tracking',
                    'params'   => array(
                        'source_order_id' => $order_id,
                    ),
                    'method'   => 'POST',
                ));
                continue;
            }
        }

        return null;
    }

    /**
     * Remove local meta that marks shipment sync as already completed.
     * Needed to force a resync when Woo meta says success but Route has no shipment.
     *
     * @param \WC_Order $order Order to clear.
     * @return void
     */
    private function clearLocalShipmentSyncMeta($order) {
        $meta_keys = array(
            'routeapp_shipment_api_called',
            'routeapp_shipment_tracking_number',
            'routeapp_shipment_tracking_provider',
            'routeapp_shipment_cron_api_called',
        );

        if ( $this->is_hpos_enabled() ) {
            $deleted = false;
            foreach ( $meta_keys as $meta_key ) {
                if ( $order->meta_exists( $meta_key ) ) {
                    $order->delete_meta_data( $meta_key );
                    $deleted = true;
                }
            }
            if ( $deleted ) {
                $order->save();
            }
            return;
        }

        foreach ( $meta_keys as $meta_key ) {
            delete_post_meta( $order->get_id(), $meta_key );
        }
    }
    
    /**
     * Trigger save for all the selected orders, which will trigger the webhook for order.update
     *
     * @param $orders
     * @return void
     */
    public function massiveOrderSave($orders) {
        foreach ($orders as $order) {
            Routeapp_Cron_Schedules::updateOrderStatusMetaIfNeeded( $order, false );
            $order->save();
        }
    }

    /**
     * Update order post_meta with route order data
     *
     * @param $orders
     * @return void
     */
    public function updateOrderPostMeta($orders) {
        foreach ($orders as $order) {
            Routeapp_Cron_Schedules::updateOrderStatusMetaIfNeeded( $order );

            $has_route_meta = method_exists( $order, 'get_meta' )
                ? $order->get_meta( '_routeapp_order_id' )
                : get_post_meta( $order->get_id(), '_routeapp_order_id', true );
            if ( ! $has_route_meta ) {
                //check if order exists on Route side
                $getOrderResponse = Routeapp_API_Client::getInstance()->get_order($order->get_id());
                if (is_wp_error($getOrderResponse) || $getOrderResponse['response']['code'] != 200) {
                    continue;
                }
                Routeapp_Cron_Schedules::updateOrderPostMeta($order, $getOrderResponse);
            }
        }
    }

    /**
     * Get the count of orders based on the specified date range.
     * Supports both High-performance order storage (HPOS) and WordPress posts storage (legacy).
     *
     * @param string $from Starting date for the orders (YYYY-MM-DD).
     * @param string $to Ending date for the orders (YYYY-MM-DD).
     * @return int The count of orders.
     */
    public function getOrderCount($from, $to) {
        global $wpdb;

        $from_date = $from . ' 00:00:00';
        $to_date   = $to . ' 23:59:59';

        if ( $this->is_hpos_enabled() ) {
            // High-performance order storage: query wc_orders table
            $orders_table = $wpdb->prefix . 'wc_orders';
            $from_gmt     = get_gmt_from_date( $from_date );
            $to_gmt       = get_gmt_from_date( $to_date );

            $query = $wpdb->prepare(
                "SELECT COUNT(1)
                FROM {$orders_table} AS orders
                WHERE orders.type = 'shop_order'
                AND orders.status NOT IN ( 'wc-auto-draft', 'auto-draft', 'trash' )
                AND orders.date_created_gmt >= %s
                AND orders.date_created_gmt <= %s",
                $from_gmt,
                $to_gmt
            );
        } else {
            // WordPress posts storage (legacy): query wp_posts
            $query = $wpdb->prepare(
                "SELECT COUNT(1)
                FROM {$wpdb->posts} AS posts
                WHERE posts.post_type = 'shop_order'
                AND posts.post_status NOT IN ( 'wc-auto-draft', 'auto-draft', 'trash' )
                AND posts.post_date >= %s
                AND posts.post_date <= %s",
                $from_date,
                $to_date
            );
        }

        return (int) $wpdb->get_var( $query );
    }

    /**
     * Check if High-performance order storage (HPOS) is enabled.
     *
     * @return bool True if HPOS is enabled, false otherwise.
     */
    private function is_hpos_enabled() {
        if ( ! class_exists( 'Automattic\WooCommerce\Utilities\OrderUtil' ) ) {
            return false;
        }
        return \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
    }

    /**
     * Determine batch size based on the number of orders.
     *
     * @param int $orderCount The number of orders.
     * @return int The batch size.
     */
    private function determineBatchSize($orderCount) {
        switch (true) {
            case ($orderCount <= 1000):
                return 100;
            case ($orderCount <= 5000):
                return 50;
            case ($orderCount <= 10000):
                return 25;
            default:
                return 10; // Fallback batch size for more than 100,000 orders
        }
    }

    /**
     * Determine wait time based on the batch size.
     *
     * @param int $batchSize The batch size.
     * @return int The wait time.
     */
    private function determineWaitTimeParameter($batchSize) {
        switch ($batchSize) {
            case 100:
                return 10;
            case 50:
                return 5;
            default:
                return 2; // Fallback wait time
        }
    }
}

```
