Commit d8c320ad83e for woocommerce
commit d8c320ad83ee4edaa74cca8dfd55319dbb80ece9
Author: Till Krüss <tillkruss@users.noreply.github.com>
Date: Wed May 6 03:14:22 2026 -0700
Fix re-entrant order reads from cached refund objects (#64232)
* Fix re-entrant order reads from cached refund objects
* Add changefile(s) from automation for the following project(s): woocommerce
* Add changefile(s) from automation for the following project(s): woocommerce
* flush orders cache on upgrade
* fix `wc_get_orders()` parameters
* switch to `10.8.0`
* use deterministic ordering
* rename cache key instead of using migration
* formatting
---------
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/64232-fix-order-refunds-cache b/plugins/woocommerce/changelog/64232-fix-order-refunds-cache
new file mode 100644
index 00000000000..6c17a568eb0
--- /dev/null
+++ b/plugins/woocommerce/changelog/64232-fix-order-refunds-cache
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent re-entrant order reads triggered by cached refund objects.
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/class-wc-order.php b/plugins/woocommerce/includes/class-wc-order.php
index 68201403a46..a05929a72a2 100644
--- a/plugins/woocommerce/includes/class-wc-order.php
+++ b/plugins/woocommerce/includes/class-wc-order.php
@@ -2283,18 +2283,36 @@ class WC_Order extends WC_Abstract_Order {
* @return WC_Order_Refund[]
*/
public function get_refunds() {
- $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $this->get_id();
- $refunds = wp_cache_get( $cache_key, $this->cache_group );
+ $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refund_ids' . $this->get_id();
+ $refund_ids = wp_cache_get( $cache_key, $this->cache_group );
- if ( false === $refunds ) {
- $refunds = wc_get_orders(
+ if ( false === $refund_ids ) {
+ $refunds = (array) wc_get_orders(
array(
'type' => 'shop_order_refund',
'parent' => $this->get_id(),
'limit' => -1,
)
);
- wp_cache_set( $cache_key, $refunds, $this->cache_group );
+ $refund_ids = array();
+ foreach ( $refunds as $refund ) {
+ if ( $refund instanceof WC_Order_Refund ) {
+ $refund_ids[] = $refund->get_id();
+ }
+ }
+ wp_cache_set( $cache_key, $refund_ids, $this->cache_group );
+ } else {
+ $refunds = ! empty( $refund_ids )
+ ? wc_get_orders(
+ array(
+ 'type' => 'shop_order_refund',
+ 'post__in' => $refund_ids,
+ 'orderby' => 'post__in',
+ 'limit' => -1,
+ 'no_found_rows' => true,
+ )
+ )
+ : array();
}
$this->refunds = array();
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 ae571880fa2..97f4a30e574 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
@@ -705,7 +705,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
$cache_keys_mapping = array();
foreach ( $order_ids as $order_id ) {
- $cache_keys_mapping[ $order_id ] = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $order_id;
+ $cache_keys_mapping[ $order_id ] = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refund_ids' . $order_id;
}
$non_cached_ids = array();
@@ -738,16 +738,15 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
)
);
- $order_refunds = array();
+ $order_refund_ids = array_fill_keys( $non_cached_ids, array() );
foreach ( $refunds as $refund ) {
- if ( $refund instanceof \WC_Order_Refund ) {
- $order_refunds[ $refund->get_parent_id() ][] = $refund;
+ if ( $refund instanceof \WC_Order_Refund && isset( $order_refund_ids[ $refund->get_parent_id() ] ) ) {
+ $order_refund_ids[ $refund->get_parent_id() ][] = $refund->get_id();
}
}
foreach ( $non_cached_ids as $order_id ) {
- $cached_refunds = isset( $order_refunds[ $order_id ] ) ? $order_refunds[ $order_id ] : array();
- wp_cache_set( $cache_keys_mapping[ $order_id ], $cached_refunds, 'orders' );
+ wp_cache_set( $cache_keys_mapping[ $order_id ], $order_refund_ids[ $order_id ], 'orders' );
}
}
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-order-refund-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-order-refund-data-store-cpt.php
index 0653b33a511..1f59eec9cf5 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-order-refund-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-order-refund-data-store-cpt.php
@@ -53,7 +53,7 @@ class WC_Order_Refund_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT im
}
$parent_order_id = $order->get_parent_id();
- $refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $parent_order_id;
+ $refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refund_ids' . $parent_order_id;
wp_delete_post( $id );
wp_cache_delete( $refund_cache_key, 'orders' );
$order->set_id( 0 );
diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php
index c44a3add351..85bed5fc1d3 100644
--- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php
+++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php
@@ -78,7 +78,7 @@ class OrdersTableRefundDataStore extends OrdersTableDataStore {
return;
}
- $refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $refund->get_parent_id();
+ $refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refund_ids' . $refund->get_parent_id();
wp_cache_delete( $refund_cache_key, 'orders' );
$this->delete_order_data_from_custom_order_tables( $refund_id );
diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
index cbbea52799a..fb874650bf0 100644
--- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
+++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
@@ -69,16 +69,16 @@ class WC_Order_Data_Store_CPT_Test extends WC_Unit_Test_Case {
)
)[0];
- $refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $order->get_id();
- $cached_refunds = wp_cache_get( $refund_cache_key, 'orders' );
+ $refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refund_ids' . $order->get_id();
+ $cached_refund_ids = wp_cache_get( $refund_cache_key, 'orders' );
- $this->assertEquals( $cached_refunds[0]->get_id(), $fetched_order->get_refunds()[0]->get_id() );
+ $this->assertEquals( $cached_refund_ids[0], $fetched_order->get_refunds()[0]->get_id() );
$refund->delete( true );
// Cache should be cleared now.
- $cached_refunds = wp_cache_get( $refund_cache_key, 'orders' );
- $this->assertEquals( false, $cached_refunds );
+ $cached_refund_ids = wp_cache_get( $refund_cache_key, 'orders' );
+ $this->assertEquals( false, $cached_refund_ids );
}
/**