status != 'auto-draft' AND orders.id IS NULL"; break; case self::ID_TYPE_MISSING_IN_POSTS_TABLE: $sql = " SELECT posts.ID FROM $wpdb->posts posts INNER JOIN $orders_table orders ON posts.id=orders.id WHERE posts.post_type = '" . self::PLACEHOLDER_ORDER_POST_TYPE . "'"; break; case self::ID_TYPE_DIFFERENT_UPDATE_DATE: $sql = " SELECT orders.id FROM $orders_table orders JOIN $wpdb->posts posts on posts.ID = orders.id WHERE posts.post_type = 'shop_order' AND orders.date_updated_gmt != posts.post_modified_gmt"; break; default: throw new \Exception( 'Invalid $type, must be one of the ID_TYPE_... constants.' ); } // phpcs:ignore WordPress.DB return array_map( 'intval', $wpdb->get_col( $sql . " LIMIT $limit" ) ); } /** * Start an orders synchronization process if all the following is true: * * 1. Data synchronization is enabled. * 2. Data synchronization isn't already in progress ($force can be used to bypass this). * 3. There's at least one out of sync order. * * This will set up the appropriate status information and schedule the first synchronization batch. * * @param bool $force If true, (re)start the sync process even if it's already in progress. */ public function maybe_start_synchronizing_pending_orders( bool $force = false ) { if ( ! $this->data_sync_is_enabled() || ( $this->pending_data_sync_is_in_progress() && ! $force ) ) { return; } $initial_pending_count = $this->get_current_orders_pending_sync_count(); if ( 0 === $initial_pending_count ) { return; } update_option( self::INITIAL_ORDERS_PENDING_SYNC_COUNT_OPTION, $initial_pending_count ); $queue = WC()->get_instance_of( \WC_Queue::class ); $queue->cancel_all( self::ORDERS_SYNC_SCHEDULED_ACTION_CALLBACK ); update_option( self::PENDING_SYNC_IS_IN_PROGRESS_OPTION, 'yes' ); $this->schedule_pending_orders_synchronization(); } /** * Schedule the next orders synchronization batch. */ private function schedule_pending_orders_synchronization() { $queue = WC()->get_instance_of( \WC_Queue::class ); $queue->schedule_single( WC()->call_function( 'time' ) + self::SECONDS_BETWEEN_BATCH_SYNCS, self::ORDERS_SYNC_SCHEDULED_ACTION_CALLBACK, array(), 'woocommerce-db-updates' ); } /** * Run one orders synchronization batch. */ private function do_pending_orders_synchronization() { if ( ! $this->pending_data_sync_is_in_progress() ) { return; } // TODO: Remove the usage of the fake pending orders count once development of the feature is complete. $fake_count = get_option( self::FAKE_ORDERS_PENDING_SYNC_COUNT_OPTION ); if ( false !== $fake_count ) { update_option( 'woocommerce_fake_orders_pending_sync_count', (int) $fake_count - 1 ); } else { $this->sync_next_batch(); } if ( 0 === $this->get_current_orders_pending_sync_count() ) { $this->cleanup_synchronization_state(); /** * Hook to signal that the orders tables synchronization process has finished (nothing left to synchronize). * * @since 6.5.0 */ do_action( self::PENDING_SYNCHRONIZATION_FINISHED_ACTION ); } else { $this->schedule_pending_orders_synchronization(); } } /** * Processes a batch of out of sync orders. * First it synchronizes orders that don't exist in the backup table, and after that, * it synchronizes orders that exist in both tables but have a different last update date. * * @return void */ private function sync_next_batch(): void { /** * Filter to customize the count of orders that will be synchronized in each step of the custom orders table to/from posts table synchronization process. * * @since 6.6.0 * * @param int Default value for the count. */ $batch_size = apply_filters( 'woocommerce_orders_cot_and_posts_sync_step_size', self::ORDERS_SYNC_BATCH_SIZE ); if ( $this->custom_orders_table_is_authoritative() ) { $order_ids = $this->get_ids_of_orders_pending_sync( self::ID_TYPE_MISSING_IN_POSTS_TABLE, $batch_size ); // TODO: Load $order_ids orders from the orders table and create them (by updating the corresponding placeholder record) in the posts table. } else { $order_ids = $this->get_ids_of_orders_pending_sync( self::ID_TYPE_MISSING_IN_ORDERS_TABLE, $batch_size ); $this->posts_to_cot_migrator->migrate_orders( $order_ids ); } $batch_size -= count( $order_ids ); if ( 0 === $batch_size ) { return; } $order_ids = $this->get_ids_of_orders_pending_sync( self::ID_TYPE_DIFFERENT_UPDATE_DATE, $batch_size ); if ( 0 === count( $order_ids ) ) { return; } // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf if ( $this->custom_orders_table_is_authoritative() ) { // TODO: Load $order_ids orders from the orders table and update them in the posts table. } else { $this->posts_to_cot_migrator->migrate_orders( $order_ids ); } } /** * Cleanup all the synchronization status information, * because the process has been disabled by the user via settings, * or because there's nothing left to syncrhonize. */ public function cleanup_synchronization_state() { delete_option( self::INITIAL_ORDERS_PENDING_SYNC_COUNT_OPTION ); delete_option( self::PENDING_SYNC_IS_IN_PROGRESS_OPTION ); } }