Commit c6add9a1060 for woocommerce
commit c6add9a10602dcb9015f12947eed58a00f4044de
Author: Jorge A. Torres <jorge.torres@automattic.com>
Date: Tue Sep 8 12:23:21 2026 +0100
Restore order notes when an HPOS order is untrashed (#68206)
* Restore order notes when an HPOS order is untrashed
* Add tests for order note restoration on untrash
* Add changelog entry
diff --git a/plugins/woocommerce/changelog/fix-68177-hpos-order-notes-hidden-after-untrash b/plugins/woocommerce/changelog/fix-68177-hpos-order-notes-hidden-after-untrash
new file mode 100644
index 00000000000..127e9ff6146
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-68177-hpos-order-notes-hidden-after-untrash
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Restore order notes when an HPOS order is untrashed, instead of leaving them permanently hidden.
diff --git a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
index d4c9d7e9fb3..03593403e73 100644
--- a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
@@ -1076,7 +1076,11 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
$keys_to_delete = array_diff(
array_keys( $existing_meta_data ),
$this->internal_meta_keys,
- array_keys( $this->get_internal_data_store_key_getters() )
+ array_keys( $this->get_internal_data_store_key_getters() ),
+ array(
+ // WordPress writes and manages this directly on the post while trashing.
+ '_wp_trash_meta_comments_status',
+ )
);
foreach ( $keys_to_delete as $meta_key ) {
diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php
index c9b072378e6..ace31dd00ef 100644
--- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php
+++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php
@@ -2931,6 +2931,31 @@ FROM $order_meta_table
// Was the status successfully restored? Let's clean up the meta and indicate success...
if ( 'wc-' . $order->get_status() === $previous_status ) {
+ wp_untrash_post_comments( $id );
+
+ // Order notes are always restored. Other comment types are left alone.
+ global $wpdb;
+ $order_note_ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT comment_ID FROM {$wpdb->comments} WHERE comment_post_ID = %d AND comment_approved = %s AND comment_type = %s",
+ $id,
+ 'post-trashed',
+ 'order_note'
+ )
+ );
+ if ( $order_note_ids ) {
+ $wpdb->update(
+ $wpdb->comments,
+ array( 'comment_approved' => '1' ),
+ array(
+ 'comment_post_ID' => $id,
+ 'comment_approved' => 'post-trashed',
+ 'comment_type' => 'order_note',
+ )
+ );
+ clean_comment_cache( $order_note_ids );
+ }
+
$order->delete_meta_data( '_wp_trash_meta_status' );
$order->delete_meta_data( '_wp_trash_meta_time' );
$order->delete_meta_data( '_wp_trash_meta_comments_status' );
diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
index 1cbbcd84aed..772ebdfb690 100644
--- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
@@ -570,7 +570,7 @@ class OrdersTableDataStoreTests extends \HposTestCase {
}
/**
- * @testdox Test the trash-untrash cycle with sync enabled.
+ * @testdox Test the trash-untrash cycle with sync enabled, including order note visibility.
*/
public function test_cot_datastore_untrash() {
global $wpdb;
@@ -583,6 +583,17 @@ class OrdersTableDataStoreTests extends \HposTestCase {
$order->set_status( OrderStatus::ON_HOLD );
$order->save();
$order_id = $order->get_id();
+ $note_id = $order->add_order_note( 'Test note' );
+
+ // A non-order-note comment type, so restore can't be relying on the notes-only fallback.
+ $third_party_comment_id = wp_insert_comment(
+ array(
+ 'comment_post_ID' => $order_id,
+ 'comment_content' => 'Third-party comment',
+ 'comment_type' => 'wc-messaging',
+ 'comment_approved' => 1,
+ )
+ );
$this->sut->trash_order( $order );
@@ -591,6 +602,8 @@ class OrdersTableDataStoreTests extends \HposTestCase {
$this->assertEquals( OrderStatus::TRASH, $wpdb->get_var( $wpdb->prepare( "SELECT status FROM {$orders_table} WHERE id = %d", $order_id ) ) );
$this->assertEquals( OrderStatus::TRASH, $wpdb->get_var( $wpdb->prepare( "SELECT post_status FROM {$wpdb->posts} WHERE id = %d", $order_id ) ) );
$this->assertNotEmpty( $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$this->sut->get_meta_table_name()} WHERE order_id = %d AND meta_key LIKE %s", $order_id, '_wp_trash_meta_%' ) ) );
+ $this->assertSame( 'post-trashed', get_comment( $note_id )->comment_approved, 'Order note should be hidden while the order is in the trash' );
+ $this->assertSame( 'post-trashed', get_comment( $third_party_comment_id )->comment_approved, 'Third-party comment should be hidden while the order is in the trash' );
$this->sut->read( $order );
$this->sut->untrash_order( $order );
@@ -600,6 +613,70 @@ class OrdersTableDataStoreTests extends \HposTestCase {
$this->assertEmpty( $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$this->sut->get_meta_table_name()} WHERE order_id = %d AND meta_key LIKE %s", $order_id, '_wp_trash_meta_%' ) ) );
$this->assertEmpty( $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key LIKE '_wp_trash_meta_%'", $order_id ) ) );
+ $this->assertSame( '1', get_comment( $note_id )->comment_approved, 'Order note should be restored when the order is untrashed' );
+ $this->assertSame( '1', get_comment( $third_party_comment_id )->comment_approved, 'Third-party comment should be restored when the order is untrashed' );
+ //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders
+ }
+
+ /**
+ * @testdox Notes are still restored on untrash even without a comment-status record.
+ */
+ public function test_cot_datastore_untrash_restores_notes_with_no_trash_meta_record() {
+ $this->enable_cot_sync();
+ $this->toggle_cot_feature_and_usage( true );
+
+ $order = $this->create_complex_cot_order();
+ $order->set_status( OrderStatus::ON_HOLD );
+ $order->save();
+ $order_id = $order->get_id();
+ $note_id = $order->add_order_note( 'Test note' );
+
+ $this->sut->trash_order( $order );
+ $this->assertSame( 'post-trashed', get_comment( $note_id )->comment_approved );
+
+ // No comment-status record, as if this order predates the fix.
+ delete_post_meta( $order_id, '_wp_trash_meta_comments_status' );
+
+ $this->sut->read( $order );
+ $this->sut->untrash_order( $order );
+
+ $this->assertSame( '1', get_comment( $note_id )->comment_approved, 'Order note should still be restored even with no trash-meta record' );
+ }
+
+ /**
+ * @testdox A leaked wc_orders_meta row for the trash comment-status is removed on untrash.
+ */
+ public function test_cot_datastore_untrash_removes_leaked_trash_comments_meta() {
+ global $wpdb;
+
+ $this->enable_cot_sync();
+ $this->toggle_cot_feature_and_usage( true );
+
+ $order = $this->create_complex_cot_order();
+ $order->set_status( OrderStatus::ON_HOLD );
+ $order->save();
+ $order_id = $order->get_id();
+
+ $this->sut->trash_order( $order );
+
+ // Simulate a copy that leaked into wc_orders_meta from a prior CPT-to-HPOS sync.
+ $wpdb->insert(
+ $this->sut->get_meta_table_name(),
+ array(
+ 'order_id' => $order_id,
+ 'meta_key' => '_wp_trash_meta_comments_status', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+ 'meta_value' => 'leaked', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
+ )
+ );
+
+ $this->sut->read( $order );
+ $this->sut->untrash_order( $order );
+
+ //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders
+ $this->assertEmpty(
+ $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$this->sut->get_meta_table_name()} WHERE order_id = %d AND meta_key = %s", $order_id, '_wp_trash_meta_comments_status' ) ),
+ 'A leaked _wp_trash_meta_comments_status row should not survive untrash'
+ );
//phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders
}