Completed
Branch feature/time-rounding (e295eb)
by Juliette
01:59
created

ZT_Debug_Bar_Cron::prerender()   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 0
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-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
					foreach ( $data as $hash => $info ) {
267
						echo '
268
						<tr>
269
							<td';
270
271
						// Add a class if past current time.
272
						if ( $this->is_time_in_past( $time ) ) {
273
							echo ' class="past"';
274
						}
275
276
						echo  // WPCS: XSS ok.
277
						'>
278
								', date( 'Y-m-d H:i:s', $time ), '<br />
279
								', $time, '<br />
280
								', esc_html( $this->display_past_time( human_time_diff( $time ), $time ) ), '
281
							</td>
282
							<td>', esc_html( $hook ), '</td>';
283
284
285
						// Report the schedule.
286
						echo '
287
							<td>';
288
						if ( $info['schedule'] ) {
289
							echo esc_html( $info['schedule'] );
290
						} else {
291
							echo esc_html__( 'Single Event', 'zt-debug-bar-cron' );
292
						}
293
						echo '</td>';
294
295
						// Report the interval.
296
						echo '
297
							<td>';
298
						if ( isset( $info['interval'] ) ) {
299
							$interval = (int) $info['interval'];
300
							/* TRANSLATORS: %s is number of seconds. */
301
							printf( esc_html__( '%ss', 'zt-debug-bar-cron' ) . '<br />', $interval ); // WPCS: XSS ok.
302
							/* TRANSLATORS: %s is number of minutes. */
303
							printf( esc_html__( '%sm', 'zt-debug-bar-cron' ) . '<br />', $this->get_minutes( $interval ) ); // WPCS: XSS ok.
304
							/* TRANSLATORS: %s is number of hours. */
305
							printf( esc_html__( '%sh', 'zt-debug-bar-cron' ), $this->get_hours( $interval ) ); // WPCS: XSS ok.
306
						} else {
307
							echo esc_html__( 'Single Event', 'zt-debug-bar-cron' );
308
						}
309
						echo '</td>';
310
311
						// Report the args.
312
						echo '
313
							<td>';
314
						$this->display_cron_arguments( $info['args'] );
315
						echo '</td>
316
						</tr>';
317
					}
318
				}
319
			}
320
321
			echo '
322
					</tbody>
323
				</table>';
324
		}
325
326
327
		/**
328
		 * Displays the cron arguments in a readable format.
329
		 *
330
		 * @param mixed $args Cron argument(s).
331
		 *
332
		 * @return void
333
		 */
334
		private function display_cron_arguments( $args ) {
335
			// Arguments defaults to an empty array if no arguments are given.
336
			if ( is_array( $args ) && array() === $args ) {
337
				echo esc_html__( 'No Args', 'zt-debug-bar-cron' );
338
				return;
339
			}
340
341
			// Ok, we have an argument, let's pretty print it.
342
			$this->print_pretty_output( $args );
343
		}
344
345
346
		/**
347
		 * Displays all of the schedules defined.
348
		 *
349
		 * @return void
350
		 */
351
		private function display_schedules() {
352
			echo '
353
				<table class="zt-debug-bar-cron-event-table">
354
					<thead><tr>
355
						<th class="col1">', esc_html__( 'Interval Hook', 'zt-debug-bar-cron' ), '</th>
356
						<th class="col2">', esc_html__( 'Interval (S)', 'zt-debug-bar-cron' ), '</th>
357
						<th class="col3">', esc_html__( 'Interval (M)', 'zt-debug-bar-cron' ), '</th>
358
						<th class="col4">', esc_html__( 'Interval (H)', 'zt-debug-bar-cron' ), '</th>
359
						<th class="col5">', esc_html__( 'Display Name', 'zt-debug-bar-cron' ), '</th>
360
					</tr></thead>
361
					<tbody>';
362
363
364
			$schedules = wp_get_schedules();
365
			foreach ( $schedules as $interval_hook => $data ) {
366
				$interval = (int) $data['interval'];
367
				echo  // WPCS: XSS ok.
368
				'
369
						<tr>
370
							<td>', esc_html( $interval_hook ), '</td>
371
							<td>', $interval, '</td>
372
							<td>', $this->get_minutes( $interval ), '</td>
373
							<td>', $this->get_hours( $interval ), '</td>
374
							<td>', esc_html( $data['display'] ) . '</td>
375
						</tr>';
376
			}
377
378
			echo '
379
					</tbody>
380
				</table>';
381
		}
