diff -r 48c4eec2b7e6 -r 8c2e4d02f4ef wp/wp-includes/block-template-utils.php --- 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; }