diff -r 48c4eec2b7e6 -r 8c2e4d02f4ef wp/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php --- a/wp/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php Fri Sep 05 18:40:08 2025 +0200 +++ b/wp/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php Fri Sep 05 18:52:52 2025 +0200 @@ -269,6 +269,11 @@ * @return WP_REST_Response */ public function get_items( $request ) { + if ( $request->is_method( 'HEAD' ) ) { + // Return early as this handler doesn't add any response headers. + return new WP_REST_Response( array() ); + } + $query = array(); if ( isset( $request['wp_id'] ) ) { $query['wp_id'] = $request['wp_id']; @@ -326,7 +331,7 @@ * @return WP_REST_Response|WP_Error */ public function get_item( $request ) { - if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { + if ( isset( $request['source'] ) && ( 'theme' === $request['source'] || 'plugin' === $request['source'] ) ) { $template = get_block_file_template( $request['id'], $this->post_type ); } else { $template = get_block_template( $request['id'], $this->post_type ); @@ -668,8 +673,15 @@ * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { - // Resolve pattern blocks so they don't need to be resolved client-side - // in the editor, improving performance. + // Don't prepare the response body for HEAD requests. + if ( $request->is_method( 'HEAD' ) ) { + return new WP_REST_Response( array() ); + } + + /* + * Resolve pattern blocks so they don't need to be resolved client-side + * in the editor, improving performance. + */ $blocks = parse_blocks( $item->content ); $blocks = resolve_pattern_blocks( $blocks ); $item->content = serialize_blocks( $blocks ); @@ -774,6 +786,13 @@ $data['original_source'] = self::get_wp_templates_original_source_field( $template ); } + if ( rest_is_field_included( 'plugin', $fields ) ) { + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template->slug ); + if ( $registered_template ) { + $data['plugin'] = $registered_template->plugin; + } + } + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); @@ -806,11 +825,13 @@ */ private static function get_wp_templates_original_source_field( $template_object ) { if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { - // Added by theme. - // Template originally provided by a theme, but customized by a user. - // Templates originally didn't have the 'origin' field so identify - // older customized templates by checking for no origin and a 'theme' - // or 'custom' source. + /* + * Added by theme. + * Template originally provided by a theme, but customized by a user. + * Templates originally didn't have the 'origin' field so identify + * older customized templates by checking for no origin and a 'theme' + * or 'custom' source. + */ if ( $template_object->has_theme_file && ( 'theme' === $template_object->origin || ( empty( $template_object->origin ) && in_array( @@ -827,14 +848,16 @@ } // Added by plugin. - if ( $template_object->has_theme_file && 'plugin' === $template_object->origin ) { + if ( 'plugin' === $template_object->origin ) { return 'plugin'; } - // Added by site. - // Template was created from scratch, but has no author. Author support - // was only added to templates in WordPress 5.9. Fallback to showing the - // site logo and title. + /* + * Added by site. + * Template was created from scratch, but has no author. Author support + * was only added to templates in WordPress 5.9. Fallback to showing the + * site logo and title. + */ if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) { return 'site'; } @@ -859,9 +882,41 @@ $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' ); return empty( $theme_name ) ? $template_object->theme : $theme_name; case 'plugin': - $plugins = get_plugins(); - $plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ) ]; - return empty( $plugin['Name'] ) ? $template_object->theme : $plugin['Name']; + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + if ( isset( $template_object->plugin ) ) { + $plugins = wp_get_active_and_valid_plugins(); + + foreach ( $plugins as $plugin_file ) { + $plugin_basename = plugin_basename( $plugin_file ); + // Split basename by '/' to get the plugin slug. + list( $plugin_slug, ) = explode( '/', $plugin_basename ); + + if ( $plugin_slug === $template_object->plugin ) { + $plugin_data = get_plugin_data( $plugin_file ); + + if ( ! empty( $plugin_data['Name'] ) ) { + return $plugin_data['Name']; + } + + break; + } + } + } + + /* + * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards + * compatibility with templates that were registered before the plugin attribute was added. + */ + $plugins = get_plugins(); + $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ); + if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) { + return $plugins[ $plugin_basename ]['Name']; + } + return isset( $template_object->plugin ) ? + $template_object->plugin : + $template_object->theme; case 'site': return get_bloginfo( 'name' ); case 'user': @@ -871,6 +926,9 @@ } return $author->get( 'display_name' ); } + + // Fail-safe to return a string should the original source ever fall through. + return ''; } @@ -1125,6 +1183,12 @@ 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ); + $schema['properties']['plugin'] = array( + 'type' => 'string', + 'description' => __( 'Plugin that registered the template.' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ); } if ( 'wp_template_part' === $this->post_type ) {