WordPress 4.0
[autoinstalls/wordpress.git] / wp-includes / cron.php
1 <?php
2 /**
3  * WordPress CRON API
4  *
5  * @package WordPress
6  */
7
8 /**
9  * Schedules a hook to run only once.
10  *
11  * Schedules a hook which will be executed once by the WordPress actions core at
12  * a time which you specify. The action will fire off when someone visits your
13  * WordPress site, if the schedule time has passed.
14  *
15  * @since 2.1.0
16  * @link http://codex.wordpress.org/Function_Reference/wp_schedule_single_event
17  *
18  * @param int $timestamp Timestamp for when to run the event.
19  * @param string $hook Action hook to execute when cron is run.
20  * @param array $args Optional. Arguments to pass to the hook's callback function.
21  */
22 function wp_schedule_single_event( $timestamp, $hook, $args = array()) {
23         // don't schedule a duplicate if there's already an identical event due in the next 10 minutes
24         $next = wp_next_scheduled($hook, $args);
25         if ( $next && $next <= $timestamp + 10 * MINUTE_IN_SECONDS )
26                 return;
27
28         $crons = _get_cron_array();
29         $event = (object) array( 'hook' => $hook, 'timestamp' => $timestamp, 'schedule' => false, 'args' => $args );
30         /**
31          * Filter a single event before it is scheduled.
32          *
33          * @since 3.1.0
34          *
35          * @param object $event An object containing an event's data.
36          */
37         $event = apply_filters( 'schedule_event', $event );
38
39         // A plugin disallowed this event
40         if ( ! $event )
41                 return false;
42
43         $key = md5(serialize($event->args));
44
45         $crons[$event->timestamp][$event->hook][$key] = array( 'schedule' => $event->schedule, 'args' => $event->args );
46         uksort( $crons, "strnatcasecmp" );
47         _set_cron_array( $crons );
48 }
49
50 /**
51  * Schedule a periodic event.
52  *
53  * Schedules a hook which will be executed by the WordPress actions core on a
54  * specific interval, specified by you. The action will trigger when someone
55  * visits your WordPress site, if the scheduled time has passed.
56  *
57  * Valid values for the recurrence are hourly, daily and twicedaily. These can
58  * be extended using the cron_schedules filter in wp_get_schedules().
59  *
60  * Use wp_next_scheduled() to prevent duplicates
61  *
62  * @since 2.1.0
63  *
64  * @param int $timestamp Timestamp for when to run the event.
65  * @param string $recurrence How often the event should recur.
66  * @param string $hook Action hook to execute when cron is run.
67  * @param array $args Optional. Arguments to pass to the hook's callback function.
68  * @return bool|null False on failure, null when complete with scheduling event.
69  */
70 function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array()) {
71         $crons = _get_cron_array();
72         $schedules = wp_get_schedules();
73
74         if ( !isset( $schedules[$recurrence] ) )
75                 return false;
76
77         $event = (object) array( 'hook' => $hook, 'timestamp' => $timestamp, 'schedule' => $recurrence, 'args' => $args, 'interval' => $schedules[$recurrence]['interval'] );
78         /** This filter is documented in wp-includes/cron.php */
79         $event = apply_filters( 'schedule_event', $event );
80
81         // A plugin disallowed this event
82         if ( ! $event )
83                 return false;
84
85         $key = md5(serialize($event->args));
86
87         $crons[$event->timestamp][$event->hook][$key] = array( 'schedule' => $event->schedule, 'args' => $event->args, 'interval' => $event->interval );
88         uksort( $crons, "strnatcasecmp" );
89         _set_cron_array( $crons );
90 }
91
92 /**
93  * Reschedule a recurring event.
94  *
95  * @since 2.1.0
96  *
97  * @param int $timestamp Timestamp for when to run the event.
98  * @param string $recurrence How often the event should recur.
99  * @param string $hook Action hook to execute when cron is run.
100  * @param array $args Optional. Arguments to pass to the hook's callback function.
101  * @return bool|null False on failure. Null when event is rescheduled.
102  */
103 function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array() ) {
104         $crons = _get_cron_array();
105         $schedules = wp_get_schedules();
106         $key = md5( serialize( $args ) );
107         $interval = 0;
108
109         // First we try to get it from the schedule
110         if ( isset( $schedules[ $recurrence ] ) ) {
111                 $interval = $schedules[ $recurrence ]['interval'];
112         }
113         // Now we try to get it from the saved interval in case the schedule disappears
114         if ( 0 == $interval ) {
115                 $interval = $crons[ $timestamp ][ $hook ][ $key ]['interval'];
116         }
117         // Now we assume something is wrong and fail to schedule
118         if ( 0 == $interval ) {
119                 return false;
120         }
121
122         $now = time();
123
124         if ( $timestamp >= $now ) {
125                 $timestamp = $now + $interval;
126         } else {
127                 $timestamp = $now + ( $interval - ( ( $now - $timestamp ) % $interval ) );
128         }
129
130         wp_schedule_event( $timestamp, $recurrence, $hook, $args );
131 }
132
133 /**
134  * Unschedule a previously scheduled cron job.
135  *
136  * The $timestamp and $hook parameters are required, so that the event can be
137  * identified.
138  *
139  * @since 2.1.0
140  *
141  * @param int $timestamp Timestamp for when to run the event.
142  * @param string $hook Action hook, the execution of which will be unscheduled.
143  * @param array $args Arguments to pass to the hook's callback function.
144  * Although not passed to a callback function, these arguments are used
145  * to uniquely identify the scheduled event, so they should be the same
146  * as those used when originally scheduling the event.
147  */
148 function wp_unschedule_event( $timestamp, $hook, $args = array() ) {
149         $crons = _get_cron_array();
150         $key = md5(serialize($args));
151         unset( $crons[$timestamp][$hook][$key] );
152         if ( empty($crons[$timestamp][$hook]) )
153                 unset( $crons[$timestamp][$hook] );
154         if ( empty($crons[$timestamp]) )
155                 unset( $crons[$timestamp] );
156         _set_cron_array( $crons );
157 }
158
159 /**
160  * Unschedule all cron jobs attached to a specific hook.
161  *
162  * @since 2.1.0
163  *
164  * @param string $hook Action hook, the execution of which will be unscheduled.
165  * @param array $args Optional. Arguments that were to be pass to the hook's callback function.
166  */
167 function wp_clear_scheduled_hook( $hook, $args = array() ) {
168         // Backward compatibility
169         // Previously this function took the arguments as discrete vars rather than an array like the rest of the API
170         if ( !is_array($args) ) {
171                 _deprecated_argument( __FUNCTION__, '3.0', __('This argument has changed to an array to match the behavior of the other cron functions.') );
172                 $args = array_slice( func_get_args(), 1 );
173         }
174
175         // This logic duplicates wp_next_scheduled()
176         // It's required due to a scenario where wp_unschedule_event() fails due to update_option() failing,
177         // and, wp_next_scheduled() returns the same schedule in an infinite loop.
178         $crons = _get_cron_array();
179         if ( empty( $crons ) )
180                 return;
181
182         $key = md5( serialize( $args ) );
183         foreach ( $crons as $timestamp => $cron ) {
184                 if ( isset( $cron[ $hook ][ $key ] ) ) {
185                         wp_unschedule_event( $timestamp, $hook, $args );
186                 }
187         }
188 }
189
190 /**
191  * Retrieve the next timestamp for a cron event.
192  *
193  * @since 2.1.0
194  *
195  * @param string $hook Action hook to execute when cron is run.
196  * @param array $args Optional. Arguments to pass to the hook's callback function.
197  * @return bool|int The UNIX timestamp of the next time the scheduled event will occur.
198  */
199 function wp_next_scheduled( $hook, $args = array() ) {
200         $crons = _get_cron_array();
201         $key = md5(serialize($args));
202         if ( empty($crons) )
203                 return false;
204         foreach ( $crons as $timestamp => $cron ) {
205                 if ( isset( $cron[$hook][$key] ) )
206                         return $timestamp;
207         }
208         return false;
209 }
210
211 /**
212  * Send request to run cron through HTTP request that doesn't halt page loading.
213  *
214  * @since 2.1.0
215  *
216  * @return null Cron could not be spawned, because it is not needed to run.
217  */
218 function spawn_cron( $gmt_time = 0 ) {
219
220         if ( ! $gmt_time )
221                 $gmt_time = microtime( true );
222
223         if ( defined('DOING_CRON') || isset($_GET['doing_wp_cron']) )
224                 return;
225
226         /*
227         * multiple processes on multiple web servers can run this code concurrently
228         * try to make this as atomic as possible by setting doing_cron switch
229         */
230         $lock = get_transient('doing_cron');
231
232         if ( $lock > $gmt_time + 10 * MINUTE_IN_SECONDS )
233                 $lock = 0;
234
235         // don't run if another process is currently running it or more than once every 60 sec.
236         if ( $lock + WP_CRON_LOCK_TIMEOUT > $gmt_time )
237                 return;
238
239         //sanity check
240         $crons = _get_cron_array();
241         if ( !is_array($crons) )
242                 return;
243
244         $keys = array_keys( $crons );
245         if ( isset($keys[0]) && $keys[0] > $gmt_time )
246                 return;
247
248         if ( defined('ALTERNATE_WP_CRON') && ALTERNATE_WP_CRON ) {
249                 if ( !empty($_POST) || defined('DOING_AJAX') )
250                         return;
251
252                 $doing_wp_cron = sprintf( '%.22F', $gmt_time );
253                 set_transient( 'doing_cron', $doing_wp_cron );
254
255                 ob_start();
256                 wp_redirect( add_query_arg( 'doing_wp_cron', $doing_wp_cron, wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
257                 echo ' ';
258
259                 // flush any buffers and send the headers
260                 while ( @ob_end_flush() );
261                 flush();
262
263                 WP_DEBUG ? include_once( ABSPATH . 'wp-cron.php' ) : @include_once( ABSPATH . 'wp-cron.php' );
264                 return;
265         }
266
267         $doing_wp_cron = sprintf( '%.22F', $gmt_time );
268         set_transient( 'doing_cron', $doing_wp_cron );
269
270         /**
271          * Filter the cron request arguments.
272          *
273          * @since 3.5.0
274          *
275          * @param array $cron_request_array {
276          *     An array of cron request URL arguments.
277          *
278          *     @type string $url  The cron request URL.
279          *     @type int    $key  The 22 digit GMT microtime.
280          *     @type array  $args {
281          *         An array of cron request arguments.
282          *
283          *         @type int  $timeout   The request timeout in seconds. Default .01 seconds.
284          *         @type bool $blocking  Whether to set blocking for the request. Default false.
285          *         @type bool $sslverify Whether SSL should be verified for the request. Default false.
286          *     }
287          * }
288          */
289         $cron_request = apply_filters( 'cron_request', array(
290                 'url'  => add_query_arg( 'doing_wp_cron', $doing_wp_cron, site_url( 'wp-cron.php' ) ),
291                 'key'  => $doing_wp_cron,
292                 'args' => array(
293                         'timeout'   => 0.01,
294                         'blocking'  => false,
295                         /** This filter is documented in wp-includes/class-http.php */
296                         'sslverify' => apply_filters( 'https_local_ssl_verify', false )
297                 )
298         ) );
299
300         wp_remote_post( $cron_request['url'], $cron_request['args'] );
301 }
302
303 /**
304  * Run scheduled callbacks or spawn cron for all scheduled events.
305  *
306  * @since 2.1.0
307  *
308  * @return null When doesn't need to run Cron.
309  */
310 function wp_cron() {
311
312         // Prevent infinite loops caused by lack of wp-cron.php
313         if ( strpos($_SERVER['REQUEST_URI'], '/wp-cron.php') !== false || ( defined('DISABLE_WP_CRON') && DISABLE_WP_CRON ) )
314                 return;
315
316         if ( false === $crons = _get_cron_array() )
317                 return;
318
319         $gmt_time = microtime( true );
320         $keys = array_keys( $crons );
321         if ( isset($keys[0]) && $keys[0] > $gmt_time )
322                 return;
323
324         $schedules = wp_get_schedules();
325         foreach ( $crons as $timestamp => $cronhooks ) {
326                 if ( $timestamp > $gmt_time ) break;
327                 foreach ( (array) $cronhooks as $hook => $args ) {
328                         if ( isset($schedules[$hook]['callback']) && !call_user_func( $schedules[$hook]['callback'] ) )
329                                 continue;
330                         spawn_cron( $gmt_time );
331                         break 2;
332                 }
333         }
334 }
335
336 /**
337  * Retrieve supported and filtered Cron recurrences.
338  *
339  * The supported recurrences are 'hourly' and 'daily'. A plugin may add more by
340  * hooking into the 'cron_schedules' filter. The filter accepts an array of
341  * arrays. The outer array has a key that is the name of the schedule or for
342  * example 'weekly'. The value is an array with two keys, one is 'interval' and
343  * the other is 'display'.
344  *
345  * The 'interval' is a number in seconds of when the cron job should run. So for
346  * 'hourly', the time is 3600 or 60*60. For weekly, the value would be
347  * 60*60*24*7 or 604800. The value of 'interval' would then be 604800.
348  *
349  * The 'display' is the description. For the 'weekly' key, the 'display' would
350  * be <code>__('Once Weekly')</code>.
351  *
352  * For your plugin, you will be passed an array. you can easily add your
353  * schedule by doing the following.
354  * <code>
355  * // filter parameter variable name is 'array'
356  *      $array['weekly'] = array(
357  *              'interval' => 604800,
358  *              'display' => __('Once Weekly')
359  *      );
360  * </code>
361  *
362  * @since 2.1.0
363  *
364  * @return array
365  */
366 function wp_get_schedules() {
367         $schedules = array(
368                 'hourly'     => array( 'interval' => HOUR_IN_SECONDS,      'display' => __( 'Once Hourly' ) ),
369                 'twicedaily' => array( 'interval' => 12 * HOUR_IN_SECONDS, 'display' => __( 'Twice Daily' ) ),
370                 'daily'      => array( 'interval' => DAY_IN_SECONDS,       'display' => __( 'Once Daily' ) ),
371         );
372         /**
373          * Filter the non-default cron schedules.
374          *
375          * @since 2.1.0
376          *
377          * @param array $new_schedules An array of non-default cron schedules. Default empty.
378          */
379         return array_merge( apply_filters( 'cron_schedules', array() ), $schedules );
380 }
381
382 /**
383  * Retrieve Cron schedule for hook with arguments.
384  *
385  * @since 2.1.0
386  *
387  * @param string $hook Action hook to execute when cron is run.
388  * @param array $args Optional. Arguments to pass to the hook's callback function.
389  * @return string|bool False, if no schedule. Schedule on success.
390  */
391 function wp_get_schedule($hook, $args = array()) {
392         $crons = _get_cron_array();
393         $key = md5(serialize($args));
394         if ( empty($crons) )
395                 return false;
396         foreach ( $crons as $timestamp => $cron ) {
397                 if ( isset( $cron[$hook][$key] ) )
398                         return $cron[$hook][$key]['schedule'];
399         }
400         return false;
401 }
402
403 //
404 // Private functions
405 //
406
407 /**
408  * Retrieve cron info array option.
409  *
410  * @since 2.1.0
411  * @access private
412  *
413  * @return array CRON info array.
414  */
415 function _get_cron_array()  {
416         $cron = get_option('cron');
417         if ( ! is_array($cron) )
418                 return false;
419
420         if ( !isset($cron['version']) )
421                 $cron = _upgrade_cron_array($cron);
422
423         unset($cron['version']);
424
425         return $cron;
426 }
427
428 /**
429  * Updates the CRON option with the new CRON array.
430  *
431  * @since 2.1.0
432  * @access private
433  *
434  * @param array $cron Cron info array from {@link _get_cron_array()}.
435  */
436 function _set_cron_array($cron) {
437         $cron['version'] = 2;
438         update_option( 'cron', $cron );
439 }
440
441 /**
442  * Upgrade a Cron info array.
443  *
444  * This function upgrades the Cron info array to version 2.
445  *
446  * @since 2.1.0
447  * @access private
448  *
449  * @param array $cron Cron info array from {@link _get_cron_array()}.
450  * @return array An upgraded Cron info array.
451  */
452 function _upgrade_cron_array($cron) {
453         if ( isset($cron['version']) && 2 == $cron['version'])
454                 return $cron;
455
456         $new_cron = array();
457
458         foreach ( (array) $cron as $timestamp => $hooks) {
459                 foreach ( (array) $hooks as $hook => $args ) {
460                         $key = md5(serialize($args['args']));
461                         $new_cron[$timestamp][$hook][$key] = $args;
462                 }
463         }
464
465         $new_cron['version'] = 2;
466         update_option( 'cron', $new_cron );
467         return $new_cron;
468 }