OrderConfirmationTemplate.php000064400000002313150732353200012374 0ustar00is_active_template() ) { global $wp_admin_bar; $wp_admin_bar->remove_menu( 'edit' ); } } /** * Returns the page object assigned to this template/page. * * @return \WP_Post|null Post object or null. */ protected function get_placeholder_page() { return null; } /** * True when viewing the Order Received endpoint. * * @return boolean */ protected function is_active_template() { return is_wc_endpoint_url( 'order-received' ); } /** * Should return the title of the page. * * @return string */ public static function get_template_title() { return __( 'Order Confirmation', 'woocommerce' ); } } ProductAttributeTemplate.php000064400000001670150732353200012261 0ustar00init(); } /** * Initialization method. */ protected function init() { add_filter( 'taxonomy_template_hierarchy', array( $this, 'update_taxonomy_template_hierarchy' ), 1, 3 ); } /** * Renders the Product by Attribute template for product attributes taxonomy pages. * * @param array $templates Templates that match the product attributes taxonomy. */ public function update_taxonomy_template_hierarchy( $templates ) { $queried_object = get_queried_object(); if ( taxonomy_is_product_attribute( $queried_object->taxonomy ) && wc_current_theme_is_fse_theme() ) { array_splice( $templates, count( $templates ) - 1, 0, self::SLUG ); } return $templates; } } CartTemplate.php000064400000003216150732353200007644 0ustar00get_placeholder_page(); return null !== $placeholder && $post instanceof \WP_Post && $placeholder->post_name === $post->post_name; } /** * When the page should be displaying the template, add it to the hierarchy. * * This places the template name e.g. `cart`, at the beginning of the template hierarchy array. The hook priority * is 1 to ensure it runs first; other consumers e.g. extensions, could therefore inject their own template instead * of this one when using the default priority of 10. * * @param array $templates Templates that match the pages_template_hierarchy. */ public function page_template_hierarchy( $templates ) { if ( $this->is_active_template() ) { array_unshift( $templates, $this->get_slug() ); array_unshift( $templates, 'cart' ); } return $templates; } } ClassicTemplatesCompatibility.php000064400000004512150732353200013251 0ustar00asset_data_registry = $asset_data_registry; $this->init(); } /** * Initialization method. */ protected function init() { if ( ! wc_current_theme_is_fse_theme() ) { add_action( 'template_redirect', array( $this, 'set_classic_template_data' ) ); // We need to set this data on the widgets screen so the filters render previews. add_action( 'load-widgets.php', array( $this, 'set_filterable_product_data' ) ); } } /** * Executes the methods which set the necessary data needed for filter blocks to work correctly as widgets in Classic templates. * * @return void */ public function set_classic_template_data() { $this->set_filterable_product_data(); $this->set_php_template_data(); } /** * This method passes the value `has_filterable_products` to the front-end for product archive pages, * so that widget product filter blocks are aware of the context they are in and can render accordingly. * * @return void */ public function set_filterable_product_data() { global $pagenow; if ( is_shop() || is_product_taxonomy() || 'widgets.php' === $pagenow ) { $this->asset_data_registry->add( 'hasFilterableProducts', true, true ); } } /** * This method passes the value `is_rendering_php_template` to the front-end of Classic themes, * so that widget product filter blocks are aware of how to filter the products. * * This data only matters on WooCommerce product archive pages. * On non-archive pages the merchant could be using the All Products block which is not a PHP template. * * @return void */ public function set_php_template_data() { if ( is_shop() || is_product_taxonomy() ) { $this->asset_data_registry->add( 'isRenderingPhpTemplate', true, true ); } } } SingleProductTemplateCompatibility.php000064400000034604150732353200014274 0ustar00remove_default_hooks(); $block_name = $block['blockName']; $block_hooks = array_filter( $this->hook_data, function( $hook ) use ( $block_name ) { return in_array( $block_name, $hook['block_names'], true ); } ); $first_or_last_block_content = $this->inject_hook_to_first_and_last_blocks( $block_content, $block, $block_hooks ); if ( isset( $first_or_last_block_content ) ) { return $first_or_last_block_content; } return sprintf( '%1$s%2$s%3$s', $this->get_hooks_buffer( $block_hooks, 'before' ), $block_content, $this->get_hooks_buffer( $block_hooks, 'after' ) ); } /** * Inject custom hooks to the first and last blocks. * Since that there is a custom logic for the first and last block, we have to inject the hooks manually. * The first block supports the following hooks: * woocommerce_before_single_product * * The last block supports the following hooks: * woocommerce_after_single_product * * @param mixed $block_content The rendered block content. * @param mixed $block The parsed block data. * @param array $block_hooks The hooks that should be injected to the block. * @return string */ private function inject_hook_to_first_and_last_blocks( $block_content, $block, $block_hooks ) { $first_block_hook = array( 'before' => array( 'woocommerce_before_main_content' => $this->hook_data['woocommerce_before_main_content'], 'woocommerce_before_single_product' => $this->hook_data['woocommerce_before_single_product'], ), 'after' => array(), ); $last_block_hook = array( 'before' => array(), 'after' => array( 'woocommerce_after_single_product' => $this->hook_data['woocommerce_after_single_product'], 'woocommerce_after_main_content' => $this->hook_data['woocommerce_after_main_content'], 'woocommerce_sidebar' => $this->hook_data['woocommerce_sidebar'], ), ); if ( isset( $block['attrs'][ self::IS_FIRST_BLOCK ] ) && isset( $block['attrs'][ self::IS_LAST_BLOCK ] ) ) { return sprintf( '%1$s%2$s', $this->inject_hooks_after_the_wrapper( $block_content, array_merge( $first_block_hook['before'], $block_hooks, $last_block_hook['before'] ) ), $this->get_hooks_buffer( array_merge( $first_block_hook['after'], $block_hooks, $last_block_hook['after'] ), 'after' ) ); } if ( isset( $block['attrs'][ self::IS_FIRST_BLOCK ] ) ) { return sprintf( '%1$s%2$s', $this->inject_hooks_after_the_wrapper( $block_content, array_merge( $first_block_hook['before'], $block_hooks ) ), $this->get_hooks_buffer( array_merge( $first_block_hook['after'], $block_hooks ), 'after' ) ); } if ( isset( $block['attrs'][ self::IS_LAST_BLOCK ] ) ) { return sprintf( '%1$s%2$s%3$s', $this->get_hooks_buffer( array_merge( $last_block_hook['before'], $block_hooks ), 'before' ), $block_content, $this->get_hooks_buffer( 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_names' => array(), 'position' => 'before', 'hooked' => array( 'woocommerce_output_content_wrapper' => 10, 'woocommerce_breadcrumb' => 20, ), ), 'woocommerce_after_main_content' => array( 'block_names' => array(), 'position' => 'after', 'hooked' => array( 'woocommerce_output_content_wrapper_end' => 10, ), ), 'woocommerce_sidebar' => array( 'block_names' => array(), 'position' => 'after', 'hooked' => array( 'woocommerce_get_sidebar' => 10, ), ), 'woocommerce_before_single_product' => array( 'block_names' => array(), 'position' => 'before', 'hooked' => array( 'woocommerce_output_all_notices' => 10, ), ), 'woocommerce_before_single_product_summary' => array( 'block_names' => array( 'core/post-excerpt' ), 'position' => 'before', 'hooked' => array( 'woocommerce_show_product_sale_flash' => 10, 'woocommerce_show_product_images' => 20, ), ), 'woocommerce_single_product_summary' => array( 'block_names' => array( 'core/post-excerpt' ), '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_names' => array(), 'position' => 'after', 'hooked' => array(), ), 'woocommerce_product_meta_start' => array( 'block_names' => array( 'woocommerce/product-meta' ), 'position' => 'before', 'hooked' => array(), ), 'woocommerce_product_meta_end' => array( 'block_names' => array( 'woocommerce/product-meta' ), 'position' => 'after', 'hooked' => array(), ), 'woocommerce_share' => array( 'block_names' => array( 'woocommerce/product-details' ), 'position' => 'before', 'hooked' => array(), ), 'woocommerce_after_single_product_summary' => 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. '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( '
%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. * @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 = array( 'woocommerce/product-image-gallery', 'woocommerce/product-details', 'woocommerce/add-to-cart-form', 'woocommerce/product-meta', 'woocommerce/product-price', 'woocommerce/breadcrumbs' ); $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( array $carry, array $block ) { if ( 'core/template-part' === $block['blockName'] ) { $carry[] = array( $block ); return $carry; } $last_element_index = count( $carry ) - 1; if ( isset( $carry[ $last_element_index ][0]['blockName'] ) && 'core/template-part' !== $carry[ $last_element_index ][0]['blockName'] ) { $carry[ $last_element_index ][] = $block; return $carry; } $carry[] = array( $block ); return $carry; }, array() ); } /** * Inject the hooks after the div wrapper. * * @param string $block_content Block Content. * @param array $hooks Hooks to inject. * @return array */ private function inject_hooks_after_the_wrapper( $block_content, $hooks ) { $closing_tag_position = strpos( $block_content, '>' ); return substr_replace( $block_content, $this->get_hooks_buffer( $hooks, 'before' ), // Add 1 to the position to inject the content after the closing tag. $closing_tag_position + 1, 0 ); } /** * Plain custom HTML block is parsed as block with an empty blockName with a filled innerHTML. * * @param array $block Parse block. * @return bool */ private static function is_custom_html( $block ) { return empty( $block['blockName'] ) && ! empty( $block['innerHTML'] ); } /** * Serialize template. * * @param array $parsed_blocks Parsed blocks. * @return string */ private static function serialize_blocks( $parsed_blocks ) { return array_reduce( $parsed_blocks, function( $carry, $item ) { if ( is_array( $item ) ) { return $carry . serialize_blocks( $item ); } return $carry . serialize_block( $item ); }, '' ); } } MiniCartTemplate.php000064400000000245150732353200010460 0ustar00init(); } /** * Initialization method. */ protected function init() { add_filter( 'search_template_hierarchy', array( $this, 'update_search_template_hierarchy' ), 10, 3 ); } /** * When the search is for products and a block theme is active, render the Product Search Template. * * @param array $templates Templates that match the search hierarchy. */ public function update_search_template_hierarchy( $templates ) { if ( ( is_search() && is_post_type_archive( 'product' ) ) && wc_current_theme_is_fse_theme() ) { array_unshift( $templates, self::SLUG ); } return $templates; } } SingleProductTemplate.php000064400000011117150732353200011534 0ustar00 $carry['blocks'], 'html_block' => null, 'removed' => true, 'is_already_replaced' => true, ); } return array( 'blocks' => $carry['blocks'], 'html_block' => parse_blocks( '' . get_the_password_form() . '' )[0], 'removed' => false, 'is_already_replaced' => $carry['is_already_replaced'], ); } if ( isset( $block['innerBlocks'] ) && count( $block['innerBlocks'] ) > 0 ) { $index = 0; $new_inner_blocks = array(); $new_inner_contents = $block['innerContent']; foreach ( $block['innerContent'] as $inner_content ) { // Don't process the closing tag of the block. if ( count( $block['innerBlocks'] ) === $index ) { break; } $blocks = self::replace_first_single_product_template_block_with_password_form( array( $block['innerBlocks'][ $index ] ), $carry['is_already_replaced'] ); $new_blocks = $blocks['blocks']; $html_block = $blocks['html_block']; $is_removed = $blocks['removed']; $carry['is_already_replaced'] = $blocks['is_already_replaced']; if ( isset( $html_block ) ) { $new_inner_blocks = array_merge( $new_inner_blocks, $new_blocks, array( $html_block ) ); $carry['is_already_replaced'] = true; } else { $new_inner_blocks = array_merge( $new_inner_blocks, $new_blocks ); } if ( $is_removed ) { unset( $new_inner_contents[ $index ] ); // The last element of the inner contents contains the closing tag of the block. We don't want to remove it. if ( $index + 1 < count( $new_inner_contents ) ) { unset( $new_inner_contents[ $index + 1 ] ); } $new_inner_contents = array_values( $new_inner_contents ); } $index++; } $block['innerBlocks'] = $new_inner_blocks; $block['innerContent'] = $new_inner_contents; return array( 'blocks' => array_merge( $carry['blocks'], array( $block ) ), 'html_block' => null, 'removed' => false, 'is_already_replaced' => $carry['is_already_replaced'], ); } return array( 'blocks' => array_merge( $carry['blocks'], array( $block ) ), 'html_block' => null, 'removed' => false, 'is_already_replaced' => $carry['is_already_replaced'], ); }, array( 'blocks' => array(), 'html_block' => null, 'removed' => false, 'is_already_replaced' => $is_already_replaced, ) ); } /** * Add password form to the Single Product Template. * * @param string $content The content of the template. * @return string */ public static function add_password_form( $content ) { $parsed_blocks = parse_blocks( $content ); $blocks = self::replace_first_single_product_template_block_with_password_form( $parsed_blocks, false ); $serialized_blocks = serialize_blocks( $blocks['blocks'] ); return $serialized_blocks; } } ArchiveProductTemplatesCompatibility.php000064400000030620150732353200014611 0ustar00is_archive_template() ) { return $parsed_block; } /** * Custom data can be injected to top level block only, as Gutenberg * will use this data to render the blocks and its nested blocks. */ if ( $parent_block ) { return $parsed_block; } $this->inner_blocks_walker( $parsed_block ); return $parsed_block; } /** * Inject hooks to rendered content of corresponding blocks. * * @param mixed $block_content The rendered block content. * @param mixed $block The parsed block data. * @return string */ public function inject_hooks( $block_content, $block ) { if ( ! $this->is_archive_template() ) { return $block_content; } /** * If the block is not inherited, we don't need to inject hooks. */ if ( empty( $block['attrs']['isInherited'] ) ) { return $block_content; } $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 in_array( $block_name, $hook['block_names'], true ); } ); // 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 ( $this->is_post_or_product_template( $block_name ) && ! empty( $block_content ) ) { $this->restore_default_hooks(); $content = sprintf( '%1$s%2$s%3$s', $this->get_hooks_buffer( $block_hooks, 'before' ), $block_content, $this->get_hooks_buffer( $block_hooks, 'after' ) ); $this->remove_default_hooks(); return $content; } $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 ) ) { return $block_content; } if ( 'core/query-no-results' === $block_name ) { /** * `core/query-no-result` is a special case because it can return two * different content depending on the context. We need to check if the * block content is empty to determine if we need to inject hooks. */ if ( empty( trim( $block_content ) ) ) { return $block_content; } $this->restore_default_hooks(); $content = sprintf( '%1$s%2$s%3$s', $this->get_hooks_buffer( $block_hooks, 'before' ), $block_content, $this->get_hooks_buffer( $block_hooks, 'after' ) ); $this->remove_default_hooks(); return $content; } if ( empty( $block_content ) ) { return $block_content; } return sprintf( '%1$s%2$s%3$s', $this->get_hooks_buffer( $block_hooks, 'before' ), $block_content, $this->get_hooks_buffer( $block_hooks, 'after' ) ); } /** * The hook data to inject to the rendered content of blocks. This also * contains hooked functions that will be removed by remove_default_hooks. * * The array format: * [ * => [ * block_name => , * position => before|after, * hooked => [ * => , * ... * ], * permanently_removed_actions => [ * * ] * ], * ] * 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. * - 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 * priority. * - permanently_removed_actions is an array of functions that we do not want to re-add after they have been removed to avoid duplicate content with the Products block and its inner blocks. */ protected function set_hook_data() { $this->hook_data = array( 'woocommerce_before_main_content' => 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_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_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_names' => array( 'core/post-title' ), 'position' => 'after', 'hooked' => array( 'woocommerce_template_loop_product_title' => 10, ), ), 'woocommerce_after_shop_loop_item_title' => 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_names' => array( self::LOOP_ITEM_ID ), 'position' => 'before', 'hooked' => array( 'woocommerce_template_loop_product_link_open' => 10, ), ), 'woocommerce_after_shop_loop_item' => 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_names' => array( 'core/post-template', '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_names' => array( 'core/post-template', 'woocommerce/product-template' ), 'position' => 'after', 'hooked' => array( 'woocommerce_pagination' => 10, ), 'permanently_removed_actions' => array( 'woocommerce_pagination', ), ), 'woocommerce_no_products_found' => array( 'block_names' => array( 'core/query-no-results' ), 'position' => 'before', 'hooked' => array( 'wc_no_products_found' => 10, ), 'permanently_removed_actions' => array( 'wc_no_products_found', ), ), 'woocommerce_archive_description' => array( 'block_names' => array( 'core/term-description' ), 'position' => 'before', 'hooked' => array( 'woocommerce_taxonomy_archive_description' => 10, 'woocommerce_product_archive_description' => 10, ), ), ); } /** * Check if current page is a product archive template. */ private function is_archive_template() { return is_shop() || is_product_taxonomy(); } /** * Loop through inner blocks recursively to find the Products blocks that * inherits query from template. * * @param array $block Parsed block data. */ private function inner_blocks_walker( &$block ) { if ( $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(); } if ( ! empty( $block['innerBlocks'] ) ) { array_walk( $block['innerBlocks'], array( $this, 'inner_blocks_walker' ) ); } } /** * Restore default hooks except the ones that are not supposed to be re-added. */ private function restore_default_hooks() { foreach ( $this->hook_data as $hook => $data ) { if ( ! isset( $data['hooked'] ) ) { continue; } foreach ( $data['hooked'] as $callback => $priority ) { if ( ! in_array( $callback, $data['permanently_removed_actions'] ?? [], true ) ) { add_action( $hook, $callback, $priority ); } } } } /** * 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. * * @param array $block Parsed block data. */ private function is_products_block_with_inherit_query( $block ) { return 'core/query' === $block['blockName'] && isset( $block['attrs']['namespace'] ) && 'woocommerce/product-query' === $block['attrs']['namespace'] && isset( $block['attrs']['query']['inherit'] ) && $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. * * @param array $block Parsed block data. */ private function inject_attribute( &$block ) { $block['attrs']['isInherited'] = 1; if ( ! empty( $block['innerBlocks'] ) ) { array_walk( $block['innerBlocks'], array( $this, 'inject_attribute' ) ); } } } CheckoutTemplate.php000064400000003256150732353200010524 0ustar00get_placeholder_page(); return null !== $placeholder && $post instanceof \WP_Post && $placeholder->post_name === $post->post_name; } /** * When the page should be displaying the template, add it to the hierarchy. * * This places the template name e.g. `cart`, at the beginning of the template hierarchy array. The hook priority * is 1 to ensure it runs first; other consumers e.g. extensions, could therefore inject their own template instead * of this one when using the default priority of 10. * * @param array $templates Templates that match the pages_template_hierarchy. */ public function page_template_hierarchy( $templates ) { if ( $this->is_active_template() ) { array_unshift( $templates, $this->get_slug() ); array_unshift( $templates, 'checkout' ); } return $templates; } } CheckoutHeaderTemplate.php000064400000000270150732353200011626 0ustar00init(); } } /** * Initialization method. */ protected function init() { add_filter( 'page_template_hierarchy', array( $this, 'page_template_hierarchy' ), 1 ); add_filter( 'pre_get_document_title', array( $this, 'page_template_title' ) ); } /** * Returns the template slug. * * @return string */ abstract public static function get_slug(); /** * Returns the page object assigned to this template/page. * * @return \WP_Post|null Post object or null. */ abstract protected function get_placeholder_page(); /** * Should return true on pages/endpoints/routes where the template should be shown. * * @return boolean */ abstract protected function is_active_template(); /** * Should return the title of the page, or an empty string if the page title should not be changed. * * @return string */ public static function get_template_title() { return ''; } /** * When the page should be displaying the template, add it to the hierarchy. * * This places the template name e.g. `cart`, at the beginning of the template hierarchy array. The hook priority * is 1 to ensure it runs first; other consumers e.g. extensions, could therefore inject their own template instead * of this one when using the default priority of 10. * * @param array $templates Templates that match the pages_template_hierarchy. */ public function page_template_hierarchy( $templates ) { if ( $this->is_active_template() ) { array_unshift( $templates, $this->get_slug() ); } return $templates; } /** * Filter the page title when the template is active. * * @param string $title Page title. * @return string */ public function page_template_title( $title ) { if ( $this->is_active_template() && $this->get_template_title() ) { return $this->get_template_title(); } return $title; } } AbstractTemplateCompatibility.php000064400000013102150732353200013243 0ustar00set_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 TBD * @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 ); 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 TBD * @param boolean. */ $is_disabled_compatility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false ); if ( $is_disabled_compatility_layer ) { return $block_content; } return $this->inject_hooks( $block_content, $block ); }, 10, 2 ); } /** * Update the render block data to inject our custom attribute needed to * determine which blocks belong to an inherited Products block. * * @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 */ abstract public function update_render_block_data( $parsed_block, $source_block, $parent_block ); /** * Inject hooks to rendered content of corresponding blocks. * * @param mixed $block_content The rendered block content. * @param mixed $block The parsed block data. * @return string */ abstract public function inject_hooks( $block_content, $block ); /** * The hook data to inject to the rendered content of blocks. This also * contains hooked functions that will be removed by remove_default_hooks. * * The array format: * [ * => [ * block_names => [ , ... ], * position => before|after, * hooked => [ * => , * ... * ], * ], * ] * Where: * - hook-name is the name of the hook that will be replaced. * - 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 * priority. */ abstract protected function set_hook_data(); /** * Remove the default callback added by WooCommerce. We replaced these * callbacks by blocks so we have to remove them to prevent duplicated * content. */ protected function remove_default_hooks() { foreach ( $this->hook_data as $hook => $data ) { if ( ! isset( $data['hooked'] ) ) { continue; } foreach ( $data['hooked'] as $callback => $priority ) { remove_action( $hook, $callback, $priority ); } } /** * When extensions implement their equivalent blocks of the template * hook functions, they can use this filter to register their old hooked * data here, so in the blockified template, the old hooked functions * can be removed in favor of the new blocks while keeping the old * hooked functions working in classic templates. * * Accepts an array of hooked data. The array should be in the following * format: * [ * [ * hook => , * function => , * priority => , * ], * ... * ] * Where: * - hook-name is the name of the hook that have the functions hooked to. * - function-name is the hooked function name. * - priority is the priority of the hooked function. * * @since 9.5.0 * @param array $data Additional hooked data. Default to empty */ $additional_hook_data = apply_filters( 'woocommerce_blocks_hook_compatibility_additional_data', array() ); if ( empty( $additional_hook_data ) || ! is_array( $additional_hook_data ) ) { return; } foreach ( $additional_hook_data as $data ) { if ( ! isset( $data['hook'], $data['function'], $data['priority'] ) ) { continue; } remove_action( $data['hook'], $data['function'], $data['priority'] ); } } /** * Get the buffer content of the hooks to append/prepend to render content. * * @param array $hooks The hooks to be rendered. * @param string $position The position of the hooks. * * @return string */ protected function get_hooks_buffer( $hooks, $position ) { ob_start(); foreach ( $hooks as $hook => $data ) { if ( $data['position'] === $position ) { /** * Action to render the content of a hook. * * @since 9.5.0 */ do_action( $hook ); } } return ob_get_clean(); } }