PluginProbe
Docket Cache – Object Cache Accelerator / trunk
Docket Cache – Object Cache Accelerator vtrunk
26.04.05 trunk 22.07.01 22.07.02 22.07.03 22.07.04 22.07.05 23.08.01 23.08.02 24.07.01 24.07.02 24.07.03 24.07.04 24.07.05 24.07.06 24.07.07 26.04.03 26.04.04
docket-cache / includes / src / EventList.php

EventList.php in Docket Cache – Object Cache Accelerator trunk, at includes/src/EventList.php

446 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Docket Cache.
4 *
5 * @author Nawawi Jamili
6 * @license MIT
7 *
8 * @see https://github.com/nawawi/docket-cache
9 */
10
11 /*
12 * Credits:
13 * plugins/wp-crontrol/src/event-list-table.php
14 * plugins/wp-crontrol/src/event.php
15 * plugins/query-monitor/classes/Util.php
16 */
17
18 namespace Nawawi\DocketCache;
19
20 \defined('ABSPATH') || exit;
21
22 if (!class_exists('\\WP_List_Table', false)) {
23 require_once trailingslashit(ABSPATH).'wp-admin/includes/class-wp-list-table.php';
24 }
25
26 class EventList extends \WP_List_Table
27 {
28 private $pt;
29
30 public function __construct(Plugin $pt)
31 {
32 parent::__construct(
33 [
34 'singular' => 'eventlist-event',
35 'plural' => 'eventlist-events',
36 'ajax' => false,
37 'screen' => 'eventlist-events',
38 ]
39 );
40
41 $this->pt = $pt;
42 }
43
44 public function get_schedules()
45 {
46 $schedules = wp_get_schedules();
47 uasort(
48 $schedules,
49 function (array $a, array $b) {
50 return $a['interval'] - $b['interval'];
51 }
52 );
53
54 array_walk(
55 $schedules,
56 function (array &$schedule, $name) {
57 $schedule['name'] = $name;
58 }
59 );
60
61 return $schedules;
62 }
63
64 public function get_crons()
65 {
66 $is_switch = $this->pt->switch_cron_site();
67
68 $crons = $this->pt->get_crons(true);
69 $events = [];
70
71 if (empty($crons)) {
72 if ($is_switch) {
73 restore_current_blog();
74 }
75
76 return [];
77 }
78
79 foreach ($crons as $time => $cron) {
80 foreach ($cron as $hook => $dings) {
81 if (!has_action($hook)) {
82 // wp_clear_scheduled_hook($hook);
83 continue;
84 }
85 foreach ($dings as $sig => $data) {
86 $events[$hook.'-'.$sig.'-'.$time] = (object) [
87 'hook' => $hook,
88 'time' => $time,
89 'sig' => $sig,
90 'args' => $data['args'],
91 'schedule' => $data['schedule'],
92 'interval' => isset($data['interval']) ? $data['interval'] : null,
93 ];
94 }
95 }
96 }
97
98 uasort(
99 $events,
100 function ($a, $b) {
101 if ($a->time === $b->time) {
102 return 0;
103 }
104
105 return ($a->time > $b->time) ? 1 : -1;
106 }
107 );
108
109 if ($is_switch) {
110 restore_current_blog();
111 }
112
113 return $events;
114 }
115
116 public function shorten_path($callback)
117 {
118 return preg_replace_callback(
119 '@\\\\[a-zA-Z0-9_\\\\]{4,}\\\\@',
120 function ($mm) {
121 preg_match_all('@\\\\([a-zA-Z0-9_])@', $mm[0], $m);
122
123 return '\\'.implode('\\', $m[1]).'\\';
124 },
125 $callback
126 );
127 }
128
129 public function populate_callback($callback)
130 {
131 $callback = (array) $callback;
132
133 if (method_exists('\QM_Util', 'populate_callback')) {
134 return \QM_Util::populate_callback($callback);
135 }
136
137 if (\is_string($callback['function']) && (false !== strpos($callback['function'], '::'))) {
138 $callback['function'] = explode('::', $callback['function']);
139 }
140
141 if (\is_array($callback['function'])) {
142 if (\is_object($callback['function'][0])) {
143 $class = \get_class($callback['function'][0]);
144 $access = '->';
145 } else {
146 $class = $callback['function'][0];
147 $access = '::';
148 }
149
150 $callback['name'] = $this->shorten_path($class.$access.$callback['function'][1].'()');
151 } elseif (\is_object($callback['function'])) {
152 if (is_a($callback['function'], 'Closure')) {
153 $callback['name'] = 'Closure';
154 } else {
155 $class = \get_class($callback['function']);
156
157 $callback['name'] = $this->shorten_path($class).'->__invoke()';
158 }
159 } else {
160 $callback['name'] = $this->shorten_path($callback['function']).'()';
161 }
162
163 return $callback;
164 }
165
166 public function pretty_args($input)
167 {
168 $json_options = 0;
169
170 if (\defined('JSON_UNESCAPED_SLASHES')) {
171 $json_options |= \JSON_UNESCAPED_SLASHES;
172 }
173 if (\defined('JSON_PRETTY_PRINT')) {
174 $json_options |= \JSON_PRETTY_PRINT;
175 }
176
177 return wp_json_encode($input, $json_options);
178 }
179
180 public function get_hook_callbacks($name)
181 {
182 global $wp_filter;
183
184 $actions = [];
185
186 if (isset($wp_filter[$name])) {
187 $action = $wp_filter[$name];
188
189 foreach ($action as $priority => $callbacks) {
190 foreach ($callbacks as $callback) {
191 $callback = $this->populate_callback($callback);
192
193 $actions[] = [
194 'priority' => $priority,
195 'callback' => $callback,
196 ];
197 }
198 }
199 }
200
201 return $actions;
202 }
203
204 public function get_schedule_name(\stdClass $event)
205 {
206 $schedules = $this->get_schedules();
207
208 if (isset($event->schedule) && isset($schedules[$event->schedule])) {
209 return $schedules[$event->schedule]['display'];
210 }
211
212 /* translators: %s: Schedule name */
213 $error_text = sprintf(__('Unknown (%s)', 'docket-cache'), $event->schedule);
214
215 return new \WP_Error('unknown_schedule', $error_text);
216 }
217
218 public function interval($since)
219 {
220 // Array of time period chunks.
221 $chunks = [
222 /* translators: %s: The number of years in an interval of time. */
223 [60 * 60 * 24 * 365, _n_noop('%s year', '%s years', 'docket-cache')],
224 /* translators: %s: The number of months in an interval of time. */
225 [60 * 60 * 24 * 30, _n_noop('%s month', '%s months', 'docket-cache')],
226 /* translators: %s: The number of weeks in an interval of time. */
227 [60 * 60 * 24 * 7, _n_noop('%s week', '%s weeks', 'docket-cache')],
228 /* translators: %s: The number of days in an interval of time. */
229 [60 * 60 * 24, _n_noop('%s day', '%s days', 'docket-cache')],
230 /* translators: %s: The number of hours in an interval of time. */
231 [60 * 60, _n_noop('%s hour', '%s hours', 'docket-cache')],
232 /* translators: %s: The number of minutes in an interval of time. */
233 [60, _n_noop('%s minute', '%s minutes', 'docket-cache')],
234 /* translators: %s: The number of seconds in an interval of time. */
235 [1, _n_noop('%s second', '%s seconds', 'docket-cache')],
236 ];
237
238 if ($since <= 0) {
239 return __('now', 'docket-cache');
240 }
241
242 $j = \count($chunks);
243
244 for ($i = 0; $i < $j; ++$i) {
245 $seconds = $chunks[$i][0];
246 $name = $chunks[$i][1];
247
248 $count = floor($since / $seconds);
249 if ($count) {
250 break;
251 }
252 }
253
254 $output = sprintf(translate_nooped_plural($name, $count, 'docket-cache'), $count);
255
256 if ($i + 1 < $j) {
257 $seconds2 = $chunks[$i + 1][0];
258 $name2 = $chunks[$i + 1][1];
259 $count2 = floor(($since - ($seconds * $count)) / $seconds2);
260 if ($count2) {
261 $output .= ' '.sprintf(translate_nooped_plural($name2, $count2, 'docket-cache'), $count2);
262 }
263 }
264
265 return $output;
266 }
267
268 public function is_late($event)
269 {
270 $event = (object) $event;
271 $until = $event->time - time();
272
273 return $until < (0 - (10 * MINUTE_IN_SECONDS));
274 }
275
276 public function prepare_items()
277 {
278 $events = $this->get_crons();
279
280 if (!empty($_GET['s'])) {
281 $s = sanitize_text_field(wp_unslash($_GET['s']));
282
283 $events = array_filter(
284 $events,
285 function ($event) use ($s) {
286 return false !== strpos($event->hook, $s);
287 }
288 );
289 }
290
291 $count = \count($events);
292 $per_page = 50;
293 $offset = ($this->get_pagenum() - 1) * $per_page;
294
295 $this->items = \array_slice($events, $offset, $per_page);
296
297 $this->set_pagination_args(
298 [
299 'total_items' => $count,
300 'per_page' => $per_page,
301 'total_pages' => ceil($count / $per_page),
302 ]
303 );
304 }
305
306 public function get_columns()
307 {
308 /* translators: %s: UTC offset */
309 $next_run_text = sprintf(esc_html__('Next Schedule (%s)', 'docket-cache'), $this->pt->get_utc_offset());
310
311 return [
312 'eventlist_hook' => esc_html__('Hook', 'docket-cache'),
313 'eventlist_args' => esc_html__('Arguments', 'docket-cache'),
314 'eventlist_next' => $next_run_text,
315 'eventlist_actions' => esc_html__('Action', 'docket-cache'),
316 'eventlist_recurrence' => esc_html__('Recurrence', 'docket-cache'),
317 ];
318 }
319
320 public function get_table_classes()
321 {
322 return ['widefat', 'striped', $this->_args['plural']];
323 }
324
325 protected function handle_row_actions($event, $column_name, $primary)
326 {
327 if ($primary !== $column_name) {
328 return '';
329 }
330
331 $action = $this->pt->action_query(
332 'runeventuno-cronbot',
333 [
334 'idx' => 'cronbot',
335 'ehk' => rawurlencode($event->hook),
336 'eky' => rawurlencode($event->sig),
337 ]
338 );
339 $links[] = "<a href='".$action."'>".esc_html__('Run Now', 'docket-cache').'</a>';
340
341 return $this->row_actions($links);
342 }
343
344 public function column_eventlist_hook($event)
345 {
346 return esc_html($event->hook);
347 }
348
349 public function column_eventlist_args($event)
350 {
351 if (!empty($event->args)) {
352 if (\count($event->args) > 1) {
353 return sprintf(
354 '<pre>%s</pre>',
355 esc_html($this->pretty_args($event->args))
356 );
357 }
358
359 return $event->args[0];
360 }
361
362 return sprintf(
363 '<em>%s</em>',
364 esc_html__('None', 'docket-cache')
365 );
366 }
367
368 public function column_eventlist_actions($event)
369 {
370 $hook_callbacks = $this->get_hook_callbacks($event->hook);
371
372 if (!empty($hook_callbacks)) {
373 $callbacks = [];
374
375 foreach ($hook_callbacks as $callback) {
376 $callbacks[] = '<code>'.$callback['callback']['name'].'</code>';
377 }
378
379 return implode('<br>', $callbacks);
380 }
381
382 return sprintf(
383 '<span class="status-eventlist-warning">%s</span>',
384 esc_html__('None', 'docket-cache')
385 );
386 }
387
388 public function column_eventlist_next($event)
389 {
390 $date_utc = gmdate('Y-m-d\TH:i:s+00:00', $event->time);
391 $date_local = get_date_from_gmt(date('Y-m-d H:i:s', $event->time), 'Y-m-d H:i:s');
392
393 $time = sprintf(
394 '<time datetime="%1$s">%2$s</time>',
395 esc_attr($date_utc),
396 esc_html($date_local)
397 );
398
399 $until = $event->time - time();
400 $late = $this->is_late($event);
401
402 if ($late) {
403 /* translators: %s: Time period, for example "8 minutes" */
404 $ago = sprintf(__('%s ago', 'docket-cache'), $this->interval(abs($until)));
405
406 return sprintf(
407 '%s<br><span class="status-eventlist-warning">%s</span>',
408 $time,
409 esc_html($ago)
410 );
411 }
412
413 return sprintf(
414 '%s<br>%s',
415 $time,
416 esc_html($this->interval($until))
417 );
418 }
419
420 public function column_eventlist_recurrence($event)
421 {
422 if ($event->schedule) {
423 $schedule_name = $this->get_schedule_name($event);
424 if (is_wp_error($schedule_name)) {
425 return sprintf(
426 '<span class="status-eventlist-error"><span class="dashicons dashicons-warning" aria-hidden="true"></span> %s</span>',
427 esc_html($schedule_name->get_error_message())
428 );
429 }
430
431 return esc_html($schedule_name);
432 }
433
434 return esc_html__('Non-repeating', 'docket-cache');
435 }
436
437 public function no_items()
438 {
439 if (empty($_GET['s'])) {
440 esc_html_e('There are currently no scheduled cron events.', 'docket-cache');
441 } else {
442 esc_html_e('No matching cron events.', 'docket-cache');
443 }
444 }
445 }
446