1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
// GET /sites/%s/cron |
4
|
|
|
class Jetpack_JSON_API_Cron_Endpoint extends Jetpack_JSON_API_Endpoint { |
5
|
|
|
protected $needed_capabilities = 'manage_options'; |
6
|
|
|
|
7
|
|
|
protected function validate_call( $_blog_id, $capability, $check_manage_active = true ) { |
8
|
|
|
return parent::validate_call( $_blog_id, $capability, false ); |
9
|
|
|
} |
10
|
|
|
|
11
|
|
|
protected function result() { |
12
|
|
|
return array( |
13
|
|
|
'cron_array' => _get_cron_array(), |
14
|
|
|
'current_timestamp' => time() |
15
|
|
|
); |
16
|
|
|
} |
17
|
|
|
|
18
|
|
|
protected function sanitize_hook( $hook ) { |
19
|
|
|
return preg_replace( '/[^A-Za-z0-9-_]/', '', $hook ); |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
protected function resolve_arguments() { |
23
|
|
|
$args = $this->input(); |
24
|
|
|
return isset( $args['arguments'] ) ? json_decode( $args['arguments'] ) : array(); |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
protected function is_cron_locked( $gmt_time ) { |
28
|
|
|
// The cron lock: a unix timestamp from when the cron was spawned. |
29
|
|
|
$doing_cron_transient = $this->get_cron_lock(); |
30
|
|
|
if ( $doing_cron_transient && ( $doing_cron_transient + WP_CRON_LOCK_TIMEOUT > $gmt_time ) ) { |
31
|
|
|
return new WP_Error( 'cron-is-locked', 'Current there is a cron already happening.', 403 ); |
|
|
|
|
32
|
|
|
} |
33
|
|
|
return $doing_cron_transient; |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
protected function maybe_unlock_cron( $doing_wp_cron ) { |
37
|
|
|
if ( $this->get_cron_lock() == $doing_wp_cron ) { |
38
|
|
|
delete_transient( 'doing_cron' ); |
39
|
|
|
} |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
protected function lock_cron() { |
43
|
|
|
$lock = sprintf( '%.22F', microtime( true ) ); |
44
|
|
|
set_transient( 'doing_cron', $lock ); |
45
|
|
|
return $lock; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
protected function get_schedules( $hook, $args ) { |
49
|
|
|
$crons = _get_cron_array(); |
50
|
|
|
$key = md5(serialize($args)); |
51
|
|
|
if ( empty( $crons ) ) |
52
|
|
|
return array(); |
53
|
|
|
$found = array(); |
54
|
|
|
foreach ( $crons as $timestamp => $cron ) { |
55
|
|
|
if ( isset( $cron[$hook][$key] ) ) |
56
|
|
|
$found[] = $timestamp; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
return $found; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* This function is based on the one found in wp-cron.php with a similar name |
64
|
|
|
* @return int |
65
|
|
|
*/ |
66
|
|
|
protected function get_cron_lock() { |
67
|
|
|
global $wpdb; |
68
|
|
|
|
69
|
|
|
$value = 0; |
70
|
|
|
if ( wp_using_ext_object_cache() ) { |
71
|
|
|
/* |
72
|
|
|
* Skip local cache and force re-fetch of doing_cron transient |
73
|
|
|
* in case another process updated the cache. |
74
|
|
|
*/ |
75
|
|
|
$value = wp_cache_get( 'doing_cron', 'transient', true ); |
76
|
|
|
} else { |
77
|
|
|
$row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", '_transient_doing_cron' ) ); |
78
|
|
|
if ( is_object( $row ) ) { |
79
|
|
|
$value = $row->option_value; |
80
|
|
|
} |
81
|
|
|
} |
82
|
|
|
return $value; |
83
|
|
|
} |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
// POST /sites/%s/cron |
87
|
|
|
class Jetpack_JSON_API_Cron_Post_Endpoint extends Jetpack_JSON_API_Cron_Endpoint { |
88
|
|
|
|
89
|
|
|
protected function result() { |
90
|
|
|
define( 'DOING_CRON', true ); |
91
|
|
|
set_time_limit( 0 ); |
92
|
|
|
$args = $this->input(); |
93
|
|
|
|
94
|
|
|
if ( false === $crons = _get_cron_array() ) { |
95
|
|
|
return new WP_Error( 'no-cron-event', 'Currently there are no cron events', 400 ); |
|
|
|
|
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
$timestamps_to_run = array_keys( $crons ); |
99
|
|
|
$gmt_time = microtime( true ); |
100
|
|
|
|
101
|
|
|
if ( isset( $timestamps_to_run[0] ) && $timestamps_to_run[0] > $gmt_time ) { |
102
|
|
|
return new WP_Error( 'no-cron-event', 'Currently there are no cron events ready to be run', 400 ); |
|
|
|
|
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
$locked = $this->is_cron_locked( $gmt_time ); |
106
|
|
|
if ( is_wp_error( $locked ) ) { |
107
|
|
|
return $locked; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
$lock = $this->lock_cron(); |
111
|
|
|
$processed_events = array(); |
112
|
|
|
|
113
|
|
|
foreach ( $crons as $timestamp => $cronhooks ) { |
114
|
|
|
if ( $timestamp > $gmt_time && ! isset( $args[ 'hook' ] ) ) { |
115
|
|
|
break; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
foreach ( $cronhooks as $hook => $hook_data ) { |
119
|
|
|
if ( isset( $args[ 'hook' ] ) && ! in_array( $hook, $args['hook'] ) ) { |
120
|
|
|
continue; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
foreach ( $hook_data as $hash => $hook_item ) { |
124
|
|
|
|
125
|
|
|
$schedule = $hook_item['schedule']; |
126
|
|
|
$arguments = $hook_item['args']; |
127
|
|
|
|
128
|
|
|
if ( $schedule != false ) { |
129
|
|
|
wp_reschedule_event( $timestamp, $schedule, $hook, $arguments ); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
wp_unschedule_event( $timestamp, $hook, $arguments ); |
133
|
|
|
|
134
|
|
|
do_action_ref_array( $hook, $arguments ); |
135
|
|
|
$processed_events[] = array( $hook => $arguments ); |
136
|
|
|
|
137
|
|
|
// If the hook ran too long and another cron process stole the lock, |
138
|
|
|
// or if we things are taking longer then 20 seconds then quit. |
139
|
|
|
if ( ( $this->get_cron_lock() != $lock ) || ( $gmt_time + 20 > microtime( true ) ) ) { |
140
|
|
|
$this->maybe_unlock_cron( $lock ); |
141
|
|
|
return array( 'success' => $processed_events ); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
$this->maybe_unlock_cron( $lock ); |
149
|
|
|
return array( 'success' => $processed_events ); |
150
|
|
|
} |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
// POST /sites/%s/cron/schedule |
154
|
|
|
class Jetpack_JSON_API_Cron_Schedule_Endpoint extends Jetpack_JSON_API_Cron_Endpoint { |
155
|
|
|
|
156
|
|
|
protected function result() { |
157
|
|
|
$args = $this->input(); |
158
|
|
|
if ( ! isset( $args['timestamp'] ) ) { |
159
|
|
|
return new WP_Error( 'missing_argument', 'Please provide the timestamp argument', 400 ); |
|
|
|
|
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
if ( ! is_int( $args['timestamp'] ) || $args['timestamp'] < time() ) { |
163
|
|
|
return new WP_Error( 'timestamp-invalid', 'Please provide timestamp that is an integer and set in the future', 400 ); |
|
|
|
|
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
if ( ! isset( $args['hook'] ) ) { |
167
|
|
|
return new WP_Error( 'missing_argument', 'Please provide the hook argument', 400 ); |
|
|
|
|
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
$hook = $this->sanitize_hook( $args['hook'] ); |
171
|
|
|
|
172
|
|
|
$locked = $this->is_cron_locked( microtime( true ) ); |
173
|
|
|
if ( is_wp_error( $locked ) ) { |
174
|
|
|
return $locked; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$arguments = $this->resolve_arguments(); |
178
|
|
|
$next_scheduled = $this->get_schedules( $hook, $arguments ); |
179
|
|
|
|
180
|
|
|
if ( isset( $args['recurrence'] ) ) { |
181
|
|
|
$schedules = wp_get_schedules(); |
182
|
|
|
if ( ! isset( $schedules[ $args['recurrence'] ] ) ) { |
183
|
|
|
return new WP_Error( 'invalid-recurrence', 'Please provide a valid recurrence argument', 400 ); |
|
|
|
|
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
if ( count( $next_scheduled ) > 0 ) { |
187
|
|
|
return new WP_Error( 'event-already-scheduled', 'This event is ready scheduled', 400 ); |
|
|
|
|
188
|
|
|
} |
189
|
|
|
$lock = $this->lock_cron(); |
190
|
|
|
wp_schedule_event( $args['timestamp'], $args['recurrence'], $hook, $arguments ); |
191
|
|
|
$this->maybe_unlock_cron( $lock ); |
192
|
|
|
return array( 'success' => true ); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
foreach( $next_scheduled as $scheduled_time ) { |
196
|
|
|
if ( abs( $scheduled_time - $args['timestamp'] ) <= 10 * MINUTE_IN_SECONDS ) { |
197
|
|
|
return new WP_Error( 'event-already-scheduled', 'This event is ready scheduled', 400 ); |
|
|
|
|
198
|
|
|
} |
199
|
|
|
} |
200
|
|
|
$lock = $this->lock_cron(); |
201
|
|
|
$next = wp_schedule_single_event( $args['timestamp'], $hook, $arguments ); |
202
|
|
|
$this->maybe_unlock_cron( $lock ); |
203
|
|
|
return array( 'success' => $next ); |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
// POST /sites/%s/cron/unschedule |
208
|
|
|
class Jetpack_JSON_API_Cron_Unschedule_Endpoint extends Jetpack_JSON_API_Cron_Endpoint { |
209
|
|
|
|
210
|
|
|
protected function result() { |
211
|
|
|
$args = $this->input(); |
212
|
|
|
|
213
|
|
|
if ( !isset( $args['hook'] ) ) { |
214
|
|
|
return new WP_Error( 'missing_argument', 'Please provide the hook argument', 400 ); |
|
|
|
|
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
$hook = $this->sanitize_hook( $args['hook'] ); |
218
|
|
|
|
219
|
|
|
$locked = $this->is_cron_locked( microtime( true ) ); |
220
|
|
|
if ( is_wp_error( $locked ) ) { |
221
|
|
|
return $locked; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
$crons = _get_cron_array(); |
225
|
|
|
if ( empty( $crons ) ) { |
226
|
|
|
return new WP_Error( 'cron-not-present', 'Unable to unschedule an event, no events in the cron', 400 ); |
|
|
|
|
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
$arguments = $this->resolve_arguments(); |
230
|
|
|
|
231
|
|
|
if ( isset( $args['timestamp'] ) ) { |
232
|
|
|
$next_schedulded = $this->get_schedules( $hook, $arguments ); |
233
|
|
|
if ( in_array( $args['timestamp'], $next_schedulded ) ) { |
234
|
|
|
return new WP_Error( 'event-not-present', 'Unable to unschedule the event, the event doesn\'t exist', 400 ); |
|
|
|
|
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
$lock = $this->lock_cron(); |
238
|
|
|
wp_unschedule_event( $args['timestamp'], $hook, $arguments ); |
239
|
|
|
$this->maybe_unlock_cron( $lock ); |
240
|
|
|
return array( 'success' => true ); |
241
|
|
|
} |
242
|
|
|
$lock = $this->lock_cron(); |
243
|
|
|
wp_clear_scheduled_hook( $hook, $arguments ); |
244
|
|
|
$this->maybe_unlock_cron( $lock ); |
245
|
|
|
return array( 'success' => true ); |
246
|
|
|
} |
247
|
|
|
} |
248
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.