This repository has been archived by the owner on Feb 23, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 219
Allow block templates to be customised and saved #5062
Merged
Merged
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
848303b
Ensure $template is object before accessing properties
opr 5acf2b6
Add Gutenberg utils for processing templates based on a post from the db
opr 4bfbc4a
Force theme to always be WooCommerce
opr 5a5e6fb
Add maybe_return_blocks_template and get_single_block_template funcs
opr 4819f63
Set theme to always be woocommerce when making templates from files
opr 5c4b0ed
Check if template has been customised and saved in the database first
opr 495f4d2
Prevent filesystem templates being used if a custom database one exists
opr 3cc5e67
Fix syntax error from rebase
opr 46d4678
Remove unnecessary code from BlockTemplateUtils
opr 2d53a52
Ensure template item is an object containing correct properties
opr 2828553
Prevent warnings from appearing
opr 00d2cdc
Ensure title is added to the template when saving
opr 12fddc9
Filter templates that don't match the queried slug.
opr ff8293b
Remove unused code
opr 21e08bc
Check if a saved version of the template exists when trying to render
opr 156cb22
Rename default_block_template_is_available to block_template_is_avail…
opr e715879
Re-hook pre_get_block_template before returning from maybe_return_blo…
opr 238560c
Make comment easier to read
opr 34c33c6
Look for template in woocommerce theme or real theme taxonomy
opr 8760a01
Remove duplicated title assignment
opr c83af04
Prevent template being added twice when loading from the db
opr 550d218
Filter templates before returning if slugs are supplied
opr c06a4e5
Simplify `get_block_templates` function into two functions
opr a365ae8
Add function to stop theme templates that are added after db ones sho…
opr 02dec60
Fix typographical errors
opr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -38,6 +38,87 @@ public function __construct() { | |||||
protected function init() { | ||||||
add_filter( 'get_block_templates', array( $this, 'add_block_templates' ), 10, 3 ); | ||||||
add_action( 'template_redirect', array( $this, 'render_block_template' ) ); | ||||||
add_filter( 'pre_get_block_template', array( $this, 'maybe_return_blocks_template' ), 10, 3 ); | ||||||
} | ||||||
|
||||||
/** | ||||||
* This function checks if there's a blocks template (ultimately it resolves either a saved blocks template from the | ||||||
* database or a template file in `woo-gutenberg-products/block/templates/block-templates/`) | ||||||
* to return to pre_get_posts short-circuiting the query in Gutenberg. | ||||||
* | ||||||
* @param \WP_Block_Template|null $template Return a block template object to short-circuit the default query, | ||||||
* or null to allow WP to run its normal queries. | ||||||
* @param string $id Template unique identifier (example: theme_slug//template_slug). | ||||||
* @param array $template_type wp_template or wp_template_part. | ||||||
* | ||||||
* @return mixed|\WP_Block_Template|\WP_Error | ||||||
*/ | ||||||
public function maybe_return_blocks_template( $template, $id, $template_type ) { | ||||||
$template_name_parts = explode( '//', $id ); | ||||||
if ( count( $template_name_parts ) < 2 ) { | ||||||
return $template; | ||||||
} | ||||||
list( , $slug ) = $template_name_parts; | ||||||
|
||||||
// Remove the filter at this point because if we don't then this function will infinite loop. | ||||||
remove_filter( 'pre_get_block_template', array( $this, 'maybe_return_blocks_template' ), 10, 3 ); | ||||||
|
||||||
// Check if the theme has a saved version of this template before falling back to the woo one. Please note how | ||||||
// the slug has not been modified at this point, we're still using the default one passed to this hook. | ||||||
$maybe_template = gutenberg_get_block_template( $id, $template_type ); | ||||||
if ( null !== $maybe_template ) { | ||||||
add_filter( 'pre_get_block_template', array( $this, 'maybe_return_blocks_template' ), 10, 3 ); | ||||||
return $maybe_template; | ||||||
Aljullu marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
// Theme-based template didn't exist, try switching the theme to woocommerce and try again. This function has | ||||||
// been unhooked so won't run again. | ||||||
add_filter( 'get_block_template', array( $this, 'get_single_block_template' ), 10, 3 ); | ||||||
$maybe_template = gutenberg_get_block_template( 'woocommerce//' . $slug, $template_type ); | ||||||
|
||||||
// Re-hook this function, it was only unhooked to stop recursion. | ||||||
add_filter( 'pre_get_block_template', array( $this, 'maybe_return_blocks_template' ), 10, 3 ); | ||||||
remove_filter( 'get_block_template', array( $this, 'get_single_block_template' ), 10, 3 ); | ||||||
if ( null !== $maybe_template ) { | ||||||
return $maybe_template; | ||||||
} | ||||||
|
||||||
// At this point we haven't had any luck finding a template. Give up and let Gutenberg take control again. | ||||||
return $template; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Runs on the get_block_template hook. If a template is already found and passed to this function, then return it | ||||||
* and don't run. | ||||||
* If a template is *not* passed, try to look for one that matches the ID in the database, if that's not found defer | ||||||
* to Blocks templates files. Priority goes: DB-Theme, DB-Blocks, Filesystem-Theme, Filesystem-Blocks. | ||||||
* | ||||||
* @param \WP_Block_Template $template The found block template. | ||||||
* @param string $id Template unique identifier (example: theme_slug//template_slug). | ||||||
* @param array $template_type wp_template or wp_template_part. | ||||||
* | ||||||
* @return mixed|null | ||||||
*/ | ||||||
public function get_single_block_template( $template, $id, $template_type ) { | ||||||
|
||||||
// The template was already found before the filter runs, just return it immediately. | ||||||
if ( null !== $template ) { | ||||||
return $template; | ||||||
} | ||||||
|
||||||
$template_name_parts = explode( '//', $id ); | ||||||
if ( count( $template_name_parts ) < 2 ) { | ||||||
return $template; | ||||||
} | ||||||
list( , $slug ) = $template_name_parts; | ||||||
|
||||||
// If this blocks template doesn't exist then we should just skip the function and let Gutenberg handle it. | ||||||
if ( ! $this->block_template_is_available( $slug ) ) { | ||||||
return $template; | ||||||
} | ||||||
|
||||||
$available_templates = $this->get_block_templates( array( $slug ) ); | ||||||
return ( is_array( $available_templates ) && count( $available_templates ) > 0 ) ? (object) $available_templates[0] : $template; | ||||||
} | ||||||
|
||||||
/** | ||||||
|
@@ -54,13 +135,32 @@ public function add_block_templates( $query_result, $query, $template_type ) { | |||||
} | ||||||
|
||||||
$post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; | ||||||
$template_files = $this->get_block_templates(); | ||||||
$slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : array(); | ||||||
$template_files = $this->get_block_templates( $slugs ); | ||||||
|
||||||
// @todo: Add apply_filters to _gutenberg_get_template_files() in Gutenberg to prevent duplication of logic. | ||||||
foreach ( $template_files as $template_file ) { | ||||||
$template = BlockTemplateUtils::gutenberg_build_template_result_from_file( $template_file, 'wp_template' ); | ||||||
|
||||||
if ( $post_type && ! $template->is_custom ) { | ||||||
// Avoid adding the same template if it's already in the array of $query_result. | ||||||
if ( | ||||||
array_filter( | ||||||
$query_result, | ||||||
function( $query_result_template ) use ( $template_file ) { | ||||||
return $query_result_template->slug === $template_file->slug && | ||||||
$query_result_template->theme === $template_file->theme; | ||||||
} | ||||||
) | ||||||
) { | ||||||
continue; | ||||||
} | ||||||
|
||||||
// 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::gutenberg_build_template_result_from_file( $template_file, 'wp_template' ); | ||||||
} else { | ||||||
$template_file->title = BlockTemplateUtils::convert_slug_to_title( $template_file->slug ); | ||||||
$query_result[] = $template_file; | ||||||
continue; | ||||||
} | ||||||
|
||||||
|
@@ -72,55 +172,167 @@ public function add_block_templates( $query_result, $query, $template_type ) { | |||||
} | ||||||
|
||||||
$is_not_custom = false === array_search( | ||||||
wp_get_theme()->get_stylesheet() . '//' . $template_file['slug'], | ||||||
wp_get_theme()->get_stylesheet() . '//' . $template_file->slug, | ||||||
array_column( $query_result, 'id' ), | ||||||
true | ||||||
); | ||||||
$fits_slug_query = | ||||||
! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true ); | ||||||
! isset( $query['slug__in'] ) || in_array( $template_file->slug, $query['slug__in'], true ); | ||||||
$fits_area_query = | ||||||
! isset( $query['area'] ) || $template_file['area'] === $query['area']; | ||||||
! isset( $query['area'] ) || $template_file->area === $query['area']; | ||||||
$should_include = $is_not_custom && $fits_slug_query && $fits_area_query; | ||||||
if ( $should_include ) { | ||||||
$query_result[] = $template; | ||||||
} | ||||||
} | ||||||
|
||||||
$query_result = $this->remove_theme_templates_with_custom_alternative( $query_result ); | ||||||
return $query_result; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Get and build the block template objects from the block template files. | ||||||
* Removes templates that were added to a theme's block-templates director, but already had a customised version saved in the database. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
* | ||||||
* @return array | ||||||
* @param \WP_Block_Template[]|\stdClass[] $templates List of templates to run the filter on. | ||||||
* | ||||||
* @return array List of templates with duplicates removed. The customised alternative is preferred over the theme default. | ||||||
*/ | ||||||
public function get_block_templates() { | ||||||
public function remove_theme_templates_with_custom_alternative( $templates ) { | ||||||
|
||||||
// Get the slugs of all templates that have been customised and saved in the database. | ||||||
$customised_template_slugs = array_map( | ||||||
function( $template ) { | ||||||
return $template->slug; | ||||||
}, | ||||||
array_values( | ||||||
array_filter( | ||||||
$templates, | ||||||
function( $template ) { | ||||||
// This template has been customised and saved as a post. | ||||||
return 'custom' === $template->source; | ||||||
} | ||||||
) | ||||||
) | ||||||
); | ||||||
|
||||||
// Remove theme (i.e. filesystem) templates that have the same slug as a customised one. We don't need to check | ||||||
// for `woocommerce` in $template->source here because woocommerce templates won't have been added to $templates | ||||||
// if a saved version was found in the db. This only affects saved templates that were saved BEFORE a theme | ||||||
// template with the same slug was added. | ||||||
return array_values( | ||||||
array_filter( | ||||||
$templates, | ||||||
function( $template ) use ( $customised_template_slugs ) { | ||||||
// This template has been customised and saved as a post, so return it. | ||||||
return ! ( 'theme' === $template->source && in_array( $template->slug, $customised_template_slugs, true ) ); | ||||||
} | ||||||
) | ||||||
); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Gets the templates saved in the database. | ||||||
* | ||||||
* @param array $slugs An array of slugs to retrieve templates for. | ||||||
* | ||||||
* @return int[]|\WP_Post[] An array of found templates. | ||||||
*/ | ||||||
public function get_block_templates_from_db( $slugs = array() ) { | ||||||
$check_query_args = array( | ||||||
'post_type' => 'wp_template', | ||||||
'posts_per_page' => -1, | ||||||
'no_found_rows' => true, | ||||||
'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query | ||||||
array( | ||||||
'taxonomy' => 'wp_theme', | ||||||
'field' => 'name', | ||||||
'terms' => array( 'woocommerce', get_stylesheet() ), | ||||||
), | ||||||
), | ||||||
); | ||||||
if ( is_array( $slugs ) && count( $slugs ) > 0 ) { | ||||||
$check_query_args['post_name__in'] = $slugs; | ||||||
} | ||||||
$check_query = new \WP_Query( $check_query_args ); | ||||||
$saved_woo_templates = $check_query->posts; | ||||||
|
||||||
return array_map( | ||||||
function( $saved_woo_template ) { | ||||||
return BlockTemplateUtils::gutenberg_build_template_result_from_post( $saved_woo_template ); | ||||||
}, | ||||||
$saved_woo_templates | ||||||
); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Gets the templates from the WooCommerce blocks directory, skipping those for which a template already exists | ||||||
* in the theme directory. | ||||||
* | ||||||
* @param string[] $slugs An array of slugs to filter templates by. Templates whose slug does not match will not be returned. | ||||||
* @param array $already_found_templates Templates that have already been found, these will customised templates that are loaded from the database. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: There seems to be a typo in the comment |
||||||
* | ||||||
* @return array Templates from the WooCommerce blocks plugin directory. | ||||||
*/ | ||||||
public function get_block_templates_from_woocommerce( $slugs, $already_found_templates ) { | ||||||
$template_files = BlockTemplateUtils::gutenberg_get_template_paths( $this->templates_directory ); | ||||||
$templates = array(); | ||||||
|
||||||
foreach ( $template_files as $template_file ) { | ||||||
Aljullu marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
$template_slug = substr( | ||||||
$template_file, | ||||||
strpos( $template_file, self::TEMPLATES_DIR_NAME . DIRECTORY_SEPARATOR ) + 1 + strlen( self::TEMPLATES_DIR_NAME ), | ||||||
-5 | ||||||
); | ||||||
|
||||||
// If the theme already has a template then there is no need to load ours in. | ||||||
if ( $this->theme_has_template( $template_slug ) ) { | ||||||
// This template does not have a slug we're looking for. Skip it. | ||||||
if ( is_array( $slugs ) && count( $slugs ) > 0 && ! in_array( $template_slug, $slugs, true ) ) { | ||||||
continue; | ||||||
} | ||||||
|
||||||
// If the theme already has a template, or the template is already in the list (i.e. it came from the | ||||||
// database) then we should not overwrite it with the one from the filesystem. | ||||||
if ( | ||||||
$this->theme_has_template( $template_slug ) || | ||||||
count( | ||||||
array_filter( | ||||||
$already_found_templates, | ||||||
function ( $template ) use ( $template_slug ) { | ||||||
$template_obj = (object) $template; //phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found | ||||||
return $template_obj->slug === $template_slug; | ||||||
} | ||||||
) | ||||||
) > 0 ) { | ||||||
continue; | ||||||
} | ||||||
|
||||||
// At this point the template only exists in the Blocks filesystem and has not been saved in the DB, | ||||||
// or superseded by the theme. | ||||||
$new_template_item = array( | ||||||
'slug' => $template_slug, | ||||||
'path' => $template_file, | ||||||
'theme' => get_template_directory(), | ||||||
'type' => 'wp_template', | ||||||
'slug' => $template_slug, | ||||||
'id' => 'woocommerce//' . $template_slug, | ||||||
'path' => $template_file, | ||||||
'type' => 'wp_template', | ||||||
'theme' => 'woocommerce', | ||||||
'source' => 'woocommerce', | ||||||
'title' => BlockTemplateUtils::convert_slug_to_title( $template_slug ), | ||||||
'description' => '', | ||||||
); | ||||||
$templates[] = $new_template_item; | ||||||
$templates[] = (object) $new_template_item; | ||||||
} | ||||||
return $templates; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Get and build the block template objects from the block template files. | ||||||
* | ||||||
* @param array $slugs An array of slugs to retrieve templates for. | ||||||
* @return array | ||||||
*/ | ||||||
public function get_block_templates( $slugs = array() ) { | ||||||
$templates_from_db = $this->get_block_templates_from_db( $slugs ); | ||||||
$templates_from_woo = $this->get_block_templates_from_woocommerce( $slugs, $templates_from_db ); | ||||||
return array_merge( $templates_from_db, $templates_from_woo ); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Check if the theme has a template. So we know if to load our own in or not. | ||||||
* | ||||||
|
@@ -138,14 +350,14 @@ public function theme_has_template( $template_name ) { | |||||
* @param string $template_name Template to check. | ||||||
* @return boolean | ||||||
*/ | ||||||
public function default_block_template_is_available( $template_name ) { | ||||||
public function block_template_is_available( $template_name ) { | ||||||
if ( ! $template_name ) { | ||||||
return false; | ||||||
} | ||||||
|
||||||
return is_readable( | ||||||
$this->templates_directory . '/' . $template_name . '.html' | ||||||
); | ||||||
) || $this->get_block_templates( array( $template_name ) ); | ||||||
} | ||||||
|
||||||
/** | ||||||
|
@@ -159,19 +371,19 @@ public function render_block_template() { | |||||
if ( | ||||||
is_singular( 'product' ) && | ||||||
! $this->theme_has_template( 'single-product' ) && | ||||||
$this->default_block_template_is_available( 'single-product' ) | ||||||
$this->block_template_is_available( 'single-product' ) | ||||||
) { | ||||||
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 ); | ||||||
} elseif ( | ||||||
is_tax() && | ||||||
! $this->theme_has_template( 'taxonomy-product_cat' ) && | ||||||
$this->default_block_template_is_available( 'taxonomy-product_cat' ) | ||||||
$this->block_template_is_available( 'taxonomy-product_cat' ) | ||||||
) { | ||||||
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 ); | ||||||
} elseif ( | ||||||
( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) && | ||||||
! $this->theme_has_template( 'archive-product' ) && | ||||||
$this->default_block_template_is_available( 'archive-product' ) | ||||||
$this->block_template_is_available( 'archive-product' ) | ||||||
) { | ||||||
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 ); | ||||||
} | ||||||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.