cron.php 16 KB

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