Commit 631d24ab184 for woocommerce
commit 631d24ab18428e3db977c15f2ec25ebe43eda7f1
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Fri Aug 21 15:19:29 2026 +0200
Avoid customized WooCommerce template parts taking priority over template parts queried directly (#67892)
* Add changelog
* Avoid customized WooCommerce template parts from taking priority over template parts queried directly
* Add tests
* Return earlier
* Simplify tests
* Revert "Return earlier"
This reverts commit c5b872f8e4fc91277a628d2a5dd0c187ac12ba7d.
* Add back test about WooCommerce template part IDs
* Fix tests
* Typo
* Use TT4 for tests
* Restore original theme in tests
diff --git a/plugins/woocommerce/changelog/fix-67862-woocommerce-template-parts-leaking-on-creation b/plugins/woocommerce/changelog/fix-67862-woocommerce-template-parts-leaking-on-creation
new file mode 100644
index 00000000000..e52c34869bc
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-67862-woocommerce-template-parts-leaking-on-creation
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Avoid customized WooCommerce template parts taking priority over template parts queried directly
diff --git a/plugins/woocommerce/src/Blocks/BlockTemplatesController.php b/plugins/woocommerce/src/Blocks/BlockTemplatesController.php
index 21555a4ca77..3e1a99a32c7 100644
--- a/plugins/woocommerce/src/Blocks/BlockTemplatesController.php
+++ b/plugins/woocommerce/src/Blocks/BlockTemplatesController.php
@@ -259,6 +259,12 @@ class BlockTemplatesController {
$new_templates = array();
foreach ( $template_files as $template_file ) {
+ // Queries for a specific post must not inject other WooCommerce templates.
+ // See https://github.com/woocommerce/woocommerce/issues/67862.
+ if ( isset( $query['wp_id'] ) && (int) ( $template_file->wp_id ?? 0 ) !== (int) $query['wp_id'] ) {
+ continue;
+ }
+
// It would be custom if the template was modified in the editor, so if it's not custom we can load it from
// the filesystem.
if ( 'custom' === $template_file->source ) {
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTemplatesControllerTest.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTemplatesControllerTest.php
new file mode 100644
index 00000000000..74ea6932c2c
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTemplatesControllerTest.php
@@ -0,0 +1,179 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Blocks;
+
+use Automattic\WooCommerce\Blocks\BlockTemplatesController;
+use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the BlockTemplatesController class.
+ */
+class BlockTemplatesControllerTest extends WC_Unit_Test_Case {
+
+ /**
+ * The System Under Test.
+ *
+ * @var BlockTemplatesController
+ */
+ private $sut;
+
+ /**
+ * Post IDs created during a test.
+ *
+ * @var int[]
+ */
+ private $created_template_part_ids = array();
+
+ /**
+ * Active theme before each test, restored in tearDown.
+ *
+ * @var string
+ */
+ private $original_theme;
+
+ /**
+ * Set up test fixtures.
+ */
+ public function setUp(): void {
+ parent::setUp();
+ $this->original_theme = get_stylesheet();
+ switch_theme( 'twentytwentyfour' );
+ $this->sut = new BlockTemplatesController();
+ $this->flush_block_template_caches();
+ }
+
+ /**
+ * Tear down test fixtures.
+ */
+ public function tearDown(): void {
+ foreach ( $this->created_template_part_ids as $post_id ) {
+ wp_delete_post( $post_id, true );
+ }
+ $this->created_template_part_ids = array();
+ $this->flush_block_template_caches();
+ switch_theme( $this->original_theme );
+ parent::tearDown();
+ }
+
+ /**
+ * @testdox Should not prepend customised WooCommerce template parts when querying by wp_id.
+ */
+ public function test_wp_id_query_does_not_prepend_unrelated_woo_templates(): void {
+ $woo_template_part = $this->create_template_part( 'woo-custom-template-part' . uniqid(), BlockTemplateUtils::PLUGIN_SLUG );
+ $theme_template_part = $this->create_template_part( 'theme-custom-template-part' . uniqid(), get_stylesheet() );
+ $this->flush_block_template_caches();
+
+ $new_template = BlockTemplateUtils::build_template_result_from_post( $theme_template_part );
+ $result = $this->sut->add_db_templates_with_woo_slug(
+ array( $new_template ),
+ array( 'wp_id' => $theme_template_part->ID ),
+ 'wp_template_part'
+ );
+
+ $this->assertNotEmpty( $result, 'A wp_id query should return the requested template part.' );
+ $this->assertSame(
+ $theme_template_part->ID,
+ (int) $result[0]->wp_id,
+ 'create_item uses the first result as the created template part.'
+ );
+
+ foreach ( $result as $template ) {
+ $this->assertSame(
+ $theme_template_part->ID,
+ (int) ( $template->wp_id ?? 0 ),
+ 'A wp_id query must not include unrelated WooCommerce templates.'
+ );
+ }
+
+ $this->assertNotContains(
+ $woo_template_part->post_name,
+ array_column( $result, 'slug' ),
+ 'Customised WooCommerce template parts must not leak into wp_id queries for other parts.'
+ );
+ }
+
+ /**
+ * @testdox Should still return a customised WooCommerce template part when queried by its own wp_id.
+ */
+ public function test_wp_id_query_returns_matching_customised_woo_template(): void {
+ $woo_template_part = $this->create_template_part( 'woo-custom-template-part' . uniqid(), BlockTemplateUtils::PLUGIN_SLUG );
+ $this->create_template_part( 'other-woo-custom-template-part' . uniqid(), BlockTemplateUtils::PLUGIN_SLUG );
+ $this->flush_block_template_caches();
+
+ $result = $this->sut->add_db_templates_with_woo_slug(
+ array(),
+ array( 'wp_id' => $woo_template_part->ID ),
+ 'wp_template_part'
+ );
+
+ $this->assertNotEmpty( $result, 'Customised WooCommerce template parts must remain findable by wp_id.' );
+ $this->assertSame(
+ $woo_template_part->ID,
+ (int) $result[0]->wp_id,
+ 'The matching customised WooCommerce template part should be returned first.'
+ );
+
+ foreach ( $result as $template ) {
+ $this->assertSame(
+ $woo_template_part->ID,
+ (int) ( $template->wp_id ?? 0 ),
+ 'A wp_id query must not include other customised WooCommerce templates.'
+ );
+ }
+ }
+
+ /**
+ * @testdox Should still include customised WooCommerce template parts when the query has no wp_id.
+ */
+ public function test_query_without_wp_id_includes_customised_woo_templates(): void {
+ $woo_template_part = $this->create_template_part( 'woo-custom-template-part' . uniqid(), BlockTemplateUtils::PLUGIN_SLUG );
+ $this->flush_block_template_caches();
+
+ $result = $this->sut->add_db_templates_with_woo_slug( array(), array(), 'wp_template_part' );
+
+ $this->assertContains(
+ $woo_template_part->post_name,
+ array_column( $result, 'slug' ),
+ 'Unfiltered queries should still surface customised WooCommerce template parts.'
+ );
+ }
+
+ /**
+ * Creates a template part post attributed to a theme.
+ *
+ * @param string $slug Post slug.
+ * @param string $theme Theme term name.
+ * @return \WP_Post
+ */
+ private function create_template_part( string $slug, string $theme ): \WP_Post {
+ $term = get_term_by( 'name', $theme, 'wp_theme', ARRAY_A );
+ if ( ! $term ) {
+ $term = wp_insert_term( $theme, 'wp_theme' );
+ }
+
+ $post_id = wp_insert_post(
+ array(
+ 'post_name' => $slug,
+ 'post_type' => 'wp_template_part',
+ 'post_title' => $slug,
+ 'post_status' => 'publish',
+ 'post_content' => '<!-- wp:paragraph --><p>Test</p><!-- /wp:paragraph -->',
+ )
+ );
+
+ wp_set_post_terms( $post_id, array( $term['term_id'] ), 'wp_theme' );
+ $this->created_template_part_ids[] = $post_id;
+
+ return get_post( $post_id );
+ }
+
+ /**
+ * Clears template ID caches so newly created posts are visible.
+ */
+ private function flush_block_template_caches(): void {
+ wp_cache_delete( 'wp_template-ids', 'woocommerce_blocks' );
+ wp_cache_delete( 'wp_template_part-ids', 'woocommerce_blocks' );
+ }
+}