382
383
384
		/**
385
		 * Verify if a given timestamp is in the past or the future.
386
		 *
387
		 * @param int $time Unix timestamp.
388
		 *
389
		 * @return bool True if the time has passed, false otherwise.
390
		 */
391
		private function is_time_in_past( $time ) {
392
			return ( time() > $time && 'No' === $this->_doing_cron );
393
		}
394
395
396
		/**
397
		 * Transform a time in seconds to minutes rounded to 2 decimals.
398
		 *
399
		 * @param int $time Unix timestamp.
400
		 *
401
		 * @return int|float
402
		 */
403
		private function get_minutes( $time ) {
404
			return $this->bc_calc( (int) $time, '/', 60, true, 2 );
405
		}
406
407
408
		/**
409
		 * Transform a time in seconds to hours rounded to 2 decimals.
410
		 *
411
		 * @param int $time Unix timestamp.
412
		 *
413
		 * @return int|float
414
		 */
415
		private function get_hours( $time ) {
416
			return $this->bc_calc( (int) $time, '/', 3600, true, 2 );
417
		}
418
419
420
		// @codingStandardsIgnoreStart Code taken from elsewhere, ignoring style to allow for easier updating.
421
		/**
422
		 * Do simple reliable floating point calculations without the risk of wrong results.
423
		 *
424
		 * @see http://floating-point-gui.de/
425
		 * @see the big red warning on http://php.net/language.types.float.php
426
		 *
427
		 * In the rare case that the bcmath extension would not be loaded, it will return the
428
		 * normal calculation results.
429
		 *
430
		 * @see Source: https://gist.github.com/jrfnl/8449978
431
		 *
432
		 * @param   mixed   $number1   Scalar (string/int/float/bool).
433
		 * @param   string  $action    Calculation action to execute. Valid input:
434
		 *                               '+' or 'add' or 'addition',
435
		 *                               '-' or 'sub' or 'subtract',
436
		 *                               '*' or 'mul' or 'multiply',
437
		 *                               '/' or 'div' or 'divide',
438
		 *                               '%' or 'mod' or 'modulus',
439
		 *                               '=' or 'comp' or 'compare'.
440
		 * @param   mixed   $number2	Scalar (string/int/float/bool).
441
		 * @param   bool    $round      Whether or not to round the result. Defaults to false.
442
		 *                              Will be disregarded for a compare operation.
443
		 * @param   int     $decimals   Decimals for rounding operation. Defaults to 0.
444
		 * @param   int     $precision  Calculation precision. Defaults to 10.
445
		 *
446
		 * @return  mixed               Calculation Result or false if either or the numbers isn't scalar or
447
		 *                              an invalid operation was passed.
448
		 *                              - for compare the result will always be an integer.
449
		 *                              - for all other operations, the result will either be an integer (preferred)
450
		 *                                or a float.
451
		 */
