Skip to content
This repository was archived by the owner on Feb 23, 2024. It is now read-only.

Commit 9b79613

Browse files
authored
Make Products by category/tag/attribute fallback to the Product catalog template (#7712)
* Add archive-product to the hierarchy * Fallback to archive-product and get titles from blocks if the templates from theme don't have one * Rename function * Fix comment * Add fallbacks to the db and blocks versions * Add missing product attribute blockified template * Update docs * Add comment and fix comment type on wp_template * Replate template name if we know the template in blocks * Add comment and fix linting error * Fix archive-product template * Clone the fallback template from db to show them in the template lsit * Return the fallback template when querying for a single template * Remove unneeded condition * Use wp function instead of gutenberg one * Fix tests * Fix tests on Product Catalog templates It was checking a single product for the customization, but it should check the /shop page * Disable tests related with deleteAllTemplates function
1 parent 1785fa9 commit 9b79613

10 files changed

+240
-37
lines changed

docs/internal-developers/templates/block-template-controller.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ This method is applied to the filter `get_block_templates`, which is executed be
3636

3737
- Giving our templates a user-friendly title (e.g. turning "single-product" into "Product Page").
3838
- It collects all the WooCommerce templates from both the filesystem and the database (customized templates are stored in the database as posts) and adds them to the returned list.
39-
- In the event the theme has a `archive-product.html` template file, but not category/tag template files, it is eligible to use the `archive-product.html` file in their place. So we trick Gutenberg in thinking some templates (e.g. category/tag) have a theme file available if it is using the `archive-product.html` template, even though _technically_ the theme does not have a specific file for them.
39+
- In the event the theme has a `archive-product.html` template file, but not category/tag/attribute template files, it is eligible to use the `archive-product.html` file in their place. So we trick Gutenberg in thinking some templates (e.g. category/tag/attribute) have a theme file available if it is using the `archive-product.html` template, even though _technically_ the theme does not have a specific file for them.
4040
- Ensuring we do not add irrelevant WooCommerce templates in the returned list. For example, if `$query['post_type']` has a value (e.g. `product`) this means the query is requesting templates related to that specific post type, so we filter out any irrelevant ones. This _could_ be used to show/hide templates from the template dropdown on the "Edit Product" screen in WP Admin.
4141

4242
### Return value

src/BlockTemplatesController.php

+121-12
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,93 @@ public function __construct( Package $package ) {
6262
*/
6363
protected function init() {
6464
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
65+
add_filter( 'pre_get_block_template', array( $this, 'get_block_template_fallback' ), 10, 3 );
6566
add_filter( 'pre_get_block_file_template', array( $this, 'get_block_file_template' ), 10, 3 );
6667
add_filter( 'get_block_templates', array( $this, 'add_block_templates' ), 10, 3 );
6768
add_filter( 'current_theme_supports-block-templates', array( $this, 'remove_block_template_support_for_shop_page' ) );
69+
add_filter( 'taxonomy_template_hierarchy', array( $this, 'add_archive_product_to_eligible_for_fallback_templates' ), 10, 1 );
6870

6971
if ( $this->package->is_experimental_build() ) {
7072
add_action( 'after_switch_theme', array( $this, 'check_should_use_blockified_product_grid_templates' ), 10, 2 );
7173
}
7274
}
7375

76+
/**
77+
* This function is used on the `pre_get_block_template` hook to return the fallback template from the db in case
78+
* the template is eligible for it.
79+
*
80+
* @param \WP_Block_Template|null $template Block template object to short-circuit the default query,
81+
* or null to allow WP to run its normal queries.
82+
* @param string $id Template unique identifier (example: theme_slug//template_slug).
83+
* @param string $template_type wp_template or wp_template_part.
84+
*
85+
* @return object|null
86+
*/
87+
public function get_block_template_fallback( $template, $id, $template_type ) {
88+
$template_name_parts = explode( '//', $id );
89+
list( $theme, $slug ) = $template_name_parts;
90+
91+
if ( ! BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $slug ) ) {
92+
return null;
93+
}
94+
95+
$wp_query_args = array(
96+
'post_name__in' => array( 'archive-product' ),
97+
'post_type' => $template_type,
98+
'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash' ),
99+
'posts_per_page' => 1,
100+
'no_found_rows' => true,
101+
'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
102+
array(
103+
'taxonomy' => 'wp_theme',
104+
'field' => 'name',
105+
'terms' => $theme,
106+
),
107+
),
108+
);
109+
$template_query = new \WP_Query( $wp_query_args );
110+
$posts = $template_query->posts;
111+
112+
if ( count( $posts ) > 0 ) {
113+
$template = _build_block_template_result_from_post( $posts[0] );
114+
115+
if ( ! is_wp_error( $template ) ) {
116+
$template->id = $theme . '//' . $slug;
117+
$template->slug = $slug;
118+
$template->title = BlockTemplateUtils::get_block_template_title( $slug );
119+
$template->description = BlockTemplateUtils::get_block_template_description( $slug );
120+
121+
return $template;
122+
}
123+
}
124+
125+
return $template;
126+
}
127+
128+
/**
129+
* Adds the `archive-product` template to the `taxonomy-product_cat`, `taxonomy-product_tag`, `taxonomy-attribute`
130+
* templates to be able to fall back to it.
131+
*
132+
* @param array $template_hierarchy A list of template candidates, in descending order of priority.
133+
*/
134+
public function add_archive_product_to_eligible_for_fallback_templates( $template_hierarchy ) {
135+
$template_slugs = array_map(
136+
'_strip_template_file_suffix',
137+
$template_hierarchy
138+
);
139+
140+
$templates_eligible_for_fallback = array_filter(
141+
$template_slugs,
142+
array( BlockTemplateUtils::class, 'template_is_eligible_for_product_archive_fallback' )
143+
);
144+
145+
if ( count( $templates_eligible_for_fallback ) > 0 ) {
146+
$template_hierarchy[] = 'archive-product';
147+
}
148+
149+
return $template_hierarchy;
150+
}
151+
74152
/**
75153
* Checks the old and current themes and determines if the "wc_blocks_use_blockified_product_grid_block_as_template"
76154
* option need to be updated accordingly.
@@ -112,7 +190,7 @@ public function get_block_file_template( $template, $id, $template_type ) {
112190
list( $template_id, $template_slug ) = $template_name_parts;
113191

114192
// 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.
115-
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $template_slug ) ) {
193+
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback_from_theme( $template_slug ) ) {
116194
$template_path = BlockTemplateUtils::get_theme_template_path( 'archive-product' );
117195
$template_object = BlockTemplateUtils::create_new_block_template_object( $template_path, $template_type, $template_slug, true );
118196
return BlockTemplateUtils::build_template_result_from_file( $template_object, $template_type );
@@ -158,9 +236,9 @@ public function get_block_file_template( $template, $id, $template_type ) {
158236
/**
159237
* Add the block template objects to be used.
160238
*
161-
* @param array $query_result Array of template objects.
162-
* @param array $query Optional. Arguments to retrieve templates.
163-
* @param array $template_type wp_template or wp_template_part.
239+
* @param array $query_result Array of template objects.
240+
* @param array $query Optional. Arguments to retrieve templates.
241+
* @param string $template_type wp_template or wp_template_part.
164242
* @return array
165243
*/
166244
public function add_block_templates( $query_result, $query, $template_type ) {
@@ -229,7 +307,7 @@ public function add_block_templates( $query_result, $query, $template_type ) {
229307
*/
230308
$query_result = array_map(
231309
function( $template ) {
232-
if ( 'theme' === $template->origin ) {
310+
if ( 'theme' === $template->origin && BlockTemplateUtils::template_has_title( $template ) ) {
233311
return $template;
234312
}
235313
if ( $template->title === $template->slug ) {
@@ -249,8 +327,8 @@ function( $template ) {
249327
/**
250328
* Gets the templates saved in the database.
251329
*
252-
* @param array $slugs An array of slugs to retrieve templates for.
253-
* @param array $template_type wp_template or wp_template_part.
330+
* @param array $slugs An array of slugs to retrieve templates for.
331+
* @param string $template_type wp_template or wp_template_part.
254332
*
255333
* @return int[]|\WP_Post[] An array of found templates.
256334
*/
@@ -331,13 +409,32 @@ function ( $template ) use ( $template_slug ) {
331409
continue;
332410
}
333411

412+
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback_from_db( $template_slug, $already_found_templates ) ) {
413+
$template = clone BlockTemplateUtils::get_fallback_template_from_db( $template_slug, $already_found_templates );
414+
$template_id = explode( '//', $template->id );
415+
$template->id = $template_id[0] . '//' . $template_slug;
416+
$template->slug = $template_slug;
417+
$template->title = BlockTemplateUtils::get_block_template_title( $template_slug );
418+
$template->description = BlockTemplateUtils::get_block_template_description( $template_slug );
419+
$templates[] = $template;
420+
continue;
421+
}
422+
334423
// 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.
335-
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $template_slug ) ) {
424+
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback_from_theme( $template_slug ) ) {
336425
$template_file = BlockTemplateUtils::get_theme_template_path( 'archive-product' );
337426
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug, true );
338427
continue;
339428
}
340429

430+
// At this point the template only exists in the Blocks filesystem, if is a taxonomy-product_cat/tag/attribute.html template
431+
// let's use the archive-product.html template from Blocks.
432+
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $template_slug ) ) {
433+
$template_file = $this->get_template_path_from_woocommerce( 'archive-product' );
434+
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug, false );
435+
continue;
436+
}
437+
341438
// At this point the template only exists in the Blocks filesystem and has not been saved in the DB,
342439
// or superseded by the theme.
343440
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug );
@@ -349,23 +446,23 @@ function ( $template ) use ( $template_slug ) {
349446
/**
350447
* Get and build the block template objects from the block template files.
351448
*
352-
* @param array $slugs An array of slugs to retrieve templates for.
353-
* @param array $template_type wp_template or wp_template_part.
449+
* @param array $slugs An array of slugs to retrieve templates for.
450+
* @param string $template_type wp_template or wp_template_part.
354451
*
355452
* @return array WP_Block_Template[] An array of block template objects.
356453
*/
357454
public function get_block_templates( $slugs = array(), $template_type = 'wp_template' ) {
358455
$templates_from_db = $this->get_block_templates_from_db( $slugs, $template_type );
359456
$templates_from_woo = $this->get_block_templates_from_woocommerce( $slugs, $templates_from_db, $template_type );
360457
$templates = array_merge( $templates_from_db, $templates_from_woo );
361-
return BlockTemplateUtils::filter_block_templates_by_feature_flag( $templates );
362458

459+
return BlockTemplateUtils::filter_block_templates_by_feature_flag( $templates );
363460
}
364461

365462
/**
366463
* Gets the directory where templates of a specific template type can be found.
367464
*
368-
* @param array $template_type wp_template or wp_template_part.
465+
* @param string $template_type wp_template or wp_template_part.
369466
*
370467
* @return string
371468
*/
@@ -382,6 +479,18 @@ protected function get_templates_directory( $template_type = 'wp_template' ) {
382479
return $this->templates_directory;
383480
}
384481

482+
/**
483+
* Returns the path of a template on the Blocks template folder.
484+
*
485+
* @param string $template_slug Block template slug e.g. single-product.
486+
* @param string $template_type wp_template or wp_template_part.
487+
*
488+
* @return string
489+
*/
490+
public function get_template_path_from_woocommerce( $template_slug, $template_type = 'wp_template' ) {
491+
return $this->get_templates_directory( $template_type ) . '/' . $template_slug . '.html';
492+
}
493+
385494
/**
386495
* Checks whether a block template with that name exists in Woo Blocks
387496
*

src/Templates/ProductAttributeTemplate.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function __construct() {
2121
* Initialization method.
2222
*/
2323
protected function init() {
24-
add_filter( 'taxonomy_template_hierarchy', array( $this, 'update_taxonomy_template_hierarchy' ), 10, 3 );
24+
add_filter( 'taxonomy_template_hierarchy', array( $this, 'update_taxonomy_template_hierarchy' ), 1, 3 );
2525
}
2626

2727
/**

src/Utils/BlockTemplateUtils.php

+73-6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* {@internal This class and its methods should only be used within the BlockTemplateController.php and is not intended for public use.}
1313
*/
1414
class BlockTemplateUtils {
15+
const ELIGIBLE_FOR_ARCHIVE_PRODUCT_FALLBACK = array( 'taxonomy-product_cat', 'taxonomy-product_tag', ProductAttributeTemplate::SLUG );
1516
/**
1617
* Directory names for block templates
1718
*
@@ -452,26 +453,82 @@ public static function get_block_template( $id, $template_type ) {
452453
}
453454

454455
/**
455-
* Checks if we can fallback to the `archive-product` template for a given slug
456+
* Checks if we can fall back to the `archive-product` template for a given slug.
456457
*
457-
* `taxonomy-product_cat`, `taxonomy-product_tag`, `taxonomy-attribute` templates can
458+
* `taxonomy-product_cat`, `taxonomy-product_tag`, `taxonomy-product_attribute` templates can
458459
* generally use the `archive-product` as a fallback if there are no specific overrides.
459460
*
460461
* @param string $template_slug Slug to check for fallbacks.
461462
* @return boolean
462463
*/
463464
public static function template_is_eligible_for_product_archive_fallback( $template_slug ) {
464-
$eligible_for_fallbacks = array( 'taxonomy-product_cat', 'taxonomy-product_tag', ProductAttributeTemplate::SLUG );
465+
return in_array( $template_slug, self::ELIGIBLE_FOR_ARCHIVE_PRODUCT_FALLBACK, true );
466+
}
467+
468+
/**
469+
* Checks if we can fall back to an `archive-product` template stored on the db for a given slug.
470+
*
471+
* @param string $template_slug Slug to check for fallbacks.
472+
* @param array $db_templates Templates that have already been found on the db.
473+
* @return boolean
474+
*/
475+
public static function template_is_eligible_for_product_archive_fallback_from_db( $template_slug, $db_templates ) {
476+
$eligible_for_fallback = self::template_is_eligible_for_product_archive_fallback( $template_slug );
477+
if ( ! $eligible_for_fallback ) {
478+
return false;
479+
}
480+
481+
$array_filter = array_filter(
482+
$db_templates,
483+
function ( $template ) use ( $template_slug ) {
484+
return 'archive-product' === $template->slug;
485+
}
486+
);
487+
488+
return count( $array_filter ) > 0;
489+
}
490+
491+
/**
492+
* Gets the `archive-product` fallback template stored on the db for a given slug.
493+
*
494+
* @param string $template_slug Slug to check for fallbacks.
495+
* @param array $db_templates Templates that have already been found on the db.
496+
* @return boolean|object
497+
*/
498+
public static function get_fallback_template_from_db( $template_slug, $db_templates ) {
499+
$eligible_for_fallback = self::template_is_eligible_for_product_archive_fallback( $template_slug );
500+
if ( ! $eligible_for_fallback ) {
501+
return false;
502+
}
503+
504+
foreach ( $db_templates as $template ) {
505+
if ( 'archive-product' === $template->slug ) {
506+
return $template;
507+
}
508+
}
465509

466-
return in_array( $template_slug, $eligible_for_fallbacks, true )
510+
return false;
511+
}
512+
513+
/**
514+
* Checks if we can fall back to the `archive-product` file template for a given slug in the current theme.
515+
*
516+
* `taxonomy-product_cat`, `taxonomy-product_tag`, `taxonomy-attribute` templates can
517+
* generally use the `archive-product` as a fallback if there are no specific overrides.
518+
*
519+
* @param string $template_slug Slug to check for fallbacks.
520+
* @return boolean
521+
*/
522+
public static function template_is_eligible_for_product_archive_fallback_from_theme( $template_slug ) {
523+
return self::template_is_eligible_for_product_archive_fallback( $template_slug )
467524
&& ! self::theme_has_template( $template_slug )
468525
&& self::theme_has_template( 'archive-product' );
469526
}
470527

471528
/**
472529
* Sets the `has_theme_file` to `true` for templates with fallbacks
473530
*
474-
* There are cases (such as tags and categories) in which fallback templates
531+
* There are cases (such as tags, categories and attributes) in which fallback templates
475532
* can be used; so, while *technically* the theme doesn't have a specific file
476533
* for them, it is important that we tell Gutenberg that we do, in fact,
477534
* have a theme file (i.e. the fallback one).
@@ -491,7 +548,7 @@ public static function set_has_theme_file_if_fallback_is_available( $query_resul
491548
$query_result_template->slug === $template->slug
492549
&& $query_result_template->theme === $template->theme
493550
) {
494-
if ( self::template_is_eligible_for_product_archive_fallback( $template->slug ) ) {
551+
if ( self::template_is_eligible_for_product_archive_fallback_from_theme( $template->slug ) ) {
495552
$query_result_template->has_theme_file = true;
496553
}
497554

@@ -586,4 +643,14 @@ public static function should_use_blockified_product_grid_templates() {
586643

587644
return wc_string_to_bool( $use_blockified_templates );
588645
}
646+
647+
/**
648+
* Returns whether the passed `$template` has a title, and it's different from the slug.
649+
*
650+
* @param object $template The template object.
651+
* @return boolean
652+
*/
653+
public static function template_has_title( $template ) {
654+
return ! empty( $template->title ) && $template->title !== $template->slug;
655+
}
589656
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!-- wp:template-part {"slug":"header"} /-->
2+
<!-- wp:group {"layout":{"inherit":true}} -->
3+
<div class="wp-block-group">
4+
<!-- wp:paragraph -->
5+
<p>Products by Attribute blockified</p>
6+
<!-- /wp:paragraph --></div>
7+
<!-- /wp:group -->
8+
<!-- wp:template-part {"slug":"footer"} /-->

0 commit comments

Comments
 (0)