From 07826b38cd2a4ea82c58c6275d534aa4a5214a8d Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Mon, 24 Jul 2023 15:48:50 +0200 Subject: [PATCH 1/8] Enable Compatibility Layer for Product Collection --- src/BlockTypes/AddToCartForm.php | 2 +- .../ArchiveProductTemplatesCompatibility.php | 108 +++++++++++++++--- 2 files changed, 96 insertions(+), 14 deletions(-) diff --git a/src/BlockTypes/AddToCartForm.php b/src/BlockTypes/AddToCartForm.php index ccde4c2d382..fd31d662417 100644 --- a/src/BlockTypes/AddToCartForm.php +++ b/src/BlockTypes/AddToCartForm.php @@ -96,7 +96,7 @@ protected function render( $attributes, $content, $block ) { $classname = $attributes['className'] ?? ''; $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); - $product_classname = $is_descendent_of_single_product_block ? 'product' : ''; + $product_classname = $is_descendent_of_single_product_block ? 'product' : ''; $form = sprintf( '
%5$s
', diff --git a/src/Templates/ArchiveProductTemplatesCompatibility.php b/src/Templates/ArchiveProductTemplatesCompatibility.php index c53ec40fee2..9681f5d68f3 100644 --- a/src/Templates/ArchiveProductTemplatesCompatibility.php +++ b/src/Templates/ArchiveProductTemplatesCompatibility.php @@ -79,9 +79,9 @@ function( $hook ) use ( $block_name ) { } ); - // 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,16 +93,7 @@ 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'] - ) { + if ( $this->is_null_post_template( $block ) ) { $block_name = self::LOOP_ITEM_ID; } @@ -196,6 +187,21 @@ protected function set_hook_data() { 'woocommerce_output_content_wrapper_end' => 10, ), ), + 'woocommerce_before_main_content' => array( + 'block_name' => 'woocommerce/product-collection', + 'position' => 'before', + 'hooked' => array( + 'woocommerce_output_content_wrapper' => 10, + 'woocommerce_breadcrumb' => 20, + ), + ), + 'woocommerce_after_main_content' => array( + 'block_name' => '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', @@ -258,6 +264,30 @@ protected function set_hook_data() { 'woocommerce_pagination', ), ), + 'woocommerce_before_shop_loop' => array( + 'block_name' => 'woocommerce/product-template', + 'position' => 'before', + 'hooked' => array( + 'woocommerce_output_all_notices' => 10, + 'woocommerce_result_count' => 20, + 'woocommerce_catalog_ordering' => 30, + ), + 'permanently_removed_actions' => array( + 'woocommerce_output_all_notices', + 'woocommerce_result_count', + 'woocommerce_catalog_ordering', + ), + ), + 'woocommerce_after_shop_loop' => array( + 'block_name' => 'woocommerce/product-template', + 'position' => 'after', + 'hooked' => array( + 'woocommerce_pagination' => 10, + ), + 'permanently_removed_actions' => array( + 'woocommerce_pagination', + ), + ), 'woocommerce_no_products_found' => array( 'block_name' => 'core/query-no-results', 'position' => 'before', @@ -294,7 +324,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(); @@ -321,6 +351,46 @@ private function restore_default_hooks() { } } + /** + * 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']; + $attributes = $block['attrs']; + + return 'core/null' === $block_name && isset( $attributes['__woocommerceNamespace'] ) && 'woocommerce/product-query/product-template' === $attributes['__woocommerceNamespace']; + } + + /** + * 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. @@ -335,6 +405,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. * From bf2db00c646fedc19ece32d5981c4652fb663aef Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Wed, 26 Jul 2023 13:29:57 +0200 Subject: [PATCH 2/8] Add another condition to recognise shop item --- .../ArchiveProductTemplatesCompatibility.php | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Templates/ArchiveProductTemplatesCompatibility.php b/src/Templates/ArchiveProductTemplatesCompatibility.php index 9681f5d68f3..18f97d11ee8 100644 --- a/src/Templates/ArchiveProductTemplatesCompatibility.php +++ b/src/Templates/ArchiveProductTemplatesCompatibility.php @@ -351,6 +351,30 @@ 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. @@ -360,9 +384,8 @@ private function restore_default_hooks() { */ private function is_null_post_template( $block ) { $block_name = $block['blockName']; - $attributes = $block['attrs']; - return 'core/null' === $block_name && isset( $attributes['__woocommerceNamespace'] ) && 'woocommerce/product-query/product-template' === $attributes['__woocommerceNamespace']; + return 'core/null' === $block_name && ( $this->is_block_inherited( $block ) || $this->is_block_within_namespace( $block ) ); } /** From 34dcf3f64216315d77c70d6443c5cda7521e868c Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Thu, 27 Jul 2023 08:55:54 +0200 Subject: [PATCH 3/8] Enable multiple blocks to be assigned to single hook in Compatibility Layer --- .../ArchiveProductTemplatesCompatibility.php | 107 ++++++------------ 1 file changed, 35 insertions(+), 72 deletions(-) diff --git a/src/Templates/ArchiveProductTemplatesCompatibility.php b/src/Templates/ArchiveProductTemplatesCompatibility.php index 18f97d11ee8..8f3a3d741fc 100644 --- a/src/Templates/ArchiveProductTemplatesCompatibility.php +++ b/src/Templates/ArchiveProductTemplatesCompatibility.php @@ -75,7 +75,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 ); } ); @@ -97,11 +97,13 @@ function( $hook ) use ( $block_name ) { $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 ) ) { @@ -173,99 +175,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( - 'woocommerce_output_content_wrapper_end' => 10, - ), - ), - 'woocommerce_before_main_content' => array( - 'block_name' => 'woocommerce/product-collection', - 'position' => 'before', - 'hooked' => array( - 'woocommerce_output_content_wrapper' => 10, - 'woocommerce_breadcrumb' => 20, - ), - ), - 'woocommerce_after_main_content' => array( - 'block_name' => 'woocommerce/product-collection', - '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', - 'position' => 'before', - 'hooked' => array( - 'woocommerce_output_all_notices' => 10, - 'woocommerce_result_count' => 20, - 'woocommerce_catalog_ordering' => 30, - ), - 'permanently_removed_actions' => array( - 'woocommerce_output_all_notices', - 'woocommerce_result_count', - 'woocommerce_catalog_ordering', - ), - ), - 'woocommerce_after_shop_loop' => array( - 'block_name' => 'core/post-template', - 'position' => 'after', - 'hooked' => array( - 'woocommerce_pagination' => 10, - ), - 'permanently_removed_actions' => array( - 'woocommerce_pagination', - ), - ), - 'woocommerce_before_shop_loop' => array( - 'block_name' => 'woocommerce/product-template', + 'block_names' => array( 'core/post-template', 'woocommerce/product-template' ), 'position' => 'before', 'hooked' => array( 'woocommerce_output_all_notices' => 10, @@ -279,7 +242,7 @@ protected function set_hook_data() { ), ), 'woocommerce_after_shop_loop' => array( - 'block_name' => 'woocommerce/product-template', + 'block_names' => array( 'core/post-template', 'woocommerce/product-template' ), 'position' => 'after', 'hooked' => array( 'woocommerce_pagination' => 10, @@ -289,7 +252,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, @@ -299,9 +262,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, ), From c038a042af7c5944f1a74642ee1e699ab48c1e6f Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:04:40 +0200 Subject: [PATCH 4/8] Move core/null block name assignment before the hooks assignment which is based on the block name --- src/Templates/ArchiveProductTemplatesCompatibility.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Templates/ArchiveProductTemplatesCompatibility.php b/src/Templates/ArchiveProductTemplatesCompatibility.php index 8f3a3d741fc..57c3c3bf287 100644 --- a/src/Templates/ArchiveProductTemplatesCompatibility.php +++ b/src/Templates/ArchiveProductTemplatesCompatibility.php @@ -72,6 +72,10 @@ 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 ) { @@ -93,10 +97,6 @@ function( $hook ) use ( $block_name ) { return $content; } - if ( $this->is_null_post_template( $block ) ) { - $block_name = self::LOOP_ITEM_ID; - } - $supported_blocks = array_merge( ...array_map( function( $hook ) { From 81342f0ee5c34ae45ac56e6ff2f7a7c03b45d7dc Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Mon, 31 Jul 2023 14:38:21 +0200 Subject: [PATCH 5/8] Make array operations safer --- src/Templates/ArchiveProductTemplatesCompatibility.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Templates/ArchiveProductTemplatesCompatibility.php b/src/Templates/ArchiveProductTemplatesCompatibility.php index 57c3c3bf287..3e68e5b4ea2 100644 --- a/src/Templates/ArchiveProductTemplatesCompatibility.php +++ b/src/Templates/ArchiveProductTemplatesCompatibility.php @@ -98,6 +98,7 @@ function( $hook ) use ( $block_name ) { } $supported_blocks = array_merge( + [], ...array_map( function( $hook ) { return $hook['block_names']; From bfadbd47ffefd2afed69718e6a7298fa60721b23 Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Mon, 31 Jul 2023 15:06:55 +0200 Subject: [PATCH 6/8] Unify hooks interface between templates --- .../SingleProductTemplateCompatibility.php | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Templates/SingleProductTemplateCompatibility.php b/src/Templates/SingleProductTemplateCompatibility.php index 3dd8551ab60..90a4955a10d 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' => ( '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. From 0802655f778ed5219114fe62d093496501afba4b Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Mon, 31 Jul 2023 21:34:11 +0200 Subject: [PATCH 7/8] Fix typo --- src/Templates/SingleProductTemplateCompatibility.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Templates/SingleProductTemplateCompatibility.php b/src/Templates/SingleProductTemplateCompatibility.php index 90a4955a10d..56e92808cd7 100644 --- a/src/Templates/SingleProductTemplateCompatibility.php +++ b/src/Templates/SingleProductTemplateCompatibility.php @@ -235,7 +235,7 @@ protected function set_hook_data() { 'hooked' => array(), ), 'woocommerce_share' => array( - 'block_names' => ( 'woocommerce/product-details' ), + 'block_names' => array( 'woocommerce/product-details' ), 'position' => 'before', 'hooked' => array(), ), From 90384921cb79602f03ac749888e30f2b5f061954 Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Fri, 4 Aug 2023 08:19:45 +0200 Subject: [PATCH 8/8] Update comment describing set_hook_data to reflect new interface --- src/Templates/AbstractTemplateCompatibility.php | 4 ++-- 1 file changed, 2 insertions(+), 2 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