452
		private function bc_calc( $number1, $action, $number2, $round = false, $decimals = 0, $precision = 10 ) {
453
			if ( ! is_scalar( $number1 ) || ! is_scalar( $number2 ) ) {
454
				return false;
455
			}
456
457
			$bc = extension_loaded( 'bcmath' );
458
459
			if ( $bc ) {
460
				$number1 = number_format( $number1, 10, '.', '' );
461
				$number2 = number_format( $number2, 10, '.', '' );
462
			}
463
464
			$result  = null;
465
			$compare = false;
466
467
			switch ( $action ) {
468
				case '+':
469
				case 'add':
470
				case 'addition':
471
					$result = ( $bc ) ? bcadd( $number1, $number2, $precision ) /* string */ : ( $number1 + $number2 );
472
					break;
473
474
				case '-':
475
				case 'sub':
476
				case 'subtract':
477
					$result = ( $bc ) ? bcsub( $number1, $number2, $precision ) /* string */ : ( $number1 - $number2 );
478
					break;
479
480
				case '*':
481
				case 'mul':
482
				case 'multiply':
483
					$result = ( $bc ) ? bcmul( $number1, $number2, $precision ) /* string */ : ( $number1 * $number2 );
484
					break;
485
486
				case '/':
487
				case 'div':
488
				case 'divide':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
489
					if ( $bc ) {
490
						$result = bcdiv( $number1, $number2, $precision ); // String, or NULL if right_operand is 0.
491
					}
492
					else if ( $number2 != 0 ) {
493
						$result = $number1 / $number2;
494
					}
495
496
					if ( ! isset( $result ) ) {
497
						$result = 0;
498
					}
499
					break;
500
501
				case '%':
502
				case 'mod':
503
				case 'modulus':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
504
					if ( $bc ) {
505
						$result = bcmod( $number1, $number2, $precision ); // String, or NULL if modulus is 0.
506
					}
507
					else if ( $number2 != 0 ) {
508
						$result = $number1 % $number2;
509
					}
510
511
					if ( ! isset( $result ) ) {
512
						$result = 0;
513
					}
514
					break;
515
516
				case '=':
517
				case 'comp':
518
				case 'compare':
519
					$compare = true;
520
					if ( $bc ) {
521
						$result = bccomp( $number1, $number2, $precision ); // Returns int 0, 1 or -1.
522
					}
523
					else {
524
						$result = ( $number1 == $number2 ) ? 0 : ( ( $number1 > $number2 ) ? 1 : -1 );
525
					}
526
					break;
527
			}
528
529
			if ( isset( $result ) ) {
530
				if ( $compare === false ) {
531
					if ( $round === true ) {
532
						$result = round( floatval( $result ), $decimals );
533
						if ( $decimals === 0 ) {
534
							$result = (int) $result;
535
						}
536
					}
537
					else {
538
						$result = ( intval( $result ) == $result ) ? intval( $result ) : floatval( $result );
539
					}
540
				}
541
				return $result;
542
			}
543
			return false;
544
		}
545
		// @codingStandardsIgnoreEnd
546
547
548
		/**
549
		 * Compares time with current time and adds ' ago' if current time is greater than event time.
550
		 *
551
		 * @param string $human_time Human readable time difference.
552
		 * @param int    $time       Unix time of event.
553
		 *
554
		 * @return string
555
		 */
556
		private function display_past_time( $human_time, $time ) {
557
			if ( time() > $time ) {
558
				/* TRANSLATORS: %s is a human readable time difference. */
559
				return sprintf( __( '%s ago', 'zt-debug-bar-cron' ), $human_time );
560
			} else {
561
				return $human_time;
562
			}
563
		}
564
565
566
		/**
567
		 * Load the pretty output class & set the recursion limit.
568
		 */
569
		private function load_debug_bar_pretty_output() {
570
			if ( ! class_exists( 'Debug_Bar_Pretty_Output' ) ) {
571
				require_once plugin_dir_path( __FILE__ ) . 'inc/debug-bar-pretty-output/class-debug-bar-pretty-output.php';
572
			}
573
574
			// Limit recursion depth if possible - method available since DBPO v1.4.
575
			if ( method_exists( 'Debug_Bar_Pretty_Output', 'limit_recursion' ) ) {
576
				Debug_Bar_Pretty_Output::limit_recursion( 2 );
577
			}
578
		}
579
580
581
		/**
582
		 * Print any type of variable with colour coding and a variable type indication.
583
		 *
584
		 * @param mixed $variable The variable to print.
585
		 */
586
		private function print_pretty_output( $variable ) {
587
			if ( defined( 'Debug_Bar_Pretty_Output::VERSION' ) ) {
588
				echo Debug_Bar_Pretty_Output::get_output( $variable, '', true ); // WPCS: XSS ok.
589
			} else {
590
				// An old version of the pretty output class was loaded.
591
				// Real possibility as there are several DB plugins using the pretty print class.
592
				Debug_Bar_Pretty_Output::output( $variable, '', true );
593
			}
594
		}
595
596
597
		/**
598
		 * Unset recursion depth limit for the pretty output class.
599
		 *
600
		 * @internal Method available since DBPO v1.4.
601
		 */
602
		private function reset_debug_bar_pretty_output() {
603
			if ( method_exists( 'Debug_Bar_Pretty_Output', 'unset_recursion_limit' ) ) {
604
				Debug_Bar_Pretty_Output::unset_recursion_limit();
605
			}
606
		}
607
	} // End of class ZT_Debug_Bar_Cron.
608
609
} // End of if class_exists wrapper.
610