fer( array_merge( $block_hooks, $last_block_hook['after'] ), 'after' ) ); } } /** * Update the render block data to inject our custom attribute needed to * determine which is the first block of the Single Product Template. * * @param array $parsed_block The block being rendered. * @param array $source_block An un-modified copy of $parsed_block, as it appeared in the source content. * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block. * * @return array */ public function update_render_block_data( $parsed_block, $source_block, $parent_block ) { return $parsed_block; } /** * Set supported hooks. */ protected function set_hook_data() { $this->hook_data = array( 'woocommerce_before_main_content' => array( 'block_name' => '', 'position' => 'before', 'hooked' => array( 'woocommerce_output_content_wrapper' => 10, 'woocommerce_breadcrumb' => 20, ), ), 'woocommerce_after_main_content' => array( 'block_name' => '', 'position' => 'after', 'hooked' => array( 'woocommerce_output_content_wrapper_end' => 10, ), ), 'woocommerce_sidebar' => array( 'block_name' => '', 'position' => 'after', 'hooked' => array( 'woocommerce_get_sidebar' => 10, ), ), 'woocommerce_before_single_product' => array( 'block_name' => '', 'position' => 'before', 'hooked' => array( 'woocommerce_output_all_notices' => 10, ), ), 'woocommerce_before_single_product_summary' => array( 'block_name' => '', '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( 'woocommerce_template_single_title' => 5, 'woocommerce_template_single_rating' => 10, 'woocommerce_template_single_price' => 10, 'woocommerce_template_single_excerpt' => 20, 'woocommerce_template_single_add_to_cart' => 30, 'woocommerce_template_single_meta' => 40, 'woocommerce_template_single_sharing' => 50, ), ), 'woocommerce_after_single_product' => array( 'block_name' => '', 'position' => 'after', 'hooked' => array(), ), 'woocommerce_product_meta_start' => array( 'block_name' => 'woocommerce/product-meta', 'position' => 'before', 'hooked' => array(), ), 'woocommerce_product_meta_end' => array( 'block_name' => 'woocommerce/product-meta', 'position' => 'after', 'hooked' => array(), ), 'woocommerce_share' => array( 'block_name' => 'woocommerce/product-details', 'position' => 'before', 'hooked' => array(), ), 'woocommerce_after_single_product_summary' => array( 'block_name' => '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. 'woocommerce_output_related_products' => 20, ), ), ); } /** * Add compatibility layer to the first and last block of the Single Product Template. * * @param string $template_content Template. * @return string */ public static function add_compatibility_layer( $template_content ) { $parsed_blocks = parse_blocks( $template_content ); if ( ! self::has_single_product_template_blocks( $parsed_blocks ) ) { $template = self::inject_custom_attributes_to_first_and_last_block_single_product_template( $parsed_blocks ); return self::serialize_blocks( $template ); } $wrapped_blocks = self::wrap_single_product_template( $template_content ); $template = self::inject_custom_attributes_to_first_and_last_block_single_product_template( $wrapped_blocks ); return self::serialize_blocks( $template ); } /** * 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 array Wrapped template content inside a div. */ private static function wrap_single_product_template( $template_content ) { $parsed_blocks = parse_blocks( $template_content ); $grouped_blocks = self::group_blocks( $parsed_blocks ); $wrapped_blocks = array_map( function( $blocks ) { if ( 'core/template-part' === $blocks[0]['blockName'] ) { return $blocks; } $has_single_product_template_blocks = self::has_single_product_template_blocks( $blocks ); if ( $has_single_product_template_blocks ) { $wrapped_block = self::create_wrap_block_group( $blocks ); return array( $wrapped_block[0] ); } return $blocks; }, $grouped_blocks ); return $wrapped_blocks; } /** * Add custom attributes to the first group block and last group block that wrap Single Product Template blocks. * * @param array $wrapped_blocks Wrapped blocks. * @return array */ private static function inject_custom_attributes_to_first_and_last_block_single_product_template( $wrapped_blocks ) { $template_with_custom_attributes = array_reduce( $wrapped_blocks, function( $carry, $item ) { $index = $carry['index']; $carry['index'] = $carry['index'] + 1; // If the block is a child of a group block, we need to get the first block of the group. $block = isset( $item[0] ) ? $item[0] : $item; if ( 'core/template-part' === $block['blockName'] || self::is_custom_html( $block ) ) { $carry['template'][] = $block; return $carry; } if ( '' === $carry['first_block']['index'] ) { $block['attrs'][ self::IS_FIRST_BLOCK ] = true; $carry['first_block']['index'] = $index; } if ( '' !== $carry['last_block']['index'] ) { $index_element = $carry['last_block']['index']; $carry['last_block']['index'] = $index; $block['attrs'][ self::IS_LAST_BLOCK ] = true; unset( $carry['template'][ $index_element ]['attrs'][ self::IS_LAST_BLOCK ] ); $carry['template'][] = $block; return $carry; } $block['attrs'][ self::IS_LAST_BLOCK ] = true; $carry['last_block']['index'] = $index; $carry['template'][] = $block; return $carry; }, array( 'template' => array(), 'first_block' => array( 'index' => '', ), 'last_block' => array( 'index' => '', ), 'index' => 0, ) ); return array( $template_with_custom_attributes['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( '