* carry the test mode metadata directly. * * @param \WC_Abstract_Order $order Order object. * @return bool * * @since 10.7.0 */ public static function is_test_order( $order ) { if ( ! $order instanceof \WC_Abstract_Order ) { return false; } // For refunds, check the parent order. $check_order = $order; if ( 'shop_order_refund' === $order->get_type() ) { $check_order = wc_get_order( $order->get_parent_id() ); if ( ! $check_order instanceof \WC_Abstract_Order ) { return false; } } $is_test = 'test' === $check_order->get_meta( '_wcpay_mode' ); /** * Filter whether an order is a test order excluded from analytics. * * Use this filter to customize test order detection beyond the default * WCPay test mode check, e.g., to exclude orders from other payment * gateways' test/sandbox modes. * * @param bool $is_test Whether the order is a test order. * @param \WC_Abstract_Order $order The order being checked (for refunds, this is the parent order). * * @since 10.7.0 */ return apply_filters( 'woocommerce_analytics_is_test_order', $is_test, $check_order ); } /** * Get the recorded failed order imports. * * @internal * @since 11.0.0 * @return array Array with 'ids' (int[]) and 'overflow' (int) keys. */ public static function get_failed_order_imports(): array { $value = get_option( self::FAILED_ORDER_IMPORTS_OPTION, array() ); if ( ! is_array( $value ) ) { $value = array(); } return array( 'ids' => isset( $value['ids'] ) && is_array( $value['ids'] ) ? array_map( 'absint', $value['ids'] ) : array(), 'overflow' => isset( $value['overflow'] ) ? absint( $value['overflow'] ) : 0, ); } /** * Record an order ID that failed analytics import. * * Deduplicates IDs. When the list exceeds FAILED_ORDER_IMPORTS_CAP, the * oldest ID is dropped and the overflow counter is incremented. * * @internal * @since 11.0.0 * @param int $order_id Order or refund ID that failed to import. * @return void */ public static function record_failed_order_import( $order_id ): void { $order_id = absint( $order_id ); if ( ! $order_id ) { return; } $failed = self::get_failed_order_imports(); if ( in_array( $order_id, $failed['ids'], true ) ) { return; } $failed['ids'][] = $order_id; $ids_count = count( $failed['ids'] ); while ( $ids_count > self::FAILED_ORDER_IMPORTS_CAP ) { array_shift( $failed['ids'] ); ++$failed['overflow']; --$ids_count; } update_option( self::FAILED_ORDER_IMPORTS_OPTION, $failed, false ); } /** * Remove an order ID from the failed imports list. * * Called after a successful import so the list always reflects orders * that have not been imported since their last failure. * * @internal * @since 11.0.0 * @param int $order_id Order or refund ID to remove. * @return void */ public static function clear_failed_order_import( $order_id ): void { $order_id = absint( $order_id ); if ( ! $order_id ) { return; } $failed = self::get_failed_order_imports(); $index = array_search( $order_id, $failed['ids'], true ); if ( false === $index ) { return; } unset( $failed['ids'][ $index ] ); $failed['ids'] = array_values( $failed['ids'] ); if ( empty( $failed['ids'] ) && 0 === $failed['overflow'] ) { delete_option( self::FAILED_ORDER_IMPORTS_OPTION ); return; } update_option( self::FAILED_ORDER_IMPORTS_OPTION, $failed, false ); } /** * Reset the failed order imports overflow counter. * * Called when a full (non-windowed) historical import starts, since that * import covers the orders whose IDs were dropped from the list. Windowed * imports must not reset the counter. * * @internal * @since 11.0.0 * @return void */ public static function reset_failed_order_imports_overflow(): void { $failed = self::get_failed_order_imports(); if ( 0 === $failed['overflow'] ) { return; } $failed['overflow'] = 0; if ( empty( $failed['ids'] ) ) { delete_option( self::FAILED_ORDER_IMPORTS_OPTION ); return; } update_option( self::FAILED_ORDER_IMPORTS_OPTION, $failed, false ); } /** * Delete a batch of orders. * * @internal * @param int $batch_size Number of items to delete. * @return void */ public static function delete( $batch_size ) { global $wpdb; $order_ids = $wpdb->get_col( $wpdb->prepare( "SELECT order_id FROM {$wpdb->prefix}wc_order_stats ORDER BY order_id ASC LIMIT %d", $batch_size ) ); foreach ( $order_ids as $order_id ) { OrdersStatsDataStore::delete_order( $order_id ); } } /** * Check whether scheduled import is enabled. * * Checks the scheduled import option value. * * @internal * @since 10.5.0 Introduced as a private method. * @since 11.0.0 Made public. * @return bool */ public static function is_scheduled_import_enabled(): bool { $value = get_option( self::SCHEDULED_IMPORT_OPTION, false ); if ( false !== $value ) { return 'yes' === $value; } // Fall back to the legacy option (pre-10.5.0) which used inverted semantics: // 'yes' meant immediate import (= not scheduled), 'no' meant scheduled. $legacy_value = get_option( self::LEGACY_IMMEDIATE_IMPORT_OPTION, false ); if ( false !== $legacy_value ) { return 'no' === $legacy_value; } // Neither option exists — use the default (not scheduled). return false; } }