Completed
Branch feature/layout-rowspans (8787cf)
by Juliette
03:01
created

ZT_Debug_Bar_Cron::get_hours()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 1
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
				$hook_count = $this->get_arg_set_count( $time_cron_array );
266
				$show_time  = true;
267
268
				foreach ( $time_cron_array as $hook => $data ) {
269
					$row_attributes = $this->get_event_row_attributes( $time, $hook );
270
					$arg_set_count  = count( $data );
271
					$show_hook      = true;
272
273
					foreach ( $data as $hash => $info ) {
274
						echo // WPCS: xss ok.
275
						'
276
						<tr', $row_attributes, '>';
277
278
						if ( true === $show_time ) {
279
							$row_span = ( $hook_count > 1 ) ? ' rowspan="' . $hook_count . '"' : '';
280
281
							echo // WPCS: xss ok.
282
							'
283
							<td' . $row_span . '>
284
								', date( 'Y-m-d H:i:s', $time ), '<br />
285
								', $time, '<br />
286
								', esc_html( $this->display_past_time( human_time_diff( $time ), $time ) ), '
287
							</td>';
288
289
							$show_time = false;
290
							unset( $row_span );
291
						}
292
						if ( true === $show_hook ) {
293
							$row_span = ( $arg_set_count > 1 ) ? ' rowspan="' . $arg_set_count . '"' : '';
294
295
							echo // WPCS: xss ok.
296
							'
297
							<td' . $row_span . '>', esc_html( $hook ), '</td>';
298
299
							$show_hook = false;
300
							unset( $row_span );
301
						}
302
303
304
						// Report the schedule.
305
						echo '
306
							<td>';
307
						if ( ! empty( $info['schedule'] ) ) {
308
							echo esc_html( $info['schedule'] );
309
						} else {
310
							echo esc_html__( 'Single Event', 'zt-debug-bar-cron' );
311
						}
312
						echo '</td>';
313
314
						// Report the interval.
315
						echo '
316
							<td class="intervals">';
317
						if ( isset( $info['interval'] ) ) {
318
							$interval = (int) $info['interval'];
319
							/* TRANSLATORS: %s is number of seconds. */
320
							printf( esc_html__( '%ss', 'zt-debug-bar-cron' ) . '<br />', $interval ); // WPCS: XSS ok.
321
							/* TRANSLATORS: %s is number of minutes. */
322
							printf( esc_html__( '%sm', 'zt-debug-bar-cron' ) . '<br />', $this->get_minutes( $interval ) ); // WPCS: XSS ok.
323
							/* TRANSLATORS: %s is number of hours. */
324
							printf( esc_html__( '%sh', 'zt-debug-bar-cron' ), $this->get_hours( $interval ) ); // WPCS: XSS ok.
325
						} else {
326
							echo esc_html__( 'Single Event', 'zt-debug-bar-cron' );
327
						}
328
						echo '</td>';
329
330
						// Report the args.
331
						echo '
332
							<td>';
333
						$this->display_cron_arguments( $info['args'] );
334
						echo '</td>
335
						</tr>';
336
					}
337
				}
338
			}
339
340
			echo '
341
					</tbody>
342
				</table>';
343
		}
344
345
346
		/**
347
		 * Count the number of argument sets for a cron time.
348
		 *
349
		 * @param array $hook_array Array of hooks with argument sets.
350
		 *
351
		 * @return int
352
		 */
353
		private function get_arg_set_count( $hook_array ) {
354
			$count = 0;
355
			foreach ( $hook_array as $set ) {
356
				$count += count( $set );
357
			}
358
			return $count;
359
		}
360
361
362
		/**
363
		 * Create a HTML attribute string for an event row.
364
		 *
365
		 * @param int    $time Unix timestamp.
366
		 * @param string $hook Action hook for the cron job.
367
		 *
368
		 * @return string
369
		 */
370
		private function get_event_row_attributes( $time, $hook ) {
371
			$attributes = '';
372
			$classes    = array();
373
374
			// Add a class if past current time.
375
			if ( $this->is_time_in_past( $time ) ) {
376
				$classes[] = 'past';
377
			}
378
379
			// Verify if any events are hooked in.
380
			if ( false === has_action( $hook ) ) {
381
				/* TRANSLATORS: This text will display as a tooltip. %1$s will be replaced by a line break. */
382
				$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" ) . '"';
383
				$classes[]   = 'empty-event';
384
			}
385
386
			if ( ! empty( $classes ) ) {
387
				$attributes .= ' class="' . implode( ' ', $classes ) . '"';
388
			}
389
390
			return $attributes;
391
		}
392
393
394
		/**
395
		 * Displays the cron arguments in a readable format.
396
		 *
397
		 * @param mixed $args Cron argument(s).
398
		 *
399
		 * @return void
400
		 */
401
		private function display_cron_arguments( $args ) {
402
			// Arguments defaults to an empty array if no arguments are given.
403
			if ( is_array( $args ) && array() === $args ) {
404
				echo esc_html__( 'No Args', 'zt-debug-bar-cron' );
405
				return;
406
			}
407
408
			// Ok, we have an argument, let's pretty print it.
409
			$this->print_pretty_output( $args );
410
		}
411
412
413
		/**
414
		 * Displays all of the schedules defined.
415
		 *
416
		 * @return void
417
		 */
