Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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