Skip to content

Commit

Permalink
Templates API: Fix template files query by post-type (#61244)
Browse files Browse the repository at this point in the history
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: creativecoder <grantmkin@git.wordpress.org>
Co-authored-by: mike-henderson <mikehend@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
  • Loading branch information
5 people authored May 1, 2024
1 parent e881a47 commit f9e725b
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 0 deletions.
249 changes: 249 additions & 0 deletions lib/compat/wordpress-6.6/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,252 @@ function gutenberg_get_template_hierarchy( $slug, $is_custom = false, $template_
}
return $template_hierarchy;
}

/**
* Retrieves the template files from the theme.
*
* @since 5.9.0
* @since 6.3.0 Added the `$query` parameter.
* @access private
*
* @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'.
* @param array $query {
* Arguments to retrieve templates. Optional, empty by default.
*
* @type string[] $slug__in List of slugs to include.
* @type string[] $slug__not_in List of slugs to skip.
* @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only).
* @type string $post_type Post type to get the templates for.
* }
*
* @return array Template
*/
function _gutenberg_get_block_templates_files( $template_type, $query = array() ) {
if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
return null;
}

// @core-merge: This code will go into Core's '_get_block_templates_files' function.
$default_template_types = array();
if ( 'wp_template' === $template_type ) {
$default_template_types = get_default_block_template_types();
}
// @core-merge: End of the code that will go into Core.

// Prepare metadata from $query.
$slugs_to_include = isset( $query['slug__in'] ) ? $query['slug__in'] : array();
$slugs_to_skip = isset( $query['slug__not_in'] ) ? $query['slug__not_in'] : array();
$area = isset( $query['area'] ) ? $query['area'] : null;
$post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';

$stylesheet = get_stylesheet();
$template = get_template();
$themes = array(
$stylesheet => get_stylesheet_directory(),
);
// Add the parent theme if it's not the same as the current theme.
if ( $stylesheet !== $template ) {
$themes[ $template ] = get_template_directory();
}
$template_files = array();
foreach ( $themes as $theme_slug => $theme_dir ) {
$template_base_paths = get_block_theme_folders( $theme_slug );
$theme_template_files = _get_block_templates_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] );
foreach ( $theme_template_files as $template_file ) {
$template_base_path = $template_base_paths[ $template_type ];
$template_slug = substr(
$template_file,
// Starting position of slug.
strpos( $template_file, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ),
// Subtract ending '.html'.
-5
);

// Skip this item if its slug doesn't match any of the slugs to include.
if ( ! empty( $slugs_to_include ) && ! in_array( $template_slug, $slugs_to_include, true ) ) {
continue;
}

// Skip this item if its slug matches any of the slugs to skip.
if ( ! empty( $slugs_to_skip ) && in_array( $template_slug, $slugs_to_skip, true ) ) {
continue;
}

/*
* The child theme items (stylesheet) are processed before the parent theme's (template).
* If a child theme defines a template, prevent the parent template from being added to the list as well.
*/
if ( isset( $template_files[ $template_slug ] ) ) {
continue;
}

$new_template_item = array(
'slug' => $template_slug,
'path' => $template_file,
'theme' => $theme_slug,
'type' => $template_type,
);

if ( 'wp_template_part' === $template_type ) {
$candidate = _add_block_template_part_area_info( $new_template_item );
if ( ! isset( $area ) || ( isset( $area ) && $area === $candidate['area'] ) ) {
$template_files[ $template_slug ] = $candidate;
}
}

if ( 'wp_template' === $template_type ) {
$candidate = _add_block_template_info( $new_template_item );
$is_custom = ! isset( $default_template_types[ $candidate['slug'] ] );

if (
! $post_type ||
( $post_type && isset( $candidate['postTypes'] ) && in_array( $post_type, $candidate['postTypes'], true ) )
) {
$template_files[ $template_slug ] = $candidate;
}

// @core-merge: This code will go into Core's '_get_block_templates_files' function.
// The custom templates with no associated post-types are available for all post-types.
if ( $post_type && ! isset( $candidate['postTypes'] ) && $is_custom ) {
$template_files[ $template_slug ] = $candidate;
}
// @core-merge: End of the code that will go into Core.
}
}
}

return array_values( $template_files );
}

/**
* Retrieves a list of unified template objects based on a query.
*
* @since 5.8.0
*
* @param array $query {
* Optional. Arguments to retrieve templates.
*
* @type string[] $slug__in List of slugs to include.
* @type int $wp_id Post ID of customized template.
* @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only).
* @type string $post_type Post type to get the templates for.
* }
* @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'.
* @return WP_Block_Template[] Array of block templates.
*/
function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_template' ) {
/**
* Filters the block templates array before the query takes place.
*
* Return a non-null value to bypass the WordPress queries.
*
* @since 5.9.0
*
* @param WP_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query,
* or null to allow WP to run its normal queries.
* @param array $query {
* Arguments to retrieve templates. All arguments are optional.
*
* @type string[] $slug__in List of slugs to include.
* @type int $wp_id Post ID of customized template.
* @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only).
* @type string $post_type Post type to get the templates for.
* }
* @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'.
*/
$templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type );
if ( ! is_null( $templates ) ) {
return $templates;
}

$post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';
$wp_query_args = array(
'post_status' => array( 'auto-draft', 'draft', 'publish' ),
'post_type' => $template_type,
'posts_per_page' => -1,
'no_found_rows' => true,
'lazy_load_term_meta' => false,
'tax_query' => array(
array(
'taxonomy' => 'wp_theme',
'field' => 'name',
'terms' => get_stylesheet(),
),
),
);

