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' );
+		}
+	}
 }