Skip to content
Open
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
11 changes: 6 additions & 5 deletions src/wp-admin/includes/privacy-tools.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,14 @@ function _wp_personal_data_cleanup_requests() {
'date_query' => array(
array(
/*
* The local-time column must be used here, not post_modified_gmt:
* An absolute UTC threshold is compared against the GMT column so that
* the expiry window matches wp_validate_user_request_key() and is not
* affected by the site's timezone changing after a request was made.
* WP_Date_Query resolves relative date strings like "N seconds ago"
* in the site's timezone, so comparing against the GMT column would
* shift the expiry window by the site's UTC offset.
* in the site's timezone, so a relative string cannot be used here.
*/
'column' => 'post_modified',
'before' => $expires . ' seconds ago',
'column' => 'post_modified_gmt',
'before' => gmdate( 'Y-m-d H:i:s', time() - $expires ),
),
Comment on lines +214 to 216
),
)
Expand Down
42 changes: 31 additions & 11 deletions tests/phpunit/tests/privacy/wpPersonalDataCleanupRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Covers:
* - wp_schedule_personal_data_cleanup_requests()
* - wp_privacy_personal_data_cleanup_requests()
* - _wp_personal_data_cleanup_requests() (including the post_modified timezone fix)
* - _wp_personal_data_cleanup_requests() (including the expiry timezone fixes)
*
* @package WordPress
* @subpackage UnitTests
Expand Down Expand Up @@ -329,17 +329,13 @@ public function test_expiry_duration_is_filterable(): void {
* Timezone fix: a request-pending post that is only 20 hours old must not
* be marked failed on a UTC+5 site.
*
* Before the fix, the date_query used 'post_modified_gmt' (a UTC column) but
* WP_Date_Query::build_mysql_datetime() resolved the relative 'before' string
* (e.g. "86400 seconds ago") using the site's local timezone and returned a
* *local-time* string. Comparing a UTC column against a local-time threshold
* The expiry threshold must be an absolute UTC time. A relative 'before'
* string such as "86400 seconds ago" is resolved by WP_Date_Query in the
* site's timezone, so comparing it against the UTC post_modified_gmt column
* shifted the effective expiry window by the UTC offset — on UTC+5 sites,
* requests expired 5 hours too early, flagging posts that were as recent as
* 19 hours old as expired.
*
* The fix changes the column to 'post_modified' (local time) so both sides
* of the comparison are in the same timezone.
*
* @ticket 44498
*/
public function test_unexpired_request_not_expired_on_utcplus_site(): void {
Expand Down Expand Up @@ -374,9 +370,9 @@ public function test_unexpired_request_not_expired_on_utcplus_site(): void {
* Symmetric check for UTC- sites: a request just over the expiry threshold
* must be correctly marked as failed even on a UTC- site.
*
* On UTC- sites, the old buggy code shifted the effective expiry window in
* the opposite direction — requests would linger longer than intended before
* being cleaned up. With the fix, the threshold is always applied consistently.
* A relative 'before' string shifted the effective expiry window in the
* opposite direction on UTC- sites — requests lingered longer than intended
* before being cleaned up. An absolute UTC threshold applies consistently.
*
* @ticket 44498
*/
Expand All @@ -400,6 +396,30 @@ public function test_expired_request_is_marked_failed_on_utcminus_site(): void {
);
}

/**
* Changing the site timezone must not retroactively expire a fresh request.
*
* @ticket 65731
*/
public function test_fresh_request_survives_a_large_timezone_change(): void {
// UTC-11 at request time.
update_option( 'timezone_string', 'Pacific/Pago_Pago' );

$id = wp_create_user_request( $this->unique_email(), 'export_personal_data' );
$this->assertIsInt( $id );

// UTC+14 afterwards: 25 hours ahead, more than the 24-hour expiry window.
update_option( 'timezone_string', 'Pacific/Kiritimati' );

_wp_personal_data_cleanup_requests();

$this->assertSame(
'request-pending',
get_post_status( $id ),
'A seconds-old request should stay request-pending after the site timezone changes.'
);
}

// =========================================================================
// wp_privacy_personal_data_cleanup_requests() (cron callback)
// =========================================================================
Expand Down
Loading