Completed
Push — master ( c703ec...e03636 )
by Juliette
02:03
created

class-debug-bar-cron.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Debug Bar Cron - Debug Bar Panel.
4
 *
5
 * @package     WordPress\Plugins\Debug Bar Cron
6
 * @author      Zack Tollman, Helen Hou-Sandi, Juliette Reinders Folmer
7
 * @link        https://github.com/tollmanz/debug-bar-cron
8
 * @version     0.1.2
9
 * @license     http://creativecommons.org/licenses/GPL/2.0/ GNU General Public License, version 2 or higher
10
 */
11
12
// Avoid direct calls to this file.
13
if ( ! function_exists( 'add_action' ) ) {
14
	header( 'Status: 403 Forbidden' );
15
	header( 'HTTP/1.1 403 Forbidden' );
16
	exit();
17
}
18
19
/**
20
 * The class in this file extends the functionality provided by the parent plugin "Debug Bar".
21
 */
22
if ( ! class_exists( 'ZT_Debug_Bar_Cron' ) && class_exists( 'Debug_Bar_Panel' ) ) {
23
24
	/**
25
	 * Add a new Debug Bar Panel.
26
	 */
27
	class ZT_Debug_Bar_Cron extends Debug_Bar_Panel {
28
29
		const DBCRON_STYLES_VERSION = '1.0';
30
31
		const DBCRON_NAME = 'debug-bar-cron';
32
33
		/**
34
		 * Holds all of the cron events.
35
		 *
36
		 * @var array
37
		 */
38
		private $_crons;
39
40
		/**
41
		 * Holds only the cron events initiated by WP core.
42
		 *
43
		 * @var array
44
		 */
45
		private $_core_crons;
46
47
		/**
48
		 * Holds the cron events created by plugins or themes.
49
		 *
50
		 * @var array
51
		 */
52
		private $_user_crons;
53
54
		/**
55
		 * Total number of cron events.
56
		 *
57
		 * @var int
58
		 */
59
		private $_total_crons = 0;
60
61
		/**
62
		 * Total number of cron events created by plugins and themes.
63
		 *
64
		 * @var int
65
		 */
66
		private $_total_user_crons = 0;
67
68
		/**
69
		 * Total number of WP core cron events.
70
		 *
71
		 * @var int
72
		 */
73
		private $_total_core_crons = 0;
74
75
		/**
76
		 * Whether cron is being executed or not.
77
		 *
78
		 * @var string
79
		 */
80
		private $_doing_cron = 'No';
81
82
83
		/**
84
		 * Give the panel a title and set the enqueues.
85
		 *
86
		 * @return void
87
		 */
88
		public function init() {
89
			load_plugin_textdomain( 'zt-debug-bar-cron', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
90
			$this->title( __( 'Cron', 'zt-debug-bar-cron' ) );
91
			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts_styles' ) );
92
			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts_styles' ) );
93
		}
94
95
96
		/**
97
		 * Enqueue styles.
98
		 *
99
		 * @return void
100
		 */
101
		public function enqueue_scripts_styles() {
102
			$suffix = ( ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min' );
103
104
			wp_enqueue_style(
105
				self::DBCRON_NAME,
106
				plugins_url( 'css/' . self::DBCRON_NAME . $suffix . '.css', __FILE__ ),
107
				array( 'debug-bar' ),
108
				self::DBCRON_STYLES_VERSION
109
			);
110
		}
111
112
113
		/**
114
		 * Show the menu item in Debug Bar.
115
		 *
116
		 * @return void
117
		 */
118
		public function prerender() {
119
			$this->set_visible( true );
120
		}
121
122
123
		/**
124
		 * Show the contents of the page.
125
		 *
126
		 * @return void
127
		 */
128
		public function render() {
129
			$this->get_crons();
130
131
			$this->_doing_cron = get_transient( 'doing_cron' ) ? __( 'Yes', 'zt-debug-bar-cron' ) : __( 'No', 'zt-debug-bar-cron' );
132
133
			// Get the time of the next event.
134
			$cron_times          = ( is_array( $this->_crons ) ? array_keys( $this->_crons ) : array() );
135
			$unix_time_next_cron = (int) $cron_times[0];
136
			$time_next_cron      = date( 'Y-m-d H:i:s', $unix_time_next_cron );
137
138
			$human_time_next_cron = human_time_diff( $unix_time_next_cron );
139
140
			// Add a class if past current time and doing cron is not running.
141
			$times_class = $this->is_time_in_past( $unix_time_next_cron ) ? ' past' : '';
142
143
			$this->load_debug_bar_pretty_output();
144
145
			echo // WPCS: XSS ok.
146
			'
147
			<div class="debug-bar-cron">
148
				<h2><span>', esc_html__( 'Total Events', 'zt-debug-bar-cron' ), ':</span>', $this->_total_crons, '</h2>
149
				<h2><span>', esc_html__( 'Core Events', 'zt-debug-bar-cron' ), ':</span>', $this->_total_core_crons, '</h2>
150
				<h2><span>', esc_html__( 'Custom Events', 'zt-debug-bar-cron' ), ':</span>', $this->_total_user_crons, '</h2>
151
				<h2><span>', esc_html__( 'Doing Cron', 'zt-debug-bar-cron' ), ':</span>', esc_html( $this->_doing_cron ), '</h2>
152
				<h2 class="times', esc_attr( $times_class ), '"><span>', esc_html__( 'Next Event', 'zt-debug-bar-cron' ), ':</span>
153
					', esc_html( $time_next_cron ), '<br />
154
					', $unix_time_next_cron, '<br />
155
					', esc_html( $this->display_past_time( $human_time_next_cron, $unix_time_next_cron ) ), '
156
				</h2>
157
				<h2><span>', esc_html__( 'Current Time', 'zt-debug-bar-cron' ), ':</span>', esc_html( date( 'H:i:s' ) ), '</h2>
158
159
				<div class="clear"></div>
160
161
				<h3>', esc_html__( 'Schedules', 'zt-debug-bar-cron' ), '</h3>';
162
163
			$this->display_schedules();
164
165
			echo '
166
				<h3>', esc_html__( 'Custom Events', 'zt-debug-bar-cron' ), '</h3>';
167
168
			$this->display_events( $this->_user_crons, __( 'No Custom Events scheduled.', 'zt-debug-bar-cron' ) );
169
170
			echo '
171
				<h3>', esc_html__( 'Core Events', 'zt-debug-bar-cron' ), '</h3>';
172
173
			$this->display_events( $this->_core_crons, __( 'No Core Events scheduled.', 'zt-debug-bar-cron' ) );
174
175
			echo '
176
			</div>';
177
178
			$this->reset_debug_bar_pretty_output();
179
		}
180
181
182
		/**
183
		 * Gets all of the cron jobs.
184
		 *
185
		 * This function sorts the cron jobs into core crons, and custom crons. It also tallies
186
		 * a total count for the crons as this number is otherwise tough to get.
187
		 *
188
		 * @return array|null Array of crons.
189
		 */
190
		private function get_crons() {
191
			if ( is_array( $this->_crons ) ) {
192
				return $this->_crons;
193
			}
194
195
			$crons = _get_cron_array();
196
			if ( ! is_array( $crons ) || array() === $crons  ) {
197
				return $this->_crons;
198
			}
199
200
			$this->_crons = $crons;
201
202
			// Lists all crons that are defined in WP Core.
203
			// @internal To find all, search WP trunk for `wp_schedule_(single_)?event`.
204
			$core_cron_hooks = array(
205
				'do_pings',
206
				'importer_scheduled_cleanup',     // WP 3.1+.
207
				'publish_future_post',
208
				'update_network_counts',          // WP 3.1+.
209
				'upgrader_scheduled_cleanup',     // WP 3.3+.
210
				'wp_maybe_auto_update',           // WP 3.7+.
211
				'wp_scheduled_auto_draft_delete', // WP 3.4+.
212
				'wp_scheduled_delete',            // WP 2.9+.
213
				'wp_split_shared_term_batch',     // WP 4.3+.
214
				'wp_update_plugins',
215
				'wp_update_themes',
216
				'wp_version_check',
217
			);
218
219
			// Sort and count crons.
220
			foreach ( $this->_crons as $time => $time_cron_array ) {
221
				foreach ( $time_cron_array as $hook => $data ) {
222
					$this->_total_crons += count( $data );
223
224
					if ( in_array( $hook, $core_cron_hooks, true ) ) {
225
						$this->_core_crons[ $time ][ $hook ] = $data;
226
						$this->_total_core_crons            += count( $data );
227
					} else {
228
						$this->_user_crons[ $time ][ $hook ] = $data;
229
						$this->_total_user_crons            += count( $data );
230
					}
231
				}
232
			}
233
234
			return $this->_crons;
235
		}
236
237
238
		/**
239
		 * Displays the events in an easy to read table.
240
		 *
241
		 * @param array  $events        Array of events.
242
		 * @param string $no_events_msg Message to display if there are no events.
243
		 */
244
		private function display_events( $events, $no_events_msg ) {
245
			// Exit early if no events found.
246
			if ( ! is_array( $events ) || empty( $events ) ) {
247
				echo '
248
				<p>', esc_html( $no_events_msg ), '</p>';
249
				return;
250
			}
251
252
			echo '
253
				<table class="zt-debug-bar-cron-table zt-debug-bar-cron-event-table">
254
					<thead><tr>
255
						<th class="col1">', esc_html__( 'Next Execution', 'zt-debug-bar-cron' ), '</th>
256
						<th class="col2">', esc_html__( 'Hook', 'zt-debug-bar-cron' ), '</th>
257
						<th class="col3">', esc_html__( 'Interval Hook', 'zt-debug-bar-cron' ), '</th>
258
						<th class="col4">', esc_html__( 'Interval Value', 'zt-debug-bar-cron' ), '</th>
259
						<th class="col5">', esc_html__( 'Args', 'zt-debug-bar-cron' ), '</th>
260
					</tr></thead>
261
					<tbody>';
262
263
			foreach ( $events as $time => $time_cron_array ) {
264
				$time = (int) $time;
265
				foreach ( $time_cron_array as $hook => $data ) {
266
					$row_attributes = $this->get_event_row_attributes( $time, $hook );
267
268
					foreach ( $data as $hash => $info ) {
269
						echo // WPCS: xss ok.
270
						'
271
						<tr', $row_attributes, '>
272
							<td>
273
								', date( 'Y-m-d H:i:s', $time ), '<br />
274
								', $time, '<br />
275
								', esc_html( $this->display_past_time( human_time_diff( $time ), $time ) ), '
276
							</td>
277
							<td>', esc_html( $hook ), '</td>';
278
279
280
						// Report the schedule.
281
						echo '
282
							<td>';
283
						if ( ! empty( $info['schedule'] ) ) {
284
							echo esc_html( $info['schedule'] );
285
						} else {
286
							echo esc_html__( 'Single Event', 'zt-debug-bar-cron' );
287
						}
288
						echo '</td>';
289
290
						// Report the interval.
291
						echo '
292
							<td>';
293
						if ( isset( $info['interval'] ) ) {
294
							$interval = (int) $info['interval'];
295
							/* TRANSLATORS: %s is number of seconds. */
296
							printf( esc_html__( '%ss', 'zt-debug-bar-cron' ) . '<br />', $interval ); // WPCS: XSS ok.
297
							/* TRANSLATORS: %s is number of minutes. */
298
							printf( esc_html__( '%sm', 'zt-debug-bar-cron' ) . '<br />', $this->get_minutes( $interval ) ); // WPCS: XSS ok.
299
							/* TRANSLATORS: %s is number of hours. */
300
							printf( esc_html__( '%sh', 'zt-debug-bar-cron' ), $this->get_hours( $interval ) ); // WPCS: XSS ok.
301
						} else {
302
							echo esc_html__( 'Single Event', 'zt-debug-bar-cron' );
303
						}
304
						echo '</td>';
305
306
						// Report the args.
307
						echo '
308
							<td>';
309
						$this->display_cron_arguments( $info['args'] );
310
						echo '</td>
311
						</tr>';
312
					}
313
				}
314
			}
315
316
			echo '
317
					</tbody>
318
				</table>';
319
		}
320
321
322
		/**
323
		 * Create a HTML attribute string for an event row.
324
		 *
325
		 * @param int    $time Unix timestamp.
326
		 * @param string $hook Action hook for the cron job.
327
		 *
328
		 * @return string
329
		 */
330
		private function get_event_row_attributes( $time, $hook ) {
331
			$attributes = '';
332
			$classes    = array();
333
334
			// Add a class if past current time.
335
			if ( $this->is_time_in_past( $time ) ) {
336
				$classes[] = 'past';
337
			}
338
339
			// Verify if any events are hooked in.
340
			if ( false === has_action( $hook ) ) {
341
				/* TRANSLATORS: This text will display as a tooltip. %1$s will be replaced by a line break. */
342
				$attributes .= ' title="' . sprintf( esc_attr__( 'No actions are hooked into this event at this time.%1$sThe most likely reason for this is that a plugin or theme was de-activated or uninstalled and didn\'t clean up after itself.%1$sHowever, a number of plugins also use the best practice of lean loading and only hook in conditionally, so check carefully if you intend to remove this event.', 'zt-debug-bar-cron' ), "\n" ) . '"';
343
				$classes[]   = 'empty-event';
344
			}
345
346
			if ( ! empty( $classes ) ) {
347
				$attributes .= ' class="' . implode( ' ', $classes ) . '"';
348
			}
349
350
			return $attributes;
351
		}
352
353
354
		/**
355
		 * Displays the cron arguments in a readable format.
356
		 *
357
		 * @param mixed $args Cron argument(s).
358
		 *
359
		 * @return void
360
		 */
361
		private function display_cron_arguments( $args ) {
362
			// Arguments defaults to an empty array if no arguments are given.
363
			if ( is_array( $args ) && array() === $args ) {
364
				echo esc_html__( 'No Args', 'zt-debug-bar-cron' );
365
				return;
366
			}
367
368
			// Ok, we have an argument, let's pretty print it.
369
			$this->print_pretty_output( $args );
370
		}
371
372
373
		/**
374
		 * Displays all of the schedules defined.
375
		 *
376
		 * @return void
377
		 */
378
		private function display_schedules() {
379
			echo '
380
				<table class="zt-debug-bar-cron-table zt-debug-bar-cron-schedule-table">
381
					<thead><tr>
382
						<th class="col1">', esc_html__( 'Interval Hook', 'zt-debug-bar-cron' ), '</th>
383
						<th class="col2">', esc_html__( 'Interval (S)', 'zt-debug-bar-cron' ), '</th>
384
						<th class="col3">', esc_html__( 'Interval (M)', 'zt-debug-bar-cron' ), '</th>
385
						<th class="col4">', esc_html__( 'Interval (H)', 'zt-debug-bar-cron' ), '</th>
386
						<th class="col5">', esc_html__( 'Display Name', 'zt-debug-bar-cron' ), '</th>
387
					</tr></thead>
388
					<tbody>';
389
390
391
			$schedules = wp_get_schedules();
392
			ksort( $schedules );
393
			uasort( $schedules, array( $this, 'schedules_sorting' ) );
394
			foreach ( $schedules as $interval_hook => $data ) {
395
				$interval = (int) $data['interval'];
396
				echo // WPCS: XSS ok.
397
				'
398
						<tr>
399
							<td>', esc_html( $interval_hook ), '</td>
400
							<td>', $interval, '</td>
401
							<td>', $this->get_minutes( $interval ), '</td>
402
							<td>', $this->get_hours( $interval ), '</td>
403
							<td>', esc_html( $data['display'] ) . '</td>
404
						</tr>';
405
			}
406
407
			echo '
408
					</tbody>
409
				</table>';
410
		}
411
412
		/**
413
		 * Sorting method for cron scheldules. Order by schedules interval.
414
		 *
415
		 * @param array $a First element of comparison pair.
416
		 * @param array $b Second element of comparison pair.
417
		 *
418
		 * @return int Return 1 if $a argument 'interval' greater then $b argument 'interval', 0 if both intervals equivalent and -1 otherwise.
419
		 */
420
		function schedules_sorting( $a, $b ) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
421
			if ( (int) $a['interval'] === (int) $b['interval'] ) {
422
				return 0;
423
			} else {
424
				return ( ( (int) $a['interval'] > (int) $b['interval'] ) ? 1 : -1 );
425
			}
426
		}
427
428
		/**
429
		 * Verify if a given timestamp is in the past or the future.
430
		 *
431
		 * @param int $time Unix timestamp.
432
		 *
433
		 * @return bool True if the time has passed, false otherwise.
434
		 */
435
		private function is_time_in_past( $time ) {
436
			return ( time() > $time && 'No' === $this->_doing_cron );
437
		}
438
439
440
		/**
441
		 * Transform a time in seconds to minutes rounded to 2 decimals.
442
		 *
443
		 * @param int $time Unix timestamp.
444
		 *
445
		 * @return int|float
446
		 */
447
		private function get_minutes( $time ) {
448
			return round( ( (int) $time / 60 ), 2 );
449
		}
450
451
452
		/**
453
		 * Transform a time in seconds to hours rounded to 2 decimals.
454
		 *
455
		 * @param int $time Unix timestamp.
456
		 *
457
		 * @return int|float
458
		 */
459
		private function get_hours( $time ) {
460
			return round( ( (int) $time / 3600 ), 2 );
461
		}
462
463
464
		/**
465
		 * Compares time with current time and adds ' ago' if current time is greater than event time.
466
		 *
467
		 * @param string $human_time Human readable time difference.
468
		 * @param int    $time       Unix time of event.
469
		 *
470
		 * @return string
471
		 */
472
		private function display_past_time( $human_time, $time ) {
473
			if ( time() > $time ) {
474
				/* TRANSLATORS: %s is a human readable time difference. */
475
				return sprintf( __( '%s ago', 'zt-debug-bar-cron' ), $human_time );
476
			} else {
477
				return $human_time;
478
			}
479
		}
480
481
482
		/**
483
		 * Load the pretty output class & set the recursion limit.
484
		 */
485
		private function load_debug_bar_pretty_output() {
486
			if ( ! class_exists( 'Debug_Bar_Pretty_Output' ) ) {
487
				require_once plugin_dir_path( __FILE__ ) . 'inc/debug-bar-pretty-output/class-debug-bar-pretty-output.php';
488
			}
489
490
			// Limit recursion depth if possible - method available since DBPO v1.4.
491
			if ( method_exists( 'Debug_Bar_Pretty_Output', 'limit_recursion' ) ) {
492
				Debug_Bar_Pretty_Output::limit_recursion( 2 );
493
			}
494
		}
495
496
497
		/**
498
		 * Print any type of variable with colour coding and a variable type indication.
499
		 *
500
		 * @param mixed $variable The variable to print.
501
		 */
502
		private function print_pretty_output( $variable ) {
503
			if ( defined( 'Debug_Bar_Pretty_Output::VERSION' ) ) {
504
				echo Debug_Bar_Pretty_Output::get_output( $variable, '', true ); // WPCS: XSS ok.
505
			} else {
506
				// An old version of the pretty output class was loaded.
507
				// Real possibility as there are several DB plugins using the pretty print class.
508
				Debug_Bar_Pretty_Output::output( $variable, '', true );
509
			}
510
		}
511
512
513
		/**
514
		 * Unset recursion depth limit for the pretty output class.
515
		 *
516
		 * @internal Method available since DBPO v1.4.
517
		 */
518
		private function reset_debug_bar_pretty_output() {
519
			if ( method_exists( 'Debug_Bar_Pretty_Output', 'unset_recursion_limit' ) ) {
520
				Debug_Bar_Pretty_Output::unset_recursion_limit();
521
			}
522
		}
523
	} // End of class ZT_Debug_Bar_Cron.
524
525
} // End of if class_exists wrapper.
526