* Checks to see if they are using a compatible version of WP, or if not they have a compatible version of the Gutenberg plugin installed.
* @param string $template_type Optional. Template type: `wp_template` or `wp_template_part`.
public static function supports_block_templates( $template_type = 'wp_template' ) {
if ( 'wp_template_part' === $template_type && ( wp_is_block_theme() || current_theme_supports( 'block-template-parts' ) ) ) {
} elseif ( 'wp_template' === $template_type && wp_is_block_theme() ) {
* Gets the `archive-product` fallback template stored on the db for a given slug.
* @param string $template_slug Slug to check for fallbacks.
* @param array $db_templates Templates that have already been found on the db.
public static function get_fallback_template_from_db( $template_slug, $db_templates ) {
$registered_template = self::get_template( $template_slug );
if ( $registered_template && isset( $registered_template->fallback_template ) ) {
foreach ( $db_templates as $template ) {
if ( $registered_template->fallback_template === $template->slug ) {
* Removes templates from the theme or WooCommerce which have the same slug
* as template saved in the database with the `woocommerce/woocommerce` theme.
* Before WC migrated to the Template Registration API from WordPress, templates
* were saved in the database with the `woocommerce/woocommerce` theme instead
* @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 static function remove_templates_with_custom_alternative( $templates ) {
// Get the slugs of all templates that have been customised and saved in the database.
$customised_template_slugs = array_column(
// This template has been customised and saved as a post.
return 'custom' === $template->source && ( self::PLUGIN_SLUG === $template->theme || self::DEPRECATED_PLUGIN_SLUG === $template->theme );
// Remove theme and WC templates that have the same slug as a customised one.
function ( $template ) use ( $customised_template_slugs ) {
// This template has been customised and saved as a post, so return it.
return ! ( 'custom' !== $template->source && in_array( $template->slug, $customised_template_slugs, true ) );
* Removes customized templates that shouldn't be available. That means customized templates based on the
* WooCommerce default template when there is a customized template based on the theme template.
* @param \WP_Block_Template[]|\stdClass[] $templates List of templates to run the filter on.
* @return array Filtered list of templates with only relevant templates available.
public static function remove_duplicate_customized_templates( $templates ) {
$theme_slug = get_stylesheet();
$customized_theme_template_slugs = array_column(
function ( $template ) use ( $theme_slug ) {
// This template has been customised and saved as a post.
return 'custom' === $template->source && $theme_slug === $template->theme;
function ( $template ) use ( $theme_slug, $customized_theme_template_slugs ) {
if ( $template->theme === $theme_slug ) {
// This is a customized template based on the theme template, so it should be returned.
// Customized from the WooCommerce default template: keep only if there is no customized theme template with same slug.
if ( 'custom' === $template->source ) {
return ! in_array( $template->slug, $customized_theme_template_slugs, true );
* Returns whether the blockified templates should be used or not.
* If the option is not stored on the db, we need to check if the current theme is a block one or not.
public static function should_use_blockified_product_grid_templates() {
$use_blockified_templates = get_option( Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE );
if ( false === $use_blockified_templates ) {
return wp_is_block_theme();
return wc_string_to_bool( $use_blockified_templates );
* Determines whether the provided $blocks contains any of the $block_names,
* or if they contain a pattern that contains any of the $block_names.
* @param string[] $block_names Full block types to look for.
* @param WP_Block[] $blocks Array of block objects.
* @return bool Whether the content contains the specified block.
public static function has_block_including_patterns( $block_names, $blocks ) {
$flattened_blocks = self::flatten_blocks( $blocks );
foreach ( $flattened_blocks as &$block ) {
if ( isset( $block['blockName'] ) && in_array( $block['blockName'], $block_names, true ) ) {
'core/pattern' === $block['blockName'] &&
isset( $block['attrs']['slug'] )
$registry = WP_Block_Patterns_Registry::get_instance();
$pattern = $registry->get_registered( $block['attrs']['slug'] );
if ( isset( $pattern['content'] ) ) {
$pattern_blocks = parse_blocks( $pattern['content'] );
if ( self::has_block_including_patterns( $block_names, $pattern_blocks ) ) {
* Returns whether the passed `$template` has the legacy template block.
* @param object $template The template object.
public static function template_has_legacy_template_block( $template ) {
if ( has_block( 'woocommerce/legacy-template', $template->content ) ) {
$blocks = parse_blocks( $template->content );
return self::has_block_including_patterns( array( 'woocommerce/legacy-template' ), $blocks );
* Updates the title, description and area of a template to the correct values and to make them more user-friendly.
* For example, instead of:
* - Title: `Tag (product_tag)`
* - Description: `Displays taxonomy: Tag.`
* - Title: `Products by Tag`
* - Description: `Displays products filtered by a tag.`.
* @param WP_Block_Template $template The template object.
* @param string $template_type wp_template or wp_template_part.
* @return WP_Block_Template
public static function update_template_data( $template, $template_type ) {
if ( empty( $template->title ) || $template->title === $template->slug ) {
$template->title = self::get_block_template_title( $template->slug );
if ( empty( $template->description ) ) {
$template->description = self::get_block_template_description( $template->slug );
if ( empty( $template->area ) || 'uncategorized' === $template->area ) {
$template->area = self::get_block_template_area( $template->slug, $template_type );
* Gets the templates saved in the database.
* @param array $slugs An array of slugs to retrieve templates for.
* @param string $template_type wp_template or wp_template_part.
* @return \WP_Block_Template[] An array of found templates.
public static function get_block_templates_from_db( $slugs = array(), $template_type = 'wp_template' ) {
$check_query_args = array(
'post_type' => $template_type,
'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
'taxonomy' => 'wp_theme',
'terms' => array( self::DEPRECATED_PLUGIN_SLUG, self::PLUGIN_SLUG, 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;
function ( $saved_woo_template ) {
return self::build_template_result_from_post( $saved_woo_template );
* Gets the template part by slug
* @param string $slug The template part slug.
* @return string The template part content.
public static function get_template_part( $slug ) {
$templates_from_db = self::get_block_templates_from_db( array( $slug ), 'wp_template_part' );
if ( count( $templates_from_db ) > 0 ) {
$template_slug_to_load = $templates_from_db[0]->theme;
$theme_has_template = self::theme_has_template_part( $slug );
$template_slug_to_load = $theme_has_template ? get_stylesheet() : self::PLUGIN_SLUG;
$template_part = get_block_template( $template_slug_to_load . '//' . $slug, 'wp_template_part' );
if ( $template_part && ! empty( $template_part->content ) ) {
return $template_part->content;
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
return file_get_contents( self::get_templates_directory( 'wp_template_part' ) . DIRECTORY_SEPARATOR . $slug . '.html' );