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