From 72cb02ac47456b1d0b77c6ef54d84a7842c22918 Mon Sep 17 00:00:00 2001 From: "Jorge A. Torres" Date: Fri, 15 Mar 2024 08:31:14 -0300 Subject: [PATCH] Try to account for and re-create backup posts when syncing orders (#45332) --- .../woocommerce/changelog/fix-42746-part-2 | 4 ++ .../DataStores/Orders/DataSynchronizer.php | 39 ++++++------ .../Orders/OrdersTableDataStore.php | 60 +++++++++++++++---- 3 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-42746-part-2 diff --git a/plugins/woocommerce/changelog/fix-42746-part-2 b/plugins/woocommerce/changelog/fix-42746-part-2 new file mode 100644 index 00000000000..792c9fda7f0 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-42746-part-2 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Make sure backup posts are restored during sync when HPOS is enabled. diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php index c1a3edc61aa..7bb47ab96d3 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php @@ -442,7 +442,7 @@ class DataSynchronizer implements BatchProcessorInterface { * * @return int */ - public function get_current_orders_pending_sync_count_cached() : int { + public function get_current_orders_pending_sync_count_cached(): int { return $this->get_current_orders_pending_sync_count( true ); } @@ -483,14 +483,14 @@ class DataSynchronizer implements BatchProcessorInterface { return 0; } + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQL.NotPrepared -- + // -- $order_post_type_placeholder, $orders_table, self::PLACEHOLDER_ORDER_POST_TYPE are all safe to use in queries. if ( ! $this->get_table_exists() ) { $count = $wpdb->get_var( - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $order_post_type_placeholder is prepared. $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->posts where post_type in ( $order_post_type_placeholder )", $order_post_types ) - // phpcs:enable ); return $count; } @@ -499,30 +499,28 @@ class DataSynchronizer implements BatchProcessorInterface { $missing_orders_count_sql = $wpdb->prepare( " SELECT COUNT(1) FROM $wpdb->posts posts -INNER JOIN $orders_table orders ON posts.id=orders.id -WHERE posts.post_type = '" . self::PLACEHOLDER_ORDER_POST_TYPE . "' - AND orders.status not in ( 'auto-draft' ) +RIGHT JOIN $orders_table orders ON posts.ID=orders.id +WHERE (posts.post_type IS NULL OR posts.post_type = '" . self::PLACEHOLDER_ORDER_POST_TYPE . "') + AND orders.status NOT IN ( 'auto-draft' ) AND orders.type IN ($order_post_type_placeholder)", $order_post_types ); $operator = '>'; } else { - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $order_post_type_placeholder is prepared. $missing_orders_count_sql = $wpdb->prepare( " SELECT COUNT(1) FROM $wpdb->posts posts -LEFT JOIN $orders_table orders ON posts.id=orders.id +LEFT JOIN $orders_table orders ON posts.ID=orders.id WHERE posts.post_type in ($order_post_type_placeholder) AND posts.post_status != 'auto-draft' AND orders.id IS NULL", $order_post_types ); - // phpcs:enable + $operator = '<'; } - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $missing_orders_count_sql is prepared. $sql = $wpdb->prepare( " SELECT( @@ -604,10 +602,9 @@ SELECT( $order_post_types = wc_get_order_types( 'cot-migration' ); $order_post_type_placeholders = implode( ', ', array_fill( 0, count( $order_post_types ), '%s' ) ); - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQL.NotPrepared switch ( $type ) { case self::ID_TYPE_MISSING_IN_ORDERS_TABLE: - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $order_post_type_placeholders is prepared. $sql = $wpdb->prepare( " SELECT posts.ID FROM $wpdb->posts posts @@ -619,23 +616,22 @@ WHERE ORDER BY posts.ID ASC", $order_post_types ); - // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare break; case self::ID_TYPE_MISSING_IN_POSTS_TABLE: $sql = $wpdb->prepare( " -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 . "' -AND orders.status not in ( 'auto-draft' ) +SELECT orders.id FROM $wpdb->posts posts +RIGHT JOIN $orders_table orders ON posts.ID=orders.id +WHERE (posts.post_type IS NULL OR posts.post_type = '" . self::PLACEHOLDER_ORDER_POST_TYPE . "') +AND orders.status NOT IN ( 'auto-draft' ) AND orders.type IN ($order_post_type_placeholders) -ORDER BY posts.id ASC", +ORDER BY posts.ID ASC", $order_post_types ); break; case self::ID_TYPE_DIFFERENT_UPDATE_DATE: $operator = $this->custom_orders_table_is_authoritative() ? '>' : '<'; - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $order_post_type_placeholders is prepared. + $sql = $wpdb->prepare( " SELECT orders.id FROM $orders_table orders @@ -647,7 +643,6 @@ ORDER BY orders.id ASC ", $order_post_types ); - // phpcs:enable break; case self::ID_TYPE_DELETED_FROM_ORDERS_TABLE: return $this->get_deleted_order_ids( true, $limit ); @@ -656,7 +651,7 @@ ORDER BY orders.id ASC default: throw new \Exception( 'Invalid $type, must be one of the ID_TYPE_... constants.' ); } - // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:enable // phpcs:ignore WordPress.DB return array_map( 'intval', $wpdb->get_col( $sql . " LIMIT $limit" ) ); @@ -701,7 +696,7 @@ ORDER BY orders.id ASC * * @param array $batch Batch details. */ - public function process_batch( array $batch ) : void { + public function process_batch( array $batch ): void { if ( empty( $batch ) ) { return; } diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php index b1d32144ede..ecae3c7f297 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php @@ -589,11 +589,24 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements } self::$backfilling_order_ids[] = $order->get_id(); + + // Attempt to create the backup post if missing. + if ( $order->get_id() && is_null( get_post( $order->get_id() ) ) ) { + if ( ! $this->maybe_create_backup_post( $order, 'backfill' ) ) { + // translators: %d is an order ID. + $this->error_logger->warning( sprintf( __( 'Unable to create backup post for order %d.', 'woocommerce' ), $order->get_id() ) ); + return; + } + } + $this->update_order_meta_from_object( $order ); $order_class = get_class( $order ); $post_order = new $order_class(); $post_order->set_id( $order->get_id() ); - $cpt_data_store->read( $post_order ); + + if ( $cpt_data_store->order_exists( $order->get_id() ) ) { + $cpt_data_store->read( $post_order ); + } // This compares the order data to the post data and set changes array for props that are changed. $post_order->set_props( $order->get_data() ); @@ -1822,20 +1835,10 @@ FROM $order_meta_table * @since 6.8.0 */ protected function persist_order_to_db( &$order, bool $force_all_fields = false ) { - $context = ( 0 === absint( $order->get_id() ) ) ? 'create' : 'update'; - $data_sync = wc_get_container()->get( DataSynchronizer::class ); + $context = ( 0 === absint( $order->get_id() ) ) ? 'create' : 'update'; if ( 'create' === $context ) { - $post_id = wp_insert_post( - array( - 'post_type' => $data_sync->data_sync_is_enabled() ? $order->get_type() : $data_sync::PLACEHOLDER_ORDER_POST_TYPE, - 'post_status' => 'draft', - 'post_parent' => $order->get_changes()['parent_id'] ?? $order->get_data()['parent_id'] ?? 0, - 'post_date' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ), - 'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), - ) - ); - + $post_id = $this->maybe_create_backup_post( $order, 'create' ); if ( ! $post_id ) { throw new \Exception( esc_html__( 'Could not create order in posts table.', 'woocommerce' ) ); } @@ -1871,6 +1874,37 @@ FROM $order_meta_table $this->set_custom_taxonomies( $order, $default_taxonomies ); } + /** + * Takes care of creating the backup post in the posts table (placeholder or actual order post, depending on sync settings). + * + * @since 8.8.0 + * + * @param \WC_Abstract_Order $order The order. + * @param string $context The context: either 'create' or 'backfill'. + * @return int The new post ID. + */ + protected function maybe_create_backup_post( &$order, string $context ): int { + $data_sync = wc_get_container()->get( DataSynchronizer::class ); + + $data = array( + 'post_type' => $data_sync->data_sync_is_enabled() ? $order->get_type() : $data_sync::PLACEHOLDER_ORDER_POST_TYPE, + 'post_status' => 'draft', + 'post_parent' => $order->get_changes()['parent_id'] ?? $order->get_data()['parent_id'] ?? 0, + 'post_date' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ), + 'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), + ); + + if ( 'backfill' === $context ) { + if ( ! $order->get_id() ) { + return 0; + } + + $data['import_id'] = $order->get_id(); + } + + return wp_insert_post( $data ); + } + /** * Set default taxonomies for the order. *