Refactor BlockTemplatesController (#44537)

* Cleanup BlockTemplatesController constructor

* Remove unnecessary elseif

* Remove unnecessary param comment

* Move Single Product Template responsibilities to the SingleProductTemplate.php file

* Move Mini-Cart Template reposnibility to the MiniCartTemplate.php file

* Create the other template files

* Code cleanup

* Move template redirect into template files

* PHP cleanup

* Add changelog entry

* Fix PHP tests

* Replace hardcoded 'archive-product' slug with ProductCatalogTemplate::SLUG

* Make it so AbstractTemplatePart extends AbstractTemplate

* Register templates in BlockTemplatesRegistry

* Fix slug usage in AbstractPageTemplate.php

* Add get_template_title and get_template_description methods to AbstractTemplate

* Cleanup

* Make init functions protected in template classes

* Avoid using static constants and methods in BlockTemplatesRegistry

* Avoid using static constants and methods in block template classes

* Fix lint errors

* Fix Single Product classes not being applied

* Fix tests

* Get BlockTemplatesRegistry directly from BlockTemplateUtils to simplify code

* Init BlockTemplatesRegistry and BlockTemplatesController from Bootstrap.php

* Fix wrong static::SLUG call

* Init template classes from BlockTemplatesRegistry

* Revert "Fix wrong static::SLUG call"

This reverts commit 866c9b913a.
This commit is contained in:
Albert Juhé Lluveras 2024-02-27 19:05:25 +01:00 committed by GitHub
parent 241b860b9f
commit b95c15fc2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 885 additions and 430 deletions

View File

@ -0,0 +1,3 @@
Significance: patch
Type: update
Comment: Updated the template logic used by block themes, to make it more performant and resilient.

View File

@ -1,16 +1,8 @@
<?php
namespace Automattic\WooCommerce\Blocks;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Blocks\Domain\Package;
use Automattic\WooCommerce\Blocks\Templates\CartTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplateCompatibility;
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplate;
/**
* BlockTypesController class.
@ -26,43 +18,16 @@ class BlockTemplatesController {
*/
const TEMPLATES_ROOT_DIR = 'templates';
/**
* Package instance.
*
* @var Package
*/
private $package;
/**
* Constructor.
*
* @param Package $package An instance of Package.
*/
public function __construct( Package $package ) {
$this->package = $package;
$feature_gating = $package->feature();
$is_block_templates_controller_refactor_enabled = $feature_gating->is_block_templates_controller_refactor_enabled();
// This feature is gated for WooCommerce versions 6.0.0 and above.
if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '6.0.0', '>=' ) && ! $is_block_templates_controller_refactor_enabled ) {
$this->init();
}
}
/**
* Initialization method.
*/
protected function init() {
add_filter( 'default_wp_template_part_areas', array( $this, 'register_mini_cart_template_part_area' ), 10, 1 );
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
public function init() {
add_filter( 'pre_get_block_template', array( $this, 'get_block_template_fallback' ), 10, 3 );
add_filter( 'pre_get_block_file_template', array( $this, 'get_block_file_template' ), 10, 3 );
add_filter( 'get_block_template', array( $this, 'add_block_template_details' ), 10, 1 );
add_filter( 'get_block_template', array( $this, 'add_block_template_details' ), 10, 3 );
add_filter( 'get_block_templates', array( $this, 'add_block_templates' ), 10, 3 );
add_filter( 'current_theme_supports-block-templates', array( $this, 'remove_block_template_support_for_shop_page' ) );
add_filter( 'taxonomy_template_hierarchy', array( $this, 'add_archive_product_to_eligible_for_fallback_templates' ), 10, 1 );
add_filter( 'post_type_archive_title', array( $this, 'update_product_archive_title' ), 10, 2 );
add_action( 'after_switch_theme', array( $this, 'check_should_use_blockified_product_grid_templates' ), 10, 2 );
if ( wc_current_theme_is_fse_theme() ) {
@ -135,23 +100,6 @@ class BlockTemplatesController {
}
}
/**
* Add Mini-Cart to the default template part areas.
*
* @param array $default_area_definitions An array of supported area objects.
* @return array The supported template part areas including the Mini-Cart one.
*/
public function register_mini_cart_template_part_area( $default_area_definitions ) {
$mini_cart_template_part_area = [
'area' => 'mini-cart',
'label' => __( 'Mini-Cart', 'woocommerce' ),
'description' => __( 'The Mini-Cart template allows shoppers to see their cart items and provides access to the Cart and Checkout pages.', 'woocommerce' ),
'icon' => 'mini-cart',
'area_tag' => 'mini-cart',
];
return array_merge( $default_area_definitions, [ $mini_cart_template_part_area ] );
}
/**
* Renders the `core/template-part` block on the server.
*
@ -201,7 +149,7 @@ class BlockTemplatesController {
}
$wp_query_args = array(
'post_name__in' => array( 'archive-product', $slug ),
'post_name__in' => array( ProductCatalogTemplate::SLUG, $slug ),
'post_type' => $template_type,
'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash' ),
'no_found_rows' => true,
@ -222,7 +170,7 @@ class BlockTemplatesController {
return null;
}
if ( count( $posts ) > 0 && 'archive-product' === $posts[0]->post_name ) {
if ( count( $posts ) > 0 && ProductCatalogTemplate::SLUG === $posts[0]->post_name ) {
$template = _build_block_template_result_from_post( $posts[0] );
if ( ! is_wp_error( $template ) ) {
@ -253,11 +201,13 @@ class BlockTemplatesController {
$templates_eligible_for_fallback = array_filter(
$template_slugs,
array( BlockTemplateUtils::class, 'template_is_eligible_for_product_archive_fallback' )
function( $template_slug ) {
return BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $template_slug );
}
);
if ( count( $templates_eligible_for_fallback ) > 0 ) {
$template_hierarchy[] = 'archive-product';
$template_hierarchy[] = ProductCatalogTemplate::SLUG;
}
return $template_hierarchy;
@ -305,7 +255,7 @@ class BlockTemplatesController {
// If the theme has an archive-product.html template, but not a taxonomy-product_cat/tag/attribute.html template let's use the themes archive-product.html template.
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback_from_theme( $template_slug ) ) {
$template_path = BlockTemplateUtils::get_theme_template_path( 'archive-product' );
$template_path = BlockTemplateUtils::get_theme_template_path( ProductCatalogTemplate::SLUG );
$template_object = BlockTemplateUtils::create_new_block_template_object( $template_path, $template_type, $template_slug, true );
return BlockTemplateUtils::build_template_result_from_file( $template_object, $template_type );
}
@ -351,9 +301,11 @@ class BlockTemplatesController {
* Add the template title and description to WooCommerce templates.
*
* @param WP_Block_Template|null $block_template The found block template, or null if there isn't one.
* @param string $id Template unique identifier (example: 'theme_slug//template_slug').
* @param array $template_type Template type: 'wp_template' or 'wp_template_part'.
* @return WP_Block_Template|null
*/
public function add_block_template_details( $block_template ) {
public function add_block_template_details( $block_template, $id, $template_type ) {
if ( ! $block_template ) {
return $block_template;
}
@ -363,6 +315,9 @@ class BlockTemplatesController {
if ( ! $block_template->description ) {
$block_template->description = BlockTemplateUtils::get_block_template_description( $block_template->slug );
}
if ( ! $block_template->area || 'uncategorized' === $block_template->area ) {
$block_template->area = BlockTemplateUtils::get_block_template_area( $block_template->slug, $template_type );
}
return $block_template;
}
@ -406,9 +361,7 @@ class BlockTemplatesController {
// It would be custom if the template was modified in the editor, so if it's not custom we can load it from
// the filesystem.
if ( 'custom' !== $template_file->source ) {
$template = BlockTemplateUtils::build_template_result_from_file( $template_file, $template_type );
} else {
if ( 'custom' === $template_file->source ) {
$query_result[] = $template_file;
continue;
}
@ -424,6 +377,7 @@ class BlockTemplatesController {
! isset( $query['area'] ) || ( property_exists( $template_file, 'area' ) && $template_file->area === $query['area'] );
$should_include = $is_not_custom && $fits_slug_query && $fits_area_query;
if ( $should_include ) {
$template = BlockTemplateUtils::build_template_result_from_file( $template_file, $template_type );
$query_result[] = $template;
}
}
@ -443,43 +397,16 @@ class BlockTemplatesController {
* templates that aren't listed in theme.json.
*/
$query_result = array_map(
function( $template ) {
if ( str_contains( $template->slug, 'single-product' ) ) {
// We don't want to add the compatibility layer on the Editor Side.
// The second condition is necessary to not apply the compatibility layer on the REST API. Gutenberg uses the REST API to clone the template.
// More details: https://github.com/woocommerce/woocommerce-blocks/issues/9662.
if ( ( ! is_admin() && ! ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) && ! BlockTemplateUtils::template_has_legacy_template_block( $template ) ) {
// Add the product class to the body. We should move this to a more appropriate place.
add_filter(
'body_class',
function( $classes ) {
return array_merge( $classes, wc_get_product_class() );
}
);
global $product;
if ( ! $product instanceof \WC_Product ) {
$product_id = get_the_ID();
if ( $product_id ) {
wc_setup_product_data( $product_id );
}
}
if ( post_password_required() ) {
$template->content = SingleProductTemplate::add_password_form( $template->content );
} else {
$template->content = SingleProductTemplateCompatibility::add_compatibility_layer( $template->content );
}
}
}
function( $template ) use ( $template_type ) {
if ( ! BlockTemplateUtils::template_has_title( $template ) ) {
$template->title = BlockTemplateUtils::get_block_template_title( $template->slug );
}
if ( ! $template->description ) {
$template->description = BlockTemplateUtils::get_block_template_description( $template->slug );
}
if ( ! $template->area || 'uncategorized' === $template->area ) {
$template->area = BlockTemplateUtils::get_block_template_area( $template->slug, $template_type );
}
return $template;
},
@ -564,7 +491,7 @@ class BlockTemplatesController {
// If the theme has an archive-product.html template, but not a taxonomy-product_cat/tag/attribute.html template let's use the themes archive-product.html template.
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback_from_theme( $template_slug ) ) {
$template_file = BlockTemplateUtils::get_theme_template_path( 'archive-product' );
$template_file = BlockTemplateUtils::get_theme_template_path( ProductCatalogTemplate::SLUG );
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug, true );
continue;
}
@ -572,7 +499,7 @@ class BlockTemplatesController {
// At this point the template only exists in the Blocks filesystem, if is a taxonomy-product_cat/tag/attribute.html template
// let's use the archive-product.html template from Blocks.
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $template_slug ) ) {
$template_file = $this->get_template_path_from_woocommerce( 'archive-product' );
$template_file = $this->get_template_path_from_woocommerce( ProductCatalogTemplate::SLUG );
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug, false );
continue;
}
@ -631,109 +558,6 @@ class BlockTemplatesController {
) || $this->get_block_templates( array( $template_name ), $template_type );
}
/**
* Renders the default block template from Woo Blocks if no theme templates exist.
*/
public function render_block_template() {
if ( is_embed() || ! BlockTemplateUtils::supports_block_templates() ) {
return;
}
if (
is_singular( 'product' ) && $this->block_template_is_available( 'single-product' )
) {
global $post;
$valid_slugs = [ 'single-product' ];
if ( 'product' === $post->post_type && $post->post_name ) {
$valid_slugs[] = 'single-product-' . $post->post_name;
}
$templates = get_block_templates( array( 'slug__in' => $valid_slugs ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( 'single-product' ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
} elseif (
( is_product_taxonomy() && is_tax( 'product_cat' ) ) && $this->block_template_is_available( 'taxonomy-product_cat' )
) {
$templates = get_block_templates( array( 'slug__in' => array( 'taxonomy-product_cat' ) ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( 'taxonomy-product_cat' ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
} elseif (
( is_product_taxonomy() && is_tax( 'product_tag' ) ) && $this->block_template_is_available( 'taxonomy-product_tag' )
) {
$templates = get_block_templates( array( 'slug__in' => array( 'taxonomy-product_tag' ) ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( 'taxonomy-product_tag' ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
} elseif ( is_post_type_archive( 'product' ) && is_search() ) {
$templates = get_block_templates( array( 'slug__in' => array( ProductSearchResultsTemplate::SLUG ) ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( ProductSearchResultsTemplate::SLUG ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
} elseif (
( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) && $this->block_template_is_available( 'archive-product' )
) {
$templates = get_block_templates( array( 'slug__in' => array( 'archive-product' ) ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( 'archive-product' ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
} elseif (
is_cart() &&
! BlockTemplateUtils::theme_has_template( CartTemplate::get_slug() ) && $this->block_template_is_available( CartTemplate::get_slug() )
) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
} elseif (
is_checkout() &&
! BlockTemplateUtils::theme_has_template( CheckoutTemplate::get_slug() ) && $this->block_template_is_available( CheckoutTemplate::get_slug() )
) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
} else {
$queried_object = get_queried_object();
if ( is_null( $queried_object ) ) {
return;
}
if ( isset( $queried_object->taxonomy ) && taxonomy_is_product_attribute( $queried_object->taxonomy ) && $this->block_template_is_available( ProductAttributeTemplate::SLUG )
) {
$templates = get_block_templates( array( 'slug__in' => array( ProductAttributeTemplate::SLUG ) ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( ProductAttributeTemplate::SLUG ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
}
}
/**
* Remove the template panel from the Sidebar of the Shop page because
* the Site Editor handles it.
@ -759,24 +583,4 @@ class BlockTemplatesController {
return $is_support;
}
/**
* Update the product archive title to "Shop".
*
* @param string $post_type_name Post type 'name' label.
* @param string $post_type Post type.
*
* @return string
*/
public function update_product_archive_title( $post_type_name, $post_type ) {
if (
function_exists( 'is_shop' ) &&
is_shop() &&
'product' === $post_type
) {
return __( 'Shop', 'woocommerce' );
}
return $post_type_name;
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Automattic\WooCommerce\Blocks;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
use Automattic\WooCommerce\Blocks\Templates\AbstractTemplate;
use Automattic\WooCommerce\Blocks\Templates\AbstractTemplatePart;
use Automattic\WooCommerce\Blocks\Templates\MiniCartTemplate;
use Automattic\WooCommerce\Blocks\Templates\CartTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutHeaderTemplate;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductCategoryTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductTagTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplate;
/**
* BlockTemplatesRegistry class.
*
* @internal
*/
class BlockTemplatesRegistry {
/**
* The array of registered templates.
*
* @var AbstractTemplate[]|AbstractTemplatePart[]
*/
private $templates = array();
/**
* Initialization method.
*/
public function init() {
if ( BlockTemplateUtils::supports_block_templates( 'wp_template' ) ) {
$templates = array(
ProductCatalogTemplate::SLUG => new ProductCatalogTemplate(),
ProductCategoryTemplate::SLUG => new ProductCategoryTemplate(),
ProductTagTemplate::SLUG => new ProductTagTemplate(),
ProductAttributeTemplate::SLUG => new ProductAttributeTemplate(),
ProductSearchResultsTemplate::SLUG => new ProductSearchResultsTemplate(),
CartTemplate::SLUG => new CartTemplate(),
CheckoutTemplate::SLUG => new CheckoutTemplate(),
OrderConfirmationTemplate::SLUG => new OrderConfirmationTemplate(),
SingleProductTemplate::SLUG => new SingleProductTemplate(),
);
} else {
$templates = array();
}
if ( BlockTemplateUtils::supports_block_templates( 'wp_template_part' ) ) {
$template_parts = array(
MiniCartTemplate::SLUG => new MiniCartTemplate(),
CheckoutHeaderTemplate::SLUG => new CheckoutHeaderTemplate(),
);
} else {
$template_parts = array();
}
$this->templates = array_merge( $templates, $template_parts );
// Init all templates.
foreach ( $this->templates as $template ) {
$template->init();
}
}
/**
* Returns the template matching the slug
*
* @param string $template_slug Slug of the template to retrieve.
*
* @return AbstractTemplate|AbstractTemplatePart|null
*/
public function get_template( $template_slug ) {
if ( array_key_exists( $template_slug, $this->templates ) ) {
$registered_template = $this->templates[ $template_slug ];
return $registered_template;
}
return null;
}
}

View File

@ -2,6 +2,9 @@
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductCategoryTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductTagTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
@ -99,7 +102,7 @@ class ClassicTemplate extends AbstractDynamicBlock {
$frontend_scripts::load_scripts();
}
if ( OrderConfirmationTemplate::get_slug() === $attributes['template'] ) {
if ( OrderConfirmationTemplate::SLUG === $attributes['template'] ) {
return $this->render_order_received();
}
@ -109,9 +112,9 @@ class ClassicTemplate extends AbstractDynamicBlock {
$valid = false;
$archive_templates = array(
'archive-product',
'taxonomy-product_cat',
'taxonomy-product_tag',
ProductCatalogTemplate::SLUG,
ProductCategoryTemplate::SLUG,
ProductTagTemplate::SLUG,
ProductAttributeTemplate::SLUG,
ProductSearchResultsTemplate::SLUG,
);

View File

@ -6,6 +6,7 @@ use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\AssetsController;
use Automattic\WooCommerce\Blocks\BlockPatterns;
use Automattic\WooCommerce\Blocks\BlockTemplatesRegistry;
use Automattic\WooCommerce\Blocks\BlockTemplatesController;
use Automattic\WooCommerce\Blocks\BlockTypesController;
use Automattic\WooCommerce\Blocks\QueryFilters;
@ -28,13 +29,7 @@ use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque;
use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use Automattic\WooCommerce\Blocks\Registry\Container;
use Automattic\WooCommerce\Blocks\Templates\CartTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutHeaderTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutTemplate;
use Automattic\WooCommerce\Blocks\Templates\ClassicTemplatesCompatibility;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
use Automattic\WooCommerce\StoreApi\RoutesController;
use Automattic\WooCommerce\StoreApi\SchemaController;
use Automattic\WooCommerce\StoreApi\StoreApi;
@ -152,13 +147,8 @@ class Bootstrap {
// regular rest requests to maintain compatibility with the store editor.
$this->container->get( BlockPatterns::class );
$this->container->get( BlockTypesController::class );
$this->container->get( BlockTemplatesController::class );
$this->container->get( ProductSearchResultsTemplate::class );
$this->container->get( ProductAttributeTemplate::class );
$this->container->get( CartTemplate::class );
$this->container->get( CheckoutTemplate::class );
$this->container->get( CheckoutHeaderTemplate::class );
$this->container->get( OrderConfirmationTemplate::class );
$this->container->get( BlockTemplatesRegistry::class )->init();
$this->container->get( BlockTemplatesController::class )->init();
$this->container->get( ClassicTemplatesCompatibility::class );
$this->container->get( ArchiveProductTemplatesCompatibility::class )->init();
$this->container->get( SingleProductTemplateCompatibility::class )->init();
@ -258,46 +248,16 @@ class Bootstrap {
return new BlockTypesController( $asset_api, $asset_data_registry );
}
);
$this->container->register(
BlockTemplatesRegistry::class,
function ( Container $container ) {
return new BlockTemplatesRegistry();
}
);
$this->container->register(
BlockTemplatesController::class,
function ( Container $container ) {
return new BlockTemplatesController( $container->get( Package::class ) );
}
);
$this->container->register(
ProductSearchResultsTemplate::class,
function () {
return new ProductSearchResultsTemplate();
}
);
$this->container->register(
ProductAttributeTemplate::class,
function () {
return new ProductAttributeTemplate();
}
);
$this->container->register(
CartTemplate::class,
function () {
return new CartTemplate();
}
);
$this->container->register(
CheckoutTemplate::class,
function () {
return new CheckoutTemplate();
}
);
$this->container->register(
CheckoutHeaderTemplate::class,
function () {
return new CheckoutHeaderTemplate();
}
);
$this->container->register(
OrderConfirmationTemplate::class,
function () {
return new OrderConfirmationTemplate();
return new BlockTemplatesController();
}
);
$this->container->register(
@ -313,7 +273,6 @@ class Bootstrap {
return new ArchiveProductTemplatesCompatibility();
}
);
$this->container->register(
SingleProductTemplateCompatibility::class,
function () {

View File

@ -8,31 +8,15 @@ namespace Automattic\WooCommerce\Blocks\Templates;
*
* @internal
*/
abstract class AbstractPageTemplate {
/**
* Page Template functionality is only initialized when using a block theme.
*/
public function __construct() {
if ( wc_current_theme_is_fse_theme() ) {
$this->init();
}
}
abstract class AbstractPageTemplate extends AbstractTemplate {
/**
* Initialization method.
*/
protected function init() {
public 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.
*
@ -47,15 +31,6 @@ abstract class AbstractPageTemplate {
*/
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.
*
@ -67,7 +42,7 @@ abstract class AbstractPageTemplate {
*/
public function page_template_hierarchy( $templates ) {
if ( $this->is_active_template() ) {
array_unshift( $templates, $this->get_slug() );
array_unshift( $templates, static::SLUG );
}
return $templates;
}

View File

@ -0,0 +1,38 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
/**
* AbstractTemplate class.
*
* Shared logic for templates.
*
* @internal
*/
abstract class AbstractTemplate {
/**
* The slug of the template.
*
* @var string
*/
const SLUG = '';
/**
* Initialization method.
*/
abstract public function init();
/**
* Should return the title of the template.
*
* @return string
*/
abstract public function get_template_title();
/**
* Should return the description of the template.
*
* @return string
*/
abstract public function get_template_description();
}

View File

@ -0,0 +1,18 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
/**
* AbstractTemplatePart class.
*
* Shared logic for templates parts.
*
* @internal
*/
abstract class AbstractTemplatePart extends AbstractTemplate {
/**
* The template part area where the template part belongs.
*
* @var string
*/
public $template_area;
}

View File

@ -1,19 +1,61 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
/**
* CartTemplate class.
*
* @internal
*/
class CartTemplate extends AbstractPageTemplate {
/**
* Template slug.
* The slug of the template.
*
* @var string
*/
const SLUG = 'page-cart';
/**
* Initialization method.
*/
public function init() {
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
parent::init();
}
/**
* Returns the title of the template.
*
* @return string
*/
public static function get_slug() {
return 'page-cart';
public function get_template_title() {
return _x( 'Page: Cart', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'The Cart template displays the items selected by the user for purchase, including quantities, prices, and discounts. It allows users to review their choices before proceeding to checkout.', 'woocommerce' );
}
/**
* Renders the default block template from Woo Blocks if no theme templates exist.
*/
public function render_block_template() {
if (
! is_embed() && is_cart() &&
! BlockTemplateUtils::theme_has_template( self::SLUG )
) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
/**
@ -48,7 +90,7 @@ class CartTemplate extends AbstractPageTemplate {
*/
public function page_template_hierarchy( $templates ) {
if ( $this->is_active_template() ) {
array_unshift( $templates, $this->get_slug() );
array_unshift( $templates, self::SLUG );
array_unshift( $templates, 'cart' );
}
return $templates;

View File

@ -6,8 +6,42 @@ namespace Automattic\WooCommerce\Blocks\Templates;
*
* @internal
*/
class CheckoutHeaderTemplate {
class CheckoutHeaderTemplate extends AbstractTemplatePart {
/**
* The slug of the template.
*
* @var string
*/
const SLUG = 'checkout-header';
/**
* The template part area where the template part belongs.
*
* @var string
*/
public $template_area = 'header';
/**
* Initialization method.
*/
public function init() {}
/**
* Returns the title of the template.
*
* @return string
*/
public function get_template_title() {
return _x( 'Checkout Header', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'Template used to display the simplified Checkout header.', 'woocommerce' );
}
}

View File

@ -1,19 +1,59 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
/**
* CheckoutTemplate class.
*
* @internal
*/
class CheckoutTemplate extends AbstractPageTemplate {
/**
* Template slug.
* The slug of the template.
*
* @var string
*/
const SLUG = 'page-checkout';
/**
* Initialization method.
*/
public function init() {
parent::init();
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
}
/**
* Returns the title of the template.
*
* @return string
*/
public static function get_slug() {
return 'page-checkout';
public function get_template_title() {
return _x( 'Page: Checkout', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'The Checkout template guides users through the final steps of the purchase process. It enables users to enter shipping and billing information, select a payment method, and review order details.', 'woocommerce' );
}
/**
* Renders the default block template from Woo Blocks if no theme templates exist.
*/
public function render_block_template() {
if (
! is_embed() && is_checkout() &&
! BlockTemplateUtils::theme_has_template( self::SLUG )
) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
/**
@ -48,7 +88,7 @@ class CheckoutTemplate extends AbstractPageTemplate {
*/
public function page_template_hierarchy( $templates ) {
if ( $this->is_active_template() ) {
array_unshift( $templates, $this->get_slug() );
array_unshift( $templates, self::SLUG );
array_unshift( $templates, 'checkout' );
}
return $templates;

View File

@ -32,7 +32,7 @@ class ClassicTemplatesCompatibility {
/**
* Initialization method.
*/
protected function init() {
protected function init() { // phpcs:ignore WooCommerce.Functions.InternalInjectionMethod.MissingPublic
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.

View File

@ -6,8 +6,61 @@ namespace Automattic\WooCommerce\Blocks\Templates;
*
* @internal
*/
class MiniCartTemplate {
class MiniCartTemplate extends AbstractTemplatePart {
/**
* The slug of the template.
*
* @var string
*/
const SLUG = 'mini-cart';
/**
* The template part area where the template part belongs.
*
* @var string
*/
public $template_area = 'mini-cart';
/**
* Initialization method.
*/
public function init() {
add_filter( 'default_wp_template_part_areas', array( $this, 'register_mini_cart_template_part_area' ), 10, 1 );
}
/**
* Returns the title of the template.
*
* @return string
*/
public function get_template_title() {
return _x( 'Mini-Cart', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'Template used to display the Mini-Cart drawer.', 'woocommerce' );
}
/**
* Add Mini-Cart to the default template part areas.
*
* @param array $default_area_definitions An array of supported area objects.
* @return array The supported template part areas including the Mini-Cart one.
*/
public function register_mini_cart_template_part_area( $default_area_definitions ) {
$mini_cart_template_part_area = [
'area' => 'mini-cart',
'label' => __( 'Mini-Cart', 'woocommerce' ),
'description' => __( 'The Mini-Cart template allows shoppers to see their cart items and provides access to the Cart and Checkout pages.', 'woocommerce' ),
'icon' => 'mini-cart',
'area_tag' => 'mini-cart',
];
return array_merge( $default_area_definitions, [ $mini_cart_template_part_area ] );
}
}

View File

@ -7,21 +7,39 @@ namespace Automattic\WooCommerce\Blocks\Templates;
* @internal
*/
class OrderConfirmationTemplate extends AbstractPageTemplate {
/**
* Template slug.
* The slug of the template.
*
* @return string
* @var string
*/
public static function get_slug() {
return 'order-confirmation';
}
const SLUG = 'order-confirmation';
/**
* Initialization method.
*/
protected function init() {
parent::init();
public function init() {
add_action( 'wp_before_admin_bar_render', array( $this, 'remove_edit_page_link' ) );
parent::init();
}
/**
* Returns the title of the template.
*
* @return string
*/
public function get_template_title() {
return _x( 'Order Confirmation', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'The Order Confirmation template serves as a receipt and confirmation of a successful purchase. It includes a summary of the ordered items, shipping, billing, and totals.', 'woocommerce' );
}
/**
@ -51,13 +69,4 @@ class OrderConfirmationTemplate extends AbstractPageTemplate {
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' );
}
}

View File

@ -2,28 +2,77 @@
namespace Automattic\WooCommerce\Blocks\Templates;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
/**
* ProductAttributeTemplate class.
*
* @internal
*/
class ProductAttributeTemplate {
class ProductAttributeTemplate extends AbstractTemplate {
/**
* The slug of the template.
*
* @var string
*/
const SLUG = 'taxonomy-product_attribute';
/**
* Constructor.
* The template used as a fallback if that one is customized.
*
* @var string
*/
public function __construct() {
$this->init();
}
public $fallback_template = ProductCatalogTemplate::SLUG;
/**
* Initialization method.
*/
protected function init() {
public function init() {
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
add_filter( 'taxonomy_template_hierarchy', array( $this, 'update_taxonomy_template_hierarchy' ), 1, 3 );
}
/**
* Returns the title of the template.
*
* @return string
*/
public function get_template_title() {
return _x( 'Products by Attribute', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'Displays products filtered by an attribute.', 'woocommerce' );
}
/**
* Renders the default block template from Woo Blocks if no theme templates exist.
*/
public function render_block_template() {
$queried_object = get_queried_object();
if ( is_null( $queried_object ) ) {
return;
}
if ( isset( $queried_object->taxonomy ) && taxonomy_is_product_attribute( $queried_object->taxonomy ) ) {
$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( self::SLUG ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
}
/**
* Renders the Product by Attribute template for product attributes taxonomy pages.
*

View File

@ -0,0 +1,83 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
/**
* ProductCatalogTemplate class.
*
* @internal
*/
class ProductCatalogTemplate extends AbstractTemplate {
/**
* The slug of the template.
*
* @var string
*/
const SLUG = 'archive-product';
/**
* Initialization method.
*/
public function init() {
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
add_filter( 'post_type_archive_title', array( $this, 'update_product_archive_title' ), 10, 2 );
}
/**
* Returns the title of the template.
*
* @return string
*/
public function get_template_title() {
return _x( 'Product Catalog', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'Displays your products.', 'woocommerce' );
}
/**
* Renders the default block template from Woo Blocks if no theme templates exist.
*/
public function render_block_template() {
if ( ! is_embed() && ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) ) {
$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( self::SLUG ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
}
/**
* Update the product archive title to "Shop".
*
* @param string $post_type_name Post type 'name' label.
* @param string $post_type Post type.
*
* @return string
*/
public function update_product_archive_title( $post_type_name, $post_type ) {
if (
function_exists( 'is_shop' ) &&
is_shop() &&
'product' === $post_type
) {
return __( 'Shop', 'woocommerce' );
}
return $post_type_name;
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
/**
* ProductCategoryTemplate class.
*
* @internal
*/
class ProductCategoryTemplate extends AbstractTemplate {
/**
* The slug of the template.
*
* @var string
*/
const SLUG = 'taxonomy-product_cat';
/**
* The template used as a fallback if that one is customized.
*
* @var string
*/
public $fallback_template = ProductCatalogTemplate::SLUG;
/**
* Initialization method.
*/
public function init() {
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
}
/**
* Returns the title of the template.
*
* @return string
*/
public function get_template_title() {
return _x( 'Products by Category', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'Displays products filtered by a category.', 'woocommerce' );
}
/**
* Renders the default block template from Woo Blocks if no theme templates exist.
*/
public function render_block_template() {
if ( ! is_embed() && is_product_taxonomy() && is_tax( 'product_cat' ) ) {
$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( self::SLUG ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
}
}

View File

@ -1,29 +1,72 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
/**
* ProductSearchResultsTemplate class.
*
* @internal
*/
class ProductSearchResultsTemplate {
class ProductSearchResultsTemplate extends AbstractTemplate {
/**
* The slug of the template.
*
* @var string
*/
const SLUG = 'product-search-results';
/**
* Constructor.
* The template used as a fallback if that one is customized.
*
* @var string
*/
public function __construct() {
$this->init();
}
public $fallback_template = ProductCatalogTemplate::SLUG;
/**
* Initialization method.
*/
protected function init() {
public function init() {
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
add_filter( 'search_template_hierarchy', array( $this, 'update_search_template_hierarchy' ), 10, 3 );
}
/**
* Returns the title of the template.
*
* @return string
*/
public function get_template_title() {
return _x( 'Product Search Results', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'Displays search results for your store.', 'woocommerce' );
}
/**
* Renders the default block template from Woo Blocks if no theme templates exist.
*/
public function render_block_template() {
if ( ! is_embed() && is_post_type_archive( 'product' ) && is_search() ) {
$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( self::SLUG ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
}
/**
* When the search is for products and a block theme is active, render the Product Search Template.
*

View File

@ -0,0 +1,69 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
/**
* ProductTagTemplate class.
*
* @internal
*/
class ProductTagTemplate extends AbstractTemplate {
/**
* The slug of the template.
*
* @var string
*/
const SLUG = 'taxonomy-product_tag';
/**
* The template used as a fallback if that one is customized.
*
* @var string
*/
public $fallback_template = ProductCatalogTemplate::SLUG;
/**
* Initialization method.
*/
public function init() {
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
}
/**
* Returns the title of the template.
*
* @return string
*/
public function get_template_title() {
return _x( 'Products by Tag', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'Displays products filtered by a tag.', 'woocommerce' );
}
/**
* Renders the default block template from Woo Blocks if no theme templates exist.
*/
public function render_block_template() {
if ( ! is_embed() && is_product_taxonomy() && is_tax( 'product_tag' ) ) {
$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( self::SLUG ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
}
}

View File

@ -1,12 +1,118 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplateCompatibility;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
/**
* SingleProductTemplae class.
*
* @internal
*/
class SingleProductTemplate {
class SingleProductTemplate extends AbstractTemplate {
/**
* The slug of the template.
*
* @var string
*/
const SLUG = 'single-product';
/**
* Initialization method.
*/
public function init() {
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
add_filter( 'get_block_templates', array( $this, 'update_single_product_content' ), 11, 3 );
}
/**
* Returns the title of the template.
*
* @return string
*/
public function get_template_title() {
return _x( 'Single Product', 'Template name', 'woocommerce' );
}
/**
* Returns the description of the template.
*
* @return string
*/
public function get_template_description() {
return __( 'Displays a single product.', 'woocommerce' );
}
/**
* Renders the default block template from Woo Blocks if no theme templates exist.
*/
public function render_block_template() {
if ( ! is_embed() && is_singular( 'product' ) ) {
global $post;
$valid_slugs = [ self::SLUG ];
if ( 'product' === $post->post_type && $post->post_name ) {
$valid_slugs[] = 'single-product-' . $post->post_name;
}
$templates = get_block_templates( array( 'slug__in' => $valid_slugs ) );
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
}
if ( ! BlockTemplateUtils::theme_has_template( self::SLUG ) ) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
}
}
}
/**
* Add the block template objects to be used.
*
* @param array $query_result Array of template objects.
* @param array $query Optional. Arguments to retrieve templates.
* @param string $template_type wp_template or wp_template_part.
* @return array
*/
public function update_single_product_content( $query_result, $query, $template_type ) {
$query_result = array_map(
function( $template ) {
if ( str_contains( $template->slug, self::SLUG ) ) {
// We don't want to add the compatibility layer on the Editor Side.
// The second condition is necessary to not apply the compatibility layer on the REST API. Gutenberg uses the REST API to clone the template.
// More details: https://github.com/woocommerce/woocommerce-blocks/issues/9662.
if ( ( ! is_admin() && ! ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) && ! BlockTemplateUtils::template_has_legacy_template_block( $template ) ) {
// Add the product class to the body. We should move this to a more appropriate place.
add_filter(
'body_class',
function( $classes ) {
return array_merge( $classes, wc_get_product_class() );
}
);
global $product;
if ( ! $product instanceof \WC_Product ) {
$product_id = get_the_ID();
if ( $product_id ) {
wc_setup_product_data( $product_id );
}
}
if ( post_password_required() ) {
$template->content = $this->add_password_form( $template->content );
} else {
$template->content = SingleProductTemplateCompatibility::add_compatibility_layer( $template->content );
}
}
}
return $template;
},
$query_result
);
return $query_result;
}
/**
* Replace the first single product template block with the password form. Remove all other single product template blocks.

View File

@ -1,22 +1,16 @@
<?php
namespace Automattic\WooCommerce\Blocks\Utils;
use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating;
use Automattic\WooCommerce\Blocks\Options;
use Automattic\WooCommerce\Blocks\Templates\CartTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutHeaderTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutTemplate;
use Automattic\WooCommerce\Blocks\Templates\MiniCartTemplate;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\BlockTemplatesRegistry;
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;
/**
* Utility methods used for serving block templates from WooCommerce Blocks.
* {@internal This class and its methods should only be used within the BlockTemplateController.php and is not intended for public use.}
*/
class BlockTemplateUtils {
const ELIGIBLE_FOR_ARCHIVE_PRODUCT_FALLBACK = array( 'taxonomy-product_cat', 'taxonomy-product_tag', ProductAttributeTemplate::SLUG );
/**
* Directory names for block templates
*
@ -59,6 +53,18 @@ class BlockTemplateUtils {
*/
const DEPRECATED_PLUGIN_SLUG = 'woocommerce';
/**
* Returns the template matching the slug
*
* @param string $template_slug Slug of the template to retrieve.
*
* @return AbstractTemplate|AbstractTemplatePart|null
*/
public static function get_template( $template_slug ) {
$block_templates_registry = Package::container()->get( BlockTemplatesRegistry::class );
return $block_templates_registry->get_template( $template_slug );
}
/**
* Returns an array containing the references of
* the passed blocks and their inner blocks.
@ -207,7 +213,7 @@ class BlockTemplateUtils {
$template->content = self::inject_theme_attribute_in_content( $template_content );
// Remove the term description block from the archive-product template
// as the Product Catalog/Shop page doesn't have a description.
if ( 'archive-product' === $template_file->slug ) {
if ( ProductCatalogTemplate::SLUG === $template_file->slug ) {
$template->content = str_replace( '<!-- wp:term-description {"align":"wide"} /-->', '', $template->content );
}
// Plugin was agreed as a valid source value despite existing inline docs at the time of creating: https://github.com/WordPress/gutenberg/issues/36597#issuecomment-976232909.
@ -221,20 +227,8 @@ class BlockTemplateUtils {
$template->origin = $template_file->source;
$template->is_custom = false; // Templates loaded from the filesystem aren't custom, ones that have been edited and loaded from the DB are.
$template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
$template->area = 'uncategorized';
$template->area = self::get_block_template_area( $template->slug, $template_type );
// Force the Mini-Cart template part to be in the Mini-Cart template part area.
// @todo When this class is refactored, move title, description, and area definition to the template classes (CheckoutHeaderTemplate, MiniCartTemplate, etc).
if ( 'wp_template_part' === $template_type ) {
switch ( $template_file->slug ) {
case 'mini-cart':
$template->area = 'mini-cart';
break;
case 'checkout-header':
$template->area = 'header';
break;
}
}
return $template;
}
@ -309,15 +303,15 @@ class BlockTemplateUtils {
}
/**
* Returns template titles.
* Returns template title.
*
* @param string $template_slug The templates slug (e.g. single-product).
* @param string $template_slug The template slug (e.g. single-product).
* @return string Human friendly title.
*/
public static function get_block_template_title( $template_slug ) {
$plugin_template_types = self::get_plugin_block_template_types();
if ( isset( $plugin_template_types[ $template_slug ] ) ) {
return $plugin_template_types[ $template_slug ]['title'];
$registered_template = self::get_template( $template_slug );
if ( isset( $registered_template ) ) {
return $registered_template->get_template_title();
} else {
// Human friendly title converted from the slug.
return ucwords( preg_replace( '/[\-_]/', ' ', $template_slug ) );
@ -325,72 +319,34 @@ class BlockTemplateUtils {
}
/**
* Returns template descriptions.
* Returns template description.
*
* @param string $template_slug The templates slug (e.g. single-product).
* @param string $template_slug The template slug (e.g. single-product).
* @return string Template description.
*/
public static function get_block_template_description( $template_slug ) {
$plugin_template_types = self::get_plugin_block_template_types();
if ( isset( $plugin_template_types[ $template_slug ] ) ) {
return $plugin_template_types[ $template_slug ]['description'];
$registered_template = self::get_template( $template_slug );
if ( isset( $registered_template ) ) {
return $registered_template->get_template_description();
}
return '';
}
/**
* Returns a filtered list of plugin template types, containing their
* localized titles and descriptions.
* Returns area for template parts.
*
* @return array The plugin template types.
* @param string $template_slug The template part slug (e.g. mini-cart).
* @param string $template_type Either `wp_template` or `wp_template_part`.
* @return string Template part area.
*/
public static function get_plugin_block_template_types() {
return array(
'single-product' => array(
'title' => _x( 'Single Product', 'Template name', 'woocommerce' ),
'description' => __( 'Displays a single product.', 'woocommerce' ),
),
'archive-product' => array(
'title' => _x( 'Product Catalog', 'Template name', 'woocommerce' ),
'description' => __( 'Displays your products.', 'woocommerce' ),
),
'taxonomy-product_cat' => array(
'title' => _x( 'Products by Category', 'Template name', 'woocommerce' ),
'description' => __( 'Displays products filtered by a category.', 'woocommerce' ),
),
'taxonomy-product_tag' => array(
'title' => _x( 'Products by Tag', 'Template name', 'woocommerce' ),
'description' => __( 'Displays products filtered by a tag.', 'woocommerce' ),
),
ProductAttributeTemplate::SLUG => array(
'title' => _x( 'Products by Attribute', 'Template name', 'woocommerce' ),
'description' => __( 'Displays products filtered by an attribute.', 'woocommerce' ),
),
ProductSearchResultsTemplate::SLUG => array(
'title' => _x( 'Product Search Results', 'Template name', 'woocommerce' ),
'description' => __( 'Displays search results for your store.', 'woocommerce' ),
),
MiniCartTemplate::SLUG => array(
'title' => _x( 'Mini-Cart', 'Template name', 'woocommerce' ),
'description' => __( 'Template used to display the Mini-Cart drawer.', 'woocommerce' ),
),
CartTemplate::get_slug() => array(
'title' => _x( 'Page: Cart', 'Template name', 'woocommerce' ),
'description' => __( 'The Cart template displays the items selected by the user for purchase, including quantities, prices, and discounts. It allows users to review their choices before proceeding to checkout.', 'woocommerce' ),
),
CheckoutTemplate::get_slug() => array(
'title' => _x( 'Page: Checkout', 'Template name', 'woocommerce' ),
'description' => __( 'The Checkout template guides users through the final steps of the purchase process. It enables users to enter shipping and billing information, select a payment method, and review order details.', 'woocommerce' ),
),
CheckoutHeaderTemplate::SLUG => array(
'title' => _x( 'Checkout Header', 'Template name', 'woocommerce' ),
'description' => __( 'Template used to display the simplified Checkout header.', 'woocommerce' ),
),
OrderConfirmationTemplate::get_slug() => array(
'title' => _x( 'Order Confirmation', 'Template name', 'woocommerce' ),
'description' => __( 'The Order Confirmation template serves as a receipt and confirmation of a successful purchase. It includes a summary of the ordered items, shipping, billing, and totals.', 'woocommerce' ),
),
);
public static function get_block_template_area( $template_slug, $template_type ) {
if ( 'wp_template_part' === $template_type ) {
$registered_template = self::get_template( $template_slug );
if ( $registered_template && property_exists( $registered_template, 'template_area' ) ) {
return $registered_template->template_area;
}
}
return 'uncategorized';
}
/**
@ -502,7 +458,11 @@ class BlockTemplateUtils {
* @return boolean
*/
public static function template_is_eligible_for_product_archive_fallback( $template_slug ) {
return in_array( $template_slug, self::ELIGIBLE_FOR_ARCHIVE_PRODUCT_FALLBACK, true );
$registered_template = self::get_template( $template_slug );
if ( $registered_template && isset( $registered_template->fallback_template ) ) {
return ProductCatalogTemplate::SLUG === $registered_template->fallback_template;
}
return false;
}
/**
@ -521,7 +481,7 @@ class BlockTemplateUtils {
$array_filter = array_filter(
$db_templates,
function ( $template ) use ( $template_slug ) {
return 'archive-product' === $template->slug;
return ProductCatalogTemplate::SLUG === $template->slug;
}
);
@ -542,7 +502,7 @@ class BlockTemplateUtils {
}
foreach ( $db_templates as $template ) {
if ( 'archive-product' === $template->slug ) {
if ( ProductCatalogTemplate::SLUG === $template->slug ) {
return $template;
}
}
@ -562,7 +522,7 @@ class BlockTemplateUtils {
public static function template_is_eligible_for_product_archive_fallback_from_theme( $template_slug ) {
return self::template_is_eligible_for_product_archive_fallback( $template_slug )
&& ! self::theme_has_template( $template_slug )
&& self::theme_has_template( 'archive-product' );
&& self::theme_has_template( ProductCatalogTemplate::SLUG );
}
/**

View File

@ -5,6 +5,8 @@ namespace Automattic\WooCommerce\Tests\Blocks\Utils;
use Automattic\WooCommerce\Blocks\Migration;
use Automattic\WooCommerce\Blocks\Options;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\BlockTemplatesRegistry;
use WP_UnitTestCase;
/**
@ -12,11 +14,25 @@ use WP_UnitTestCase;
*/
class BlockTemplateUtilsTest extends WP_UnitTestCase {
/**
* Holds an instance of the dependency injection container.
*
* @var Container
*/
private $container;
/**
* Setup test environment.
*/
protected function setUp(): void {
parent::setUp();
// Switch to a block theme and initialize template logic.
switch_theme( 'twentytwentytwo' );
$this->container = Package::container();
$this->container->get( BlockTemplatesRegistry::class )->init();
// Reset options.
delete_option( Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE );
delete_option( Options::WC_BLOCK_VERSION );
}