2021-10-25 09:45:50 +00:00
< ? php
namespace Automattic\WooCommerce\Blocks ;
2022-07-06 07:51:39 +00:00
use Automattic\WooCommerce\Blocks\Domain\Package ;
2021-10-25 09:45:50 +00:00
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils ;
/**
* BlockTypesController class .
*
* @ internal
*/
class BlockTemplatesController {
2022-07-06 07:51:39 +00:00
/**
* Holds the Package instance
*
* @ var Package
*/
private $package ;
2021-10-25 09:45:50 +00:00
/**
* Holds the path for the directory where the block templates will be kept .
*
* @ var string
*/
private $templates_directory ;
2021-11-19 11:47:48 +00:00
/**
* Holds the path for the directory where the block template parts will be kept .
*
* @ var string
*/
private $template_parts_directory ;
2021-10-25 09:45:50 +00:00
/**
2022-01-05 18:28:51 +00:00
* Directory which contains all templates
2021-10-25 09:45:50 +00:00
*
* @ var string
*/
2022-01-05 18:28:51 +00:00
const TEMPLATES_ROOT_DIR = 'templates' ;
2021-11-19 11:47:48 +00:00
2021-10-25 09:45:50 +00:00
/**
* Constructor .
2022-07-06 07:51:39 +00:00
*
* @ param Package $package An instance of Package .
2021-10-25 09:45:50 +00:00
*/
2022-07-06 07:51:39 +00:00
public function __construct ( Package $package ) {
$this -> package = $package ;
2021-11-22 11:25:53 +00:00
// This feature is gated for WooCommerce versions 6.0.0 and above.
if ( defined ( 'WC_VERSION' ) && version_compare ( WC_VERSION , '6.0.0' , '>=' ) ) {
2022-02-10 14:15:09 +00:00
$root_path = plugin_dir_path ( __DIR__ ) . self :: TEMPLATES_ROOT_DIR . DIRECTORY_SEPARATOR ;
2022-01-05 18:28:51 +00:00
$this -> templates_directory = $root_path . BlockTemplateUtils :: DIRECTORY_NAMES [ 'TEMPLATES' ];
$this -> template_parts_directory = $root_path . BlockTemplateUtils :: DIRECTORY_NAMES [ 'TEMPLATE_PARTS' ];
2021-11-22 11:25:53 +00:00
$this -> init ();
}
2021-10-25 09:45:50 +00:00
}
/**
* Initialization method .
*/
protected function init () {
2021-10-29 11:44:29 +00:00
add_action ( 'template_redirect' , array ( $this , 'render_block_template' ) );
2022-02-10 14:15:09 +00:00
add_filter ( 'pre_get_block_file_template' , array ( $this , 'get_block_file_template' ), 10 , 3 );
2021-11-17 14:40:15 +00:00
add_filter ( 'get_block_templates' , array ( $this , 'add_block_templates' ), 10 , 3 );
2022-05-05 01:08:10 +00:00
add_filter ( 'current_theme_supports-block-templates' , array ( $this , 'remove_block_template_support_for_shop_page' ) );
2022-07-06 07:51:39 +00:00
if ( $this -> package -> is_experimental_build () ) {
add_action ( 'after_switch_theme' , array ( $this , 'check_should_use_blockified_product_grid_templates' ), 10 , 2 );
}
}
/**
* Checks the old and current themes and determines if the " wc_blocks_use_blockified_product_grid_block_as_template "
* option need to be updated accordingly .
*
* @ param string $old_name Old theme name .
* @ param \WP_Theme $old_theme Instance of the old theme .
* @ return void
*/
public function check_should_use_blockified_product_grid_templates ( $old_name , $old_theme ) {
if ( ! wc_current_theme_is_fse_theme () ) {
update_option ( Options :: WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE , wc_bool_to_string ( false ) );
return ;
}
if ( ! $old_theme -> is_block_theme () && wc_current_theme_is_fse_theme () ) {
update_option ( Options :: WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE , wc_bool_to_string ( true ) );
return ;
}
2021-11-05 19:07:34 +00:00
}
/**
2022-02-10 14:15:09 +00:00
* This function checks if there ' s a block template file in `woo-gutenberg-products-block/templates/templates/`
2021-11-05 19:07:34 +00:00
* 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).
2022-02-10 14:15:09 +00:00
* @ param string $template_type wp_template or wp_template_part .
2021-11-05 19:07:34 +00:00
*
* @ return mixed | \WP_Block_Template | \WP_Error
*/
2022-02-10 14:15:09 +00:00
public function get_block_file_template ( $template , $id , $template_type ) {
2021-11-05 19:07:34 +00:00
$template_name_parts = explode ( '//' , $id );
2022-02-10 14:15:09 +00:00
2021-11-05 19:07:34 +00:00
if ( count ( $template_name_parts ) < 2 ) {
return $template ;
}
2022-02-10 14:15:09 +00:00
list ( $template_id , $template_slug ) = $template_name_parts ;
2021-12-21 10:55:51 +00:00
2022-02-10 14:15:09 +00:00
// If the theme has an archive-product.html template, but not a taxonomy-product_cat/tag.html template let's use the themes archive-product.html template.
if ( BlockTemplateUtils :: template_is_eligible_for_product_archive_fallback ( $template_slug ) ) {
$template_path = BlockTemplateUtils :: get_theme_template_path ( 'archive-product' );
$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 );
2021-11-05 19:07:34 +00:00
}
2022-02-10 14:15:09 +00:00
// This is a real edge-case, we are supporting users who have saved templates under the deprecated slug. See its definition for more information.
// You can likely ignore this code unless you're supporting/debugging early customised templates.
if ( BlockTemplateUtils :: DEPRECATED_PLUGIN_SLUG === strtolower ( $template_id ) ) {
// Because we are using get_block_templates we have to unhook this method to prevent a recursive loop where this filter is applied.
remove_filter ( 'pre_get_block_file_template' , array ( $this , 'get_block_file_template' ), 10 , 3 );
$template_with_deprecated_id = BlockTemplateUtils :: get_block_template ( $id , $template_type );
// Let's hook this method back now that we have used the function.
add_filter ( 'pre_get_block_file_template' , array ( $this , 'get_block_file_template' ), 10 , 3 );
if ( null !== $template_with_deprecated_id ) {
return $template_with_deprecated_id ;
}
2021-11-05 19:07:34 +00:00
}
2022-02-10 14:15:09 +00:00
// If we are not dealing with a WooCommerce template let's return early and let it continue through the process.
if ( BlockTemplateUtils :: PLUGIN_SLUG !== $template_id ) {
2021-11-05 19:07:34 +00:00
return $template ;
}
2022-02-10 14:15:09 +00:00
// If we don't have a template let Gutenberg do its thing.
if ( ! $this -> block_template_is_available ( $template_slug , $template_type ) ) {
2021-11-05 19:07:34 +00:00
return $template ;
}
2022-02-10 14:15:09 +00:00
$directory = $this -> get_templates_directory ( $template_type );
$template_file_path = $directory . '/' . $template_slug . '.html' ;
$template_object = BlockTemplateUtils :: create_new_block_template_object ( $template_file_path , $template_type , $template_slug );
$template_built = BlockTemplateUtils :: build_template_result_from_file ( $template_object , $template_type );
if ( null !== $template_built ) {
return $template_built ;
2021-11-05 19:07:34 +00:00
}
2022-02-10 14:15:09 +00:00
// Hand back over to Gutenberg if we can't find a template.
return $template ;
2021-10-25 09:45:50 +00:00
}
/**
* Add the block template objects to be used .
*
* @ param array $query_result Array of template objects .
2021-10-28 13:33:42 +00:00
* @ param array $query Optional . Arguments to retrieve templates .
* @ param array $template_type wp_template or wp_template_part .
2021-10-25 09:45:50 +00:00
* @ return array
*/
2021-10-28 13:33:42 +00:00
public function add_block_templates ( $query_result , $query , $template_type ) {
2021-12-20 12:53:57 +00:00
if ( ! BlockTemplateUtils :: supports_block_templates () ) {
2021-10-25 09:45:50 +00:00
return $query_result ;
}
2021-11-02 12:15:41 +00:00
$post_type = isset ( $query [ 'post_type' ] ) ? $query [ 'post_type' ] : '' ;
2021-11-05 19:07:34 +00:00
$slugs = isset ( $query [ 'slug__in' ] ) ? $query [ 'slug__in' ] : array ();
2021-11-19 11:47:48 +00:00
$template_files = $this -> get_block_templates ( $slugs , $template_type );
2021-10-25 09:45:50 +00:00
2021-11-02 12:15:41 +00:00
// @todo: Add apply_filters to _gutenberg_get_template_files() in Gutenberg to prevent duplication of logic.
2021-10-25 09:45:50 +00:00
foreach ( $template_files as $template_file ) {
2021-11-02 12:15:41 +00:00
2021-12-24 15:15:19 +00:00
// If we have a template which is eligible for a fallback, we need to explicitly tell Gutenberg that
// it has a theme file (because it is using the fallback template file). And then `continue` to avoid
// adding duplicates.
if ( BlockTemplateUtils :: set_has_theme_file_if_fallback_is_available ( $query_result , $template_file ) ) {
2021-11-05 19:07:34 +00:00
continue ;
}
2021-11-17 14:40:15 +00:00
// If the current $post_type is set (e.g. on an Edit Post screen), and isn't included in the available post_types
// on the template file, then lets skip it so that it doesn't get added. This is typically used to hide templates
// in the template dropdown on the Edit Post page.
if ( $post_type &&
isset ( $template_file -> post_types ) &&
! in_array ( $post_type , $template_file -> post_types , true )
) {
continue ;
}
2021-11-05 19:07:34 +00:00
// 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 ) {
2022-02-10 14:15:09 +00:00
$template = BlockTemplateUtils :: build_template_result_from_file ( $template_file , $template_type );
2021-11-05 19:07:34 +00:00
} else {
2022-06-20 13:49:18 +00:00
$template_file -> title = BlockTemplateUtils :: get_block_template_title ( $template_file -> slug );
$template_file -> description = BlockTemplateUtils :: get_block_template_description ( $template_file -> slug );
$query_result [] = $template_file ;
2021-11-02 12:15:41 +00:00
continue ;
}
$is_not_custom = false === array_search (
2021-11-05 19:07:34 +00:00
wp_get_theme () -> get_stylesheet () . '//' . $template_file -> slug ,
2021-11-02 12:15:41 +00:00
array_column ( $query_result , 'id' ),
true
);
$fits_slug_query =
2021-11-05 19:07:34 +00:00
! isset ( $query [ 'slug__in' ] ) || in_array ( $template_file -> slug , $query [ 'slug__in' ], true );
2021-11-02 12:15:41 +00:00
$fits_area_query =
2021-11-05 19:07:34 +00:00
! isset ( $query [ 'area' ] ) || $template_file -> area === $query [ 'area' ];
2021-11-02 12:15:41 +00:00
$should_include = $is_not_custom && $fits_slug_query && $fits_area_query ;
if ( $should_include ) {
$query_result [] = $template ;
}
2021-10-25 09:45:50 +00:00
}
2022-03-23 09:16:07 +00:00
// We need to remove theme (i.e. filesystem) templates that have the same slug as a customised one.
// This only affects saved templates that were saved BEFORE a theme template with the same slug was added.
$query_result = BlockTemplateUtils :: remove_theme_templates_with_custom_alternative ( $query_result );
2022-06-21 15:26:09 +00:00
/**
* WC templates from theme aren ' t included in `$this->get_block_templates()` but are handled by Gutenberg .
* We need to do additional search through all templates file to update title and description for WC
* templates that aren ' t listed in theme . json .
*/
$query_result = array_map (
function ( $template ) {
if ( 'theme' === $template -> origin ) {
return $template ;
}
if ( $template -> title === $template -> slug ) {
$template -> title = BlockTemplateUtils :: get_block_template_title ( $template -> slug );
}
if ( ! $template -> description ) {
$template -> description = BlockTemplateUtils :: get_block_template_description ( $template -> slug );
}
return $template ;
},
$query_result
);
2021-10-25 09:45:50 +00:00
return $query_result ;
}
2021-11-05 19:07:34 +00:00
/**
* Gets the templates saved in the database .
*
* @ param array $slugs An array of slugs to retrieve templates for .
2021-11-19 11:47:48 +00:00
* @ param array $template_type wp_template or wp_template_part .
2021-11-05 19:07:34 +00:00
*
* @ return int [] | \WP_Post [] An array of found templates .
*/
2021-11-19 11:47:48 +00:00
public function get_block_templates_from_db ( $slugs = array (), $template_type = 'wp_template' ) {
2021-11-05 19:07:34 +00:00
$check_query_args = array (
2021-11-19 11:47:48 +00:00
'post_type' => $template_type ,
2021-11-05 19:07:34 +00:00
'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' ,
2022-02-10 14:15:09 +00:00
'terms' => array ( BlockTemplateUtils :: DEPRECATED_PLUGIN_SLUG , BlockTemplateUtils :: PLUGIN_SLUG , get_stylesheet () ),
2021-11-05 19:07:34 +00:00
),
),
);
2022-01-07 10:17:08 +00:00
2021-11-05 19:07:34 +00:00
if ( is_array ( $slugs ) && count ( $slugs ) > 0 ) {
$check_query_args [ 'post_name__in' ] = $slugs ;
}
2022-01-07 10:17:08 +00:00
2021-11-05 19:07:34 +00:00
$check_query = new \WP_Query ( $check_query_args );
$saved_woo_templates = $check_query -> posts ;
return array_map (
function ( $saved_woo_template ) {
2022-03-23 09:16:07 +00:00
return BlockTemplateUtils :: build_template_result_from_post ( $saved_woo_template );
2021-11-05 19:07:34 +00:00
},
$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 are customised templates that are loaded from the database .
2021-11-24 17:33:57 +00:00
* @ param string $template_type wp_template or wp_template_part .
2021-11-05 19:07:34 +00:00
*
* @ return array Templates from the WooCommerce blocks plugin directory .
*/
2021-11-19 11:47:48 +00:00
public function get_block_templates_from_woocommerce ( $slugs , $already_found_templates , $template_type = 'wp_template' ) {
$directory = $this -> get_templates_directory ( $template_type );
2022-03-23 09:16:07 +00:00
$template_files = BlockTemplateUtils :: get_template_paths ( $directory );
2021-10-25 09:45:50 +00:00
$templates = array ();
2021-11-19 11:47:48 +00:00
2021-10-25 09:45:50 +00:00
foreach ( $template_files as $template_file ) {
2022-07-06 07:51:39 +00:00
// Skip the template if it's blockified, and we should only use classic ones.
if ( $this -> package -> is_experimental_build () &&
! BlockTemplateUtils :: should_use_blockified_product_grid_templates () &&
strpos ( $template_file , 'blockified' ) !== false ) {
continue ;
}
2022-01-05 18:28:51 +00:00
$template_slug = BlockTemplateUtils :: generate_template_slug_from_path ( $template_file );
2021-10-25 09:45:50 +00:00
2021-11-05 19:07:34 +00:00
// 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 (
2021-12-02 10:35:39 +00:00
BlockTemplateUtils :: theme_has_template ( $template_slug ) ||
2021-11-05 19:07:34 +00:00
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 ) {
2021-10-25 09:45:50 +00:00
continue ;
}
2021-12-20 12:53:57 +00:00
// If the theme has an archive-product.html template, but not a taxonomy-product_cat.html template let's use the themes archive-product.html template.
2021-12-24 15:15:19 +00:00
if ( BlockTemplateUtils :: template_is_eligible_for_product_archive_fallback ( $template_slug ) ) {
2021-12-28 22:32:15 +00:00
$template_file = BlockTemplateUtils :: get_theme_template_path ( 'archive-product' );
2021-12-20 12:53:57 +00:00
$templates [] = BlockTemplateUtils :: create_new_block_template_object ( $template_file , $template_type , $template_slug , true );
continue ;
}
2021-11-05 19:07:34 +00:00
// At this point the template only exists in the Blocks filesystem and has not been saved in the DB,
// or superseded by the theme.
2021-12-20 12:53:57 +00:00
$templates [] = BlockTemplateUtils :: create_new_block_template_object ( $template_file , $template_type , $template_slug );
2021-10-25 09:45:50 +00:00
}
2021-12-20 12:53:57 +00:00
2021-10-25 09:45:50 +00:00
return $templates ;
}
2021-11-05 19:07:34 +00:00
/**
* Get and build the block template objects from the block template files .
*
* @ param array $slugs An array of slugs to retrieve templates for .
2021-11-19 11:47:48 +00:00
* @ param array $template_type wp_template or wp_template_part .
*
2022-01-24 08:41:34 +00:00
* @ return array WP_Block_Template [] An array of block template objects .
2021-11-05 19:07:34 +00:00
*/
2021-11-19 11:47:48 +00:00
public function get_block_templates ( $slugs = array (), $template_type = 'wp_template' ) {
$templates_from_db = $this -> get_block_templates_from_db ( $slugs , $template_type );
$templates_from_woo = $this -> get_block_templates_from_woocommerce ( $slugs , $templates_from_db , $template_type );
2022-01-24 08:41:34 +00:00
$templates = array_merge ( $templates_from_db , $templates_from_woo );
return BlockTemplateUtils :: filter_block_templates_by_feature_flag ( $templates );
2021-11-05 19:07:34 +00:00
}
2021-11-19 11:47:48 +00:00
/**
* Gets the directory where templates of a specific template type can be found .
*
* @ param array $template_type wp_template or wp_template_part .
*
* @ return string
*/
protected function get_templates_directory ( $template_type = 'wp_template' ) {
if ( 'wp_template_part' === $template_type ) {
return $this -> template_parts_directory ;
}
2022-07-06 07:51:39 +00:00
if ( $this -> package -> is_experimental_build () && BlockTemplateUtils :: should_use_blockified_product_grid_templates () ) {
return $this -> templates_directory . '/blockified' ;
}
2021-11-19 11:47:48 +00:00
return $this -> templates_directory ;
}
2021-10-29 11:44:29 +00:00
/**
* Checks whether a block template with that name exists in Woo Blocks
*
* @ param string $template_name Template to check .
2021-11-19 11:47:48 +00:00
* @ param array $template_type wp_template or wp_template_part .
*
2021-10-29 11:44:29 +00:00
* @ return boolean
*/
2021-11-19 11:47:48 +00:00
public function block_template_is_available ( $template_name , $template_type = 'wp_template' ) {
2021-10-29 11:44:29 +00:00
if ( ! $template_name ) {
return false ;
}
2021-11-19 11:47:48 +00:00
$directory = $this -> get_templates_directory ( $template_type ) . '/' . $template_name . '.html' ;
2021-10-29 11:44:29 +00:00
return is_readable (
2021-11-19 11:47:48 +00:00
$directory
) || $this -> get_block_templates ( array ( $template_name ), $template_type );
2021-10-29 11:44:29 +00:00
}
/**
* Renders the default block template from Woo Blocks if no theme templates exist .
*/
public function render_block_template () {
2021-12-20 12:53:57 +00:00
if ( is_embed () || ! BlockTemplateUtils :: supports_block_templates () ) {
2021-10-29 11:44:29 +00:00
return ;
}
if (
is_singular ( 'product' ) &&
2021-12-02 10:35:39 +00:00
! BlockTemplateUtils :: theme_has_template ( 'single-product' ) &&
2021-11-05 19:07:34 +00:00
$this -> block_template_is_available ( 'single-product' )
2021-10-29 11:44:29 +00:00
) {
2021-10-29 12:29:42 +00:00
add_filter ( 'woocommerce_has_block_template' , '__return_true' , 10 , 0 );
2021-11-02 12:15:41 +00:00
} elseif (
2021-11-03 16:55:52 +00:00
( is_product_taxonomy () && is_tax ( 'product_cat' ) ) &&
2021-12-02 10:35:39 +00:00
! BlockTemplateUtils :: theme_has_template ( 'taxonomy-product_cat' ) &&
2021-11-05 19:07:34 +00:00
$this -> block_template_is_available ( 'taxonomy-product_cat' )
2021-11-02 12:15:41 +00:00
) {
add_filter ( 'woocommerce_has_block_template' , '__return_true' , 10 , 0 );
2021-11-03 16:55:52 +00:00
} elseif (
( is_product_taxonomy () && is_tax ( 'product_tag' ) ) &&
2021-12-02 10:35:39 +00:00
! BlockTemplateUtils :: theme_has_template ( 'taxonomy-product_tag' ) &&
2021-11-09 11:03:41 +00:00
$this -> block_template_is_available ( 'taxonomy-product_tag' )
2021-11-03 16:55:52 +00:00
) {
add_filter ( 'woocommerce_has_block_template' , '__return_true' , 10 , 0 );
2021-11-02 12:15:41 +00:00
} elseif (
( is_post_type_archive ( 'product' ) || is_page ( wc_get_page_id ( 'shop' ) ) ) &&
2021-12-02 10:35:39 +00:00
! BlockTemplateUtils :: theme_has_template ( 'archive-product' ) &&
2021-11-05 19:07:34 +00:00
$this -> block_template_is_available ( 'archive-product' )
2021-11-02 12:15:41 +00:00
) {
add_filter ( 'woocommerce_has_block_template' , '__return_true' , 10 , 0 );
2021-10-29 11:44:29 +00:00
}
}
2021-11-24 10:36:39 +00:00
2022-05-05 01:08:10 +00:00
/**
* Remove the template panel from the Sidebar of the Shop page because
* the Site Editor handles it .
*
* @ see https :// github . com / woocommerce / woocommerce - gutenberg - products - block / issues / 6278
*
* @ param bool $is_support Whether the active theme supports block templates .
*
* @ return bool
*/
public function remove_block_template_support_for_shop_page ( $is_support ) {
global $pagenow , $post ;
if (
is_admin () &&
'post.php' === $pagenow &&
function_exists ( 'wc_get_page_id' ) &&
is_a ( $post , 'WP_Post' ) &&
wc_get_page_id ( 'shop' ) === $post -> ID
) {
return false ;
}
return $is_support ;
}
2021-10-25 09:45:50 +00:00
}