OrderConfirmationTemplate.php 0000644 00000002313 15073235320 0012374 0 ustar 00 is_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.php 0000644 00000001670 15073235320 0012261 0 ustar 00 init();
}
/**
* 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.php 0000644 00000003216 15073235320 0007644 0 ustar 00 get_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.php 0000644 00000004512 15073235320 0013251 0 ustar 00 asset_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.php 0000644 00000034604 15073235320 0014274 0 ustar 00 remove_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.php 0000644 00000000245 15073235320 0010460 0 ustar 00 init();
}
/**
* 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.php 0000644 00000011117 15073235320 0011534 0 ustar 00 $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.php 0000644 00000030620 15073235320 0014611 0 ustar 00 is_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.php 0000644 00000003256 15073235320 0010524 0 ustar 00 get_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.php 0000644 00000000270 15073235320 0011626 0 ustar 00 init();
}
}
/**
* 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.php 0000644 00000013102 15073235320 0013243 0 ustar 00 set_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();
}
}