diff --git a/wp-includes/cron.php b/wp-includes/cron.php index ffc78728ff..9434822bf4 100644 --- a/wp-includes/cron.php +++ b/wp-includes/cron.php @@ -21,7 +21,8 @@ * Use wp_schedule_event() to schedule a recurring event. * * @since 2.1.0 - * @since 5.0.0 Return value modified to boolean indicating success or failure. + * @since 5.0.0 Return value modified to boolean indicating success or failure, + * {@see pre_schedule_event} filter added to short-circuit the function. * * @link https://codex.wordpress.org/Function_Reference/wp_schedule_single_event * @@ -36,13 +37,6 @@ function wp_schedule_single_event( $timestamp, $hook, $args = array() ) { return false; } - // 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 ) { - return false; - } - - $crons = _get_cron_array(); $event = (object) array( 'hook' => $hook, 'timestamp' => $timestamp, @@ -50,6 +44,47 @@ function wp_schedule_single_event( $timestamp, $hook, $args = array() ) { 'args' => $args, ); + /** + * Filter to preflight or hijack scheduling an event. + * + * Returning a non-null value will short-circuit adding the event to the + * cron array, causing the function to return the filtered value instead. + * + * Both single events and recurring events are passed through this filter; + * single events have `$event->schedule` as false, whereas recurring events + * have this set to a recurrence from {@see wp_get_schedules}. Recurring + * events also have the integer recurrence interval set as `$event->interval`. + * + * For plugins replacing wp-cron, it is recommended you check for an + * identical event within ten minutes and apply the {@see schedule_event} + * filter to check if another plugin has disallowed the event before scheduling. + * + * Return true if the event was scheduled, false if not. + * + * @since 5.0.0 + * + * @param null|bool $pre Value to return instead. Default null to continue adding the event. + * @param stdClass $event { + * An object containing an event's data. + * + * @type string $hook Action hook to execute when the event is run. + * @type int $timestamp Unix timestamp (UTC) for when to next run the event. + * @type string|false $schedule How often the event should subsequently recur. + * @type array $args Array containing each separate argument to pass to the hook's callback function. + * @type int $interval The interval time in seconds for the schedule. Only present for recurring events. + * } + */ + $pre = apply_filters( 'pre_schedule_event', null, $event ); + if ( null !== $pre ) { + 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 ) { + return false; + } + /** * Filters a single event before it is scheduled. * @@ -74,6 +109,7 @@ function wp_schedule_single_event( $timestamp, $hook, $args = array() ) { $key = md5( serialize( $event->args ) ); + $crons = _get_cron_array(); $crons[ $event->timestamp ][ $event->hook ][ $key ] = array( 'schedule' => $event->schedule, 'args' => $event->args, @@ -101,7 +137,8 @@ function wp_schedule_single_event( $timestamp, $hook, $args = array() ) { * Use wp_schedule_single_event() to schedule a non-recurring event. * * @since 2.1.0 - * @since 5.0.0 Return value modified to boolean indicating success or failure. + * @since 5.0.0 Return value modified to boolean indicating success or failure, + * {@see pre_schedule_event} filter added to short-circuit the function. * * @link https://codex.wordpress.org/Function_Reference/wp_schedule_event * @@ -117,7 +154,6 @@ function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array() ) { return false; } - $crons = _get_cron_array(); $schedules = wp_get_schedules(); if ( ! isset( $schedules[ $recurrence ] ) ) { @@ -131,6 +167,13 @@ function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array() ) { 'args' => $args, 'interval' => $schedules[ $recurrence ]['interval'], ); + + /** This filter is documented in wp-includes/cron.php */ + $pre = apply_filters( 'pre_schedule_event', null, $event ); + if ( null !== $pre ) { + return $pre; + } + /** This filter is documented in wp-includes/cron.php */ $event = apply_filters( 'schedule_event', $event ); @@ -141,6 +184,7 @@ function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array() ) { $key = md5( serialize( $event->args ) ); + $crons = _get_cron_array(); $crons[ $event->timestamp ][ $event->hook ][ $key ] = array( 'schedule' => $event->schedule, 'args' => $event->args, @@ -154,7 +198,8 @@ function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array() ) { * Reschedules a recurring event. * * @since 2.1.0 - * @since 5.0.0 Return value modified to boolean indicating success or failure. + * @since 5.0.0 Return value modified to boolean indicating success or failure, + * {@see pre_reschedule_event} filter added to short-circuit the function. * * @param int $timestamp Unix timestamp (UTC) for when to next run the event. * @param string $recurrence How often the event should subsequently recur. See wp_get_schedules() for accepted values. @@ -168,19 +213,57 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array() ) return false; } - $crons = _get_cron_array(); $schedules = wp_get_schedules(); - $key = md5( serialize( $args ) ); $interval = 0; - // First we try to get it from the schedule + // First we try to get the interval from the schedule. if ( isset( $schedules[ $recurrence ] ) ) { $interval = $schedules[ $recurrence ]['interval']; } - // Now we try to get it from the saved interval in case the schedule disappears - if ( 0 == $interval ) { - $interval = $crons[ $timestamp ][ $hook ][ $key ]['interval']; + + // Now we try to get it from the saved interval in case the schedule disappears. + if ( 0 === $interval ) { + $scheduled_event = wp_get_scheduled_event( $hook, $args, $timestamp ); + if ( $scheduled_event && isset( $scheduled_event->interval ) ) { + $interval = $scheduled_event->interval; + } } + + $event = (object) array( + 'hook' => $hook, + 'timestamp' => $timestamp, + 'schedule' => $recurrence, + 'args' => $args, + 'interval' => $interval, + ); + + /** + * Filter to preflight or hijack rescheduling of events. + * + * Returning a non-null value will short-circuit the normal rescheduling + * process, causing the function to return the filtered value instead. + * + * For plugins replacing wp-cron, return true if the event was successfully + * rescheduled, false if not. + * + * @since 5.0.0 + * + * @param null|bool $pre Value to return instead. Default null to continue adding the event. + * @param stdClass $event { + * An object containing an event's data. + * + * @type string $hook Action hook to execute when the event is run. + * @type int $timestamp Unix timestamp (UTC) for when to next run the event. + * @type string|false $schedule How often the event should subsequently recur. + * @type array $args Array containing each separate argument to pass to the hook's callback function. + * @type int $interval The interval time in seconds for the schedule. Only present for recurring events. + * } + */ + $pre = apply_filters( 'pre_reschedule_event', null, $event ); + if ( null !== $pre ) { + return $pre; + } + // Now we assume something is wrong and fail to schedule if ( 0 == $interval ) { return false; @@ -204,7 +287,8 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array() ) * identified. * * @since 2.1.0 - * @since 5.0.0 Return value modified to boolean indicating success or failure. + * @since 5.0.0 Return value modified to boolean indicating success or failure, + * {@see pre_unschedule_event} filter added to short-circuit the function. * * @param int $timestamp Unix timestamp (UTC) of the event. * @param string $hook Action hook of the event. @@ -219,6 +303,27 @@ function wp_unschedule_event( $timestamp, $hook, $args = array() ) { return false; } + /** + * Filter to preflight or hijack unscheduling of events. + * + * Returning a non-null value will short-circuit the normal unscheduling + * process, causing the function to return the filtered value instead. + * + * For plugins replacing wp-cron, return true if the event was successfully + * unscheduled, false if not. + * + * @since 5.0.0 + * + * @param null|bool $pre Value to return instead. Default null to continue unscheduling the event. + * @param int $timestamp Timestamp for when to run the event. + * @param string $hook Action hook, the execution of which will be unscheduled. + * @param array $args Arguments to pass to the hook's callback function. + */ + $pre = apply_filters( 'pre_unschedule_event', null, $timestamp, $hook, $args ); + if ( null !== $pre ) { + return $pre; + } + $crons = _get_cron_array(); $key = md5( serialize( $args ) ); unset( $crons[ $timestamp ][ $hook ][ $key ] ); @@ -240,7 +345,8 @@ function wp_unschedule_event( $timestamp, $hook, $args = array() ) { * the `===` operator for testing the return value of this function. * * @since 2.1.0 - * @since 5.0.0 Return value modified to indicate success or failure. + * @since 5.0.0 Return value modified to indicate success or failure, + * {@see pre_clear_scheduled_hook} filter added to short-circuit the function. * * @param string $hook Action hook, the execution of which will be unscheduled. * @param array $args Optional. Arguments that were to be passed to the hook's callback function. @@ -256,6 +362,27 @@ function wp_clear_scheduled_hook( $hook, $args = array() ) { $args = array_slice( func_get_args(), 1 ); } + /** + * Filter to preflight or hijack clearing a scheduled hook. + * + * Returning a non-null value will short-circuit the normal unscheduling + * process, causing the function to return the filtered value instead. + * + * For plugins replacing wp-cron, return the number of events successfully + * unscheduled (zero if no events were registered with the hook) or false + * if unscheduling one or more events fails. + * + * @since 5.0.0 + * + * @param null|array $pre Value to return instead. Default null to continue unscheduling the event. + * @param string $hook Action hook, the execution of which will be unscheduled. + * @param array $args Arguments to pass to the hook's callback function. + */ + $pre = apply_filters( 'pre_clear_scheduled_hook', null, $hook, $args ); + if ( null !== $pre ) { + return $pre; + } + // This logic duplicates wp_next_scheduled() // It's required due to a scenario where wp_unschedule_event() fails due to update_option() failing, // and, wp_next_scheduled() returns the same schedule in an infinite loop. @@ -295,6 +422,26 @@ function wp_clear_scheduled_hook( $hook, $args = array() ) { * events were registered on the hook), false if unscheduling fails. */ function wp_unschedule_hook( $hook ) { + /** + * Filter to preflight or hijack clearing all events attached to the hook. + * + * Returning a non-null value will short-circuit the normal unscheduling + * process, causing the function to return the filtered value instead. + * + * For plugins replacing wp-cron, return the number of events successfully + * unscheduled (zero if no events were registered with the hook) or false + * if unscheduling one or more events fails. + * + * @since 5.0.0 + * + * @param null|array $pre Value to return instead. Default null to continue unscheduling the hook. + * @param string $hook Action hook, the execution of which will be unscheduled. + */ + $pre = apply_filters( 'pre_unschedule_hook', null, $hook ); + if ( null !== $pre ) { + return $pre; + } + $crons = _get_cron_array(); if ( empty( $crons ) ) { return 0; @@ -325,10 +472,80 @@ function wp_unschedule_hook( $hook ) { return false; } +/** + * Retrieve a scheduled event. + * + * Retrieve the full event object for a given event. + * + * @since 5.0.0 + * + * @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. + * Although not passed to a callback, these arguments are used to uniquely identify the + * event, so they should be the same as those used when originally scheduling the event. + * @param int|null $timestamp Optional. Unix timestamp (UTC) of the event. If not specified, the next scheduled event is returned. + * @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. + * + * Returning a non-null value will short-circuit the normal process, + * returning the filtered value instead. + * + * Return false if the event does not exist, otherwise an event object + * should be returned. + * + * @since 5.0.0 + * + * @param null|bool $pre Value to return instead. Default null to continue retrieving the event. + * @param string $hook Action hook of the event. + * @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. + */ + $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. + return false; + } + + if ( ! isset( $crons[ $timestamp ][ $hook ] ) || ! isset( $crons[ $timestamp ][ $hook ][ $key ] ) ) { + return false; + } + + $event = (object) array( + 'hook' => $hook, + 'timestamp' => $timestamp, + 'schedule' => $crons[ $timestamp ][ $hook ][ $key ]['schedule'], + 'args' => $args, + ); + + if ( isset( $crons[ $timestamp ][ $hook ][ $key ]['interval'] ) ) { + $event->interval = $crons[ $timestamp ][ $hook ][ $key ]['interval']; + } + + return $event; +} + /** * Retrieve the next timestamp for an event. * * @since 2.1.0 + * @since 5.0.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. @@ -337,17 +554,48 @@ function wp_unschedule_hook( $hook ) { * @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.0.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; + } + $crons = _get_cron_array(); $key = md5( serialize( $args ) ); - if ( empty( $crons ) ) { - return false; - } - foreach ( $crons as $timestamp => $cron ) { - if ( isset( $cron[ $hook ][ $key ] ) ) { - return $timestamp; + $next = false; + + if ( ! empty( $crons ) ) { + foreach ( $crons as $timestamp => $cron ) { + if ( isset( $cron[ $hook ][ $key ] ) ) { + $next = $timestamp; + break; + } } } - return false; + + /** + * Filter the next scheduled event timestamp. + * + * @since 5.0.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 ); } /** @@ -572,23 +820,30 @@ function wp_get_schedules() { * @see wp_get_schedules() for available schedules. * * @since 2.1.0 + * @since 5.0.0 {@see get_schedule} filter added. * * @param string $hook Action hook to identify the event. * @param array $args Optional. Arguments passed to the event's callback function. * @return string|false False, if no schedule. Schedule name on success. */ function wp_get_schedule( $hook, $args = array() ) { - $crons = _get_cron_array(); - $key = md5( serialize( $args ) ); - if ( empty( $crons ) ) { - return false; + $schedule = false; + $event = wp_get_scheduled_event( $hook, $args ); + + if ( $event ) { + $schedule = $event->schedule; } - foreach ( $crons as $timestamp => $cron ) { - if ( isset( $cron[ $hook ][ $key ] ) ) { - return $cron[ $hook ][ $key ]['schedule']; - } - } - return false; + + /** + * Filter the schedule for a hook. + * + * @since 5.0.0 + * + * @param string|bool $schedule Schedule for the hook. False if not found. + * @param string $hook Action hook to execute when cron is run. + * @param array $args Optional. Arguments to pass to the hook's callback function. + */ + return apply_filters( 'get_schedule', $schedule, $hook, $args ); } // diff --git a/wp-includes/version.php b/wp-includes/version.php index 9d740945f8..64be4ee3b7 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '5.0-alpha-43535'; +$wp_version = '5.0-alpha-43540'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.