diff --git a/plugins/woocommerce-blocks/src/BlockTemplatesController.php b/plugins/woocommerce-blocks/src/BlockTemplatesController.php
index 06a339b779f..0e48bab5527 100644
--- a/plugins/woocommerce-blocks/src/BlockTemplatesController.php
+++ b/plugins/woocommerce-blocks/src/BlockTemplatesController.php
@@ -2,6 +2,7 @@
namespace Automattic\WooCommerce\Blocks;
use Automattic\WooCommerce\Blocks\Domain\Package;
+use Automattic\WooCommerce\Blocks\Templates\BlockTemplatesCompatibility;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
@@ -322,6 +323,15 @@ class BlockTemplatesController {
if ( ! $template->description ) {
$template->description = BlockTemplateUtils::get_block_template_description( $template->slug );
}
+
+ if ( 'single-product' === $template->slug ) {
+ if ( ! is_admin() ) {
+ $new_content = BlockTemplatesCompatibility::wrap_single_product_template( $template->content );
+ $template->content = $new_content;
+ }
+ return $template;
+ }
+
return $template;
},
$query_result
diff --git a/plugins/woocommerce-blocks/src/Templates/BlockTemplatesCompatibility.php b/plugins/woocommerce-blocks/src/Templates/BlockTemplatesCompatibility.php
index 8177b8bc7ed..49b6d18b0bb 100644
--- a/plugins/woocommerce-blocks/src/Templates/BlockTemplatesCompatibility.php
+++ b/plugins/woocommerce-blocks/src/Templates/BlockTemplatesCompatibility.php
@@ -361,4 +361,133 @@ class BlockTemplatesCompatibility {
}
}
+ /**
+ * For compatibility reason, we need to wrap the Single Product template in a div with specific class.
+ * For more details, see https://github.com/woocommerce/woocommerce-blocks/issues/8314.
+ *
+ * @param string $template_content Template Content.
+ * @return string Wrapped template content inside a div.
+ */
+ public static function wrap_single_product_template( $template_content ) {
+ $parsed_blocks = parse_blocks( $template_content );
+ $grouped_blocks = self::group_blocks( $parsed_blocks );
+
+ // WIP: The list of blocks is WIP.
+ $single_product_template_blocks = array( 'woocommerce/product-image-gallery', 'woocommerce/product-details', 'woocommerce/add-to-cart-form' );
+
+ $wrapped_blocks = array_map(
+ function( $blocks ) use ( $single_product_template_blocks ) {
+ if ( 'core/template-part' === $blocks[0]['blockName'] ) {
+ return $blocks;
+ }
+
+ $has_single_product_template_blocks = self::has_single_product_template_blocks( $blocks, $single_product_template_blocks );
+
+ if ( $has_single_product_template_blocks ) {
+ $wrapped_block = self::create_wrap_block_group( $blocks );
+ return array( $wrapped_block[0] );
+ }
+ return $blocks;
+ },
+ $grouped_blocks
+ );
+
+ $template = array_reduce(
+ $wrapped_blocks,
+ function( $carry, $item ) {
+ if ( is_array( $item ) ) {
+ return $carry . serialize_blocks( $item );
+ }
+ return $carry . serialize_block( $item );
+ },
+ ''
+ );
+
+ return $template;
+ }
+
+ /**
+ * Wrap all the blocks inside the template in a group block.
+ *
+ * @param array $blocks Array of parsed block objects.
+ * @return array Group block with the blocks inside.
+ */
+ private static function create_wrap_block_group( $blocks ) {
+ $serialized_blocks = serialize_blocks( $blocks );
+
+ $new_block = parse_blocks(
+ sprintf(
+ '
+
+ %1$s
+
+ ',
+ $serialized_blocks
+ )
+ );
+
+ $new_block['innerBlocks'] = $blocks;
+
+ return $new_block;
+
+ }
+
+ /**
+ * Check if the Single Product template has a single product template block:
+ * woocommerce/product-gallery-image, woocommerce/product-details, woocommerce/add-to-cart-form]
+ *
+ * @param array $parsed_blocks Array of parsed block objects.
+ * @param array $single_product_template_blocks Array of single product template blocks.
+ * @return bool True if the template has a single product template block, false otherwise.
+ */
+ private static function has_single_product_template_blocks( $parsed_blocks, $single_product_template_blocks ) {
+ $found = false;
+
+ foreach ( $parsed_blocks as $block ) {
+ if ( isset( $block['blockName'] ) && in_array( $block['blockName'], $single_product_template_blocks, true ) ) {
+ $found = true;
+ break;
+ }
+ $found = self::has_single_product_template_blocks( $block['innerBlocks'], $single_product_template_blocks );
+ if ( $found ) {
+ break;
+ }
+ }
+ return $found;
+ }
+
+
+ /**
+ * Group blocks in this way:
+ * B1 + TP1 + B2 + B3 + B4 + TP2 + B5
+ * (B = Block, TP = Template Part)
+ * becomes:
+ * [[B1], [TP1], [B2, B3, B4], [TP2], [B5]]
+ *
+ * @param array $parsed_blocks Array of parsed block objects.
+ * @return array Array of blocks grouped by template part.
+ */
+ private static function group_blocks( $parsed_blocks ) {
+ return array_reduce(
+ $parsed_blocks,
+ function( $carry, $block ) {
+ if ( 'core/template-part' === $block['blockName'] ) {
+ array_push( $carry, array( $block ) );
+ return $carry;
+ }
+ if ( empty( $block['blockName'] ) ) {
+ return $carry;
+ }
+ $last_element_index = count( $carry ) - 1 < 0 ? 0 : count( $carry ) - 1;
+ if ( isset( $carry[ $last_element_index ][0]['blockName'] ) && 'core/template-part' !== $carry[ $last_element_index ][0]['blockName'] ) {
+ array_push( $carry[ $last_element_index ], $block );
+ return $carry;
+ }
+ array_push( $carry, array( $block ) );
+ return $carry;
+ },
+ array()
+ );
+ }
+
}
diff --git a/plugins/woocommerce-blocks/tests/php/Templates/BlockTemplatesCompatibilityTests.php b/plugins/woocommerce-blocks/tests/php/Templates/BlockTemplatesCompatibilityTests.php
new file mode 100644
index 00000000000..071fd19ae55
--- /dev/null
+++ b/plugins/woocommerce-blocks/tests/php/Templates/BlockTemplatesCompatibilityTests.php
@@ -0,0 +1,330 @@
+
+
+
+
+
+
+ ';
+
+ $expected_single_product_template = '
+
+
+
+
+
+
+ ';
+
+ $result = BlockTemplatesCompatibility::wrap_single_product_template( $default_single_product_template );
+
+ $result_without_withespace = preg_replace( '/\s+/', '', $result );
+ $expected_single_product_template_without_whitespace = preg_replace( '/\s+/', '', $expected_single_product_template );
+
+ $this->assertEquals( $result_without_withespace, $expected_single_product_template_without_whitespace, '' );
+ }
+
+ /**
+ * Test that the Single Product Template is wrapped in a div with the correct class if it contains a block related to the Single Product Template.
+ */
+ public function test_wrap_single_product_template_if_contains_single_product_blocks() {
+
+ $default_single_product_template = '
+
+
+
+
+
+
+ ';
+
+ $expected_single_product_template = '
+
+
+
+
+ ';
+
+ $result = BlockTemplatesCompatibility::wrap_single_product_template( $default_single_product_template );
+
+ $result_without_withespace = preg_replace( '/\s+/', '', $result );
+ $expected_single_product_template_without_whitespace = preg_replace( '/\s+/', '', $expected_single_product_template );
+
+ $this->assertEquals( $result_without_withespace, $expected_single_product_template_without_whitespace, '' );
+ }
+
+ /**
+ * Test that the Single Product Template is wrapped in a div with the correct class if it contains a block related to the Single Product Template in a nested structure.
+ */
+ public function test_wrap_single_product_template_if_contains_nested_single_product_blocks() {
+
+ $default_single_product_template = '
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
+
+ $expected_single_product_template = '
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
+
+ $result = BlockTemplatesCompatibility::wrap_single_product_template( $default_single_product_template );
+
+ $result_without_withespace = preg_replace( '/\s+/', '', $result );
+ $expected_single_product_template_without_whitespace = preg_replace( '/\s+/', '', $expected_single_product_template );
+
+ $this->assertEquals( $result_without_withespace, $expected_single_product_template_without_whitespace, '' );
+ }
+
+ /**
+ * Test that the Single Product Template is wrapped in a div with the correct class if it contains a block related to the Single Product Template in a nested structure.
+ */
+ public function test_wrap_single_product_template_without_a_main_wrapper() {
+
+ $default_single_product_template = '
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
+
+ $expected_single_product_template = '
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
+
+ $result = BlockTemplatesCompatibility::wrap_single_product_template( $default_single_product_template );
+
+ $result_without_withespace = preg_replace( '/\s+/', '', $result );
+ $expected_single_product_template_without_whitespace = preg_replace( '/\s+/', '', $expected_single_product_template );
+
+ $this->assertEquals( $result_without_withespace, $expected_single_product_template_without_whitespace, '' );
+ }
+
+ /**
+ * Test that the Single Product Template is wrapped in a div with the correct class if it contains a block related to the Single Product Template.
+ */
+ public function test_wrap_single_product_template_with_multiple_header() {
+
+ $default_single_product_template = '
+
+
+
+ ';
+
+ $expected_single_product_template = '
+
+
+
+
+
+
+
+ ';
+
+ $result = BlockTemplatesCompatibility::wrap_single_product_template( $default_single_product_template );
+
+ $result_without_withespace = preg_replace( '/\s+/', '', $result );
+ $expected_single_product_template_without_whitespace = preg_replace( '/\s+/', '', $expected_single_product_template );
+
+ $this->assertEquals( $result_without_withespace, $expected_single_product_template_without_whitespace, '' );
+ }
+
+
+ /**
+ * Test that the Single Product Template is wrapped in a div with the correct class if it contains a block related to the Single Product Template.
+ */
+ public function test_wrap_single_product_template_with_multiple_footer() {
+
+ $default_single_product_template = '
+
+
+
+ ';
+
+ $expected_single_product_template = '
+
+
+
+
+
+
+
+ ';
+
+ $result = BlockTemplatesCompatibility::wrap_single_product_template( $default_single_product_template );
+
+ $result_without_withespace = preg_replace( '/\s+/', '', $result );
+ $expected_single_product_template_without_whitespace = preg_replace( '/\s+/', '', $expected_single_product_template );
+
+ $this->assertEquals( $result_without_withespace, $expected_single_product_template_without_whitespace, '' );
+ }
+
+ /**
+ * Test that the Single Product Template is wrapped in a div with the correct class if it contains a block related to the Single Product Template.
+ */
+ public function test_wrap_single_product_template_with_multiple_blocks_related_to_the_single_product_template() {
+
+ $default_single_product_template = '
+
+ test
+
+
+
+
+
+ ';
+
+ $expected_single_product_template = '
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
+
+ $result = BlockTemplatesCompatibility::wrap_single_product_template( $default_single_product_template );
+
+ $result_without_withespace = preg_replace( '/\s+/', '', $result );
+ $expected_single_product_template_without_whitespace = preg_replace( '/\s+/', '', $expected_single_product_template );
+
+ $this->assertEquals( $result_without_withespace, $expected_single_product_template_without_whitespace, '' );
+ }
+}