Commit bf5534ac49f for woocommerce
commit bf5534ac49f7439dc85f5f6bc9226eebd5931c8b
Author: Jorge A. Torres <jorge.torres@automattic.com>
Date: Mon Oct 19 23:41:53 2026 -0500
[HPOS] Prevent deletion of order items when placeholder post is deleted via `wp_delete_post()` (#69573)
diff --git a/plugins/woocommerce/changelog/wooplug-7591-hpos-placeholder-deletion b/plugins/woocommerce/changelog/wooplug-7591-hpos-placeholder-deletion
new file mode 100644
index 00000000000..b4b4f57b5bc
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooplug-7591-hpos-placeholder-deletion
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Keep order items intact on HPOS stores when an order's placeholder post is deleted through WordPress functions.
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index da3620e3110..978bbbd236d 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -59496,7 +59496,7 @@ parameters:
-
message: '#^Access to property \$post_type on an unknown class Automattic\\WooCommerce\\Internal\\DataStores\\Orders\\WP_Post\.$#'
identifier: class.notFound
- count: 2
+ count: 1
path: src/Internal/DataStores/Orders/DataSynchronizer.php
-
diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php
index 43d5bb047fd..147eb413462 100644
--- a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php
+++ b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php
@@ -979,7 +979,7 @@ ORDER BY orders.id ASC
* @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed.
*/
public function maybe_prevent_deletion_of_post( $delete, $post ) {
- if ( self::PLACEHOLDER_ORDER_POST_TYPE !== $post->post_type && $this->custom_orders_table_is_authoritative() && $this->data_store->order_exists( $post->ID ) ) {
+ if ( $this->custom_orders_table_is_authoritative() && $this->data_store->order_exists( $post->ID ) ) {
$delete = false;
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php
index 7a92c84e386..04c2cd54b0f 100644
--- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php
@@ -278,6 +278,52 @@ class DataSynchronizerTests extends \HposTestCase {
);
}
+ /**
+ * @testdox When HPOS is authoritative and sync is disabled, deleting a post with the ID of an existing order is prevented and the order keeps its items.
+ *
+ * @testWith ["shop_order_placehold"]
+ * ["post"]
+ *
+ * @param string $post_type Post type of the post sharing the order's ID.
+ */
+ public function test_post_deletion_is_prevented_while_order_exists_in_hpos( string $post_type ): void {
+ global $wpdb;
+
+ $this->toggle_cot_authoritative( true );
+ $this->disable_cot_sync();
+
+ $order_id = OrderHelper::create_order()->get_id();
+ $wpdb->update( $wpdb->posts, array( 'post_type' => $post_type ), array( 'ID' => $order_id ) );
+ clean_post_cache( $order_id );
+ $this->assertSame( $post_type, get_post_type( $order_id ) );
+
+ $count_items_query = $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $order_id );
+ $items_before = (int) $wpdb->get_var( $count_items_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+ $this->assertGreaterThan( 0, $items_before );
+
+ $result = wp_delete_post( $order_id, true );
+
+ $this->assertFalse( $result, 'Deleting the post should fail.' );
+ $this->assertSame( $post_type, get_post_type( $order_id ), 'The post should still exist.' );
+ $this->assertSame( $items_before, (int) $wpdb->get_var( $count_items_query ), 'The order should keep its items.' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+ }
+
+ /**
+ * @testdox When HPOS is authoritative, a placeholder post whose order no longer exists in HPOS can be deleted.
+ */
+ public function test_orphaned_placeholder_post_can_be_deleted(): void {
+ $this->toggle_cot_authoritative( true );
+ $this->disable_cot_sync();
+
+ $order_id = OrderHelper::create_order()->get_id();
+ $this->direct_delete_cot_order( $order_id );
+
+ $result = wp_delete_post( $order_id, true );
+
+ $this->assertInstanceOf( \WP_Post::class, $result, 'Deleting an orphaned placeholder post should succeed.' );
+ $this->assertNull( get_post( $order_id ), 'The orphaned placeholder post should be gone.' );
+ }
+
/**
* @testdox When sync is disabled and the posts table is authoritative, deleting an order generates a deletion record in the meta table if the order exists in the backup table.
*