Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/php/Admin/Menus/Manage/Manage_Menu_Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ public function enqueue( array $script_dependencies, array $style_dependencies )
function ( Snippet $snippet ) {
$fields = $snippet->get_fields();
$fields['code'] = '';
// Match the REST response: a UTC value with no offset is read
// as local time by the browser.
$fields['modified'] = $snippet->modified_iso;
return $fields;
},
get_snippets()
Expand Down
18 changes: 18 additions & 0 deletions src/php/Model/Snippet.php
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,24 @@ protected function get_modified_timestamp(): int {
return $datetime ? $datetime->getTimestamp() : 0;
}

/**
* Retrieve the modification date as an ISO 8601 string, including the UTC
* offset.
*
* `$modified` is stored as 'Y-m-d H:i:s' in UTC, which carries no offset, so
* anything parsing it — a browser, in particular — is free to read it as
* local time and land the snippet hours away from when it was really saved.
* This is the form to hand to clients.
*
* @return string|null ISO 8601 date, or null if no modification date is set.
* @noinspection PhpUnused
*/
protected function get_modified_iso(): ?string {
$timestamp = $this->get_modified_timestamp();

return $timestamp ? gmdate( 'c', $timestamp ) : null;
}

/**
* Retrieve the modification time in the local timezone.
*
Expand Down
4 changes: 4 additions & 0 deletions src/php/REST_API/Snippets/Snippets_REST_Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,10 @@ public function prepare_item_for_response( $item, $request ) {
$response[ $property ] = $item->$property;
}

// The schema declares this as a date-time, so send one: the stored value
// is UTC without an offset, which clients read as local time.
$response['modified'] = $item->modified_iso;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We control the schema – instead of rewriting variables like this, it would be better to introduce modified_iso to the front-end and use that where appropriate.


return rest_ensure_response( $response );
}

Expand Down
36 changes: 36 additions & 0 deletions tests/unit/Model/Snippet_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,40 @@ public function test_description_round_trips_without_sanitization(): void {

$this->assertSame( $description, $snippet->desc );
}

/**
* The modification date is exposed to clients with an explicit UTC offset.
*
* Stored as 'Y-m-d H:i:s' in UTC, the raw value carries no offset, so a
* browser reads it as local time and shows a snippet saved moments ago as
* hours into the future on any site behind UTC.
*
* @return void
*/
public function test_modified_is_exposed_as_iso_8601_utc(): void {
$snippet = new Snippet(
[
'name' => 'Timezone',
'modified' => '2026-08-27 07:35:20',
]
);

$this->assertSame( '2026-08-27T07:35:20+00:00', $snippet->modified_iso );
$this->assertNotSame(
$snippet->modified,
$snippet->modified_iso,
'the stored value has no offset, so it must not be handed to clients as-is'
);
}

/**
* A snippet with no modification date exposes null rather than an epoch date.
*
* @return void
*/
public function test_modified_iso_is_null_when_unset(): void {
$snippet = new Snippet( [ 'name' => 'Never modified' ] );

$this->assertNull( $snippet->modified_iso );
}
}
Loading