From 101f58fd1f07395a2bb64448b26f340b5f3bb574 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 11 Dec 2025 14:59:06 -0800 Subject: [PATCH 1/8] Handle bad interval for cron schedule --- src/wp-includes/cron.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index afea4bce9771f..afb9d23a19222 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -359,7 +359,7 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $ } // Now we try to get it from the saved interval in case the schedule disappears. - if ( 0 === $interval ) { + if ( 0 === (int) $interval ) { $scheduled_event = wp_get_scheduled_event( $hook, $args, $timestamp ); if ( $scheduled_event && isset( $scheduled_event->interval ) ) { @@ -417,11 +417,15 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $ } // Now we assume something is wrong and fail to schedule. - if ( 0 === $interval ) { + if ( ! is_int( $interval ) || 0 <= $interval ) { if ( $wp_error ) { return new WP_Error( 'invalid_schedule', - __( 'Event schedule does not exist.' ) + sprintf( + /* translators: %s is the interval encoded as JSON */ + __( 'Event schedule is invalid. Interval must be positive integer, but got: %s' ), + wp_json_encode( $interval ) + ) ); } From b39b67d3d2e03d22236b78861f7b3804064b72a3 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 11 Dec 2025 15:20:10 -0800 Subject: [PATCH 2/8] Fix logic inversion --- src/wp-includes/cron.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index afb9d23a19222..2f361f06bcd8d 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -417,7 +417,7 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $ } // Now we assume something is wrong and fail to schedule. - if ( ! is_int( $interval ) || 0 <= $interval ) { + if ( ! is_int( $interval ) || $interval <= 0 ) { if ( $wp_error ) { return new WP_Error( 'invalid_schedule', From 76f390119c1e3b0e1e16cf5f64fee4a6e5e3c8c4 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 16 Dec 2025 22:00:33 -0800 Subject: [PATCH 3/8] Revert initial casting to int Co-authored-by: Peter Wilson --- src/wp-includes/cron.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index aa155d2125c5e..f64b79843fb7c 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -359,7 +359,7 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $ } // Now we try to get it from the saved interval in case the schedule disappears. - if ( 0 === (int) $interval ) { + if ( 0 === $interval ) { $scheduled_event = wp_get_scheduled_event( $hook, $args, $timestamp ); if ( $scheduled_event && isset( $scheduled_event->interval ) ) { From 0184a4dd1eacde7d7cbfa7f2ede7aca30b0d2301 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 16 Dec 2025 22:01:48 -0800 Subject: [PATCH 4/8] Use is_numeric() instead of is_int() Co-authored-by: Peter Wilson --- src/wp-includes/cron.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index f64b79843fb7c..7b1e24e808bd4 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -417,7 +417,7 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $ } // Now we assume something is wrong and fail to schedule. - if ( ! is_int( $interval ) || $interval <= 0 ) { + if ( ! is_numeric( $interval ) || $interval <= 0 ) { if ( $wp_error ) { return new WP_Error( 'invalid_schedule', From 063c21eca7c481f8d1df5a171acc5c8571839e9c Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 16 Dec 2025 22:05:02 -0800 Subject: [PATCH 5/8] Add recurrence to the error message --- src/wp-includes/cron.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index 7b1e24e808bd4..1075dddb657be 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -422,8 +422,9 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $ return new WP_Error( 'invalid_schedule', sprintf( - /* translators: %s is the interval encoded as JSON */ - __( 'Event schedule is invalid. Interval must be positive integer, but got: %s' ), + /* translators: 1: the event schedule recurrence, 2: the interval encoded as JSON */ + __( 'Event schedule with recurrence "%$1s" does not exist or has is invalid. Interval must be positive integer, but got: %$2s' ), + $recurrence, wp_json_encode( $interval ) ) ); From cda593584a5bebe64406547b646e5cdf03cb6bee Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 18 Dec 2025 16:31:43 -0800 Subject: [PATCH 6/8] Fix failing test Co-authored-by: John Blackbourn --- src/wp-includes/cron.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index 1075dddb657be..e55e245c6cb49 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -423,7 +423,7 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $ 'invalid_schedule', sprintf( /* translators: 1: the event schedule recurrence, 2: the interval encoded as JSON */ - __( 'Event schedule with recurrence "%$1s" does not exist or has is invalid. Interval must be positive integer, but got: %$2s' ), + __( 'Event schedule with recurrence "%1$s" does not exist or has is invalid. Interval must be positive integer, but got: %2$s' ), $recurrence, wp_json_encode( $interval ) ) From c6efb677ca54000fedcb51688b2c22517753e008 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 18 Dec 2025 16:51:52 -0800 Subject: [PATCH 7/8] Add interval check to wp_schedule_event() and differentiate errors in wp_reschedule_event() Co-authored-by: John Blackbourn --- src/wp-includes/cron.php | 31 ++++++++++++++++++++++--------- tests/phpunit/tests/cron.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index e55e245c6cb49..ded6c48716cfb 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -298,6 +298,17 @@ function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array(), $wp return false; } + if ( ! is_numeric( $event->interval ) || $event->interval <= 0 ) { + if ( $wp_error ) { + return new WP_Error( + 'invalid_interval', + __( 'Event schedule has invalid interval.' ) + ); + } + + return false; + } + $key = md5( serialize( $event->args ) ); $crons = _get_cron_array(); @@ -419,15 +430,17 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $ // Now we assume something is wrong and fail to schedule. if ( ! is_numeric( $interval ) || $interval <= 0 ) { if ( $wp_error ) { - return new WP_Error( - 'invalid_schedule', - sprintf( - /* translators: 1: the event schedule recurrence, 2: the interval encoded as JSON */ - __( 'Event schedule with recurrence "%1$s" does not exist or has is invalid. Interval must be positive integer, but got: %2$s' ), - $recurrence, - wp_json_encode( $interval ) - ) - ); + if ( ! isset( $schedules[ $recurrence ] ) ) { + return new WP_Error( + 'invalid_schedule', + __( 'Event schedule does not exist.' ) + ); + } else { + return new WP_Error( + 'invalid_interval', + __( 'Event schedule has invalid interval.' ) + ); + } } return false; diff --git a/tests/phpunit/tests/cron.php b/tests/phpunit/tests/cron.php index f79772074fc83..ee9223bfc3094 100644 --- a/tests/phpunit/tests/cron.php +++ b/tests/phpunit/tests/cron.php @@ -847,6 +847,7 @@ public function test_invalid_timestamp_for_event_returns_error() { /** * @ticket 49961 + * @ticket 64404 * * @covers ::wp_schedule_event * @covers ::wp_reschedule_event @@ -862,6 +863,33 @@ public function test_invalid_recurrence_for_event_returns_error() { $this->assertSame( 'invalid_schedule', $rescheduled_event->get_error_code() ); } + /** + * @ticket 64404 + * + * @covers ::wp_schedule_event + * @covers ::wp_reschedule_event + */ + public function test_invalid_schedule_interval_returns_error() { + add_filter( + 'cron_schedules', + static function ( $schedules ) { + $schedules['backwards_in_time'] = array( + 'interval' => -3600, + 'display' => 'Back to the future!', + ); + return $schedules; + } + ); + + $event = wp_schedule_event( time(), 'backwards_in_time', 'hook', array(), true ); + $this->assertWPError( $event ); + $this->assertSame( 'invalid_interval', $event->get_error_code() ); + + $rescheduled_event = wp_reschedule_event( time(), 'backwards_in_time', 'hook', array(), true ); + $this->assertWPError( $rescheduled_event ); + $this->assertSame( 'invalid_interval', $rescheduled_event->get_error_code() ); + } + /** * @ticket 49961 * From 0fae0406a01d4183327fc5e13167da33d1721465 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 24 Feb 2026 18:08:01 -0800 Subject: [PATCH 8/8] Add additional test cases Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/phpunit/tests/cron.php | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/phpunit/tests/cron.php b/tests/phpunit/tests/cron.php index 61ca04511a05a..b4536c499fa82 100644 --- a/tests/phpunit/tests/cron.php +++ b/tests/phpunit/tests/cron.php @@ -890,6 +890,72 @@ static function ( $schedules ) { $this->assertSame( 'invalid_interval', $rescheduled_event->get_error_code() ); } + /** + * @ticket 64404 + * + * @covers ::wp_schedule_event + * @covers ::wp_reschedule_event + */ + public function test_zero_schedule_interval_returns_error() { + add_filter( + 'cron_schedules', + static function ( $schedules ) { + $schedules['zero_interval'] = array( + 'interval' => 0, + 'display' => 'Zero interval', + ); + return $schedules; + } + ); + + $event = wp_schedule_event( time(), 'zero_interval', 'hook', array(), true ); + $this->assertWPError( $event ); + $this->assertSame( 'invalid_interval', $event->get_error_code() ); + + $rescheduled_event = wp_reschedule_event( time(), 'zero_interval', 'hook', array(), true ); + $this->assertWPError( $rescheduled_event ); + $this->assertSame( 'invalid_interval', $rescheduled_event->get_error_code() ); + } + + /** + * @ticket 64404 + * + * @covers ::wp_schedule_event + * @covers ::wp_reschedule_event + */ + public function test_non_numeric_schedule_interval_returns_error() { + add_filter( + 'cron_schedules', + static function ( $schedules ) { + $schedules['non_numeric_interval_string'] = array( + 'interval' => 'invalid', + 'display' => 'Non numeric interval (string)', + ); + $schedules['non_numeric_interval_null'] = array( + 'interval' => null, + 'display' => 'Non numeric interval (null)', + ); + return $schedules; + } + ); + + $string_event = wp_schedule_event( time(), 'non_numeric_interval_string', 'hook', array(), true ); + $this->assertWPError( $string_event ); + $this->assertSame( 'invalid_interval', $string_event->get_error_code() ); + + $string_rescheduled_event = wp_reschedule_event( time(), 'non_numeric_interval_string', 'hook', array(), true ); + $this->assertWPError( $string_rescheduled_event ); + $this->assertSame( 'invalid_interval', $string_rescheduled_event->get_error_code() ); + + $null_event = wp_schedule_event( time(), 'non_numeric_interval_null', 'hook', array(), true ); + $this->assertWPError( $null_event ); + $this->assertSame( 'invalid_interval', $null_event->get_error_code() ); + + $null_rescheduled_event = wp_reschedule_event( time(), 'non_numeric_interval_null', 'hook', array(), true ); + $this->assertWPError( $null_rescheduled_event ); + $this->assertSame( 'invalid_interval', $null_rescheduled_event->get_error_code() ); + } + /** * @ticket 49961 *