prefix . 'wc_orders'; } private static function hpos_enabled() { return AIEO_Carts_Store::table_exists( self::orders_table() ); } public function run_capture( $capture_limit = 300, $resolve_limit = 800, $force = false ) { if ( ! $force && '1' !== (string) get_option( 'aieo_carts_capture_enabled', '1' ) ) { return array( 'captured' => 0, 'resolved' => 0, ); } if ( ! class_exists( 'AIEO_Carts_Store' ) || ! function_exists( 'wc_get_order' ) ) { return array( 'captured' => 0, 'resolved' => 0, ); } if ( class_exists( 'AIEO_Carts' ) ) { AIEO_Carts::maybe_install(); } if ( ! AIEO_Carts_Store::table_exists( AIEO_Carts_Store::carts_table() ) ) { return array( 'captured' => 0, 'resolved' => 0, ); } $restocked = $this->restocked_product_ids(); $captured = $this->capture_new_drafts( (int) $capture_limit, $restocked ); $resolved = $this->resolve_outcomes( (int) $resolve_limit ); update_option( self::LAST_OPT, array( 'time' => current_time( 'mysql', true ), 'captured' => $captured, 'resolved' => $resolved, ), false ); return array( 'captured' => $captured, 'resolved' => $resolved, ); } private function capture_new_drafts( $limit, array $restocked ) { global $wpdb; $carts = AIEO_Carts_Store::carts_table(); if ( self::hpos_enabled() ) { $o = self::orders_table(); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT o.id FROM `{$o}` o LEFT JOIN `{$carts}` c ON c.wc_order_id = o.id WHERE o.type = 'shop_order' AND o.status = 'wc-checkout-draft' AND c.id IS NULL ORDER BY o.id DESC LIMIT %d", $limit ) ); } else { $p = $wpdb->posts; $rows = $wpdb->get_results( $wpdb->prepare( "SELECT p.ID AS id FROM `{$p}` p LEFT JOIN `{$carts}` c ON c.wc_order_id = p.ID WHERE p.post_type = 'shop_order' AND p.post_status = 'wc-checkout-draft' AND c.id IS NULL ORDER BY p.ID DESC LIMIT %d", $limit ) ); } if ( empty( $rows ) ) { return 0; } $now = current_time( 'mysql', true ); $n = 0; foreach ( $rows as $r ) { $order = wc_get_order( (int) $r->id ); if ( ! $order ) { continue; } $lines = array(); $items_blob = array(); $has_restock = false; $count = 0; $subtotal = 0.0; foreach ( $order->get_items() as $item ) { if ( ! is_a( $item, 'WC_Order_Item_Product' ) ) { continue; } $pid = (int) $item->get_product_id(); $vid = (int) $item->get_variation_id(); $qty = (int) $item->get_quantity(); $line_total = (float) $item->get_total() + (float) $item->get_total_tax(); $product = $item->get_product(); $key = $vid ? $vid : $pid; $is_r = isset( $restocked[ $key ] ) || isset( $restocked[ $pid ] ); if ( $is_r ) { $has_restock = true; } $count += $qty; $subtotal += $line_total; $lines[] = array( 'product_id' => $pid, 'variation_id' => $vid, 'sku' => $product ? (string) $product->get_sku() : '', 'name' => (string) $item->get_name(), 'quantity' => $qty, 'line_total' => $line_total, 'is_restocked' => $is_r ? 1 : 0, ); $items_blob[] = array( 'product_id' => $pid, 'variation_id' => $vid, 'quantity' => $qty, ); } if ( empty( $lines ) ) { continue; } $created = $order->get_date_created(); $created_at = $created ? gmdate( 'Y-m-d H:i:s', $created->getTimestamp() ) : $now; AIEO_Carts_Store::insert_cart( array( 'token' => null, 'kind' => 'abandoned', 'source' => 'checkout_draft', 'wc_order_id' => (int) $r->id, 'user_id' => (int) $order->get_customer_id(), 'customer_email' => (string) $order->get_billing_email(), 'name' => null, 'items' => $items_blob, 'coupons' => $order->get_coupon_codes(), 'currency' => $order->get_currency(), 'item_count' => $count, 'subtotal' => $subtotal, 'total' => (float) $order->get_total(), 'status' => 'abandoned', 'outcome' => 'open', 'contains_restocked' => $has_restock ? 1 : 0, 'created_at' => $created_at, 'last_seen_at' => $now, ), $lines ); $n++; } return $n; } private function resolve_outcomes( $limit ) { global $wpdb; $carts = AIEO_Carts_Store::carts_table(); $open = $wpdb->get_results( $wpdb->prepare( "SELECT id, wc_order_id, created_at FROM `{$carts}` WHERE kind = 'abandoned' AND outcome = 'open' AND wc_order_id IS NOT NULL ORDER BY id ASC LIMIT %d", $limit ) ); if ( empty( $open ) ) { return 0; } $statuses = $this->order_statuses( wp_list_pluck( $open, 'wc_order_id' ) ); $now = current_time( 'mysql', true ); $stale_cut = gmdate( 'Y-m-d H:i:s', time() - ( self::STALE_DAYS * DAY_IN_SECONDS ) ); $drafts = array( 'wc-checkout-draft', 'auto-draft' ); $lost = array( 'trash', 'wc-failed', 'wc-cancelled' ); $n = 0; foreach ( $open as $row ) { $oid = (int) $row->wc_order_id; $upd = array( 'last_seen_at' => $now ); if ( ! isset( $statuses[ $oid ] ) ) { $upd['outcome'] = 'expired'; } else { $st = $statuses[ $oid ]; if ( in_array( $st, $drafts, true ) ) { if ( ! empty( $row->created_at ) && $row->created_at < $stale_cut ) { $upd['outcome'] = 'expired'; } } elseif ( in_array( $st, $lost, true ) ) { $upd['outcome'] = 'expired'; } else { $upd['outcome'] = 'converted'; $upd['converted_order_id'] = $oid; $upd['converted_at'] = $now; $ord = wc_get_order( $oid ); if ( $ord ) { $upd['total'] = (float) $ord->get_total(); } } } $wpdb->update( $carts, $upd, array( 'id' => (int) $row->id ) ); $n++; } return $n; } private function order_statuses( array $ids ) { global $wpdb; $ids = array_values( array_unique( array_filter( array_map( 'intval', $ids ) ) ) ); if ( empty( $ids ) ) { return array(); } $in = implode( ',', $ids ); $map = array(); if ( self::hpos_enabled() ) { $o = self::orders_table(); $res = $wpdb->get_results( "SELECT id, status FROM `{$o}` WHERE id IN ({$in})" ); } else { $p = $wpdb->posts; $res = $wpdb->get_results( "SELECT ID AS id, post_status AS status FROM `{$p}` WHERE ID IN ({$in})" ); } foreach ( (array) $res as $r ) { $map[ (int) $r->id ] = (string) $r->status; } return $map; } private function restocked_product_ids() { global $wpdb; $events = $wpdb->prefix . 'aieo_dmm_stock_recovery_events'; if ( ! AIEO_Carts_Store::table_exists( $events ) ) { return array(); } $ids = $wpdb->get_col( "SELECT DISTINCT product_id FROM `{$events}` WHERE reverted_at IS NULL" ); $map = array(); foreach ( (array) $ids as $id ) { $map[ (int) $id ] = true; } return $map; } }