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 b821ca09453e3..cb901c5637f0e 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 @@ -668,6 +668,8 @@ protected function prepare_item_for_database( $request ) { * @since 5.9.0 Renamed `$template` to `$item` to match parent class for PHP 8 named parameter support. * @since 6.3.0 Added `modified` property to the response. * @since 7.1.0 Added `date` property to the response. + * @since 7.2.0 The `modified` property is `null` for templates that have no + * modification date. * * @param WP_Block_Template $item Template instance. * @param WP_REST_Request $request Request object. @@ -776,7 +778,12 @@ public function prepare_item_for_response( $item, $request ) { } if ( rest_is_field_included( 'modified', $fields ) ) { - $data['modified'] = mysql_to_rfc3339( $template->modified ); + /* + * File-backed templates have no modification date. Return `null` in that + * case, as `mysql_to_rfc3339()` would return `false` for an empty value, + * which the schema does not allow. + */ + $data['modified'] = empty( $template->modified ) ? null : mysql_to_rfc3339( $template->modified ); } if ( rest_is_field_included( 'date', $fields ) ) { @@ -1154,7 +1161,7 @@ public function get_item_schema() { ), 'modified' => array( 'description' => __( "The date the template was last modified, in the site's timezone." ), - 'type' => 'string', + 'type' => array( 'string', 'null' ), 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, diff --git a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php index 42eed8dfa9c35..2f4f6e82fc1e5 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php @@ -638,6 +638,39 @@ public function test_get_item_from_registry() { $this->assertSame( 404, $response->get_status(), 'Fetching an unregistered template should return 404.' ); } + /** + * A file-backed template has no modification date, which should be exposed as + * `null` rather than the `false` returned by `mysql_to_rfc3339()`. + * + * @ticket 65730 + * @covers WP_REST_Templates_Controller::prepare_item_for_response + */ + public function test_get_item_modified_is_null_for_file_backed_template() { + wp_set_current_user( self::$admin_id ); + switch_theme( 'block-theme' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/block-theme//page-home' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'Fetching a file-backed template should return 200.' ); + $this->assertNull( $data['modified'], 'The modified date should be null for a file-backed template.' ); + } + + /** + * @ticket 65730 + * @covers WP_REST_Templates_Controller::get_item_schema + */ + public function test_modified_schema_allows_null() { + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/templates' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $properties = $data['schema']['properties']; + + $this->assertArrayHasKey( 'modified', $properties ); + $this->assertSame( array( 'string', 'null' ), $properties['modified']['type'] ); + } + /** * @ticket 54507 * @dataProvider data_sanitize_template_id