--- a/wp/wp-includes/block-template-utils.php Fri Sep 05 18:40:08 2025 +0200
+++ b/wp/wp-includes/block-template-utils.php Fri Sep 05 18:52:52 2025 +0200
@@ -206,6 +206,23 @@
),
);
+ // Add a title and description to post format templates.
+ $post_formats = get_post_format_strings();
+ foreach ( $post_formats as $post_format_slug => $post_format_name ) {
+ $default_template_types[ 'taxonomy-post_format-post-format-' . $post_format_slug ] = array(
+ 'title' => sprintf(
+ /* translators: %s: Post format name. */
+ _x( 'Post Format: %s', 'Template name' ),
+ $post_format_name
+ ),
+ 'description' => sprintf(
+ /* translators: %s: Post format name. */
+ __( 'Displays the %s post format archive.' ),
+ $post_format_name
+ ),
+ );
+ }
+
/**
* Filters the list of default template types.
*
@@ -355,7 +372,7 @@
* @type string $post_type Post type to get the templates for.
* }
*
- * @return array Template
+ * @return array|null Template files on success, null if `$template_type` is not matched.
*/
function _get_block_templates_files( $template_type, $query = array() ) {
if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
@@ -592,6 +609,15 @@
$template->is_custom = true;
$template->modified = null;
+ if ( 'wp_template' === $template_type ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template_file['slug'] );
+ if ( $registered_template ) {
+ $template->plugin = $registered_template->plugin;
+ $template->title = empty( $template->title ) || $template->title === $template->slug ? $registered_template->title : $template->title;
+ $template->description = empty( $template->description ) ? $registered_template->description : $template->description;
+ }
+ }
+
if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) {
$template->description = $default_template_types[ $template_file['slug'] ]['description'];
$template->title = $default_template_types[ $template_file['slug'] ]['title'];
@@ -606,15 +632,29 @@
$template->area = $template_file['area'];
}
- $before_block_visitor = '_inject_theme_attribute_in_template_part_block';
- $after_block_visitor = null;
- $hooked_blocks = get_hooked_blocks();
- if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
- $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
- $after_block_visitor = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
+ if ( 'wp_template_part' === $template->type ) {
+ /*
+ * In order for hooked blocks to be inserted at positions first_child and last_child in a template part,
+ * we need to wrap its content a mock template part block and traverse it.
+ */
+ $content = get_comment_delimited_block_content(
+ 'core/template-part',
+ array(),
+ $template->content
+ );
+ $content = apply_block_hooks_to_content(
+ $content,
+ $template,
+ 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata'
+ );
+ $template->content = remove_serialized_parent_block( $content );
+ } else {
+ $template->content = apply_block_hooks_to_content(
+ $template->content,
+ $template,
+ 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata'
+ );
}
- $blocks = parse_blocks( $template->content );
- $template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
return $template;
}
@@ -994,12 +1034,44 @@
}
}
- $hooked_blocks = get_hooked_blocks();
- if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
- $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
- $after_block_visitor = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
- $blocks = parse_blocks( $template->content );
- $template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
+ if ( 'wp_template' === $post->post_type ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template->slug );
+ if ( $registered_template ) {
+ $template->plugin = $registered_template->plugin;
+ $template->origin =
+ 'theme' !== $template->origin && 'theme' !== $template->source ?
+ 'plugin' :
+ $template->origin;
+ $template->title = empty( $template->title ) || $template->title === $template->slug ? $registered_template->title : $template->title;
+ $template->description = empty( $template->description ) ? $registered_template->description : $template->description;
+ }
+ }
+
+ if ( 'wp_template_part' === $template->type ) {
+ $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
+ $attributes = ! empty( $existing_ignored_hooked_blocks ) ? array( 'metadata' => array( 'ignoredHookedBlocks' => json_decode( $existing_ignored_hooked_blocks, true ) ) ) : array();
+
+ /*
+ * In order for hooked blocks to be inserted at positions first_child and last_child in a template part,
+ * we need to wrap its content a mock template part block and traverse it.
+ */
+ $content = get_comment_delimited_block_content(
+ 'core/template-part',
+ $attributes,
+ $template->content
+ );
+ $content = apply_block_hooks_to_content(
+ $content,
+ $template,
+ 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata'
+ );
+ $template->content = remove_serialized_parent_block( $content );
+ } else {
+ $template->content = apply_block_hooks_to_content(
+ $template->content,
+ $template,
+ 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata'
+ );
}
return $template;
@@ -1109,13 +1181,56 @@
if ( ! isset( $query['wp_id'] ) ) {
/*
- * If the query has found some use templates, those have priority
+ * If the query has found some user 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 = _get_block_templates_files( $template_type, $query );
+ /*
+ * We need to unset the post_type query param because some templates
+ * would be excluded otherwise, like `page.html` when looking for
+ * `page` templates. We need all templates so we can exclude duplicates
+ * from plugin-registered templates.
+ * See: https://github.com/WordPress/gutenberg/issues/65584
+ */
+ $template_files_query = $query;
+ unset( $template_files_query['post_type'] );
+ $template_files = _get_block_templates_files( $template_type, $template_files_query );
foreach ( $template_files as $template_file ) {
- $query_result[] = _build_block_template_result_from_file( $template_file, $template_type );
+ // If the query doesn't specify a post type, or it does and the template matches the post type, add it.
+ if (
+ ! isset( $query['post_type'] ) ||
+ (
+ isset( $template_file['postTypes'] ) &&
+ in_array( $query['post_type'], $template_file['postTypes'], true )
+ )
+ ) {
+ $query_result[] = _build_block_template_result_from_file( $template_file, $template_type );
+ } elseif ( ! isset( $template_file['postTypes'] ) ) {
+ // The custom templates with no associated post types are available for all post types as long
+ // as they are not default templates.
+ $candidate = _build_block_template_result_from_file( $template_file, $template_type );
+ $default_template_types = get_default_block_template_types();
+ if ( ! isset( $default_template_types[ $candidate->slug ] ) ) {
+ $query_result[] = $candidate;
+ }
+ }
+ }
+
+ if ( 'wp_template' === $template_type ) {
+ // Add templates registered in the template registry. Filtering out the ones which have a theme file.
+ $registered_templates = WP_Block_Templates_Registry::get_instance()->get_by_query( $query );
+ $matching_registered_templates = array_filter(
+ $registered_templates,
+ function ( $registered_template ) use ( $template_files ) {
+ foreach ( $template_files as $template_file ) {
+ if ( $template_file['slug'] === $registered_template->slug ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ );
+ $query_result = array_merge( $query_result, $matching_registered_templates );
}
}
@@ -1247,18 +1362,17 @@
}
list( $theme, $slug ) = $parts;
- if ( get_stylesheet() !== $theme ) {
- /** This filter is documented in wp-includes/block-template-utils.php */
- return apply_filters( 'get_block_file_template', null, $id, $template_type );
+ if ( get_stylesheet() === $theme ) {
+ $template_file = _get_block_template_file( $template_type, $slug );
+ if ( null !== $template_file ) {
+ $block_template = _build_block_template_result_from_file( $template_file, $template_type );
+
+ /** This filter is documented in wp-includes/block-template-utils.php */
+ return apply_filters( 'get_block_file_template', $block_template, $id, $template_type );
+ }
}
- $template_file = _get_block_template_file( $template_type, $slug );
- if ( null === $template_file ) {
- /** This filter is documented in wp-includes/block-template-utils.php */
- return apply_filters( 'get_block_file_template', null, $id, $template_type );
- }
-
- $block_template = _build_block_template_result_from_file( $template_file, $template_type );
+ $block_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $slug );
/**
* Filters the block template object after it has been (potentially) fetched from the theme file.
@@ -1333,12 +1447,10 @@
* @since 5.9.0
* @since 6.0.0 Adds the whole theme to the export archive.
*
- * @global string $wp_version The WordPress version string.
- *
* @return WP_Error|string Path of the ZIP file or error on failure.
*/
function wp_generate_block_templates_export_file() {
- global $wp_version;
+ $wp_version = wp_get_wp_version();
if ( ! class_exists( 'ZipArchive' ) ) {
return new WP_Error( 'missing_zip_package', __( 'Zip Export not supported.' ) );
@@ -1615,7 +1727,36 @@
return $template;
}
- $changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' );
+ if ( 'wp_template_part' === $post->post_type ) {
+ $attributes = array();
+ $existing_ignored_hooked_blocks = isset( $post->ID ) ? get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ) : '';
+
+ if ( ! empty( $existing_ignored_hooked_blocks ) ) {
+ $attributes['metadata'] = array(
+ 'ignoredHookedBlocks' => json_decode( $existing_ignored_hooked_blocks, true ),
+ );
+ }
+
+ $content = get_comment_delimited_block_content(
+ 'core/template-part',
+ $attributes,
+ $changes->post_content
+ );
+ $content = apply_block_hooks_to_content( $content, $template, 'set_ignored_hooked_blocks_metadata' );
+ $changes->post_content = remove_serialized_parent_block( $content );
+
+ $wrapper_block_markup = extract_serialized_parent_block( $content );
+ $wrapper_block = parse_blocks( $wrapper_block_markup )[0];
+ $ignored_hooked_blocks = $wrapper_block['attrs']['metadata']['ignoredHookedBlocks'] ?? array();
+ if ( ! empty( $ignored_hooked_blocks ) ) {
+ if ( ! isset( $changes->meta_input ) ) {
+ $changes->meta_input = array();
+ }
+ $changes->meta_input['_wp_ignored_hooked_blocks'] = wp_json_encode( $ignored_hooked_blocks );
+ }
+ } else {
+ $changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' );
+ }
return $changes;
}