418
		private function display_schedules() {
419
			echo '
420
				<table class="zt-debug-bar-cron-table zt-debug-bar-cron-schedule-table">
421
					<thead><tr>
422
						<th class="col1">', esc_html__( 'Interval Hook', 'zt-debug-bar-cron' ), '</th>
423
						<th class="col2">', esc_html__( 'Interval (S)', 'zt-debug-bar-cron' ), '</th>
424
						<th class="col3">', esc_html__( 'Interval (M)', 'zt-debug-bar-cron' ), '</th>
425
						<th class="col4">', esc_html__( 'Interval (H)', 'zt-debug-bar-cron' ), '</th>
426
						<th class="col5">', esc_html__( 'Display Name', 'zt-debug-bar-cron' ), '</th>
427
					</tr></thead>
428
					<tbody>';
429
430
431
			$schedules = wp_get_schedules();
432
			ksort( $schedules );
433
			uasort( $schedules, array( $this, 'schedules_sorting' ) );
434
			foreach ( $schedules as $interval_hook => $data ) {
435
				$interval = (int) $data['interval'];
436
				echo // WPCS: XSS ok.
437
				'
438
						<tr>
439
							<td>', esc_html( $interval_hook ), '</td>
440
							<td>', $interval, '</td>
441
							<td>', $this->get_minutes( $interval ), '</td>
442
							<td>', $this->get_hours( $interval ), '</td>
443
							<td>', esc_html( $data['display'] ) . '</td>
444
						</tr>';
445
			}
446
447
			echo '
448
					</tbody>
449
				</table>';
450
		}
451
452
		/**
453
		 * Sorting method for cron scheldules. Order by schedules interval.
454
		 *
455
		 * @param array $a First element of comparison pair.
456
		 * @param array $b Second element of comparison pair.
457
		 *
458
		 * @return int Return 1 if $a argument 'interval' greater then $b argument 'interval', 0 if both intervals equivalent and -1 otherwise.
459
		 */
460
		function schedules_sorting( $a, $b ) {
0 ignored issues
show
Best Practice introduced by
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...
461
			if ( (int) $a['interval'] === (int) $b['interval'] ) {
462
				return 0;
463
			} else {
464
				return ( ( (int) $a['interval'] > (int) $b['interval'] ) ? 1 : -1 );
465
			}
466
		}
467
468
		/**
469
		 * Verify if a given timestamp is in the past or the future.
470
		 *
471
		 * @param int $time Unix timestamp.
472
		 *
473
		 * @return bool True if the time has passed, false otherwise.
474
		 */
475
		private function is_time_in_past( $time ) {
476
			return ( time() > $time && 'No' === $this->_doing_cron );
477
		}
478
479
480
		/**
481
		 * Transform a time in seconds to minutes rounded to 2 decimals.
482
		 *
483
		 * @param int $time Unix timestamp.
484
		 *
485
		 * @return int|float
486
		 */
487
		private function get_minutes( $time ) {
488
			return round( ( (int) $time / 60 ), 2 );
489
		}
490
491
492
		/**
493
		 * Transform a time in seconds to hours rounded to 2 decimals.
494
		 *
495
		 * @param int $time Unix timestamp.
496
		 *
497
		 * @return int|float
498
		 */
499
		private function get_hours( $time ) {
500
			return round( ( (int) $time / 3600 ), 2 );
501
		}
502
503
504
		/**
505
		 * Compares time with current time and adds ' ago' if current time is greater than event time.
506
		 *
507
		 * @param string $human_time Human readable time difference.
508
		 * @param int    $time       Unix time of event.
509
		 *
510
		 * @return string
511
		 */
512
		private function display_past_time( $human_time, $time ) {
513
			if ( time() > $time ) {
514
				/* TRANSLATORS: %s is a human readable time difference. */
515
				return sprintf( __( '%s ago', 'zt-debug-bar-cron' ), $human_time );
516
			} else {
517
				return $human_time;
518
			}
519
		}
520
521
522
		/**
523
		 * Load the pretty output class & set the recursion limit.
524
		 */
525
		private function load_debug_bar_pretty_output() {
526
			if ( ! class_exists( 'Debug_Bar_Pretty_Output' ) ) {
527
				require_once plugin_dir_path( __FILE__ ) . 'inc/debug-bar-pretty-output/class-debug-bar-pretty-output.php';
528
			}
529
530
			// Limit recursion depth if possible - method available since DBPO v1.4.
531
			if ( method_exists( 'Debug_Bar_Pretty_Output', 'limit_recursion' ) ) {
532
				Debug_Bar_Pretty_Output::limit_recursion( 2 );
533
			}
534
		}
535
536
537
		/**
538
		 * Print any type of variable with colour coding and a variable type indication.
539
		 *
540
		 * @param mixed $variable The variable to print.
541
		 */
542
		private function print_pretty_output( $variable ) {
543
			if ( defined( 'Debug_Bar_Pretty_Output::VERSION' ) ) {
544
				echo Debug_Bar_Pretty_Output::get_output( $variable, '', true ); // WPCS: XSS ok.
545
			} else {
546
				// An old version of the pretty output class was loaded.
547
				// Real possibility as there are several DB plugins using the pretty print class.
548
				Debug_Bar_Pretty_Output::output( $variable, '', true );
549
			}
550
		}
551
552
553
		/**
554
		 * Unset recursion depth limit for the pretty output class.
555
		 *
556
		 * @internal Method available since DBPO v1.4.
557
		 */
558
		private function reset_debug_bar_pretty_output() {
559
			if ( method_exists( 'Debug_Bar_Pretty_Output', 'unset_recursion_limit' ) ) {
560
				Debug_Bar_Pretty_Output::unset_recursion_limit();
561
			}
562
		}
563
	} // End of class ZT_Debug_Bar_Cron.
564
565
} // End of if class_exists wrapper.
566