wp/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
--- 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 ) {