From 24fca478c2e2651fe939229ad63f09d1e621141b Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Fri, 4 Aug 2023 10:07:02 +0200 Subject: [PATCH] Enable Compatibility Layer for Product Collection (#10332) * Enable Compatibility Layer for Product Collection * Add another condition to recognise shop item * Enable multiple blocks to be assigned to single hook in Compatibility Layer * Move core/null block name assignment before the hooks assignment which is based on the block name * Make array operations safer * Unify hooks interface between templates * Fix typo * Update comment describing set_hook_data to reflect new interface --- .../AbstractTemplateCompatibility.php | 4 +- .../ArchiveProductTemplatesCompatibility.php | 167 +++++++++++++----- .../SingleProductTemplateCompatibility.php | 68 +++---- 3 files changed, 154 insertions(+), 85 deletions(-) diff --git a/src/Templates/AbstractTemplateCompatibility.php b/src/Templates/AbstractTemplateCompatibility.php index 23c660828f7..ee5c4f45384 100644 --- a/src/Templates/AbstractTemplateCompatibility.php +++ b/src/Templates/AbstractTemplateCompatibility.php @@ -103,7 +103,7 @@ abstract public function inject_hooks( $block_content, $block ); * The array format: * [ * => [ - * block_name => , + * block_names => [ , ... ], * position => before|after, * hooked => [ * => , @@ -113,7 +113,7 @@ abstract public function inject_hooks( $block_content, $block ); * ] * Where: * - hook-name is the name of the hook that will be replaced. - * - block-name is the name of the block that will replace the hook. + * - block-names is the array block names that hook will be attached to. * - position is the position of the block relative to the hook. * - hooked is an array of functions hooked to the hook that will be * replaced. The key is the function name and the value is the diff --git a/src/Templates/ArchiveProductTemplatesCompatibility.php b/src/Templates/ArchiveProductTemplatesCompatibility.php index 739a146cec9..5b333d98324 100644 --- a/src/Templates/ArchiveProductTemplatesCompatibility.php +++ b/src/Templates/ArchiveProductTemplatesCompatibility.php @@ -72,16 +72,20 @@ public function inject_hooks( $block_content, $block ) { $block_name = $block['blockName']; + if ( $this->is_null_post_template( $block ) ) { + $block_name = self::LOOP_ITEM_ID; + } + $block_hooks = array_filter( $this->hook_data, function( $hook ) use ( $block_name ) { - return $hook['block_name'] === $block_name; + return in_array( $block_name, $hook['block_names'], true ); } ); - // We want to inject hooks to the core/post-template block only when the products exist: + // We want to inject hooks to the core/post-template or product template block only when the products exist: // https://github.com/woocommerce/woocommerce-blocks/issues/9463. - if ( 'core/post-template' === $block_name && ! empty( $block_content ) ) { + if ( $this->is_post_or_product_template( $block_name ) && ! empty( $block_content ) ) { $this->restore_default_hooks(); $content = sprintf( '%1$s%2$s%3$s', @@ -93,24 +97,14 @@ function( $hook ) use ( $block_name ) { return $content; } - /** - * The core/post-template has two different block names: - * - core/post-template when the wrapper is rendered. - * - core/null when the loop item is rendered. - */ - if ( - 'core/null' === $block_name && - isset( $block['attrs']['__woocommerceNamespace'] ) && - 'woocommerce/product-query/product-template' === $block['attrs']['__woocommerceNamespace'] - ) { - $block_name = self::LOOP_ITEM_ID; - } - - $supported_blocks = array_map( - function( $hook ) { - return $hook['block_name']; - }, - array_values( $this->hook_data ) + $supported_blocks = array_merge( + [], + ...array_map( + function( $hook ) { + return $hook['block_names']; + }, + array_values( $this->hook_data ) + ) ); if ( ! in_array( $block_name, $supported_blocks, true ) ) { @@ -186,60 +180,60 @@ function( $hook ) { protected function set_hook_data() { $this->hook_data = array( 'woocommerce_before_main_content' => array( - 'block_name' => 'core/query', - 'position' => 'before', - 'hooked' => array( + 'block_names' => array( 'core/query', 'woocommerce/product-collection' ), + 'position' => 'before', + 'hooked' => array( 'woocommerce_output_content_wrapper' => 10, 'woocommerce_breadcrumb' => 20, ), ), 'woocommerce_after_main_content' => array( - 'block_name' => 'core/query', - 'position' => 'after', - 'hooked' => array( + 'block_names' => array( 'core/query', 'woocommerce/product-collection' ), + 'position' => 'after', + 'hooked' => array( 'woocommerce_output_content_wrapper_end' => 10, ), ), 'woocommerce_before_shop_loop_item_title' => array( - 'block_name' => 'core/post-title', - 'position' => 'before', - 'hooked' => array( + 'block_names' => array( 'core/post-title' ), + 'position' => 'before', + 'hooked' => array( 'woocommerce_show_product_loop_sale_flash' => 10, 'woocommerce_template_loop_product_thumbnail' => 10, ), ), 'woocommerce_shop_loop_item_title' => array( - 'block_name' => 'core/post-title', - 'position' => 'after', - 'hooked' => array( + 'block_names' => array( 'core/post-title' ), + 'position' => 'after', + 'hooked' => array( 'woocommerce_template_loop_product_title' => 10, ), ), 'woocommerce_after_shop_loop_item_title' => array( - 'block_name' => 'core/post-title', - 'position' => 'after', - 'hooked' => array( + 'block_names' => array( 'core/post-title' ), + 'position' => 'after', + 'hooked' => array( 'woocommerce_template_loop_rating' => 5, 'woocommerce_template_loop_price' => 10, ), ), 'woocommerce_before_shop_loop_item' => array( - 'block_name' => self::LOOP_ITEM_ID, - 'position' => 'before', - 'hooked' => array( + 'block_names' => array( self::LOOP_ITEM_ID ), + 'position' => 'before', + 'hooked' => array( 'woocommerce_template_loop_product_link_open' => 10, ), ), 'woocommerce_after_shop_loop_item' => array( - 'block_name' => self::LOOP_ITEM_ID, - 'position' => 'after', - 'hooked' => array( + 'block_names' => array( self::LOOP_ITEM_ID ), + 'position' => 'after', + 'hooked' => array( 'woocommerce_template_loop_product_link_close' => 5, 'woocommerce_template_loop_add_to_cart' => 10, ), ), 'woocommerce_before_shop_loop' => array( - 'block_name' => 'core/post-template', + 'block_names' => array( 'core/post-template', 'woocommerce/product-template' ), 'position' => 'before', 'hooked' => array( 'woocommerce_output_all_notices' => 10, @@ -253,7 +247,7 @@ protected function set_hook_data() { ), ), 'woocommerce_after_shop_loop' => array( - 'block_name' => 'core/post-template', + 'block_names' => array( 'core/post-template', 'woocommerce/product-template' ), 'position' => 'after', 'hooked' => array( 'woocommerce_pagination' => 10, @@ -263,7 +257,7 @@ protected function set_hook_data() { ), ), 'woocommerce_no_products_found' => array( - 'block_name' => 'core/query-no-results', + 'block_names' => array( 'core/query-no-results' ), 'position' => 'before', 'hooked' => array( 'wc_no_products_found' => 10, @@ -273,9 +267,9 @@ protected function set_hook_data() { ), ), 'woocommerce_archive_description' => array( - 'block_name' => 'core/term-description', - 'position' => 'before', - 'hooked' => array( + 'block_names' => array( 'core/term-description' ), + 'position' => 'before', + 'hooked' => array( 'woocommerce_taxonomy_archive_description' => 10, 'woocommerce_product_archive_description' => 10, ), @@ -298,7 +292,7 @@ private function is_archive_template() { */ private function inner_blocks_walker( &$block ) { if ( - $this->is_products_block_with_inherit_query( $block ) + $this->is_products_block_with_inherit_query( $block ) || $this->is_product_collection_block_with_inherit_query( $block ) ) { $this->inject_attribute( $block ); $this->remove_default_hooks(); @@ -325,6 +319,69 @@ private function restore_default_hooks() { } } + /** + * Check if block is within the product-query namespace + * + * @param array $block Parsed block data. + */ + private function is_block_within_namespace( $block ) { + $attributes = $block['attrs']; + + return isset( $attributes['__woocommerceNamespace'] ) && 'woocommerce/product-query/product-template' === $attributes['__woocommerceNamespace']; + } + + /** + * Check if block has isInherited attribute asigned + * + * @param array $block Parsed block data. + */ + private function is_block_inherited( $block ) { + $attributes = $block['attrs']; + + $outcome = isset( $attributes['isInherited'] ) && 1 === $attributes['isInherited']; + + return $outcome; + } + + /** + * The core/post-template has two different block names: + * - core/post-template when the wrapper is rendered. + * - core/null when the loop item is rendered. + * + * @param array $block Parsed block data. + */ + private function is_null_post_template( $block ) { + $block_name = $block['blockName']; + + return 'core/null' === $block_name && ( $this->is_block_inherited( $block ) || $this->is_block_within_namespace( $block ) ); + } + + /** + * Check if block is a Post template + * + * @param string $block_name Block name. + */ + private function is_post_template( $block_name ) { + return 'core/post-template' === $block_name; + } + + /** + * Check if block is a Product Template + * + * @param string $block_name Block name. + */ + private function is_product_template( $block_name ) { + return 'woocommerce/product-template' === $block_name; + } + + /** + * Check if block is eaither a Post template or Product Template + * + * @param string $block_name Block name. + */ + private function is_post_or_product_template( $block_name ) { + return $this->is_post_template( $block_name ) || $this->is_product_template( $block_name ); + } /** * Check if the block is a Products block that inherits query from template. @@ -339,6 +396,18 @@ private function is_products_block_with_inherit_query( $block ) { $block['attrs']['query']['inherit']; } + /** + * Check if the block is a Product Collection block that inherits query from template. + * + * @param array $block Parsed block data. + */ + private function is_product_collection_block_with_inherit_query( $block ) { + return 'woocommerce/product-collection' === $block['blockName'] && + isset( $block['attrs']['query']['inherit'] ) && + $block['attrs']['query']['inherit']; + } + + /** * Recursively inject the custom attribute to all nested blocks. * diff --git a/src/Templates/SingleProductTemplateCompatibility.php b/src/Templates/SingleProductTemplateCompatibility.php index 3dd8551ab60..56e92808cd7 100644 --- a/src/Templates/SingleProductTemplateCompatibility.php +++ b/src/Templates/SingleProductTemplateCompatibility.php @@ -32,7 +32,7 @@ public function inject_hooks( $block_content, $block ) { $block_hooks = array_filter( $this->hook_data, function( $hook ) use ( $block_name ) { - return $hook['block_name'] === $block_name; + return in_array( $block_name, $hook['block_names'], true ); } ); @@ -170,46 +170,46 @@ public function update_render_block_data( $parsed_block, $source_block, $parent_ protected function set_hook_data() { $this->hook_data = array( 'woocommerce_before_main_content' => array( - 'block_name' => '', - 'position' => 'before', - 'hooked' => array( + 'block_names' => array(), + 'position' => 'before', + 'hooked' => array( 'woocommerce_output_content_wrapper' => 10, 'woocommerce_breadcrumb' => 20, ), ), 'woocommerce_after_main_content' => array( - 'block_name' => '', - 'position' => 'after', - 'hooked' => array( + 'block_names' => array(), + 'position' => 'after', + 'hooked' => array( 'woocommerce_output_content_wrapper_end' => 10, ), ), 'woocommerce_sidebar' => array( - 'block_name' => '', - 'position' => 'after', - 'hooked' => array( + 'block_names' => array(), + 'position' => 'after', + 'hooked' => array( 'woocommerce_get_sidebar' => 10, ), ), 'woocommerce_before_single_product' => array( - 'block_name' => '', - 'position' => 'before', - 'hooked' => array( + 'block_names' => array(), + 'position' => 'before', + 'hooked' => array( 'woocommerce_output_all_notices' => 10, ), ), 'woocommerce_before_single_product_summary' => array( - 'block_name' => '', - 'position' => 'before', - 'hooked' => array( + 'block_names' => array(), + 'position' => 'before', + 'hooked' => array( 'woocommerce_show_product_sale_flash' => 10, 'woocommerce_show_product_images' => 20, ), ), 'woocommerce_single_product_summary' => array( - 'block_name' => '', - 'position' => 'before', - 'hooked' => array( + 'block_names' => array(), + 'position' => 'before', + 'hooked' => array( 'woocommerce_template_single_title' => 5, 'woocommerce_template_single_rating' => 10, 'woocommerce_template_single_price' => 10, @@ -220,29 +220,29 @@ protected function set_hook_data() { ), ), 'woocommerce_after_single_product' => array( - 'block_name' => '', - 'position' => 'after', - 'hooked' => array(), + 'block_names' => array(), + 'position' => 'after', + 'hooked' => array(), ), 'woocommerce_product_meta_start' => array( - 'block_name' => 'woocommerce/product-meta', - 'position' => 'before', - 'hooked' => array(), + 'block_names' => array( 'woocommerce/product-meta' ), + 'position' => 'before', + 'hooked' => array(), ), 'woocommerce_product_meta_end' => array( - 'block_name' => 'woocommerce/product-meta', - 'position' => 'after', - 'hooked' => array(), + 'block_names' => array( 'woocommerce/product-meta' ), + 'position' => 'after', + 'hooked' => array(), ), 'woocommerce_share' => array( - 'block_name' => 'woocommerce/product-details', - 'position' => 'before', - 'hooked' => array(), + 'block_names' => array( 'woocommerce/product-details' ), + 'position' => 'before', + 'hooked' => array(), ), 'woocommerce_after_single_product_summary' => array( - 'block_name' => 'woocommerce/product-details', - 'position' => 'after', - 'hooked' => array( + 'block_names' => array( 'woocommerce/product-details' ), + 'position' => 'after', + 'hooked' => array( 'woocommerce_output_product_data_tabs' => 10, // We want to display the upsell products after the last block that belongs to the Single Product. // 'woocommerce_upsell_display' => 15.