if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) {
$wp_query_args['tax_query'][] = array(
'taxonomy' => 'wp_template_part_area',
'field' => 'name',
'terms' => $query['area'],
);
$wp_query_args['tax_query']['relation'] = 'AND';
}

if ( ! empty( $query['slug__in'] ) ) {
$wp_query_args['post_name__in'] = $query['slug__in'];
$wp_query_args['posts_per_page'] = count( array_unique( $query['slug__in'] ) );
}

// This is only needed for the regular templates/template parts post type listing and editor.
if ( isset( $query['wp_id'] ) ) {
$wp_query_args['p'] = $query['wp_id'];
} else {
$wp_query_args['post_status'] = 'publish';
}

$template_query = new WP_Query( $wp_query_args );
$query_result = array();
foreach ( $template_query->posts as $post ) {
$template = _build_block_template_result_from_post( $post );

if ( is_wp_error( $template ) ) {
continue;
}

if ( $post_type && ! $template->is_custom ) {
continue;
}

if (
$post_type &&
isset( $template->post_types ) &&
! in_array( $post_type, $template->post_types, true )
) {
continue;
}

$query_result[] = $template;
}

if ( ! isset( $query['wp_id'] ) ) {
/*
* If the query has found some use templates, those have priority
* over the theme-provided ones, so we skip querying and building them.
*/
$query['slug__not_in'] = wp_list_pluck( $query_result, 'slug' );
$template_files = _gutenberg_get_block_templates_files( $template_type, $query );
foreach ( $template_files as $template_file ) {
$query_result[] = _build_block_template_result_from_file( $template_file, $template_type );
}
}

/**
* Filters the array of queried block templates array after they've been fetched.
*
* @since 5.9.0
*
* @param WP_Block_Template[] $query_result Array of found block templates.
* @param array $query {
* Arguments to retrieve templates. All arguments are optional.
*
* @type string[] $slug__in List of slugs to include.
* @type int $wp_id Post ID of customized template.
* @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only).
* @type string $post_type Post type to get the templates for.
* }
* @param string $template_type wp_template or wp_template_part.
*/
return apply_filters( 'get_block_templates', $query_result, $query, $template_type );
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,35 @@ public function get_items_permissions_check( $request ) { // phpcs:ignore Variab
);
}

/**
* Returns a list of templates.
*
* @since 5.8.0
*
* @param WP_REST_Request $request The request instance.
* @return WP_REST_Response
*/
public function get_items( $request ) {
$query = array();
if ( isset( $request['wp_id'] ) ) {
$query['wp_id'] = $request['wp_id'];
}
if ( isset( $request['area'] ) ) {
$query['area'] = $request['area'];
}
if ( isset( $request['post_type'] ) ) {
$query['post_type'] = $request['post_type'];
}

$templates = array();
foreach ( gutenberg_get_block_templates( $query, $this->post_type ) as $template ) {
$data = $this->prepare_item_for_response( $template, $request );
$templates[] = $this->prepare_response_for_collection( $data );
}

return rest_ensure_response( $templates );
}

/**
* Checks if a given request has access to read templates.
*
Expand Down
109 changes: 109 additions & 0 deletions phpunit/blocks/get-block-templates-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php
/**
* @group blocks
* @group block-templates
*
* @covers ::get_block_templates
*/
class Tests_Blocks_GetBlockTemplates extends WP_UnitTestCase {

const TEST_THEME = 'block-theme';

/**
* Theme root directory.
*
* @var string
*/
private $theme_root;

/**
* Original theme directory.
*
* @var string
*/
private $orig_theme_dir;

public function set_up() {
parent::set_up();
$this->theme_root = realpath( __DIR__ . '/../data/themedir1' );
$this->orig_theme_dir = $GLOBALS['wp_theme_directories'];

// /themes is necessary as theme.php functions assume /themes is the root if there is only one root.
$GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root );

add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) );
add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) );
add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) );
// Clear caches.
wp_clean_themes_cache();
unset( $GLOBALS['wp_themes'] );
switch_theme( self::TEST_THEME );
}

public function tear_down() {
$GLOBALS['wp_theme_directories'] = $this->orig_theme_dir;
wp_clean_themes_cache();
unset( $GLOBALS['wp_themes'] );
parent::tear_down();
}

public function filter_set_theme_root() {
return $this->theme_root;
}

/**
* Gets the template IDs from the given array.
*
* @param object[] $templates Array of template objects to parse.
* @return string[] The template IDs.
*/
private function get_template_ids( $templates ) {
return array_map(
static function ( $template ) {
return $template->id;
},
$templates
);
}

/**
* @dataProvider data_get_block_templates_should_respect_posttypes_property
* @ticket 55881
*
* @param string $post_type Post type for query.
* @param array $expected Expected template IDs.
*/
public function test_get_block_templates_should_respect_posttypes_property( $post_type, $expected ) {
$templates = gutenberg_get_block_templates( array( 'post_type' => $post_type ) );

$this->assertSameSets(
$expected,
$this->get_template_ids( $templates )
);
}

/**
* Data provider.
*
* @return array
*/
public function data_get_block_templates_should_respect_posttypes_property() {
// @code-merge: This code will go into Core's tests/phpunit/tests/blocks/getBlockTemplates.php.
return array(
'post' => array(
'post_type' => 'post',
'expected' => array(
'block-theme//custom-hero-template',
'block-theme//custom-single-post-template',
),
),
'page' => array(
'post_type' => 'page',
'expected' => array(
'block-theme//custom-hero-template',
'block-theme//page-home',
),
),
);
}
}
Loading

1 comment on commit f9e725b

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in f9e725b.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/8908759823
📝 Reported issues:

Please sign in to comment.