&& $template_file->area === $query['area'] );
$should_include = $is_not_custom && $fits_slug_query && $fits_area_query;
if ( $should_include ) {
$query_result[] = $template;
}
}
// 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 );
/**
* 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 ( 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 );
}
}
$new_content = SingleProductTemplateCompatibility::add_compatibility_layer( $template->content );
$template->content = $new_content;
}
}
if ( 'theme' === $template->origin && BlockTemplateUtils::template_has_title( $template ) ) {
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
);
return $query_result;
}
/**
* 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 int[]|\WP_Post[] An array of found templates.
*/
public function get_block_templates_from_db( $slugs = array(), $template_type = 'wp_template' ) {
wc_deprecated_function( 'BlockTemplatesController::get_block_templates_from_db()', '7.8', '\Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils::get_block_templates_from_db()' );
return BlockTemplateUtils::get_block_templates_from_db( $slugs, $template_type );
}
/**
* 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.
* @param string $template_type wp_template or wp_template_part.
*
* @return array Templates from the WooCommerce blocks plugin directory.
*/
public function get_block_templates_from_woocommerce( $slugs, $already_found_templates, $template_type = 'wp_template' ) {
$directory = $this->get_templates_directory( $template_type );
$template_files = BlockTemplateUtils::get_template_paths( $directory );
$templates = array();
foreach ( $template_files as $template_file ) {
// Skip the template if it's blockified, and we should only use classic ones.
if ( ! BlockTemplateUtils::should_use_blockified_product_grid_templates() && strpos( $template_file, 'blockified' ) !== false ) {
continue;
}
$template_slug = BlockTemplateUtils::generate_template_slug_from_path( $template_file );
// 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 (
BlockTemplateUtils::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;
}
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback_from_db( $template_slug, $already_found_templates ) ) {
$template = clone BlockTemplateUtils::get_fallback_template_from_db( $template_slug, $already_found_templates );
$template_id = explode( '//', $template->id );
$template->id = $template_id[0] . '//' . $template_slug;
$template->slug = $template_slug;
$template->title = BlockTemplateUtils::get_block_template_title( $template_slug );
$template->description = BlockTemplateUtils::get_block_template_description( $template_slug );
$templates[] = $template;
continue;
}
// 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' );
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug, true );
continue;
}
// 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' );
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug, false );
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.
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug );
}
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.
* @param string $template_type wp_template or wp_template_part.
*
* @return array WP_Block_Template[] An array of block template objects.
*/
public function get_block_templates( $slugs = array(), $template_type = 'wp_template' ) {
$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( $slugs, $template_type );
$templates_from_woo = $this->get_block_templates_from_woocommerce( $slugs, $templates_from_db, $template_type );
$templates = array_merge( $templates_from_db, $templates_from_woo );
return BlockTemplateUtils::filter_block_templates_by_feature_flag( $templates );
}
/**
* Gets the directory where templates of a specific template type can be found.
*
* @param string $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;
}
if ( BlockTemplateUtils::should_use_blockified_product_grid_templates() ) {
return $this->templates_directory . '/blockified';
}
return $this->templates_directory;
}
/**
* Returns the path of a template on the Blocks template folder.
*
* @param string $template_slug Block template slug e.g. single-product.
* @param string $template_type wp_template or wp_template_part.
*
* @return string
*/
public function get_template_path_from_woocommerce( $template_slug, $template_type = 'wp_template' ) {
return $this->get_templates_directory( $template_type ) . '/' . $template_slug . '.html';
}
/**
* Checks whether a block template with that name exists in Woo Blocks
*
* @param string $template_name Template to check.
* @param array $template_type wp_template or wp_template_part.
*
* @return boolean
*/
public function block_template_is_available( $template_name, $template_type = 'wp_template' ) {
if ( ! $template_name ) {
return false;
}
$directory = $this->get_templates_directory( $template_type ) . '/' . $template_name . '.html';
return is_readable(
$directory
) || $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 );
} elseif (
is_wc_endpoint_url( 'order-received' )
&& ! BlockTemplateUtils::theme_has_template( OrderConfirmationTemplate::get_slug() )
&& $this->block_template_is_available( OrderConfirmationTemplate::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.
*
* @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;
}
/**
* 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;
}
/**
* Migrates page content to templates if needed.
*/
public function maybe_migrate_content() {
// Migration should occur on a normal request to ensure every requirement is met.
// We are postponing it if WP is in maintenance mode, installing, WC installing or if the request is part of a WP-CLI command.
if ( wp_is_maintenance_mode() || ! get_option( 'woocommerce_db_version', false ) || Constants::is_defined( 'WP_SETUP_CONFIG' ) || Constants::is_defined( 'WC_INSTALLING' ) || Constants::is_defined( 'WP_CLI' ) ) {
return;
}
if ( ! BlockTemplateMigrationUtils::has_migrated_page( 'cart' ) ) {
BlockTemplateMigrationUtils::migrate_page( 'cart', CartTemplate::get_placeholder_page() );
}
if ( ! BlockTemplateMigrationUtils::has_migrated_page( 'checkout' ) ) {
BlockTemplateMigrationUtils::migrate_page( 'checkout', CheckoutTemplate::get_placeholder_page() );
}
}
/**
* Replaces page settings in WooCommerce with text based permalinks which point to a template.
*
* @param array $settings Settings pages.
* @return array
*/
public function template_permalink_settings( $settings ) {
foreach ( $settings as $key => $setting ) {
if ( 'woocommerce_checkout_page_id' === $setting['id'] ) {
$checkout_page = CheckoutTemplate::get_placeholder_page();
$settings[ $key ] = [
'title' => __( 'Checkout page', 'woocommerce' ),
'desc' => sprintf(
// translators: %1$s: opening anchor tag, %2$s: closing anchor tag.
__( 'The checkout template can be %1$s edited here%2$s.', 'woocommerce' ),
'',
''
),
'desc_tip' => __( 'This is the URL to the checkout page.', 'woocommerce' ),
'id' => 'woocommerce_checkout_page_endpoint',
'type' => 'permalink',
'default' => $checkout_page ? $checkout_page->post_name : CheckoutTemplate::get_slug(),
'autoload' => false,
];
}
if ( 'woocommerce_cart_page_id' === $setting['id'] ) {
$cart_page = CartTemplate::get_placeholder_page();
$settings[ $key ] = [
'title' => __( 'Cart page', 'woocommerce' ),
'desc' => sprintf(
// translators: %1$s: opening anchor tag, %2$s: closing anchor tag.
__( 'The cart template can be %1$s edited here%2$s.', 'woocommerce' ),
'',
''
),
'desc_tip' => __( 'This is the URL to the cart page.', 'woocommerce' ),
'id' => 'woocommerce_cart_page_endpoint',
'type' => 'permalink',
'default' => $cart_page ? $cart_page->post_name : CartTemplate::get_slug(),
'autoload' => false,
];
}
}
return $settings;
}
/**
* Syncs entered permalink with the pages and returns the correct value.
*
* @param string $value Value of the option.
* @param string $option Name of the option.
* @return string
*/
public function update_template_permalink( $value, $option ) {
if ( 'woocommerce_checkout_page_endpoint' === $option ) {
return $this->sync_endpoint_with_page( CheckoutTemplate::get_placeholder_page(), 'checkout', $value );
}
if ( 'woocommerce_cart_page_endpoint' === $option ) {
return $this->sync_endpoint_with_page( CartTemplate::get_placeholder_page(), 'cart', $value );
}
return $value;
}
/**
* Syncs the provided permalink with the actual WP page.
*
* @param WP_Post|null $page The page object, or null if it does not exist.
* @param string $page_slug The identifier for the page e.g. cart, checkout.
* @param string $permalink The new permalink to use.
* @return string THe actual permalink assigned to the page. May differ from $permalink if it was already taken.
*/
protected function sync_endpoint_with_page( $page, $page_slug, $permalink ) {
$matching_page = get_page_by_path( $permalink, OBJECT, 'page' );
/**
* Filters whether to attempt to guess a redirect URL for a 404 request.
*
* Returning a false value from the filter will disable the URL guessing
* and return early without performing a redirect.
*
* @since 11.0.0
*
* @param bool $do_redirect_guess Whether to attempt to guess a redirect URL
* for a 404 request. Default true.
*/
if ( ! $matching_page instanceof WP_Post && apply_filters( 'do_redirect_guess_404_permalink', true ) ) {
// If it is a subpage and url guessing is on, then we will need to get it via post_name as path will not match.
$query = new \WP_Query(
[
'post_type' => 'page',
'name' => $permalink,
]
);
$matching_page = $query->have_posts() ? $query->posts[0] : null;
}
if ( $matching_page && 'publish' === $matching_page->post_status ) {
// Existing page matches given permalink; use its ID.
update_option( 'woocommerce_' . $page_slug . '_page_id', $matching_page->ID );
return get_page_uri( $matching_page );
}
// No matching page; either update current page (by ID stored in option table) or create a new page.
if ( ! $page ) {
$updated_page_id = wc_create_page(
esc_sql( $permalink ),
'woocommerce_' . $page_slug . '_page_id',
$page_slug,
'',
'',
'publish'
);
} else {
$updated_page_id = wp_update_post(
[
'ID' => $page->ID,
'post_name' => esc_sql( $permalink ),
]
);
}
// Get post again in case slug was updated with a suffix.
if ( $updated_page_id && ! is_wp_error( $updated_page_id ) ) {
return get_page_uri( get_post( $updated_page_id ) );
}
return $permalink;
}
}