diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index dd348784b9..bd6138256c 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -79,9 +79,49 @@ function wp_schedule_single_event( $timestamp, $hook, $args = array() ) { return $pre; } - // Don't schedule a duplicate if there's already an identical event due within 10 minutes of it - $next = wp_next_scheduled( $hook, $args ); - if ( $next && abs( $next - $timestamp ) <= 10 * MINUTE_IN_SECONDS ) { + /* + * Check for a duplicated event. + * + * Don't schedule an event if there's already an identical event + * within 10 minutes. + * + * When scheduling events within ten minutes of the current time, + * all past identical events are considered duplicates. + * + * When scheduling an event with a past timestamp (ie, before the + * current time) all events scheduled within the next ten minutes + * are considered duplicates. + */ + $crons = (array) _get_cron_array(); + $key = md5( serialize( $event->args ) ); + $duplicate = false; + + if ( $event->timestamp < time() + 10 * MINUTE_IN_SECONDS ) { + $min_timestamp = 0; + } else { + $min_timestamp = $event->timestamp - 10 * MINUTE_IN_SECONDS; + } + + if ( $event->timestamp < time() ) { + $max_timestamp = time() + 10 * MINUTE_IN_SECONDS; + } else { + $max_timestamp = $event->timestamp + 10 * MINUTE_IN_SECONDS; + } + + foreach ( $crons as $event_timestamp => $cron ) { + if ( $event_timestamp < $min_timestamp ) { + continue; + } + if ( $event_timestamp > $max_timestamp ) { + break; + } + if ( isset( $cron[ $event->hook ][ $key ] ) ) { + $duplicate = true; + break; + } + } + + if ( $duplicate ) { return false; } @@ -107,9 +147,6 @@ function wp_schedule_single_event( $timestamp, $hook, $args = array() ) { return false; } - $key = md5( serialize( $event->args ) ); - - $crons = _get_cron_array(); $crons[ $event->timestamp ][ $event->hook ][ $key ] = array( 'schedule' => $event->schedule, 'args' => $event->args, diff --git a/tests/phpunit/tests/cron.php b/tests/phpunit/tests/cron.php index 8d2f337ed8..3503b1c0d7 100644 --- a/tests/phpunit/tests/cron.php +++ b/tests/phpunit/tests/cron.php @@ -596,4 +596,88 @@ class Tests_Cron extends WP_UnitTestCase { $this->assertFalse( wp_get_scheduled_event( $hook, $args, 'Words Fail!' ) ); } + + /** + * Ensure any past event counts as a duplicate. + * + * @ticket 44818 + */ + function test_duplicate_past_event() { + $hook = __FUNCTION__; + $args = array( 'arg1' ); + $ts1 = strtotime( '-14 minutes' ); + $ts2 = strtotime( '+5 minutes' ); + $ts3 = strtotime( '-2 minutes' ); + + // First event scheduled successfully. + $this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) ); + + // Second event fails. + $this->assertFalse( wp_schedule_single_event( $ts2, $hook, $args ) ); + + // Third event fails. + $this->assertFalse( wp_schedule_single_event( $ts3, $hook, $args ) ); + } + + /** + * Ensure any near future event counts as a duplicate. + * + * @ticket 44818 + */ + function test_duplicate_near_future_event() { + $hook = __FUNCTION__; + $args = array( 'arg1' ); + $ts1 = strtotime( '+4 minutes' ); + $ts2 = strtotime( '-15 minutes' ); + $ts3 = strtotime( '+12 minutes' ); + + // First event scheduled successfully. + $this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) ); + + // Second event fails. + $this->assertFalse( wp_schedule_single_event( $ts2, $hook, $args ) ); + + // Third event fails. + $this->assertFalse( wp_schedule_single_event( $ts3, $hook, $args ) ); + } + + /** + * Duplicate future events are disallowed. + * + * @ticket 44818 + */ + function test_duplicate_future_event() { + $hook = __FUNCTION__; + $args = array( 'arg1' ); + $ts1 = strtotime( '+15 minutes' ); + $ts2 = strtotime( '-600 seconds', $ts1 ); + $ts3 = strtotime( '+600 seconds', $ts1 ); + + // First event scheduled successfully. + $this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) ); + + // Events within ten minutes should fail. + $this->assertFalse( wp_schedule_single_event( $ts2, $hook, $args ) ); + $this->assertFalse( wp_schedule_single_event( $ts3, $hook, $args ) ); + } + + /** + * Future events are allowed. + * + * @ticket 44818 + */ + function test_not_duplicate_future_event() { + $hook = __FUNCTION__; + $args = array( 'arg1' ); + $ts1 = strtotime( '+15 minutes' ); + $ts2 = strtotime( '-601 seconds', $ts1 ); + $ts3 = strtotime( '+601 seconds', $ts1 ); + + // First event scheduled successfully. + $this->assertTrue( wp_schedule_single_event( $ts1, $hook, $args ) ); + + // Events over ten minutes should work. + $this->assertTrue( wp_schedule_single_event( $ts2, $hook, $args ) ); + $this->assertTrue( wp_schedule_single_event( $ts3, $hook, $args ) ); + } }