Commit ecffc2d108d for woocommerce
commit ecffc2d108da345a7975bb1254fafed087d2981b
Author: Luis Felipe Zaguini <26530524+zaguiini@users.noreply.github.com>
Date: Mon Nov 2 10:53:31 2026 -0300
Invalidate the product cache group when a product post is deleted (#70376)
* Invalidate the product cache group when a product post is deleted
Deleting a variable product removes its variations with wp_delete_post(),
which skips the data store and leaves each variation's cached product type
in place. wc_get_product() then built an empty WC_Product_Variation for a
variation that no longer exists instead of returning false. Clearing the
product cache group on delete_post covers every product deletion, including
those that bypass the data store.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Invalidate the product cache after the lookup table cleanup
With direct attribute lookup updates on, the cleanup loads the product
inside the delete_post action, while the row still exists, and caches
its type again. The invalidation now runs after it in both branches,
and a test covers that setting.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Keep the parent ID before the delete in the test
A forced delete resets the object's ID to 0, so the assertion checked
product 0 instead of the deleted parent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Drop the deleted parent assertion from the direct lookup test
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/fix-1586-stale-variation-type-cache b/plugins/woocommerce/changelog/fix-1586-stale-variation-type-cache
new file mode 100644
index 00000000000..c36d6801b18
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-1586-stale-variation-type-cache
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Return false from wc_get_product() for a variation whose variable product was permanently deleted.
diff --git a/plugins/woocommerce/includes/class-wc-post-data.php b/plugins/woocommerce/includes/class-wc-post-data.php
index 22e8afc2af6..6533bf9b52f 100644
--- a/plugins/woocommerce/includes/class-wc-post-data.php
+++ b/plugins/woocommerce/includes/class-wc-post-data.php
@@ -464,6 +464,7 @@ class WC_Post_Data {
$data_store->delete_variations( $id, true );
$data_store->delete_from_lookup_table( $id, 'wc_product_meta_lookup' );
$container->get( ProductAttributesLookupDataStore::class )->on_product_deleted( $id );
+ WC_Cache_Helper::invalidate_cache_group( 'product_' . $id );
$parent_id = wp_get_post_parent_id( $id );
if ( $parent_id ) {
@@ -476,6 +477,7 @@ class WC_Post_Data {
$data_store->delete_from_lookup_table( $id, 'wc_product_meta_lookup' );
wc_delete_product_transients( wp_get_post_parent_id( $id ) );
$container->get( ProductAttributesLookupDataStore::class )->on_product_deleted( $id );
+ WC_Cache_Helper::invalidate_cache_group( 'product_' . $id );
break;
case 'shop_order':
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-product-factory-test.php b/plugins/woocommerce/tests/php/includes/class-wc-product-factory-test.php
index b78f617e8bb..c0be3427ce9 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-product-factory-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-product-factory-test.php
@@ -25,4 +25,37 @@ class WC_Product_Factory_Test extends WC_Unit_Test_Case {
$GLOBALS['post'] = $old_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the prior global.
}
}
+
+ /**
+ * @testdox get_product returns false for a variation whose parent was deleted after the variation was loaded.
+ */
+ public function test_get_product_returns_false_for_variation_of_deleted_parent(): void {
+ $parent = WC_Helper_Product::create_variation_product();
+ $variation_id = $parent->get_children()[0];
+
+ $this->assertInstanceOf( WC_Product_Variation::class, wc_get_product( $variation_id ) );
+
+ $parent->delete( true );
+
+ $this->assertFalse( wc_get_product( $variation_id ) );
+ }
+
+ /**
+ * @testdox get_product returns false for a deleted variation when the attribute lookup table updates directly, which loads the product during the delete.
+ */
+ public function test_get_product_returns_false_for_deleted_variation_with_direct_lookup_updates(): void {
+ $parent = WC_Helper_Product::create_variation_product();
+ $variation_id = $parent->get_children()[0];
+ update_option( 'woocommerce_attribute_lookup_direct_updates', 'yes' );
+
+ try {
+ $this->assertInstanceOf( WC_Product_Variation::class, wc_get_product( $variation_id ) );
+
+ $parent->delete( true );
+
+ $this->assertFalse( wc_get_product( $variation_id ) );
+ } finally {
+ delete_option( 'woocommerce_attribute_lookup_direct_updates' );
+ }
+ }
}