WordPress.org

Make WordPress Core

Ticket #15148: cron.php

File cron.php, 8.9 KB (added by mintindeed, 4 years ago)

Proposed improved cron store (as a drop-in)

Line 
1<?php
2class PMC_Cron_Store extends WP_Cron_Store {
3        /**
4         * Status enum for "complete" state
5         */
6        const STATUS_COMPLETE = 'complete';
7
8        /**
9         * Status enum for "processing" state.  For future use.
10         */
11        const STATUS_PROCESSING = 'processing';
12
13        /**
14         * Status enum for "pending" state
15         */
16        const STATUS_PENDING = 'pending';
17
18        /**
19         * Array of pending cron jobs for time() + 10 minutes
20         */
21        protected $_cron_array = array();
22
23        /**
24         * Internal cache of next scheduled events, since we're no longer using the
25         * options table and WP object cache
26         */
27        protected $_next_scheduled_events = array();
28
29        /**
30         * Constructor sets up $this->_cron_array
31         *
32         * @since ?
33         */
34        public function __construct() {
35                // Set up the cron array
36                $this->_setup_cron_array();
37        }
38
39        /**
40         * Delete any completed cron jobs older than one day on shutdown
41         *
42         * @since ?
43         */
44        public function __destruct() {
45                global $wpdb;
46
47                $wpdb->query("DELETE FROM `" . $wpdb->prefix . "cron` WHERE `status`='" . self::STATUS_COMPLETE . "' AND `timestamp`<" . strtotime('-1 day'));
48
49        }
50
51        /**
52         * Marks the event as completed and removes it from the current instance of
53         * $this->_cron_array
54         *
55         * @since ?
56         *
57         * @param int $timestamp
58         * @param string $hook
59         * @param array $args
60         */
61        public function complete_event( $timestamp, $hook, $args ) {
62                global $wpdb;
63
64                $args = serialize($args);
65                $key = md5($args);
66                $id = $this->_get_cron_id( $timestamp, $hook, $args, $key );
67
68                unset( $this->_cron_array[$timestamp][$hook][$key] );
69
70                if ( empty($this->_cron_array[$timestamp][$hook]) )
71                        unset( $this->_cron_array[$timestamp][$hook] );
72
73                if ( empty($this->_cron_array[$timestamp]) )
74                        unset( $this->_cron_array[$timestamp] );
75
76                $wpdb->query("UPDATE `" . $wpdb->prefix . "cron` SET `status`='" . self::STATUS_COMPLETE . "' WHERE `id`='" . $id . "' LIMIT 1");
77        }
78
79        /**
80         * Searches for a scheduled event
81         *
82         * @since ?
83         *
84         * @param string $hook
85         * @param int $timestamp
86         * @param array $args
87         *
88         * @return obj|false $event object or false if it doesn't exist
89         */
90        public function get_event( $hook, $timestamp, $args = array() ) {
91                global $wpdb;
92
93                $key = md5(serialize($args));
94
95                // Check if the event exists in the current cron array
96                if ( isset($this->_cron_array[$timestamp][$hook][$key]) ) {
97                        return (object) array(
98                                'hook' => $hook,
99                                'args' => $args,
100                                'timestamp' => $timestamp,
101                                'schedule' => $this->_cron_array[$timestamp][$hook][$key]['schedule'],
102                                'interval' => $this->_cron_array[$timestamp][$hook][$key]['interval']
103                        );
104                }
105
106                // If the timestamp is empty or "next", see if we have it in the current cron array
107                if ( empty($timestamp) || 'next' == $timestamp ) {
108                        if ( isset($this->_next_scheduled_events[$hook][$key]) )
109                                return $this->_next_scheduled_events[$hook][$key];
110
111                        foreach ( $this->_cron_array as $cron_timestamp => $cron ) {
112                                if ( isset( $cron[$hook][$key] ) ) {
113                                        $this->_next_scheduled_events[$hook][$key] = (object) array(
114                                                'hook' => $hook,
115                                                'args' => $args,
116                                                'timestamp' => $cron_timestamp,
117                                                'schedule' => $cron[$hook][$key]['schedule'],
118                                                'interval' => $cron[$hook][$key]['interval']
119                                        );
120
121                                        return $this->_next_scheduled_events[$hook][$key];
122                                }
123                        }
124
125                }
126
127                // Query the event from the database
128                $event = $wpdb->get_row("SELECT `hook`, `args`, `timestamp`, `schedule`, `interval` FROM `" . $wpdb->prefix . "cron` WHERE `hook`='" . $hook . "' AND `key`='" . $key . "' AND `status`!='" . self::STATUS_COMPLETE . "' ORDER BY `timestamp` ASC LIMIT 1");
129
130                if ( $event ) {
131                        $event->args = unserialize($event->args);
132
133                        // Add the event to $this->_next_scheduled_events so we won't have to query it next time
134                        if ( empty($timestamp) || 'next' == $timestamp )
135                                $this->_next_scheduled_events[$hook][$key] = $event;
136
137                        return $event;
138                }
139
140                return false;
141        }
142
143        /**
144         * Returns an array of all pending jobs within the next 10 minutes
145         *
146         * @since ?
147         *
148         * @param array $args Unused
149         *
150         * @return array
151         */
152        public function get_events( $args = array() ) {
153                return $this->_get_cron_array();
154        }
155
156        /**
157         * Inserts a new event
158         *
159         * @since ?
160         *
161         * @param int $timestamp
162         * @param string $hook
163         * @param array $args
164         * @param null|string $recurrence
165         * @param null|int $interval
166         */
167        public function insert_event( $timestamp, $hook, $args, $recurrence = null, $interval = null ) {
168                $key = md5(serialize($args));
169
170                $this->_cron_array[$timestamp][$hook][$key] = array(
171                        'schedule' => $recurrence,
172                        'args' => $args,
173                        'interval' => $interval
174                );
175                uksort( $this->_cron_array, 'strnatcasecmp' );
176                $this->_set_cron_array();
177        }
178
179        /**
180         * Retrieve cron info array option.
181         *
182         * @since 2.1.0
183         *
184         * @return array CRON info array.
185         */
186        protected function _get_cron_array()  {
187                return $this->_setup_cron_array();
188        }
189
190        /**
191         * Sets up the cron array with data.
192         *
193         * @since ?
194         *
195         * @param array $crons Optional, default WP_Cron_Store passes this for legacy reasons
196         *
197         * @return array $this->_cron_array
198         */
199        protected function _setup_cron_array( $crons = array() ) {
200                global $wpdb;
201
202                if ( !empty($this->_cron_array) ) {
203                        return $this->_cron_array;
204                }
205
206                $old_crons = get_option('cron', array());
207                if ( !empty($old_crons) )
208                        $this->_upgrade_cron_array($old_crons);
209
210                // Get jobs that need to run + jobs for 10 minutes in the future (for wp_schedule_single_event())
211                $crons = $wpdb->get_results("SELECT `key`, `hook`, `timestamp`, `schedule`, `args`, `interval` FROM `" . $wpdb->prefix . "cron` WHERE `status`='" . self::STATUS_PENDING . "' AND `timestamp`<=" . strtotime('+10 minutes') . " ORDER BY `timestamp` ASC");
212                if ( $crons ) {
213                        foreach ( $crons as $cron ) {
214                                $this->_cron_array[$cron->timestamp][$cron->hook][$cron->key] = array(
215                                        'schedule' => $cron->schedule,
216                                        'args' => unserialize($cron->args),
217                                        'interval' => $cron->interval
218                                );
219                        }
220                }
221
222                return $this->_cron_array;
223        }
224
225        /**
226         * Updates the CRON option with the new CRON array.
227         *
228         * @since 2.1.0
229         *
230         * @param array $crons Cron info array from {@link _get_cron_array()}.
231         */
232        protected function _set_cron_array( $crons = array() ) {
233                global $wpdb;
234
235                if ( empty($crons) ) {
236                        $crons = $this->_cron_array;
237                }
238
239                $crons['version'] = 3;
240
241                foreach ( $crons as $timestamp => $hooks ) {
242                        if ( 'version' === $timestamp ) {
243                                continue;
244                        }
245                        foreach ( $hooks as $hook => $args ) {
246                                foreach ( $args as $key => $params ) {
247                                        // @todo use wpdb prepare, insert
248                                        $id = $this->_get_cron_id( $timestamp, $hook, $params['args'] );
249
250                                        $result = $wpdb->query("INSERT INTO `" . $wpdb->prefix . "cron` SET `id`='" . $id . "', `key`='" . $key . "', `hook`='" . $hook . "', `timestamp`='" . $timestamp . "', `schedule`='" . $params['schedule'] . "', `args`='" . serialize($params['args']) . "', `interval`='" . $params['interval'] . "'");
251
252                                        if ( !$result ) {
253                                                error_log($wpdb->last_error);
254                                        }
255                                }
256                        }
257                }
258        }
259
260        /**
261         * Generates an id for storing the cron in the database
262         *
263         * This method is private because nothing should override how the cron id's
264         * are generated
265         *
266         * @since ?
267         *
268         * @param int $timestamp
269         * @param string $hook
270         * @param array $args
271         * @param null|string $key Key made from md5 hash of serialized $args array
272         *
273         * @return string
274         */
275        private function _get_cron_id( $timestamp, $hook, $args = array(), $key = null ) {
276                $args = (is_array($args)) ? serialize($args) : $args;
277                $key = (is_null($key)) ? md5($args) : $key;
278                return md5($key . $timestamp . $hook . $args);
279        }
280
281        /**
282         * Upgrade a Cron info array.
283         *
284         * This function upgrades the Cron info array to version 3.
285         *
286         * @since 2.1.0
287         *
288         * @param array $cron Cron info array from {@link _get_cron_array()}.
289         * @return array An upgraded Cron info array.
290         */
291        protected function _upgrade_cron_array($crons) {
292                global $wpdb;
293
294                if ( isset($crons['version']) && 3 == $crons['version'])
295                        return $crons;
296
297                unset($crons['version']);
298                if ( $wpdb->get_var("SHOW TABLES LIKE '" . $wpdb->prefix . "cron'") != $wpdb->prefix . 'cron' ) {
299                        $wpdb->query("CREATE TABLE `" . $wpdb->prefix . "cron` (
300                                  `id` char(32) NOT NULL,
301                                  `key` char(32) NOT NULL,
302                                  `hook` varchar(255) NOT NULL,
303                                  `timestamp` int(11) NOT NULL,
304                                  `schedule` varchar(32) NOT NULL,
305                                  `args` text NOT NULL,
306                                  `interval` int(11) NOT NULL,
307                                  `status` enum('complete','processing','pending') NOT NULL DEFAULT 'pending',
308                                  `last_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
309                                  PRIMARY KEY (`id`),
310                                  KEY `timestamp` (`timestamp`),
311                                  KEY `status` (`status`),
312                                  KEY `hook` (`hook`),
313                                  KEY `key` (`key`)
314                                ) DEFAULT CHARSET=utf8;");
315                }
316
317                foreach ( (array) $crons as $timestamp => $hooks) {
318                        foreach ( (array) $hooks as $hook => $args ) {
319                            $args = array_pop($args);
320                                $key = md5(serialize($args['args']));
321                                $this->_cron_array[$timestamp][$hook][$key] = $args;
322                        }
323                }
324
325                $this->_cron_array['version'] = 3;
326                delete_option('cron');
327        }
328}
329
330
331if ( !isset( $wp_cron_store ) )
332        $wp_cron_store = new PMC_Cron_Store;