Commit e577679fd67 for woocommerce

commit e577679fd67d4d645d08fae767442f0db1d07e70
Author: Tung Du <dinhtungdu@gmail.com>
Date:   Mon Jun 15 13:56:58 2026 +0700

    Fix Product Collection context inside Query Loop (#65637)

    * fix: scope product template context

    * test: cover product template context scoping

    * Add changefile(s) from automation for the following project(s): woocommerce

    ---------

    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/65637-wooplug-4673-product-collection-block-several-inner-blocks-dont-work b/plugins/woocommerce/changelog/65637-wooplug-4673-product-collection-block-several-inner-blocks-dont-work
new file mode 100644
index 00000000000..b707d3d86f3
--- /dev/null
+++ b/plugins/woocommerce/changelog/65637-wooplug-4673-product-collection-block-several-inner-blocks-dont-work
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix Product Collection inner blocks when rendered inside Query Loop post content.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php
index f9867646ac9..354e7825c61 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php
@@ -93,28 +93,29 @@ class ProductTemplate extends AbstractBlock {
 			// Get an instance of the current Post Template block.
 			$block_instance = $block->parsed_block;
 			$product_id     = (int) get_the_ID();
+			$post_type      = get_post_type();

 			// Set the block name to one that does not correspond to an existing registered block.
 			// This ensures that for the inner instances of the Post Template block, we do not render any block supports.
 			$block_instance['blockName'] = 'core/null';

-			// Relay the block context to the inner blocks.
-			$available_context = array_merge(
-				(array) $block->context,
-				array(
-					'postType' => get_post_type(),
-					'postId'   => $product_id,
-				)
-			);
+			$filter_block_context = static function ( $context ) use ( $product_id, $post_type ) {
+				$context['postType'] = $post_type;
+				$context['postId']   = $product_id;
+				return $context;
+			};

+			// Use an early priority so that other 'render_block_context' filters have access to the values.
+			add_filter( 'render_block_context', $filter_block_context, 1 );
 			// Render the inner blocks of the Post Template block with `dynamic` set to `false` to prevent calling
 			// `render_callback` and ensure that no wrapper markup is included.
 			$block_content = (
 				new WP_Block(
 					$block_instance,
-					$available_context
+					$block->context
 				)
 			)->render( array( 'dynamic' => false ) );
+			remove_filter( 'render_block_context', $filter_block_context, 1 );

 			// Load product into the shared products store.
 			wc_interactivity_api_load_product(
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductTemplateTest.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductTemplateTest.php
new file mode 100644
index 00000000000..adf229a86e7
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductTemplateTest.php
@@ -0,0 +1,110 @@
+<?php
+
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Blocks\BlockTypes;
+
+use WC_Helper_Product;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the Product Template block type.
+ */
+class ProductTemplateTest extends WC_Unit_Test_Case {
+
+	/**
+	 * Renders a Product Collection inside a Query Loop post content block.
+	 *
+	 * @param int $product_id Product ID.
+	 * @param int $author_id  Outer post author ID.
+	 * @return string Rendered block markup.
+	 */
+	private function render_product_collection_inside_query_loop( int $product_id, int $author_id ): string {
+		$product_collection = $this->get_product_collection_markup( $product_id );
+		$post_id            = self::factory()->post->create(
+			array(
+				'post_author'  => $author_id,
+				'post_title'   => 'Post containing Product Collection',
+				'post_content' => $product_collection,
+			)
+		);
+
+		$query_loop = sprintf(
+			'<!-- wp:query {"query":{"perPage":1,"postType":"post","order":"desc","orderBy":"date","author":"%1$d","search":"","exclude":[],"sticky":"","inherit":false}} -->
+<div class="wp-block-query"><!-- wp:post-template --><!-- wp:post-content /--><!-- /wp:post-template --></div>
+<!-- /wp:query -->',
+			$author_id
+		);
+
+		try {
+			return do_blocks( $query_loop );
+		} finally {
+			wp_delete_post( $post_id, true );
+		}
+	}
+
+	/**
+	 * Gets Product Collection block markup for a hand-picked product.
+	 *
+	 * @param int $product_id Product ID.
+	 * @return string Product Collection block markup.
+	 */
+	private function get_product_collection_markup( int $product_id ): string {
+		$attributes = array(
+			'queryId'    => 0,
+			'query'      => array(
+				'perPage'                       => 1,
+				'pages'                         => 1,
+				'offset'                        => 0,
+				'postType'                      => 'product',
+				'order'                         => 'asc',
+				'orderBy'                       => 'post__in',
+				'search'                        => '',
+				'exclude'                       => array(),
+				'inherit'                       => false,
+				'taxQuery'                      => array(),
+				'isProductCollectionBlock'      => true,
+				'featured'                      => false,
+				'woocommerceOnSale'             => false,
+				'woocommerceStockStatus'        => array( 'instock' ),
+				'woocommerceAttributes'         => array(),
+				'woocommerceHandPickedProducts' => array( $product_id ),
+				'filterable'                    => false,
+			),
+			'collection' => 'woocommerce/product-collection/hand-picked',
+		);
+
+		return sprintf(
+			'<!-- wp:woocommerce/product-collection %1$s -->
+<div class="wp-block-woocommerce-product-collection"><!-- wp:woocommerce/product-template -->
+<!-- wp:woocommerce/product-image /-->
+<!-- wp:woocommerce/product-price /-->
+<!-- /wp:woocommerce/product-template --></div>
+<!-- /wp:woocommerce/product-collection -->',
+			wp_json_encode( $attributes )
+		);
+	}
+
+	/**
+	 * @testdox Should preserve product context when rendered inside a Query Loop post content block.
+	 */
+	public function test_preserves_product_context_inside_query_loop_post_content(): void {
+		$product   = WC_Helper_Product::create_simple_product(
+			true,
+			array(
+				'regular_price' => 25,
+				'price'         => 25,
+			)
+		);
+		$author_id = self::factory()->user->create();
+
+		try {
+			$markup = $this->render_product_collection_inside_query_loop( $product->get_id(), $author_id );
+
+			$this->assertStringContainsString( 'wc-block-components-product-image', $markup, 'Product image should render using product context.' );
+			$this->assertStringContainsString( 'wc-block-components-product-price', $markup, 'Product price should render using product context.' );
+		} finally {
+			WC_Helper_Product::delete_product( $product->get_id() );
+		}
+	}
+}