From e4836206aec7d79f413a46daaa65a9adda9b126c Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 3 Sep 2026 19:23:54 +1000 Subject: [PATCH] REST API: Prevent fatal error when a null template reaches prepare_item_for_response(). WP_REST_Templates_Controller::update_item() passes its get_block_template() refetches to prepare_item_for_response() without checking them, both after writing an update and on its revert-to-theme path, which force-deletes the template's post first. When a refetch returns null, reading $item->content is a fatal error. Return a rest_template_not_found error (404) instead when the template is null. Backports the guard from Gutenberg PR 82374. See #66032. --- .../class-wp-rest-templates-controller.php | 18 +++++++++++++++--- .../rest-api/wpRestTemplatesController.php | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php index b6691c588ca7d..340d3381806e4 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php @@ -670,12 +670,24 @@ protected function prepare_item_for_database( $request ) { * @since 7.1.0 Added `date` property to the response. * @since 7.1.0 The `modified` property is `null` for templates that have no * modification date. + * @since 7.2.0 Returns a `WP_Error` instead of causing a fatal error + * when the template is `null`. * - * @param WP_Block_Template $item Template instance. - * @param WP_REST_Request $request Request object. - * @return WP_REST_Response Response object. + * @param WP_Block_Template|null $item Template instance. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error when the template is `null`. */ public function prepare_item_for_response( $item, $request ) { + /* + * `update_item()` passes its `get_block_template()` refetches here + * unchecked, both after writing an update and after deleting the + * template's post on its revert-to-theme path. Reading `$item->content` + * on `null` is a fatal error, so answer with an error response instead. + */ + if ( ! $item ) { + return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); + } + // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { return new WP_REST_Response( array() ); diff --git a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php index e8ff29beaac33..3eee1426ecb87 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php @@ -972,6 +972,23 @@ public function test_prepare_item() { // Controller does not implement prepare_item(). } + /** + * A `null` template must produce an error response, not a fatal error from + * reading properties on `null`. + * + * @ticket 66032 + * @covers WP_REST_Templates_Controller::prepare_item_for_response + */ + public function test_prepare_item_for_response_with_null_template() { + $controller = new WP_REST_Templates_Controller( 'wp_template' ); + $request = new WP_REST_Request( 'PUT', '/wp/v2/templates/default//does-not-exist' ); + + $response = $controller->prepare_item_for_response( null, $request ); + + $this->assertWPError( $response, 'A null template should produce a WP_Error, not a fatal error.' ); + $this->assertSame( 'rest_template_not_found', $response->get_error_code() ); + } + public function test_prepare_item_limit_fields() { wp_set_current_user( self::$admin_id );