Completed
Branch feature/reduce-code-complexity (2a6dde)
by Juliette
02:17
created

ZT_Debug_Bar_Cron::display_cron_arguments()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 10
rs 9.4286
cc 3
eloc 5
nc 2
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
		 * Lists all crons that are defined in WP Core.
84
		 *
85
		 * @var array
86
		 *
87
		 * @internal To find all, search WP trunk for `wp_schedule_(single_)?event`.
88
		 */
89
		private $core_cron_hooks = array(
90
			'do_pings',
91
			'importer_scheduled_cleanup',     // WP 3.1+.
92
			'publish_future_post',
93
			'update_network_counts',          // WP 3.1+.
94
			'upgrader_scheduled_cleanup',     // WP 3.3+.
95
			'wp_maybe_auto_update',           // WP 3.7+.
96
			'wp_scheduled_auto_draft_delete', // WP 3.4+.
97
			'wp_scheduled_delete',            // WP 2.9+.
98
			'wp_split_shared_term_batch',     // WP 4.3+.
99
			'wp_update_plugins',
100
			'wp_update_themes',
101
			'wp_version_check',
102
		);
103
104
105
106
		/**
107
		 * Give the panel a title and set the enqueues.
108
		 *
109
		 * @return void
110
		 */
111
		public function init() {
112
			load_plugin_textdomain( 'zt-debug-bar-cron', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
113
			$this->title( __( 'Cron', 'zt-debug-bar-cron' ) );
114
			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts_styles' ) );
115
			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts_styles' ) );
116
		}
117
118
119
		/**
120
		 * Enqueue styles.
121
		 *
122
		 * @return void
123
		 */
124
		public function enqueue_scripts_styles() {
125
			$suffix = ( ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min' );
126
127
			wp_enqueue_style(
128
				self::DBCRON_NAME,
129
				plugins_url( 'css/' . self::DBCRON_NAME . $suffix . '.css', __FILE__ ),
130
				array( 'debug-bar' ),
131
				self::DBCRON_STYLES_VERSION
132
			);
133
		}
134
135
136
		/**
137
		 * Show the menu item in Debug Bar.
138
		 *
139
		 * @return void
140
		 */
141
		public function prerender() {
142
			$this->set_visible( true );
143
		}
144
145
146
		/**
147
		 * Show the contents of the page.
148
		 *
149
		 * @return void
150
		 */
151
		public function render() {
152
			$this->get_crons();
153
154
			$this->_doing_cron = get_transient( 'doing_cron' ) ? __( 'Yes', 'zt-debug-bar-cron' ) : __( 'No', 'zt-debug-bar-cron' );
155
156
			// Get the time of the next event.
157
			$cron_times          = ( is_array( $this->_crons ) ? array_keys( $this->_crons ) : array() );
158
			$unix_time_next_cron = (int) $cron_times[0];
159
			$time_next_cron      = date( 'Y-m-d H:i:s', $unix_time_next_cron );
160
161
			$human_time_next_cron = human_time_diff( $unix_time_next_cron );
162
163
			// Add a class if past current time and doing cron is not running.
164
			$times_class = $this->is_time_in_past( $unix_time_next_cron ) ? ' past' : '';
165
166
			$this->load_debug_bar_pretty_output();
167
168
			echo // WPCS: XSS ok.
169
			'
170
			<div class="debug-bar-cron">
171
				<h2><span>', esc_html__( 'Total Events', 'zt-debug-bar-cron' ), ':</span>', $this->_total_crons, '</h2>
172
				<h2><span>', esc_html__( 'Core Events', 'zt-debug-bar-cron' ), ':</span>', $this->_total_core_crons, '</h2>
173
				<h2><span>', esc_html__( 'Custom Events', 'zt-debug-bar-cron' ), ':</span>', $this->_total_user_crons, '</h2>
174
				<h2><span>', esc_html__( 'Doing Cron', 'zt-debug-bar-cron' ), ':</span>', esc_html( $this->_doing_cron ), '</h2>
175
				<h2 class="times', esc_attr( $times_class ), '"><span>', esc_html__( 'Next Event', 'zt-debug-bar-cron' ), ':</span>
176
					', esc_html( $time_next_cron ), '<br />
177
					', $unix_time_next_cron, '<br />
178
					', esc_html( $this->display_past_time( $human_time_next_cron, $unix_time_next_cron ) ), '
179
				</h2>
180
				<h2><span>', esc_html__( 'Current Time', 'zt-debug-bar-cron' ), ':</span>', esc_html( date( 'H:i:s' ) ), '</h2>
181
182
				<div class="clear"></div>
183
184
				<h3>', esc_html__( 'Schedules', 'zt-debug-bar-cron' ), '</h3>';
185
186
			$this->display_schedules();
187
188
			echo '
189
				<h3>', esc_html__( 'Custom Events', 'zt-debug-bar-cron' ), '</h3>';
190
191
			$this->display_events( $this->_user_crons, __( 'No Custom Events scheduled.', 'zt-debug-bar-cron' ) );
192
193
			echo '
194
				<h3>', esc_html__( 'Core Events', 'zt-debug-bar-cron' ), '</h3>';
195
196
			$this->display_events( $this->_core_crons, __( 'No Core Events scheduled.', 'zt-debug-bar-cron' ) );
197
198
			echo '
199
			</div>';
200
201
			$this->reset_debug_bar_pretty_output();
202
		}
203
204
205
		/**
206
		 * Gets all of the cron jobs.
207
		 *
208
		 * @return array|null Array of crons.
209
		 */
210
		private function get_crons() {
211
			if ( is_array( $this->_crons ) ) {
212
				return $this->_crons;
213
			}
214
215
			$crons = _get_cron_array();
216
			if ( is_array( $crons ) && ! empty( $crons ) ) {
217
				$this->_crons = $crons;
218
				$this->sort_count_crons();
219
			}
220
221
			return $this->_crons;
222
		}
223
224
225
		/**
226
		 * Sort and count crons.
227
		 *
228
		 * This function sorts the cron jobs into core crons, and custom crons. It also tallies
229
		 * a total count for the crons as this number is otherwise tough to get.
230
		 */
231
		private function sort_count_crons() {
232
			foreach ( $this->_crons as $time => $time_cron_array ) {
233
				foreach ( $time_cron_array as $hook => $data ) {
234
					$this->_total_crons += count( $data );
235
236
					if ( in_array( $hook, $this->core_cron_hooks, true ) ) {
237
						$this->_core_crons[ $time ][ $hook ] = $data;
238
						$this->_total_core_crons            += count( $data );
239
					} else {
240
						$this->_user_crons[ $time ][ $hook ] = $data;
241
						$this->_total_user_crons            += count( $data );
242
					}
243
				}
244
			}
245
		}
246
247
248
		/**
249
		 * Displays the events in an easy to read table.
250
		 *
251
		 * @param array  $events        Array of events.
252
		 * @param string $no_events_msg Message to display if there are no events.
253
		 */
254
		private function display_events( $events, $no_events_msg ) {
255
			// Exit early if no events found.
256
			if ( ! is_array( $events ) || empty( $events ) ) {
257
				echo '
258
				<p>', esc_html( $no_events_msg ), '</p>';
259
				return;
260
			}
261
262
			echo '
263
				<table class="zt-debug-bar-cron-table zt-debug-bar-cron-event-table">
264
					<thead><tr>
265
						<th class="col1">', esc_html__( 'Next Execution', 'zt-debug-bar-cron' ), '</th>
266
						<th class="col2">', esc_html__( 'Hook', 'zt-debug-bar-cron' ), '</th>
267
						<th class="col3">', esc_html__( 'Interval Hook', 'zt-debug-bar-cron' ), '</th>
268
						<th class="col4">', esc_html__( 'Interval Value', 'zt-debug-bar-cron' ), '</th>
269
						<th class="col5">', esc_html__( 'Args', 'zt-debug-bar-cron' ), '</th>
270
					</tr></thead>
271
					<tbody>';
272
273
			foreach ( $events as $time => $time_cron_array ) {
274
				$time       = (int) $time;
275
				$hook_count = $this->get_arg_set_count( $time_cron_array );
276
				$show_time  = true;
277
278
				foreach ( $time_cron_array as $hook => $data ) {
279
					$row_attributes = $this->get_event_row_attributes( $time, $hook );
280
					$arg_set_count  = count( $data );
281
					$show_hook      = true;
282
283
					foreach ( $data as $hash => $info ) {
284
						echo // WPCS: xss ok.
285
						'
286
						<tr', $row_attributes, '>';
287
288
						if ( true === $show_time ) {
289
							$row_span = ( $hook_count > 1 ) ? ' rowspan="' . $hook_count . '"' : '';
290
291
							echo // WPCS: xss ok.
292
							'
293
							<td' . $row_span . '>
294
								', date( 'Y-m-d H:i:s', $time ), '<br />
295
								', $time, '<br />
296
								', esc_html( $this->display_past_time( human_time_diff( $time ), $time ) ), '
297
							</td>';
298
299
							$show_time = false;
300
							unset( $row_span );
301
						}
302
						if ( true === $show_hook ) {
303
							$row_span = ( $arg_set_count > 1 ) ? ' rowspan="' . $arg_set_count . '"' : '';
304
305
							echo // WPCS: xss ok.
306
							'
307
							<td' . $row_span . '>', esc_html( $hook ), '</td>';
308
309
							$show_hook = false;
310
							unset( $row_span );
311
						}
312
313
314
						// Report the schedule.
315
						echo '
316
							<td>';
317
						$this->display_event_schedule( $info );
318
						echo '</td>';
319
320
						// Report the interval.
321
						echo '
322
							<td class="intervals">';
323
						$this->display_event_intervals( $info );
324
						echo '</td>';
325
326
						// Report the args.
327
						echo '
328
							<td>';
329
						$this->display_event_cron_arguments( $info['args'] );
330
						echo '</td>
331
						</tr>';
332
					}
333
					unset( $hash, $info );
334
				}
335
				unset( $hook, $data, $row_attributes, $arg_set_count, $show_hook );
336
			}
337
			unset( $time, $time_cron_array, $hook_count, $show_time );
338
339
			echo '
340
					</tbody>
341
				</table>';
342
		}
343
344
345
		/**
346
		 * Count the number of argument sets for a cron time.
347
		 *
348
		 * @param array $hook_array Array of hooks with argument sets.
349
		 *
350
		 * @return int
351
		 */
352
		private function get_arg_set_count( $hook_array ) {
353
			$count = 0;
354
			foreach ( $hook_array as $set ) {
355
				$count += count( $set );
356
			}
357
			return $count;
358
		}
359
360
361
		/**
362
		 * Create a HTML attribute string for an event row.
363
		 *
364
		 * @param int    $time Unix timestamp.
365
		 * @param string $hook Action hook for the cron job.
366
		 *
367
		 * @return string
368
		 */
369
		private function get_event_row_attributes( $time, $hook ) {
370
			$attributes = '';
371
			$classes    = array();
372
373
			// Add a class if past current time.
374
			if ( $this->is_time_in_past( $time ) ) {
375
				$classes[] = 'past';
376
			}
377
378
			// Verify if any events are hooked in.
379
			if ( false === has_action( $hook ) ) {
380
				/* TRANSLATORS: This text will display as a tooltip. %1$s will be replaced by a line break. */
381
				$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" ) . '"';
382
				$classes[]   = 'empty-event';
383
			}
384
385
			if ( ! empty( $classes ) ) {
386
				$attributes .= ' class="' . implode( ' ', $classes ) . '"';
387
			}
388
389
			return $attributes;
390
		}
391
392
393
		/**
394
		 * Displays the the event schedule name for recurring events or else 'single event'.
395
		 *
396
		 * @param array $info Event info array.
397
		 */
398
		private function display_event_schedule( $info ) {
399
			if ( ! empty( $info['schedule'] ) ) {
400
				echo esc_html( $info['schedule'] );
401
			} else {
402
				echo esc_html__( 'Single Event', 'zt-debug-bar-cron' );
403
			}
404
		}
405
406
407
		/**
408
		 * Displays the event interval in seconds, minutes and hours.
409
		 *
410
		 * @param array $info Event info array.
411
		 */
412
		private function display_event_intervals( $info ) {
413
			if ( ! empty( $info['interval'] ) ) {
414
				$interval = (int) $info['interval'];
415
				/* TRANSLATORS: %s is number of seconds. */
416
				printf( esc_html__( '%ss', 'zt-debug-bar-cron' ) . '<br />', $interval ); // WPCS: XSS ok.
417
				/* TRANSLATORS: %s is number of minutes. */
418
				printf( esc_html__( '%sm', 'zt-debug-bar-cron' ) . '<br />', $this->get_minutes( $interval ) ); // WPCS: XSS ok.
419
				/* TRANSLATORS: %s is number of hours. */
420
				printf( esc_html__( '%sh', 'zt-debug-bar-cron' ), $this->get_hours( $interval ) ); // WPCS: XSS ok.
421
				unset( $interval );
422
			} else {
423
				echo esc_html__( 'Single Event', 'zt-debug-bar-cron' );
424
			}
425
		}
426
427
428
		/**
429
		 * Displays the cron arguments in a readable format.
430
		 *
431
		 * @param mixed $args Cron argument(s).
432
		 *
433
		 * @return void
434
		 */
435
		private function display_event_cron_arguments( $args ) {
436
			// Arguments defaults to an empty array if no arguments are given.
437
			if ( is_array( $args ) && array() === $args ) {
438
				echo esc_html__( 'No Args', 'zt-debug-bar-cron' );
439
				return;
440
			}
441
442
			// Ok, we have an argument, let's pretty print it.
443
			$this->print_pretty_output( $args );
444
		}
445
446
447
		/**
448
		 * Displays all of the schedules defined.
449
		 *
450
		 * @return void
451
		 */
452
		private function display_schedules() {
453
			echo '
454
				<table class="zt-debug-bar-cron-table zt-debug-bar-cron-schedule-table">
455
					<thead><tr>
456
						<th class="col1">', esc_html__( 'Interval Hook', 'zt-debug-bar-cron' ), '</th>
457
						<th class="col2">', esc_html__( 'Interval (S)', 'zt-debug-bar-cron' ), '</th>
458
						<th class="col3">', esc_html__( 'Interval (M)', 'zt-debug-bar-cron' ), '</th>
459
						<th class="col4">', esc_html__( 'Interval (H)', 'zt-debug-bar-cron' ), '</th>
460
						<th class="col5">', esc_html__( 'Display Name', 'zt-debug-bar-cron' ), '</th>
461
					</tr></thead>
462
					<tbody>';
463
464
465
			$schedules = wp_get_schedules();
466
			ksort( $schedules );
467
			uasort( $schedules, array( $this, 'schedules_sorting' ) );
468
			foreach ( $schedules as $interval_hook => $data ) {
469
				$interval = (int) $data['interval'];
470
				echo // WPCS: XSS ok.
471
				'
472
						<tr>
473
							<td>', esc_html( $interval_hook ), '</td>
474
							<td>', $interval, '</td>
475
							<td>', $this->get_minutes( $interval ), '</td>
476
							<td>', $this->get_hours( $interval ), '</td>
477
							<td>', esc_html( $data['display'] ) . '</td>
478
						</tr>';
479
			}
480
481
			echo '
482
					</tbody>
483
				</table>';
484
		}
485
486
		/**
487
		 * Sorting method for cron scheldules. Order by schedules interval.
488
		 *
489
		 * @param array $a First element of comparison pair.
490
		 * @param array $b Second element of comparison pair.
491
		 *
492
		 * @return int Return 1 if $a argument 'interval' greater then $b argument 'interval', 0 if both intervals equivalent and -1 otherwise.
493
		 */
494
		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...
495
			if ( (int) $a['interval'] === (int) $b['interval'] ) {
496
				return 0;
497
			} else {
498
				return ( ( (int) $a['interval'] > (int) $b['interval'] ) ? 1 : -1 );
499
			}
500
		}
501
502
		/**
503
		 * Verify if a given timestamp is in the past or the future.
504
		 *
505
		 * @param int $time Unix timestamp.
506
		 *
507
		 * @return bool True if the time has passed, false otherwise.
508
		 */
509
		private function is_time_in_past( $time ) {
510
			return ( time() > $time && 'No' === $this->_doing_cron );
511
		}
512
513
514
		/**
515
		 * Transform a time in seconds to minutes rounded to 2 decimals.
516
		 *
517
		 * @param int $time Unix timestamp.
518
		 *
519
		 * @return int|float
520
		 */
521
		private function get_minutes( $time ) {
522
			return round( ( (int) $time / 60 ), 2 );
523
		}
524
525
526
		/**
527
		 * Transform a time in seconds to hours rounded to 2 decimals.
528
		 *
529
		 * @param int $time Unix timestamp.
530
		 *
531
		 * @return int|float
532
		 */
533
		private function get_hours( $time ) {
534
			return round( ( (int) $time / 3600 ), 2 );
535
		}
536
537
538
		/**
539
		 * Compares time with current time and adds ' ago' if current time is greater than event time.
540
		 *
541
		 * @param string $human_time Human readable time difference.
542
		 * @param int    $time       Unix time of event.
543
		 *
544
		 * @return string
545
		 */
546
		private function display_past_time( $human_time, $time ) {
547
			if ( time() > $time ) {
548
				/* TRANSLATORS: %s is a human readable time difference. */
549
				return sprintf( __( '%s ago', 'zt-debug-bar-cron' ), $human_time );
550
			} else {
551
				return $human_time;
552
			}
553
		}
554
555
556
		/**
557
		 * Load the pretty output class & set the recursion limit.
558
		 */
559
		private function load_debug_bar_pretty_output() {
560
			if ( ! class_exists( 'Debug_Bar_Pretty_Output' ) ) {
561
				require_once plugin_dir_path( __FILE__ ) . 'inc/debug-bar-pretty-output/class-debug-bar-pretty-output.php';
562
			}
563
564
			// Limit recursion depth if possible - method available since DBPO v1.4.
565
			if ( method_exists( 'Debug_Bar_Pretty_Output', 'limit_recursion' ) ) {
566
				Debug_Bar_Pretty_Output::limit_recursion( 2 );
567
			}
568
		}
569
570
571
		/**
572
		 * Print any type of variable with colour coding and a variable type indication.
573
		 *
574
		 * @param mixed $variable The variable to print.
575
		 */
576
		private function print_pretty_output( $variable ) {
577
			if ( defined( 'Debug_Bar_Pretty_Output::VERSION' ) ) {
578
				echo Debug_Bar_Pretty_Output::get_output( $variable, '', true ); // WPCS: XSS ok.
579
			} else {
580
				// An old version of the pretty output class was loaded.
581
				// Real possibility as there are several DB plugins using the pretty print class.
582
				Debug_Bar_Pretty_Output::output( $variable, '', true );
583
			}
584
		}
585
586
587
		/**
588
		 * Unset recursion depth limit for the pretty output class.
589
		 *
590
		 * @internal Method available since DBPO v1.4.
591
		 */
592
		private function reset_debug_bar_pretty_output() {
593
			if ( method_exists( 'Debug_Bar_Pretty_Output', 'unset_recursion_limit' ) ) {
594
				Debug_Bar_Pretty_Output::unset_recursion_limit();
595
			}
596
		}
597
	} // End of class ZT_Debug_Bar_Cron.
598
599
} // End of if class_exists wrapper.
600