diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index c6bb637092..8437ad4110 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -481,7 +481,8 @@ function wp_unschedule_hook( $hook ) { /** * Retrieve a scheduled event. * - * Retrieve the full event object for a given event. + * Retrieve the full event object for a given event, if no timestamp is specified the next + * scheduled event is returned. * * @since 5.1.0 * @@ -493,11 +494,6 @@ function wp_unschedule_hook( $hook ) { * @return bool|object The event object. False if the event does not exist. */ function wp_get_scheduled_event( $hook, $args = array(), $timestamp = null ) { - if ( ! $timestamp ) { - // Get the next scheduled event. - $timestamp = wp_next_scheduled( $hook, $args ); - } - /** * Filter to preflight or hijack retrieving a scheduled event. * @@ -514,22 +510,39 @@ function wp_get_scheduled_event( $hook, $args = array(), $timestamp = null ) { * @param array $args Array containing each separate argument to pass to the hook's callback function. * Although not passed to a callback, these arguments are used to uniquely identify the * event. - * @param int $timestamp Unix timestamp (UTC) of the event. + * @param int|null $timestamp Unix timestamp (UTC) of the event. Null to retrieve next scheduled event. */ $pre = apply_filters( 'pre_get_scheduled_event', null, $hook, $args, $timestamp ); if ( null !== $pre ) { return $pre; } - $crons = _get_cron_array(); - $key = md5( serialize( $args ) ); - - if ( ! $timestamp || ! isset( $crons[ $timestamp ] ) ) { - // No such event. + if ( null !== $timestamp && ! is_numeric( $timestamp ) ) { return false; } - if ( ! isset( $crons[ $timestamp ][ $hook ] ) || ! isset( $crons[ $timestamp ][ $hook ][ $key ] ) ) { + $crons = _get_cron_array(); + if ( empty( $crons ) ) { + return false; + } + + $key = md5( serialize( $args ) ); + + if ( ! $timestamp ) { + // Get next event. + $next = false; + foreach ( $crons as $timestamp => $cron ) { + if ( isset( $cron[ $hook ][ $key ] ) ) { + $next = $timestamp; + break; + } + } + if ( ! $next ) { + return false; + } + + $timestamp = $next; + } elseif ( ! isset( $crons[ $timestamp ][ $hook ][ $key ] ) ) { return false; } @@ -551,7 +564,6 @@ function wp_get_scheduled_event( $hook, $args = array(), $timestamp = null ) { * Retrieve the next timestamp for an event. * * @since 2.1.0 - * @since 5.1.0 {@see 'pre_next_scheduled'} and {@see 'next_scheduled'} filters added. * * @param string $hook Action hook of the event. * @param array $args Optional. Array containing each separate argument to pass to the hook's callback function. @@ -560,48 +572,12 @@ function wp_get_scheduled_event( $hook, $args = array(), $timestamp = null ) { * @return false|int The Unix timestamp of the next time the event will occur. False if the event doesn't exist. */ function wp_next_scheduled( $hook, $args = array() ) { - /** - * Filter to preflight or hijack retrieving the next scheduled event timestamp. - * - * Returning a non-null value will short-circuit the normal retrieval - * process, causing the function to return the filtered value instead. - * - * Pass the timestamp of the next event if it exists, false if not. - * - * @since 5.1.0 - * - * @param null|bool $pre Value to return instead. Default null to continue unscheduling the event. - * @param string $hook Action hook of the event. - * @param array $args Arguments to pass to the hook's callback function. - */ - $pre = apply_filters( 'pre_next_scheduled', null, $hook, $args ); - if ( null !== $pre ) { - return $pre; + $next_event = wp_get_scheduled_event( $hook, $args ); + if ( ! $next_event ) { + return false; } - $crons = _get_cron_array(); - $key = md5( serialize( $args ) ); - $next = false; - - if ( ! empty( $crons ) ) { - foreach ( $crons as $timestamp => $cron ) { - if ( isset( $cron[ $hook ][ $key ] ) ) { - $next = $timestamp; - break; - } - } - } - - /** - * Filter the next scheduled event timestamp. - * - * @since 5.1.0 - * - * @param int|bool $next The UNIX timestamp when the scheduled event will next occur, or false if not found. - * @param string $hook Action hook to execute when cron is run. - * @param array $args Arguments to be passed to the callback function. Used for deduplicating events. - */ - return apply_filters( 'next_scheduled', $next, $hook, $args ); + return $next_event->timestamp; } /** diff --git a/tests/phpunit/tests/cron.php b/tests/phpunit/tests/cron.php index 8b9878e53c..8d2f337ed8 100644 --- a/tests/phpunit/tests/cron.php +++ b/tests/phpunit/tests/cron.php @@ -458,7 +458,6 @@ class Tests_Cron extends WP_UnitTestCase { */ function test_pre_scheduled_event_hooks() { add_filter( 'pre_get_scheduled_event', array( $this, 'filter_pre_scheduled_event_hooks' ) ); - add_filter( 'pre_next_scheduled', array( $this, 'filter_pre_scheduled_event_hooks' ) ); $actual = wp_get_scheduled_event( 'preflight_event', array(), $this->plus_thirty_minutes ); $actual2 = wp_next_scheduled( 'preflight_event', array() ); @@ -471,7 +470,7 @@ class Tests_Cron extends WP_UnitTestCase { ); $this->assertEquals( $expected, $actual ); - $this->assertEquals( $expected, $actual2 ); + $this->assertEquals( $expected->timestamp, $actual2 ); } function filter_pre_scheduled_event_hooks() { @@ -482,4 +481,119 @@ class Tests_Cron extends WP_UnitTestCase { 'args' => array(), ); } + + /** + * Ensure wp_get_scheduled_event() returns the expected one off events. + * + * When no timestamp is specified, the next event should be returned. + * When a timestamp is specified, a particular event should be returned. + * + * @ticket 45976. + */ + function test_get_scheduled_event_singles() { + $hook = __FUNCTION__; + $args = array( 'arg1' ); + $ts_late = strtotime( '+30 minutes' ); + $ts_next = strtotime( '+3 minutes' ); + + $expected1 = (object) array( + 'hook' => $hook, + 'timestamp' => $ts_late, + 'schedule' => false, + 'args' => $args, + ); + + $expected2 = (object) array( + 'hook' => $hook, + 'timestamp' => $ts_next, + 'schedule' => false, + 'args' => $args, + ); + + // Schedule late running event. + wp_schedule_single_event( $ts_late, $hook, $args ); + // Schedule next running event. + wp_schedule_single_event( $ts_next, $hook, $args ); + + // Late running, timestamp specified. + $this->assertEquals( $expected1, wp_get_scheduled_event( $hook, $args, $ts_late ) ); + + // Next running, timestamp specified. + $this->assertEquals( $expected2, wp_get_scheduled_event( $hook, $args, $ts_next ) ); + + // Next running, no timestamp specified. + $this->assertEquals( $expected2, wp_get_scheduled_event( $hook, $args ) ); + } + + /** + * Ensure wp_get_scheduled_event() returns the expected recurring events. + * + * When no timestamp is specified, the next event should be returned. + * When a timestamp is specified, a particular event should be returned. + * + * @ticket 45976. + */ + function test_get_scheduled_event_recurring() { + $hook = __FUNCTION__; + $args = array( 'arg1' ); + $ts_late = strtotime( '+30 minutes' ); + $ts_next = strtotime( '+3 minutes' ); + $schedule = 'hourly'; + $interval = HOUR_IN_SECONDS; + + $expected1 = (object) array( + 'hook' => $hook, + 'timestamp' => $ts_late, + 'schedule' => $schedule, + 'args' => $args, + 'interval' => $interval, + ); + + $expected2 = (object) array( + 'hook' => $hook, + 'timestamp' => $ts_next, + 'schedule' => $schedule, + 'args' => $args, + 'interval' => $interval, + ); + + // Schedule late running event. + wp_schedule_event( $ts_late, $schedule, $hook, $args ); + // Schedule next running event. + wp_schedule_event( $ts_next, $schedule, $hook, $args ); + + // Late running, timestamp specified. + $this->assertEquals( $expected1, wp_get_scheduled_event( $hook, $args, $ts_late ) ); + + // Next running, timestamp specified. + $this->assertEquals( $expected2, wp_get_scheduled_event( $hook, $args, $ts_next ) ); + + // Next running, no timestamp specified. + $this->assertEquals( $expected2, wp_get_scheduled_event( $hook, $args ) ); + } + + /** + * Ensure wp_get_scheduled_event() returns false when expected. + * + * @ticket 45976. + */ + function test_get_scheduled_event_false() { + $hook = __FUNCTION__; + $args = array( 'arg1' ); + $ts = strtotime( '+3 minutes' ); + + // No scheduled events. + // - With timestamp + $this->assertFalse( wp_get_scheduled_event( $hook, $args, $ts ) ); + // - Get next, none scheduled. + $this->assertFalse( wp_get_scheduled_event( $hook, $args ) ); + + // Schedule an event. + wp_schedule_event( $ts, $hook, $args ); + // - unregistered timestamp + $this->assertFalse( wp_get_scheduled_event( $hook, $args, strtotime( '+30 minutes' ) ) ); + // - invalid timestamp. + $this->assertFalse( wp_get_scheduled_event( $hook, $args, 'Words Fail!' ) ); + + } }