Commit 20c8388e6ab for woocommerce
commit 20c8388e6ab13aabc7bba647590b7bd034e446d6
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Wed Jun 3 18:51:48 2026 +0200
Simplify block templates compatibility layer checks (#64504)
* Simplify compatibility layer checks
* Add changelog
* Linting and PHPStan
* Add a wc_doing_it_wrong() for extensions that were hooking into the filter too early
* Improve message
* Improve backwards compatibility
* Update changelog
* Simplify logic to run previous woocommerce_disable_compatibility_layer filters
* Add tests
diff --git a/plugins/woocommerce/changelog/fix-simplify-compatibility-layer-checks b/plugins/woocommerce/changelog/fix-simplify-compatibility-layer-checks
new file mode 100644
index 00000000000..15ffd625786
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-simplify-compatibility-layer-checks
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fix
+
+Move logic to enable or disable the compatibility layer in block templates from 'template_redirect' to 'template_include' to be more accurate with the resolved template
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 37ba3481e29..7badf4992b4 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -55539,24 +55539,6 @@ parameters:
count: 1
path: src/Blocks/Templates/AbstractTemplateCompatibility.php
- -
- message: '#^One or more @param tags has an invalid name or invalid syntax\.$#'
- identifier: phpDoc.parseError
- count: 2
- path: src/Blocks/Templates/AbstractTemplateCompatibility.php
-
- -
- message: '#^PHPDoc tag @param has invalid value \(boolean\.\)\: Unexpected token "\.", expected variable at offset 211 on line 7$#'
- identifier: phpDoc.parseError
- count: 1
- path: src/Blocks/Templates/AbstractTemplateCompatibility.php
-
- -
- message: '#^PHPDoc tag @param has invalid value \(boolean\.\)\: Unexpected token "\.", expected variable at offset 221 on line 7$#'
- identifier: phpDoc.parseError
- count: 1
- path: src/Blocks/Templates/AbstractTemplateCompatibility.php
-
-
message: '#^Parameter \$parent_block of method Automattic\\WooCommerce\\Blocks\\Templates\\AbstractTemplateCompatibility\:\:update_render_block_data\(\) has invalid type Automattic\\WooCommerce\\Blocks\\Templates\\WP_Block\.$#'
identifier: class.notFound
diff --git a/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php b/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php
index aa574e0e29e..41dc4b179c3 100644
--- a/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php
+++ b/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php
@@ -1,6 +1,8 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
+use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
+
/**
* AbstractTemplateCompatibility class.
*
@@ -24,49 +26,60 @@ abstract class AbstractTemplateCompatibility {
$this->set_hook_data();
add_filter(
- 'render_block_data',
- function ( $parsed_block, $source_block, $parent_block ) {
- /**
- * Filter to disable the compatibility layer for the blockified templates.
- *
- * This hook allows to disable the compatibility layer for the blockified templates.
- *
- * @since 7.6.0
- * @param boolean.
- */
- $is_disabled_compatility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false );
-
- if ( $is_disabled_compatility_layer ) {
- return $parsed_block;
- }
-
- return $this->update_render_block_data( $parsed_block, $source_block, $parent_block );
- },
- 10,
- 3
- );
+ 'template_include',
+ function ( $template ) {
+ $this->set_compatibility_layer_flag();
- add_filter(
- 'render_block',
- function ( $block_content, $block ) {
- /**
- * Filter to disable the compatibility layer for the blockified templates.
- *
- * This hook allows to disable the compatibility layer for the blockified.
- *
- * @since 7.6.0
- * @param boolean.
- */
- $is_disabled_compatibility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false );
-
- if ( $is_disabled_compatibility_layer ) {
- return $block_content;
- }
-
- return $this->inject_hooks( $block_content, $block );
+ add_filter(
+ 'render_block_data',
+ function ( $parsed_block, $source_block, $parent_block ) {
+ /**
+ * Filter to disable the compatibility layer for the blockified templates.
+ *
+ * This hook allows to disable the compatibility layer for the blockified templates.
+ *
+ * @since 7.6.0
+ * @param bool $is_disabled_compatibility_layer Whether the compatibility layer should be disabled.
+ */
+ $is_disabled_compatibility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false );
+
+ if ( $is_disabled_compatibility_layer ) {
+ return $parsed_block;
+ }
+
+ return $this->update_render_block_data( $parsed_block, $source_block, $parent_block );
+ },
+ 10,
+ 3
+ );
+
+ add_filter(
+ 'render_block',
+ function ( $block_content, $block ) {
+ /**
+ * Filter to disable the compatibility layer for the blockified templates.
+ *
+ * This hook allows to disable the compatibility layer for the blockified.
+ *
+ * @since 7.6.0
+ * @param bool $is_disabled_compatibility_layer Whether the compatibility layer should be disabled.
+ */
+ $is_disabled_compatibility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false );
+
+ if ( $is_disabled_compatibility_layer ) {
+ return $block_content;
+ }
+
+ return $this->inject_hooks( $block_content, $block );
+ },
+ 10,
+ 2
+ );
+
+ return $template;
},
10,
- 2
+ 1
);
}
@@ -195,4 +208,50 @@ abstract class AbstractTemplateCompatibility {
}
return ob_get_clean();
}
+
+ /**
+ * Check if the current template has a legacy template block.
+ *
+ * @return bool True if the current template has a legacy template block, false otherwise.
+ *
+ * @internal
+ */
+ public function current_template_has_legacy_template_block() {
+ global $_wp_current_template_id;
+
+ if ( empty( $_wp_current_template_id ) ) {
+ return false;
+ }
+
+ $current_template = get_block_template( $_wp_current_template_id, 'wp_template' );
+
+ if ( isset( $current_template ) && BlockTemplateUtils::template_has_legacy_template_block( $current_template ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if the current template has a legacy template block and disable the compatibility layer if it does.
+ *
+ * @return void
+ *
+ * @internal
+ */
+ public function set_compatibility_layer_flag() {
+ $current_template_has_legacy_template_block = $this->current_template_has_legacy_template_block();
+
+ /**
+ * Filter to determine whether the compatibility layer should be disabled.
+ *
+ * @since 11.0.0
+ * @param bool $should_disable_compatibility_layer Whether the compatibility layer should be disabled.
+ */
+ $should_disable_compatibility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', $current_template_has_legacy_template_block );
+
+ if ( $should_disable_compatibility_layer ) {
+ add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
+ }
+ }
}
diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductAttributeTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductAttributeTemplate.php
index 953314d97ca..24d023283ac 100644
--- a/plugins/woocommerce/src/Blocks/Templates/ProductAttributeTemplate.php
+++ b/plugins/woocommerce/src/Blocks/Templates/ProductAttributeTemplate.php
@@ -56,12 +56,6 @@ class ProductAttributeTemplate extends AbstractTemplateWithFallback {
if ( isset( $queried_object->taxonomy ) && taxonomy_is_product_attribute( $queried_object->taxonomy ) ) {
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
$compatibility_layer->init();
-
- $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
-
- if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
- add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
- }
}
}
diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductBrandTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductBrandTemplate.php
index 572930052a3..476c329dfbd 100644
--- a/plugins/woocommerce/src/Blocks/Templates/ProductBrandTemplate.php
+++ b/plugins/woocommerce/src/Blocks/Templates/ProductBrandTemplate.php
@@ -59,12 +59,6 @@ class ProductBrandTemplate extends AbstractTemplateWithFallback {
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
$compatibility_layer->init();
- $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
-
- if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
- add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
- }
-
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php
index 6f2d94b1017..149eec9e4e6 100644
--- a/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php
+++ b/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php
@@ -52,12 +52,6 @@ class ProductCatalogTemplate extends AbstractTemplate {
if ( ! is_embed() && ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) && ! is_search() ) {
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
$compatibility_layer->init();
-
- $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
-
- if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
- add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
- }
}
}
diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductCategoryTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductCategoryTemplate.php
index f7a08ad571a..560fd77ce00 100644
--- a/plugins/woocommerce/src/Blocks/Templates/ProductCategoryTemplate.php
+++ b/plugins/woocommerce/src/Blocks/Templates/ProductCategoryTemplate.php
@@ -59,12 +59,6 @@ class ProductCategoryTemplate extends AbstractTemplateWithFallback {
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
$compatibility_layer->init();
- $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
-
- if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
- add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
- }
-
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductSearchResultsTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductSearchResultsTemplate.php
index 4f4183abc45..a26d6a7a794 100644
--- a/plugins/woocommerce/src/Blocks/Templates/ProductSearchResultsTemplate.php
+++ b/plugins/woocommerce/src/Blocks/Templates/ProductSearchResultsTemplate.php
@@ -51,12 +51,6 @@ class ProductSearchResultsTemplate extends AbstractTemplate {
if ( ! is_embed() && is_post_type_archive( 'product' ) && is_search() ) {
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
$compatibility_layer->init();
-
- $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
-
- if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
- add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
- }
}
}
diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductTagTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductTagTemplate.php
index 8718b8a52c8..66525dec03e 100644
--- a/plugins/woocommerce/src/Blocks/Templates/ProductTagTemplate.php
+++ b/plugins/woocommerce/src/Blocks/Templates/ProductTagTemplate.php
@@ -59,12 +59,6 @@ class ProductTagTemplate extends AbstractTemplateWithFallback {
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
$compatibility_layer->init();
- $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
-
- if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
- add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
- }
-
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
diff --git a/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php b/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php
index 41d2a2bcb72..e97edf1d1d1 100644
--- a/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php
+++ b/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php
@@ -55,34 +55,6 @@ class SingleProductTemplate extends AbstractTemplate {
$compatibility_layer = new SingleProductTemplateCompatibility();
$compatibility_layer->init();
- $valid_slugs = array( self::SLUG );
- $single_product_slug = 'product' === $post->post_type && $post->post_name ? 'single-product-' . $post->post_name : '';
- if ( $single_product_slug ) {
- $valid_slugs[] = 'single-product-' . $post->post_name;
- }
- $templates = get_block_templates( array( 'slug__in' => $valid_slugs ) );
-
- if ( count( $templates ) === 0 ) {
- return;
- }
-
- // Use the first template by default.
- $template = reset( $templates );
-
- // Check if there is a template matching the slug `single-product-{post_name}`.
- if ( count( $valid_slugs ) > 1 && count( $templates ) > 1 ) {
- foreach ( $templates as $t ) {
- if ( $single_product_slug === $t->slug ) {
- $template = $t;
- break;
- }
- }
- }
-
- if ( isset( $template ) && BlockTemplateUtils::template_has_legacy_template_block( $template ) ) {
- add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
- }
-
$product = wc_get_product( $post->ID );
if ( $product ) {
$consent = 'I acknowledge that using experimental APIs means my theme or plugin will inevitably break in the next version of WooCommerce';
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Templates/AbstractTemplateCompatibilityTest.php b/plugins/woocommerce/tests/php/src/Blocks/Templates/AbstractTemplateCompatibilityTest.php
new file mode 100644
index 00000000000..4363511a082
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Blocks/Templates/AbstractTemplateCompatibilityTest.php
@@ -0,0 +1,195 @@
+<?php
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Tests\Blocks\Templates;
+
+use Automattic\WooCommerce\Blocks\Templates\AbstractTemplateCompatibility;
+use WP_UnitTestCase;
+
+/**
+ * Tests for AbstractTemplateCompatibility::set_compatibility_layer_flag().
+ */
+class AbstractTemplateCompatibilityTest extends WP_UnitTestCase {
+
+ /**
+ * The System Under Test.
+ *
+ * @var AbstractTemplateCompatibility
+ */
+ private $sut;
+
+ /**
+ * @inheritdoc
+ */
+ public function tearDown(): void {
+ $this->remove_compatibility_layer_filters();
+ parent::tearDown();
+ }
+
+ /**
+ * @testdox Disables the compatibility layer when the template has a legacy template block.
+ */
+ public function test_disables_compatibility_layer_when_template_has_legacy_block(): void {
+ $this->sut = $this->create_sut( true );
+
+ $this->sut->set_compatibility_layer_flag();
+
+ $this->assertTrue(
+ $this->is_compatibility_layer_disabled(),
+ 'Legacy templates should disable the compatibility layer for subsequent checks.'
+ );
+ }
+
+ /**
+ * @testdox Keeps the compatibility layer enabled when the template is blockified.
+ */
+ public function test_keeps_compatibility_layer_enabled_when_template_is_blockified(): void {
+ $this->sut = $this->create_sut( false );
+
+ $this->sut->set_compatibility_layer_flag();
+
+ $this->assertFalse(
+ $this->is_compatibility_layer_disabled(),
+ 'Blockified templates should keep the compatibility layer enabled by default.'
+ );
+ }
+
+ /**
+ * @testdox Passes legacy detection as the filter default value.
+ */
+ public function test_filter_receives_legacy_detection_as_default(): void {
+ $received_default = null;
+
+ add_filter(
+ 'woocommerce_disable_compatibility_layer',
+ function ( $should_disable ) use ( &$received_default ) {
+ $received_default = $should_disable;
+
+ return $should_disable;
+ },
+ 10,
+ 1
+ );
+
+ $this->sut = $this->create_sut( true );
+ $this->sut->set_compatibility_layer_flag();
+
+ $this->assertTrue( $received_default, 'Filter default should reflect legacy template detection.' );
+ }
+
+ /**
+ * @testdox Keeps the compatibility layer enabled when an extension overrides a legacy template default to false.
+ */
+ public function test_keeps_compatibility_layer_enabled_when_extension_overrides_legacy_default_to_false(): void {
+ add_filter( 'woocommerce_disable_compatibility_layer', '__return_false' );
+
+ $this->sut = $this->create_sut( true );
+ $this->sut->set_compatibility_layer_flag();
+
+ $this->assertFalse(
+ $this->is_compatibility_layer_disabled(),
+ 'Extensions should be able to keep the compatibility layer enabled on legacy templates.'
+ );
+ }
+
+ /**
+ * @testdox Disables the compatibility layer when an extension overrides a blockified template default to true.
+ */
+ public function test_disables_compatibility_layer_when_extension_overrides_blockified_default_to_true(): void {
+ add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
+
+ $this->sut = $this->create_sut( false );
+ $this->sut->set_compatibility_layer_flag();
+
+ $this->assertTrue(
+ $this->is_compatibility_layer_disabled(),
+ 'Extensions should be able to disable the compatibility layer on blockified templates.'
+ );
+ }
+
+ /**
+ * Applies the compatibility layer filter the same way render callbacks do.
+ *
+ * @return bool
+ */
+ private function is_compatibility_layer_disabled(): bool {
+ /**
+ * Filter to disable the compatibility layer for the blockified templates.
+ *
+ * @since 7.6.0
+ * @param bool $is_disabled_compatibility_layer Whether the compatibility layer should be disabled.
+ */
+ return apply_filters( 'woocommerce_disable_compatibility_layer', false );
+ }
+
+ /**
+ * Creates a test double with a fixed legacy-template detection result.
+ *
+ * @param bool $has_legacy_template_block Legacy template detection result to return.
+ * @return AbstractTemplateCompatibility
+ */
+ private function create_sut( bool $has_legacy_template_block ): AbstractTemplateCompatibility {
+ return new class( $has_legacy_template_block ) extends AbstractTemplateCompatibility {
+
+ /**
+ * Whether the current template is detected as having a legacy template block.
+ *
+ * @var bool
+ */
+ private $has_legacy_template_block;
+
+ /**
+ * @param bool $has_legacy_template_block Legacy template detection result to return.
+ */
+ public function __construct( bool $has_legacy_template_block ) {
+ $this->has_legacy_template_block = $has_legacy_template_block;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function current_template_has_legacy_template_block() {
+ return $this->has_legacy_template_block;
+ }
+
+ /**
+ * @param array $parsed_block The block being rendered.
+ * @param array $source_block An un-modified copy of the parsed block.
+ * @param object|null $parent_block Parent block, if any.
+ * @return array
+ */
+ public function update_render_block_data( $parsed_block, $source_block, $parent_block ) {
+ unset( $source_block, $parent_block );
+
+ return $parsed_block;
+ }
+
+ /**
+ * @param mixed $block_content The rendered block content.
+ * @param mixed $block The parsed block data.
+ * @return string
+ */
+ public function inject_hooks( $block_content, $block ) {
+ unset( $block );
+
+ return $block_content;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function set_hook_data() {
+ $this->hook_data = array();
+ }
+ };
+ }
+
+ /**
+ * Removes filters registered during tests.
+ */
+ private function remove_compatibility_layer_filters(): void {
+ remove_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
+ remove_filter( 'woocommerce_disable_compatibility_layer', '__return_false' );
+ remove_all_filters( 'woocommerce_disable_compatibility_layer' );
+ }
+}