WP Crontrol - Version 1.6

Version Description

Download this release

Release Info

Developer johnbillion
Plugin Icon 128x128 WP Crontrol
Version 1.6
Comparing to
See all releases

Code changes from version 1.5 to 1.6

Files changed (2) hide show
  1. readme.txt +11 -2
  2. wp-crontrol.php +336 -127
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: johnbillion, scompt
3
  Tags: admin, cron, plugin, control, wp-cron, crontrol
4
  Requires at least: 4.1
5
- Tested up to: 4.7
6
- Stable tag: 1.5
7
 
8
  WP Crontrol lets you view and control what's happening in the WP-Cron system.
9
 
@@ -14,6 +14,7 @@ WP Crontrol lets you view and control what's happening in the WP-Cron system. Fr
14
  * View all cron events along with their arguments, recurrence, callback functions, and when they are next due.
15
  * Edit, delete, and immediately run any cron events.
16
  * Add new cron events.
 
17
  * Add, edit, and remove custom cron schedules.
18
 
19
  The admin screen will show you a warning message if your cron system doesn't appear to be working (for example if your server can't connect to itself to fire scheduled cron events).
@@ -53,6 +54,14 @@ The next step is to write your function. Here's a simple example:
53
  wp_mail( 'hello@example.com', 'WP Crontrol', 'WP Crontrol rocks!' );
54
  }`
55
 
 
 
 
 
 
 
 
 
56
  = Are any WP-CLI commands available? =
57
 
58
  The cron commands which were previously included in WP Crontrol are now part of WP-CLI (since 0.16), so this plugin no longer provides any WP-CLI commands. See `wp help cron` for more info.
2
  Contributors: johnbillion, scompt
3
  Tags: admin, cron, plugin, control, wp-cron, crontrol
4
  Requires at least: 4.1
5
+ Tested up to: 4.9
6
+ Stable tag: 1.6
7
 
8
  WP Crontrol lets you view and control what's happening in the WP-Cron system.
9
 
14
  * View all cron events along with their arguments, recurrence, callback functions, and when they are next due.
15
  * Edit, delete, and immediately run any cron events.
16
  * Add new cron events.
17
+ * Bulk delete cron events.
18
  * Add, edit, and remove custom cron schedules.
19
 
20
  The admin screen will show you a warning message if your cron system doesn't appear to be working (for example if your server can't connect to itself to fire scheduled cron events).
54
  wp_mail( 'hello@example.com', 'WP Crontrol', 'WP Crontrol rocks!' );
55
  }`
56
 
57
+ = Which users can manage cron events and schedules? =
58
+
59
+ Only users with the `manage_options` capability can manage cron events and schedules. By default, only Administrators have this capability.
60
+
61
+ = Which users can manage PHP cron events? =
62
+
63
+ Only users with the `edit_files` capability can manage PHP cron events. By default, only Administrators have this capability, and with Multisite enabled only Super Admins have this capability.
64
+
65
  = Are any WP-CLI commands available? =
66
 
67
  The cron commands which were previously included in WP Crontrol are now part of WP-CLI (since 0.16), so this plugin no longer provides any WP-CLI commands. See `wp help cron` for more info.
wp-crontrol.php CHANGED
@@ -5,7 +5,7 @@
5
  * Description: WP Crontrol lets you view and control what's happening in the WP-Cron system.
6
  * Author: John Blackbourn & contributors
7
  * Author URI: https://github.com/johnbillion/wp-crontrol/graphs/contributors
8
- * Version: 1.5
9
  * Text Domain: wp-crontrol
10
  * Domain Path: /languages/
11
  * License: GPL v2 or later
@@ -54,6 +54,9 @@ class Crontrol {
54
  add_action( 'init', array( $this, 'action_handle_posts' ) );
55
  add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
56
  add_filter( "plugin_action_links_{$plugin_file}", array( $this, 'plugin_action_links' ), 10, 4 );
 
 
 
57
 
58
  register_activation_hook( __FILE__, array( $this, 'action_activate' ) );
59
 
@@ -65,7 +68,7 @@ class Crontrol {
65
  * Evaluates the provided code using eval.
66
  */
67
  public function action_php_cron_event( $code ) {
68
- eval( $code );
69
  }
70
 
71
  /**
@@ -83,6 +86,9 @@ class Crontrol {
83
  if ( ! current_user_can( 'manage_options' ) ) {
84
  wp_die( esc_html__( 'You are not allowed to add new cron events.', 'wp-crontrol' ) );
85
  }
 
 
 
86
  check_admin_referer( 'new-cron' );
87
  extract( wp_unslash( $_POST ), EXTR_PREFIX_ALL, 'in' );
88
  $in_args = json_decode( $in_args, true );
@@ -91,12 +97,12 @@ class Crontrol {
91
  $redirect = array(
92
  'page' => 'crontrol_admin_manage_page',
93
  'crontrol_message' => '5',
94
- 'crontrol_name' => urlencode( $in_hookname ),
95
  );
96
- wp_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
97
  exit;
98
 
99
- } else if ( isset( $_POST['new_php_cron'] ) ) {
100
  if ( ! current_user_can( 'edit_files' ) ) {
101
  wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ) );
102
  }
@@ -112,12 +118,12 @@ class Crontrol {
112
  $redirect = array(
113
  'page' => 'crontrol_admin_manage_page',
114
  'crontrol_message' => '5',
115
- 'crontrol_name' => urlencode( $hookname ),
116
  );
117
- wp_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
118
  exit;
119
 
120
- } else if ( isset( $_POST['edit_cron'] ) ) {
121
  if ( ! current_user_can( 'manage_options' ) ) {
122
  wp_die( esc_html__( 'You are not allowed to edit cron events.', 'wp-crontrol' ) );
123
  }
@@ -131,12 +137,12 @@ class Crontrol {
131
  $redirect = array(
132
  'page' => 'crontrol_admin_manage_page',
133
  'crontrol_message' => '4',
134
- 'crontrol_name' => urlencode( $in_hookname ),
135
  );
136
- wp_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
137
  exit;
138
 
139
- } else if ( isset( $_POST['edit_php_cron'] ) ) {
140
  if ( ! current_user_can( 'edit_files' ) ) {
141
  wp_die( esc_html__( 'You are not allowed to edit cron events.', 'wp-crontrol' ) );
142
  }
@@ -156,12 +162,12 @@ class Crontrol {
156
  $redirect = array(
157
  'page' => 'crontrol_admin_manage_page',
158
  'crontrol_message' => '4',
159
- 'crontrol_name' => urlencode( $hookname ),
160
  );
161
- wp_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
162
  exit;
163
 
164
- } else if ( isset( $_POST['new_schedule'] ) ) {
165
  if ( ! current_user_can( 'manage_options' ) ) {
166
  wp_die( esc_html__( 'You are not allowed to add new cron schedules.', 'wp-crontrol' ) );
167
  }
@@ -179,19 +185,19 @@ class Crontrol {
179
  $redirect = array(
180
  'page' => 'crontrol_admin_options_page',
181
  'crontrol_message' => '7',
182
- 'crontrol_name' => urlencode( $interval ),
183
  );
184
- wp_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
185
  exit;
186
  }
187
  $interval = $future - $now;
188
- } else if ( $interval <= 0 ) {
189
  $redirect = array(
190
  'page' => 'crontrol_admin_options_page',
191
  'crontrol_message' => '7',
192
- 'crontrol_name' => urlencode( $interval ),
193
  );
194
- wp_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
195
  exit;
196
  }
197
 
@@ -199,12 +205,12 @@ class Crontrol {
199
  $redirect = array(
200
  'page' => 'crontrol_admin_options_page',
201
  'crontrol_message' => '3',
202
- 'crontrol_name' => urlencode( $name ),
203
  );
204
- wp_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
205
  exit;
206
 
207
- } else if ( isset( $_GET['action'] ) && 'delete-sched' == $_GET['action'] ) {
208
  if ( ! current_user_can( 'manage_options' ) ) {
209
  wp_die( esc_html__( 'You are not allowed to delete cron schedules.', 'wp-crontrol' ) );
210
  }
@@ -214,39 +220,68 @@ class Crontrol {
214
  $redirect = array(
215
  'page' => 'crontrol_admin_options_page',
216
  'crontrol_message' => '2',
217
- 'crontrol_name' => urlencode( $id ),
218
  );
219
- wp_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
220
  exit;
221
 
222
- } else if ( isset( $_GET['action'] ) && 'delete-cron' == $_GET['action'] ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  if ( ! current_user_can( 'manage_options' ) ) {
224
  wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ) );
225
  }
226
  $id = wp_unslash( $_GET['id'] );
227
  $sig = wp_unslash( $_GET['sig'] );
228
- $next_run = $_GET['next_run'];
229
  check_admin_referer( "delete-cron_{$id}_{$sig}_{$next_run}" );
230
  if ( $this->delete_cron( $id, $sig, $next_run ) ) {
231
  $redirect = array(
232
  'page' => 'crontrol_admin_manage_page',
233
  'crontrol_message' => '6',
234
- 'crontrol_name' => urlencode( $id ),
235
  );
236
- wp_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
237
  exit;
238
  } else {
239
  $redirect = array(
240
  'page' => 'crontrol_admin_manage_page',
241
  'crontrol_message' => '7',
242
- 'crontrol_name' => urlencode( $id ),
243
  );
244
- wp_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
245
  exit;
246
 
247
  };
248
 
249
- } else if ( isset( $_GET['action'] ) && 'run-cron' == $_GET['action'] ) {
250
  if ( ! current_user_can( 'manage_options' ) ) {
251
  wp_die( esc_html__( 'You are not allowed to run cron events.', 'wp-crontrol' ) );
252
  }
@@ -257,17 +292,17 @@ class Crontrol {
257
  $redirect = array(
258
  'page' => 'crontrol_admin_manage_page',
259
  'crontrol_message' => '1',
260
- 'crontrol_name' => urlencode( $id ),
261
  );
262
- wp_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
263
  exit;
264
  } else {
265
  $redirect = array(
266
  'page' => 'crontrol_admin_manage_page',
267
  'crontrol_message' => '8',
268
- 'crontrol_name' => urlencode( $id ),
269
  );
270
- wp_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
271
  exit;
272
  }
273
  }
@@ -312,7 +347,7 @@ class Crontrol {
312
  if ( ! is_array( $args ) ) {
313
  $args = array();
314
  }
315
- if ( '_oneoff' == $schedule ) {
316
  return wp_schedule_single_event( $next_run, $hookname, $args ) === null;
317
  } else {
318
  return wp_schedule_event( $next_run, $schedule, $hookname, $args ) === null;
@@ -343,7 +378,10 @@ class Crontrol {
343
  */
344
  public function add_schedule( $name, $interval, $display ) {
345
  $old_scheds = get_option( 'crontrol_schedules', array() );
346
- $old_scheds[ $name ] = array( 'interval' => $interval, 'display' => $display );
 
 
 
347
  update_option( 'crontrol_schedules', $old_scheds );
348
  }
349
 
@@ -385,12 +423,12 @@ class Crontrol {
385
  public function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) {
386
  $actions['crontrol-events'] = sprintf(
387
  '<a href="%s">%s</a>',
388
- admin_url( 'tools.php?page=crontrol_admin_manage_page' ),
389
  esc_html__( 'Cron Events', 'wp-crontrol' )
390
  );
391
  $actions['crontrol-schedules'] = sprintf(
392
  '<a href="%s">%s</a>',
393
- admin_url( 'options-general.php?page=crontrol_admin_options_page' ),
394
  esc_html__( 'Cron Schedules', 'wp-crontrol' )
395
  );
396
  return $actions;
@@ -414,9 +452,16 @@ class Crontrol {
414
  */
415
  public function admin_options_page() {
416
  $schedules = $this->get_schedules();
 
417
  $custom_schedules = get_option( 'crontrol_schedules', array() );
418
  $custom_keys = array_keys( $custom_schedules );
419
 
 
 
 
 
 
 
420
  $messages = array(
421
  /* translators: 1: The name of the cron schedule. */
422
  '2' => __( 'Successfully deleted the cron schedule %s.', 'wp-crontrol' ),
@@ -427,7 +472,8 @@ class Crontrol {
427
  );
428
  if ( isset( $_GET['crontrol_message'] ) && isset( $_GET['crontrol_name'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
429
  $hook = wp_unslash( $_GET['crontrol_name'] );
430
- $msg = sprintf( esc_html( $messages[ $_GET['crontrol_message'] ] ), '<strong>' . esc_html( $hook ) . '</strong>' );
 
431
 
432
  printf( '<div id="message" class="updated notice is-dismissible"><p>%s</p></div>', $msg ); // WPCS:: XSS ok.
433
  }
@@ -442,7 +488,7 @@ class Crontrol {
442
  <th scope="col"><?php esc_html_e( 'Name', 'wp-crontrol' ); ?></th>
443
  <th scope="col"><?php esc_html_e( 'Interval', 'wp-crontrol' ); ?></th>
444
  <th scope="col"><?php esc_html_e( 'Display Name', 'wp-crontrol' ); ?></th>
445
- <th>&nbsp;</th>
446
  </tr>
447
  </thead>
448
  <tbody>
@@ -466,20 +512,27 @@ class Crontrol {
466
  printf( '<td>%s</td>',
467
  esc_html( $data['display'] )
468
  );
469
- if ( in_array( $name, $custom_keys ) ) {
470
- $url = add_query_arg( array(
471
- 'page' => 'crontrol_admin_options_page',
472
- 'action' => 'delete-sched',
473
- 'id' => urlencode( $name ),
474
- ), admin_url( 'options-general.php' ) );
475
- $url = wp_nonce_url( $url, 'delete-sched_' . $name );
476
- printf( '<td><span class="row-actions visible"><span class="delete"><a href="%s">%s</a></span></span></td>',
477
- esc_url( $url ),
478
- esc_html__( 'Delete', 'wp-crontrol' )
479
- );
 
 
 
 
 
 
480
  } else {
481
- echo '<td>&nbsp;</td>';
482
  }
 
483
  echo '</tr>';
484
  }
485
  }
@@ -487,8 +540,18 @@ class Crontrol {
487
  </tbody>
488
  </table>
489
  </div>
 
 
 
 
 
 
 
 
 
 
490
  <div class="wrap narrow">
491
- <h2 class="title"><?php esc_html_e( 'Add new cron schedule', 'wp-crontrol' ); ?></h2>
492
  <p><?php esc_html_e( 'Adding a new cron schedule will allow you to schedule events that re-occur at the given interval.', 'wp-crontrol' ); ?></p>
493
  <form method="post" action="options-general.php?page=crontrol_admin_options_page">
494
  <table class="form-table">
@@ -518,10 +581,14 @@ class Crontrol {
518
  */
519
  public function get_schedules() {
520
  $schedules = wp_get_schedules();
521
- uasort( $schedules, create_function( '$a, $b', 'return $a["interval"] - $b["interval"];' ) );
522
  return $schedules;
523
  }
524
 
 
 
 
 
525
  /**
526
  * Displays a dropdown filled with the possible schedules, including non-repeating.
527
  *
@@ -589,7 +656,7 @@ class Crontrol {
589
 
590
  if ( is_wp_error( $result ) ) {
591
  return $result;
592
- } else if ( wp_remote_retrieve_response_code( $result ) >= 300 ) {
593
  return new WP_Error( 'unexpected_http_response_code', sprintf(
594
  /* translators: 1: The HTTP response code. */
595
  __( 'Unexpected HTTP response code: %s', 'wp-crontrol' ),
@@ -611,7 +678,7 @@ class Crontrol {
611
  $status = $this->test_cron_spawn();
612
 
613
  if ( is_wp_error( $status ) ) {
614
- if ( 'crontrol_info' === $status->get_error_code() ) {
615
  ?>
616
  <div id="cron-status-notice" class="notice notice-info">
617
  <p><?php echo esc_html( $status->get_error_message() ); ?></p>
@@ -624,7 +691,8 @@ class Crontrol {
624
  printf(
625
  /* translators: 1: Error message text. */
626
  esc_html__( 'There was a problem spawning a call to the WP-Cron system on your site. This means WP-Cron events on your site may not work. The problem was: %s', 'wp-crontrol' ),
627
- '<br><strong>' . esc_html( $status->get_error_message() ) . '</strong>' );
 
628
  ?></p>
629
  </div>
630
  <?php
@@ -679,6 +747,7 @@ class Crontrol {
679
  $helper_text = esc_html__( 'Cron events trigger actions in your code. Enter the schedule of the event, as well as the PHP code to execute when the action is triggered.', 'wp-crontrol' );
680
  } else {
681
  $helper_text = sprintf(
 
682
  esc_html__( 'Cron events trigger actions in your code. A cron event needs a corresponding action hook somewhere in code, e.g. the %1$s file in your theme.', 'wp-crontrol' ),
683
  '<code>functions.php</code>'
684
  );
@@ -703,11 +772,17 @@ class Crontrol {
703
  list( $next_run_date, $next_run_time ) = explode( ' ', get_date_from_gmt( date( 'Y-m-d H:i:s', $existing['next_run'] ), 'Y-m-d H:i:s' ) );
704
  } else {
705
  $other_fields = wp_nonce_field( 'new-cron', '_wpnonce', true, false );
706
- $existing = array( 'hookname' => '', 'args' => array(), 'next_run' => 'now', 'schedule' => false );
 
 
 
 
 
707
  $action = $is_php ? 'new_php_cron' : 'new_cron';
708
  $button = $is_php ? $new_tabs['php-cron'] : $new_tabs['cron'];
709
  $show_edit_tab = false;
710
- $next_run_time = $next_run_date = '';
 
711
  }
712
  if ( $is_php ) {
713
  if ( ! isset( $existing['args']['code'] ) ) {
@@ -736,7 +811,18 @@ class Crontrol {
736
  <?php if ( $is_php ) : ?>
737
  <tr>
738
  <th valign="top" scope="row"><label for="hookcode"><?php esc_html_e( 'PHP Code', 'wp-crontrol' ); ?></label></th>
739
- <td><textarea class="large-text code" rows="10" cols="50" id="hookcode" name="hookcode" required><?php echo esc_textarea( $existing['args']['code'] ); ?></textarea></td>
 
 
 
 
 
 
 
 
 
 
 
740
  </tr>
741
  <tr>
742
  <th valign="top" scope="row"><label for="eventname"><?php esc_html_e( 'Event Name (optional)', 'wp-crontrol' ); ?></label></th>
@@ -751,15 +837,17 @@ class Crontrol {
751
  <th valign="top" scope="row"><label for="args"><?php esc_html_e( 'Arguments (optional)', 'wp-crontrol' ); ?></label></th>
752
  <td>
753
  <input type="text" class="regular-text" id="args" name="args" value="<?php echo esc_attr( $display_args ); ?>"/>
754
- <p class="description"><?php
755
- /* translators: 1, 2, and 3: Example values for an input field. */
756
- echo esc_html( sprintf(
757
- __( 'e.g. %s, %s, or %s', 'wp-crontrol' ),
758
- '[25]',
759
- '["asdf"]',
760
- '["i","want",25,"cakes"]'
761
- ) );
762
- ?></p>
 
 
763
  </td>
764
  </tr>
765
  <?php endif; ?>
@@ -782,23 +870,38 @@ class Crontrol {
782
  </script>
783
  <input type="date" placeholder="YYYY-MM-DD" id="next_run_date" name="next_run_date" value="<?php echo esc_attr( $next_run_date ); ?>" maxlength="10" pattern="\d{4}\-\d{2}\-\d{2}" required />
784
  <input type="time" step="1" placeholder="HH:MM:SS" id="next_run_time" name="next_run_time" value="<?php echo esc_attr( $next_run_time ); ?>" maxlength="8" pattern="\d{2}:\d{2}:\d{2}" required />
785
- <?php printf(
786
- /* translators: %s Timezone name. */
787
- esc_html__( 'Timezone: %s', 'wp-crontrol' ),
788
- '<code>' . esc_html( $this->get_timezone_name() ) . '</code>'
789
- ); ?>
790
- <p class="description datetime-fallback hidden"><?php
791
- /* translators: %s Date/time format for an input field. */
792
- echo esc_html( sprintf(
793
- __( 'Format: %s', 'wp-crontrol' ),
794
- date( 'Y' ) . '-02-25 12:34:00'
795
- ) );
796
- ?></p>
 
 
 
 
 
 
797
  </td>
798
  </tr><tr>
799
  <th valign="top" scope="row"><label for="schedule"><?php esc_html_e( 'Recurrence', 'wp-crontrol' ); ?></label></th>
800
  <td>
801
  <?php $this->schedules_dropdown( $existing['schedule'] ); ?>
 
 
 
 
 
 
 
 
 
802
  </td>
803
  </tr>
804
  </tbody></table>
@@ -864,15 +967,17 @@ class Crontrol {
864
  '7' => __( 'Failed to the delete the cron event %s.', 'wp-crontrol' ),
865
  /* translators: 1: The name of the cron event. */
866
  '8' => __( 'Failed to the execute the cron event %s.', 'wp-crontrol' ),
 
867
  );
868
  if ( isset( $_GET['crontrol_name'] ) && isset( $_GET['crontrol_message'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
869
  $hook = wp_unslash( $_GET['crontrol_name'] );
870
- $msg = sprintf( esc_html( $messages[ $_GET['crontrol_message'] ] ), '<strong>' . esc_html( $hook ) . '</strong>' );
 
871
 
872
  printf( '<div id="message" class="updated notice is-dismissible"><p>%s</p></div>', $msg ); // WPCS:: XSS ok.
873
  }
874
  $events = $this->get_cron_events();
875
- $doing_edit = ( isset( $_GET['action'] ) && 'edit-cron' == $_GET['action'] ) ? wp_unslash( $_GET['id'] ) : false ;
876
  $time_format = 'Y-m-d H:i:s';
877
 
878
  $core_hooks = array(
@@ -882,6 +987,7 @@ class Crontrol {
882
  'wp_scheduled_delete',
883
  'wp_scheduled_auto_draft_delete',
884
  'update_network_counts',
 
885
  );
886
 
887
  $this->show_cron_status();
@@ -889,9 +995,11 @@ class Crontrol {
889
  ?>
890
  <div class="wrap">
891
  <h1><?php esc_html_e( 'WP-Cron Events', 'wp-crontrol' ); ?></h1>
 
892
  <table class="widefat striped">
893
  <thead>
894
  <tr>
 
895
  <th scope="col"><?php esc_html_e( 'Hook Name', 'wp-crontrol' ); ?></th>
896
  <th scope="col"><?php esc_html_e( 'Arguments', 'wp-crontrol' ); ?></th>
897
  <th scope="col"><?php esc_html_e( 'Actions', 'wp-crontrol' ); ?></th>
@@ -904,7 +1012,7 @@ class Crontrol {
904
  <?php
905
  if ( is_wp_error( $events ) ) {
906
  ?>
907
- <tr><td colspan="6"><?php echo esc_html( $events->get_error_message() ); ?></td></tr>
908
  <?php
909
  } else {
910
  foreach ( $events as $id => $event ) {
@@ -922,16 +1030,32 @@ class Crontrol {
922
  if ( empty( $event->args ) ) {
923
  $args = '<em>' . esc_html__( 'None', 'wp-crontrol' ) . '</em>';
924
  } else {
 
 
925
  if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) {
926
- $args = '<code>' . wp_json_encode( $event->args, JSON_UNESCAPED_SLASHES ) . '</code>';
927
- } else {
928
- $args = '<code>' . stripslashes( wp_json_encode( $event->args ) ) . '</code>';
929
  }
 
 
 
 
 
930
  }
931
 
932
- echo '<tr id="cron-' . esc_attr( $id ) . '" class="">';
 
 
 
 
 
 
 
 
 
 
 
933
 
934
- if ( 'crontrol_cron_job' == $event->hook ) {
935
  if ( ! empty( $event->args['name'] ) ) {
936
  /* translators: 1: The name of the PHP cron event. */
937
  echo '<td><em>' . esc_html( sprintf( __( 'PHP Cron (%s)', 'wp-crontrol' ), $event->args['name'] ) ) . '</em></td>';
@@ -946,39 +1070,37 @@ class Crontrol {
946
  echo '<td>';
947
  $callbacks = array();
948
  foreach ( $this->get_action_callbacks( $event->hook ) as $callback ) {
949
- $callbacks[] = '<code>' . esc_html( $callback['callback']['name'] ) . '</code>';
950
  }
951
- echo implode( '<br>', $callbacks ); // WPCS:: XSS ok.
952
  echo '</td>';
953
  }
954
 
955
- echo '<td>';
956
  printf( '%s (%s)',
957
  esc_html( get_date_from_gmt( date( 'Y-m-d H:i:s', $event->time ), $time_format ) ),
958
  esc_html( $this->time_since( time(), $event->time ) )
959
  );
960
  echo '</td>';
961
 
 
962
  if ( $event->schedule ) {
963
- echo '<td>';
964
- echo esc_html( $this->interval( $event->interval ) );
965
- echo '</td>';
966
  } else {
967
- echo '<td>';
968
  esc_html_e( 'Non-repeating', 'wp-crontrol' );
969
- echo '</td>';
970
  }
 
971
 
972
  $links = array();
973
 
974
- echo '<td><span class="row-actions visible">';
975
 
976
  $link = array(
977
  'page' => 'crontrol_admin_manage_page',
978
  'action' => 'edit-cron',
979
- 'id' => urlencode( $event->hook ),
980
- 'sig' => urlencode( $event->sig ),
981
- 'next_run' => urlencode( $event->time ),
982
  );
983
  $link = add_query_arg( $link, admin_url( 'tools.php' ) ) . '#crontrol_form';
984
  $links[] = "<a href='" . esc_url( $link ) . "'>" . esc_html__( 'Edit', 'wp-crontrol' ) . '</a>';
@@ -986,9 +1108,9 @@ class Crontrol {
986
  $link = array(
987
  'page' => 'crontrol_admin_manage_page',
988
  'action' => 'run-cron',
989
- 'id' => urlencode( $event->hook ),
990
- 'sig' => urlencode( $event->sig ),
991
- 'next_run' => urlencode( $event->time ),
992
  );
993
  $link = add_query_arg( $link, admin_url( 'tools.php' ) );
994
  $link = wp_nonce_url( $link, "run-cron_{$event->hook}_{$event->sig}" );
@@ -998,9 +1120,9 @@ class Crontrol {
998
  $link = array(
999
  'page' => 'crontrol_admin_manage_page',
1000
  'action' => 'delete-cron',
1001
- 'id' => urlencode( $event->hook ),
1002
- 'sig' => urlencode( $event->sig ),
1003
- 'next_run' => urlencode( $event->time ),
1004
  );
1005
  $link = add_query_arg( $link, admin_url( 'tools.php' ) );
1006
  $link = wp_nonce_url( $link, "delete-cron_{$event->hook}_{$event->sig}_{$event->time}" );
@@ -1016,13 +1138,22 @@ class Crontrol {
1016
  ?>
1017
  </tbody>
1018
  </table>
 
 
 
 
 
 
 
 
 
1019
 
1020
  </div>
1021
  <?php
1022
  if ( is_array( $doing_edit ) ) {
1023
  $this->show_cron_form( 'crontrol_cron_job' == $doing_edit['hookname'], $doing_edit );
1024
  } else {
1025
- $this->show_cron_form( ( isset( $_GET['action'] ) and 'new-php-cron' == $_GET['action'] ), false );
1026
  }
1027
  }
1028
 
@@ -1031,30 +1162,24 @@ class Crontrol {
1031
 
1032
  $actions = array();
1033
 
1034
- if ( isset( $wp_filter[$name] ) ) {
1035
 
1036
  # http://core.trac.wordpress.org/ticket/17817
1037
- $action = $wp_filter[$name];
1038
 
1039
  foreach ( $action as $priority => $callbacks ) {
1040
-
1041
  foreach ( $callbacks as $callback ) {
1042
-
1043
  $callback = self::populate_callback( $callback );
1044
 
1045
  $actions[] = array(
1046
  'priority' => $priority,
1047
  'callback' => $callback,
1048
  );
1049
-
1050
  }
1051
-
1052
  }
1053
-
1054
  }
1055
 
1056
  return $actions;
1057
-
1058
  }
1059
 
1060
  public static function populate_callback( array $callback ) {
@@ -1069,7 +1194,6 @@ class Crontrol {
1069
  }
1070
 
1071
  if ( is_array( $callback['function'] ) ) {
1072
-
1073
  if ( is_object( $callback['function'][0] ) ) {
1074
  $class = get_class( $callback['function'][0] );
1075
  $access = '->';
@@ -1079,26 +1203,44 @@ class Crontrol {
1079
  }
1080
 
1081
  $callback['name'] = $class . $access . $callback['function'][1] . '()';
1082
-
1083
  } elseif ( is_object( $callback['function'] ) ) {
1084
-
1085
  if ( is_a( $callback['function'], 'Closure' ) ) {
1086
  $callback['name'] = 'Closure';
1087
  } else {
1088
  $class = get_class( $callback['function'] );
1089
  $callback['name'] = $class . '->__invoke()';
1090
  }
1091
-
1092
  } else {
1093
-
1094
  $callback['name'] = $callback['function'] . '()';
1095
-
1096
  }
1097
 
1098
  return $callback;
1099
 
1100
  }
1101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1102
  /**
1103
  * Pretty-prints the difference in two times.
1104
  *
@@ -1150,13 +1292,16 @@ class Crontrol {
1150
  // x days, xx hours
1151
  // so there's only two bits of calculation below:
1152
 
 
 
1153
  // step one: the first chunk
1154
- for ( $i = 0, $j = count( $chunks ); $i < $j; $i++ ) {
1155
  $seconds = $chunks[ $i ][0];
1156
  $name = $chunks[ $i ][1];
1157
 
1158
  // finding the biggest chunk (if the chunk fits, break)
1159
- if ( ( $count = floor( $since / $seconds ) ) != 0 ) {
 
1160
  break;
1161
  }
1162
  }
@@ -1168,8 +1313,8 @@ class Crontrol {
1168
  if ( $i + 1 < $j ) {
1169
  $seconds2 = $chunks[ $i + 1 ][0];
1170
  $name2 = $chunks[ $i + 1 ][1];
1171
-
1172
- if ( ( $count2 = floor( ( $since - ( $seconds * $count ) ) / $seconds2 ) ) != 0 ) {
1173
  // add to output var
1174
  $output .= ' ' . sprintf( translate_nooped_plural( $name2, $count2, 'wp-crontrol' ), $count2 );
1175
  }
@@ -1178,6 +1323,70 @@ class Crontrol {
1178
  return $output;
1179
  }
1180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1181
  public static function init() {
1182
 
1183
  static $instance = null;
5
  * Description: WP Crontrol lets you view and control what's happening in the WP-Cron system.
6
  * Author: John Blackbourn & contributors
7
  * Author URI: https://github.com/johnbillion/wp-crontrol/graphs/contributors
8
+ * Version: 1.6
9
  * Text Domain: wp-crontrol
10
  * Domain Path: /languages/
11
  * License: GPL v2 or later
54
  add_action( 'init', array( $this, 'action_handle_posts' ) );
55
  add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
56
  add_filter( "plugin_action_links_{$plugin_file}", array( $this, 'plugin_action_links' ), 10, 4 );
57
+ add_filter( 'removable_query_args', array( $this, 'filter_removable_query_args' ) );
58
+
59
+ add_action( 'load-tools_page_crontrol_admin_manage_page', array( $this, 'enqueue_code_editor' ) );
60
 
61
  register_activation_hook( __FILE__, array( $this, 'action_activate' ) );
62
 
68
  * Evaluates the provided code using eval.
69
  */
70
  public function action_php_cron_event( $code ) {
71
+ eval( $code ); // @codingStandardsIgnoreLine
72
  }
73
 
74
  /**
86
  if ( ! current_user_can( 'manage_options' ) ) {
87
  wp_die( esc_html__( 'You are not allowed to add new cron events.', 'wp-crontrol' ) );
88
  }
89
+ if ( 'crontrol_cron_job' === $in_hookname && ! current_user_can( 'edit_files' ) ) {
90
+ wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ) );
91
+ }
92
  check_admin_referer( 'new-cron' );
93
  extract( wp_unslash( $_POST ), EXTR_PREFIX_ALL, 'in' );
94
  $in_args = json_decode( $in_args, true );
97
  $redirect = array(
98
  'page' => 'crontrol_admin_manage_page',
99
  'crontrol_message' => '5',
100
+ 'crontrol_name' => rawurlencode( $in_hookname ),
101
  );
102
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
103
  exit;
104
 
105
+ } elseif ( isset( $_POST['new_php_cron'] ) ) {
106
  if ( ! current_user_can( 'edit_files' ) ) {
107
  wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ) );
108
  }
118
  $redirect = array(
119
  'page' => 'crontrol_admin_manage_page',
120
  'crontrol_message' => '5',
121
+ 'crontrol_name' => rawurlencode( $hookname ),
122
  );
123
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
124
  exit;
125
 
126
+ } elseif ( isset( $_POST['edit_cron'] ) ) {
127
  if ( ! current_user_can( 'manage_options' ) ) {
128
  wp_die( esc_html__( 'You are not allowed to edit cron events.', 'wp-crontrol' ) );
129
  }
137
  $redirect = array(
138
  'page' => 'crontrol_admin_manage_page',
139
  'crontrol_message' => '4',
140
+ 'crontrol_name' => rawurlencode( $in_hookname ),
141
  );
142
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
143
  exit;
144
 
145
+ } elseif ( isset( $_POST['edit_php_cron'] ) ) {
146
  if ( ! current_user_can( 'edit_files' ) ) {
147
  wp_die( esc_html__( 'You are not allowed to edit cron events.', 'wp-crontrol' ) );
148
  }
162
  $redirect = array(
163
  'page' => 'crontrol_admin_manage_page',
164
  'crontrol_message' => '4',
165
+ 'crontrol_name' => rawurlencode( $hookname ),
166
  );
167
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
168
  exit;
169
 
170
+ } elseif ( isset( $_POST['new_schedule'] ) ) {
171
  if ( ! current_user_can( 'manage_options' ) ) {
172
  wp_die( esc_html__( 'You are not allowed to add new cron schedules.', 'wp-crontrol' ) );
173
  }
185
  $redirect = array(
186
  'page' => 'crontrol_admin_options_page',
187
  'crontrol_message' => '7',
188
+ 'crontrol_name' => rawurlencode( $interval ),
189
  );
190
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
191
  exit;
192
  }
193
  $interval = $future - $now;
194
+ } elseif ( $interval <= 0 ) {
195
  $redirect = array(
196
  'page' => 'crontrol_admin_options_page',
197
  'crontrol_message' => '7',
198
+ 'crontrol_name' => rawurlencode( $interval ),
199
  );
200
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
201
  exit;
202
  }
203
 
205
  $redirect = array(
206
  'page' => 'crontrol_admin_options_page',
207
  'crontrol_message' => '3',
208
+ 'crontrol_name' => rawurlencode( $name ),
209
  );
210
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
211
  exit;
212
 
213
+ } elseif ( isset( $_GET['action'] ) && 'delete-sched' == $_GET['action'] ) {
214
  if ( ! current_user_can( 'manage_options' ) ) {
215
  wp_die( esc_html__( 'You are not allowed to delete cron schedules.', 'wp-crontrol' ) );
216
  }
220
  $redirect = array(
221
  'page' => 'crontrol_admin_options_page',
222
  'crontrol_message' => '2',
223
+ 'crontrol_name' => rawurlencode( $id ),
224
  );
225
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
226
  exit;
227
 
228
+ } elseif ( isset( $_POST['delete_crons'] ) ) {
229
+ if ( ! current_user_can( 'manage_options' ) ) {
230
+ wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ) );
231
+ }
232
+ check_admin_referer( 'bulk-delete-crons' );
233
+
234
+ if ( empty( $_POST['delete'] ) ) {
235
+ return;
236
+ }
237
+
238
+ $delete = wp_unslash( $_POST['delete'] );
239
+ $deleted = 0;
240
+
241
+ foreach ( $delete as $next_run => $events ) {
242
+ foreach ( $events as $id => $sig ) {
243
+ if ( $this->delete_cron( urldecode( $id ), $sig, $next_run ) ) {
244
+ $deleted++;
245
+ }
246
+ }
247
+ }
248
+
249
+ $redirect = array(
250
+ 'page' => 'crontrol_admin_manage_page',
251
+ 'crontrol_name' => $deleted,
252
+ 'crontrol_message' => '9',
253
+ );
254
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
255
+ exit;
256
+
257
+ } elseif ( isset( $_GET['action'] ) && 'delete-cron' == $_GET['action'] ) {
258
  if ( ! current_user_can( 'manage_options' ) ) {
259
  wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ) );
260
  }
261
  $id = wp_unslash( $_GET['id'] );
262
  $sig = wp_unslash( $_GET['sig'] );
263
+ $next_run = intval( $_GET['next_run'] );
264
  check_admin_referer( "delete-cron_{$id}_{$sig}_{$next_run}" );
265
  if ( $this->delete_cron( $id, $sig, $next_run ) ) {
266
  $redirect = array(
267
  'page' => 'crontrol_admin_manage_page',
268
  'crontrol_message' => '6',
269
+ 'crontrol_name' => rawurlencode( $id ),
270
  );
271
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
272
  exit;
273
  } else {
274
  $redirect = array(
275
  'page' => 'crontrol_admin_manage_page',
276
  'crontrol_message' => '7',
277
+ 'crontrol_name' => rawurlencode( $id ),
278
  );
279
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
280
  exit;
281
 
282
  };
283
 
284
+ } elseif ( isset( $_GET['action'] ) && 'run-cron' == $_GET['action'] ) {
285
  if ( ! current_user_can( 'manage_options' ) ) {
286
  wp_die( esc_html__( 'You are not allowed to run cron events.', 'wp-crontrol' ) );
287
  }
292
  $redirect = array(
293
  'page' => 'crontrol_admin_manage_page',
294
  'crontrol_message' => '1',
295
+ 'crontrol_name' => rawurlencode( $id ),
296
  );
297
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
298
  exit;
299
  } else {
300
  $redirect = array(
301
  'page' => 'crontrol_admin_manage_page',
302
  'crontrol_message' => '8',
303
+ 'crontrol_name' => rawurlencode( $id ),
304
  );
305
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
306
  exit;
307
  }
308
  }
347
  if ( ! is_array( $args ) ) {
348
  $args = array();
349
  }
350
+ if ( '_oneoff' === $schedule ) {
351
  return wp_schedule_single_event( $next_run, $hookname, $args ) === null;
352
  } else {
353
  return wp_schedule_event( $next_run, $schedule, $hookname, $args ) === null;
378
  */
379
  public function add_schedule( $name, $interval, $display ) {
380
  $old_scheds = get_option( 'crontrol_schedules', array() );
381
+ $old_scheds[ $name ] = array(
382
+ 'interval' => $interval,
383
+ 'display' => $display,
384
+ );
385
  update_option( 'crontrol_schedules', $old_scheds );
386
  }
387
 
423
  public function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) {
424
  $actions['crontrol-events'] = sprintf(
425
  '<a href="%s">%s</a>',
426
+ esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page' ) ),
427
  esc_html__( 'Cron Events', 'wp-crontrol' )
428
  );
429
  $actions['crontrol-schedules'] = sprintf(
430
  '<a href="%s">%s</a>',
431
+ esc_url( admin_url( 'options-general.php?page=crontrol_admin_options_page' ) ),
432
  esc_html__( 'Cron Schedules', 'wp-crontrol' )
433
  );
434
  return $actions;
452
  */
453
  public function admin_options_page() {
454
  $schedules = $this->get_schedules();
455
+ $events = $this->get_cron_events();
456
  $custom_schedules = get_option( 'crontrol_schedules', array() );
457
  $custom_keys = array_keys( $custom_schedules );
458
 
459
+ if ( is_wp_error( $events ) ) {
460
+ $events = array();
461
+ }
462
+
463
+ $used_schedules = array_unique( wp_list_pluck( $events, 'schedule' ) );
464
+
465
  $messages = array(
466
  /* translators: 1: The name of the cron schedule. */
467
  '2' => __( 'Successfully deleted the cron schedule %s.', 'wp-crontrol' ),
472
  );
473
  if ( isset( $_GET['crontrol_message'] ) && isset( $_GET['crontrol_name'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
474
  $hook = wp_unslash( $_GET['crontrol_name'] );
475
+ $message = wp_unslash( $_GET['crontrol_message'] );
476
+ $msg = sprintf( esc_html( $messages[ $message ] ), '<strong>' . esc_html( $hook ) . '</strong>' );
477
 
478
  printf( '<div id="message" class="updated notice is-dismissible"><p>%s</p></div>', $msg ); // WPCS:: XSS ok.
479
  }
488
  <th scope="col"><?php esc_html_e( 'Name', 'wp-crontrol' ); ?></th>
489
  <th scope="col"><?php esc_html_e( 'Interval', 'wp-crontrol' ); ?></th>
490
  <th scope="col"><?php esc_html_e( 'Display Name', 'wp-crontrol' ); ?></th>
491
+ <th scope="col"><?php esc_html_e( 'Delete', 'wp-crontrol' ); ?></th>
492
  </tr>
493
  </thead>
494
  <tbody>
512
  printf( '<td>%s</td>',
513
  esc_html( $data['display'] )
514
  );
515
+
516
+ echo '<td>';
517
+ if ( in_array( $name, $custom_keys, true ) ) {
518
+ if ( in_array( $name, $used_schedules, true ) ) {
519
+ esc_html_e( 'This custom schedule is in use and cannot be deleted', 'wp-crontrol' );
520
+ } else {
521
+ $url = add_query_arg( array(
522
+ 'page' => 'crontrol_admin_options_page',
523
+ 'action' => 'delete-sched',
524
+ 'id' => rawurlencode( $name ),
525
+ ), admin_url( 'options-general.php' ) );
526
+ $url = wp_nonce_url( $url, 'delete-sched_' . $name );
527
+ printf( '<span class="row-actions visible"><span class="delete"><a href="%s">%s</a></span></span>',
528
+ esc_url( $url ),
529
+ esc_html__( 'Delete', 'wp-crontrol' )
530
+ );
531
+ }
532
  } else {
533
+ echo '&nbsp;';
534
  }
535
+ echo '</td>';
536
  echo '</tr>';
537
  }
538
  }
540
  </tbody>
541
  </table>
542
  </div>
543
+ <div class="wrap">
544
+ <p class="description">
545
+ <?php printf(
546
+ '<a href="%s">%s</a>',
547
+ esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page' ) ),
548
+ esc_html__( 'Manage Cron Events', 'wp-crontrol' )
549
+ );
550
+ ?>
551
+ </p>
552
+ </div>
553
  <div class="wrap narrow">
554
+ <h2 class="title"><?php esc_html_e( 'Add Cron Schedule', 'wp-crontrol' ); ?></h2>
555
  <p><?php esc_html_e( 'Adding a new cron schedule will allow you to schedule events that re-occur at the given interval.', 'wp-crontrol' ); ?></p>
556
  <form method="post" action="options-general.php?page=crontrol_admin_options_page">
557
  <table class="form-table">
581
  */
582
  public function get_schedules() {
583
  $schedules = wp_get_schedules();
584
+ uasort( $schedules, array( $this, 'sort_schedules' ) );
585
  return $schedules;
586
  }
587
 
588
+ protected function sort_schedules( $a, $b ) {
589
+ return ( $a['interval'] - $b['interval'] );
590
+ }
591
+
592
  /**
593
  * Displays a dropdown filled with the possible schedules, including non-repeating.
594
  *
656
 
657
  if ( is_wp_error( $result ) ) {
658
  return $result;
659
+ } elseif ( wp_remote_retrieve_response_code( $result ) >= 300 ) {
660
  return new WP_Error( 'unexpected_http_response_code', sprintf(
661
  /* translators: 1: The HTTP response code. */
662
  __( 'Unexpected HTTP response code: %s', 'wp-crontrol' ),
678
  $status = $this->test_cron_spawn();
679
 
680
  if ( is_wp_error( $status ) ) {
681
+ if ( 'crontrol_info' === $status->get_error_code() ) {
682
  ?>
683
  <div id="cron-status-notice" class="notice notice-info">
684
  <p><?php echo esc_html( $status->get_error_message() ); ?></p>
691
  printf(
692
  /* translators: 1: Error message text. */
693
  esc_html__( 'There was a problem spawning a call to the WP-Cron system on your site. This means WP-Cron events on your site may not work. The problem was: %s', 'wp-crontrol' ),
694
+ '<br><strong>' . esc_html( $status->get_error_message() ) . '</strong>'
695
+ );
696
  ?></p>
697
  </div>
698
  <?php
747
  $helper_text = esc_html__( 'Cron events trigger actions in your code. Enter the schedule of the event, as well as the PHP code to execute when the action is triggered.', 'wp-crontrol' );
748
  } else {
749
  $helper_text = sprintf(
750
+ /* translators: %s: A file name */
751
  esc_html__( 'Cron events trigger actions in your code. A cron event needs a corresponding action hook somewhere in code, e.g. the %1$s file in your theme.', 'wp-crontrol' ),
752
  '<code>functions.php</code>'
753
  );
772
  list( $next_run_date, $next_run_time ) = explode( ' ', get_date_from_gmt( date( 'Y-m-d H:i:s', $existing['next_run'] ), 'Y-m-d H:i:s' ) );
773
  } else {
774
  $other_fields = wp_nonce_field( 'new-cron', '_wpnonce', true, false );
775
+ $existing = array(
776
+ 'hookname' => '',
777
+ 'args' => array(),
778
+ 'next_run' => 'now',
779
+ 'schedule' => false,
780
+ );
781
  $action = $is_php ? 'new_php_cron' : 'new_cron';
782
  $button = $is_php ? $new_tabs['php-cron'] : $new_tabs['cron'];
783
  $show_edit_tab = false;
784
+ $next_run_time = '';
785
+ $next_run_date = '';
786
  }
787
  if ( $is_php ) {
788
  if ( ! isset( $existing['args']['code'] ) ) {
811
  <?php if ( $is_php ) : ?>
812
  <tr>
813
  <th valign="top" scope="row"><label for="hookcode"><?php esc_html_e( 'PHP Code', 'wp-crontrol' ); ?></label></th>
814
+ <td>
815
+ <p class="description">
816
+ <?php
817
+ printf(
818
+ /* translators: The PHP tag name */
819
+ esc_html__( 'The opening %s tag must not be included.', 'wp-crontrol' ),
820
+ '<code>&lt;?php</code>'
821
+ );
822
+ ?>
823
+ </p>
824
+ <p><textarea class="large-text code" rows="10" cols="50" id="hookcode" name="hookcode"><?php echo esc_textarea( $existing['args']['code'] ); ?></textarea></p>
825
+ </td>
826
  </tr>
827
  <tr>
828
  <th valign="top" scope="row"><label for="eventname"><?php esc_html_e( 'Event Name (optional)', 'wp-crontrol' ); ?></label></th>
837
  <th valign="top" scope="row"><label for="args"><?php esc_html_e( 'Arguments (optional)', 'wp-crontrol' ); ?></label></th>
838
  <td>
839
  <input type="text" class="regular-text" id="args" name="args" value="<?php echo esc_attr( $display_args ); ?>"/>
840
+ <p class="description">
841
+ <?php
842
+ printf(
843
+ /* translators: 1, 2, and 3: Example values for an input field. */
844
+ esc_html__( 'Use a JSON encoded array, e.g. %1$s, %2$s, or %3$s', 'wp-crontrol' ),
845
+ '<code>[25]</code>',
846
+ '<code>["asdf"]</code>',
847
+ '<code>["i","want",25,"cakes"]</code>'
848
+ );
849
+ ?>
850
+ </p>
851
  </td>
852
  </tr>
853
  <?php endif; ?>
870
  </script>
871
  <input type="date" placeholder="YYYY-MM-DD" id="next_run_date" name="next_run_date" value="<?php echo esc_attr( $next_run_date ); ?>" maxlength="10" pattern="\d{4}\-\d{2}\-\d{2}" required />
872
  <input type="time" step="1" placeholder="HH:MM:SS" id="next_run_time" name="next_run_time" value="<?php echo esc_attr( $next_run_time ); ?>" maxlength="8" pattern="\d{2}:\d{2}:\d{2}" required />
873
+ <p class="description">
874
+ <?php
875
+ printf(
876
+ /* translators: %s Timezone name. */
877
+ esc_html__( 'Timezone: %s', 'wp-crontrol' ),
878
+ '<code>' . esc_html( $this->get_timezone_name() ) . '</code>'
879
+ );
880
+ ?>
881
+ </p>
882
+ <p class="description datetime-fallback hidden">
883
+ <?php
884
+ echo esc_html( sprintf(
885
+ /* translators: %s Date/time format for an input field. */
886
+ __( 'Format: %s', 'wp-crontrol' ),
887
+ date( 'Y' ) . '-02-25 12:34:00'
888
+ ) );
889
+ ?>
890
+ </p>
891
  </td>
892
  </tr><tr>
893
  <th valign="top" scope="row"><label for="schedule"><?php esc_html_e( 'Recurrence', 'wp-crontrol' ); ?></label></th>
894
  <td>
895
  <?php $this->schedules_dropdown( $existing['schedule'] ); ?>
896
+ <p class="description">
897
+ <?php
898
+ printf(
899
+ '<a href="%s">%s</a>',
900
+ esc_url( admin_url( 'options-general.php?page=crontrol_admin_options_page' ) ),
901
+ esc_html__( 'Manage Cron Schedules', 'wp-crontrol' )
902
+ );
903
+ ?>
904
+ </p>
905
  </td>
906
  </tr>
907
  </tbody></table>
967
  '7' => __( 'Failed to the delete the cron event %s.', 'wp-crontrol' ),
968
  /* translators: 1: The name of the cron event. */
969
  '8' => __( 'Failed to the execute the cron event %s.', 'wp-crontrol' ),
970
+ '9' => __( 'Successfully deleted the selected cron events.', 'wp-crontrol' ),
971
  );
972
  if ( isset( $_GET['crontrol_name'] ) && isset( $_GET['crontrol_message'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
973
  $hook = wp_unslash( $_GET['crontrol_name'] );
974
+ $message = wp_unslash( $_GET['crontrol_message'] );
975
+ $msg = sprintf( esc_html( $messages[ $message ] ), '<strong>' . esc_html( $hook ) . '</strong>' );
976
 
977
  printf( '<div id="message" class="updated notice is-dismissible"><p>%s</p></div>', $msg ); // WPCS:: XSS ok.
978
  }
979
  $events = $this->get_cron_events();
980
+ $doing_edit = ( isset( $_GET['action'] ) && 'edit-cron' === $_GET['action'] ) ? wp_unslash( $_GET['id'] ) : false ;
981
  $time_format = 'Y-m-d H:i:s';
982
 
983
  $core_hooks = array(
987
  'wp_scheduled_delete',
988
  'wp_scheduled_auto_draft_delete',
989
  'update_network_counts',
990
+ 'delete_expired_transients',
991
  );
992
 
993
  $this->show_cron_status();
995
  ?>
996
  <div class="wrap">
997
  <h1><?php esc_html_e( 'WP-Cron Events', 'wp-crontrol' ); ?></h1>
998
+ <form method="post" action="tools.php?page=crontrol_admin_manage_page">
999
  <table class="widefat striped">
1000
  <thead>
1001
  <tr>
1002
+ <td id="cb" class="manage-column column-cb check-column"><label class="screen-reader-text" for="cb-select-all-1"><?php esc_html_e( 'Select All', 'wp-crontrol' ); ?></label><input id="cb-select-all-1" type="checkbox"></td>
1003
  <th scope="col"><?php esc_html_e( 'Hook Name', 'wp-crontrol' ); ?></th>
1004
  <th scope="col"><?php esc_html_e( 'Arguments', 'wp-crontrol' ); ?></th>
1005
  <th scope="col"><?php esc_html_e( 'Actions', 'wp-crontrol' ); ?></th>
1012
  <?php
1013
  if ( is_wp_error( $events ) ) {
1014
  ?>
1015
+ <tr><td colspan="7"><?php echo esc_html( $events->get_error_message() ); ?></td></tr>
1016
  <?php
1017
  } else {
1018
  foreach ( $events as $id => $event ) {
1030
  if ( empty( $event->args ) ) {
1031
  $args = '<em>' . esc_html__( 'None', 'wp-crontrol' ) . '</em>';
1032
  } else {
1033
+ $json_options = 0;
1034
+
1035
  if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) {
1036
+ $json_options |= JSON_UNESCAPED_SLASHES;
 
 
1037
  }
1038
+ if ( defined( 'JSON_PRETTY_PRINT' ) ) {
1039
+ $json_options |= JSON_PRETTY_PRINT;
1040
+ }
1041
+
1042
+ $args = '<pre style="white-space:pre-wrap;margin-top:0">' . wp_json_encode( $event->args, $json_options ) . '</pre>';
1043
  }
1044
 
1045
+ echo '<tr>';
1046
+
1047
+ echo '<th scope="row" class="check-column">';
1048
+ if ( ! in_array( $event->hook, $core_hooks, true ) ) {
1049
+ printf(
1050
+ '<input type="checkbox" name="delete[%1$s][%2$s]" value="%3$s">',
1051
+ esc_attr( $event->time ),
1052
+ esc_attr( rawurlencode( $event->hook ) ),
1053
+ esc_attr( $event->sig )
1054
+ );
1055
+ }
1056
+ echo '</th>';
1057
 
1058
+ if ( 'crontrol_cron_job' === $event->hook ) {
1059
  if ( ! empty( $event->args['name'] ) ) {
1060
  /* translators: 1: The name of the PHP cron event. */
1061
  echo '<td><em>' . esc_html( sprintf( __( 'PHP Cron (%s)', 'wp-crontrol' ), $event->args['name'] ) ) . '</em></td>';
1070
  echo '<td>';
1071
  $callbacks = array();
1072
  foreach ( $this->get_action_callbacks( $event->hook ) as $callback ) {
1073
+ $callbacks[] = '<pre style="margin-top:0">' . self::output_callback( $callback ) . '</pre>';
1074
  }
1075
+ echo implode( '', $callbacks ); // WPCS:: XSS ok.
1076
  echo '</td>';
1077
  }
1078
 
1079
+ echo '<td style="white-space:nowrap">';
1080
  printf( '%s (%s)',
1081
  esc_html( get_date_from_gmt( date( 'Y-m-d H:i:s', $event->time ), $time_format ) ),
1082
  esc_html( $this->time_since( time(), $event->time ) )
1083
  );
1084
  echo '</td>';
1085
 
1086
+ echo '<td style="white-space:nowrap">';
1087
  if ( $event->schedule ) {
1088
+ echo esc_html( $this->get_schedule_name( $event->interval ) );
 
 
1089
  } else {
 
1090
  esc_html_e( 'Non-repeating', 'wp-crontrol' );
 
1091
  }
1092
+ echo '</td>';
1093
 
1094
  $links = array();
1095
 
1096
+ echo '<td style="white-space:nowrap"><span class="row-actions visible">';
1097
 
1098
  $link = array(
1099
  'page' => 'crontrol_admin_manage_page',
1100
  'action' => 'edit-cron',
1101
+ 'id' => rawurlencode( $event->hook ),
1102
+ 'sig' => rawurlencode( $event->sig ),
1103
+ 'next_run' => rawurlencode( $event->time ),
1104
  );
1105
  $link = add_query_arg( $link, admin_url( 'tools.php' ) ) . '#crontrol_form';
1106
  $links[] = "<a href='" . esc_url( $link ) . "'>" . esc_html__( 'Edit', 'wp-crontrol' ) . '</a>';
1108
  $link = array(
1109
  'page' => 'crontrol_admin_manage_page',
1110
  'action' => 'run-cron',
1111
+ 'id' => rawurlencode( $event->hook ),
1112
+ 'sig' => rawurlencode( $event->sig ),
1113
+ 'next_run' => rawurlencode( $event->time ),
1114
  );
1115
  $link = add_query_arg( $link, admin_url( 'tools.php' ) );
1116
  $link = wp_nonce_url( $link, "run-cron_{$event->hook}_{$event->sig}" );
1120
  $link = array(
1121
  'page' => 'crontrol_admin_manage_page',
1122
  'action' => 'delete-cron',
1123
+ 'id' => rawurlencode( $event->hook ),
1124
+ 'sig' => rawurlencode( $event->sig ),
1125
+ 'next_run' => rawurlencode( $event->time ),
1126
  );
1127
  $link = add_query_arg( $link, admin_url( 'tools.php' ) );
1128
  $link = wp_nonce_url( $link, "delete-cron_{$event->hook}_{$event->sig}_{$event->time}" );
1138
  ?>
1139
  </tbody>
1140
  </table>
1141
+ <?php
1142
+ wp_nonce_field( 'bulk-delete-crons' );
1143
+ submit_button(
1144
+ __( 'Delete Selected Events', 'wp-crontrol' ),
1145
+ 'primary large',
1146
+ 'delete_crons'
1147
+ );
1148
+ ?>
1149
+ </form>
1150
 
1151
  </div>
1152
  <?php
1153
  if ( is_array( $doing_edit ) ) {
1154
  $this->show_cron_form( 'crontrol_cron_job' == $doing_edit['hookname'], $doing_edit );
1155
  } else {
1156
+ $this->show_cron_form( ( isset( $_GET['action'] ) and 'new-php-cron' === $_GET['action'] ), false );
1157
  }
1158
  }
1159
 
1162
 
1163
  $actions = array();
1164
 
1165
+ if ( isset( $wp_filter[ $name ] ) ) {
1166
 
1167
  # http://core.trac.wordpress.org/ticket/17817
1168
+ $action = $wp_filter[ $name ];
1169
 
1170
  foreach ( $action as $priority => $callbacks ) {
 
1171
  foreach ( $callbacks as $callback ) {
 
1172
  $callback = self::populate_callback( $callback );
1173
 
1174
  $actions[] = array(
1175
  'priority' => $priority,
1176
  'callback' => $callback,
1177
  );
 
1178
  }
 
1179
  }
 
1180
  }
1181
 
1182
  return $actions;
 
1183
  }
1184
 
1185
  public static function populate_callback( array $callback ) {
1194
  }
1195
 
1196
  if ( is_array( $callback['function'] ) ) {
 
1197
  if ( is_object( $callback['function'][0] ) ) {
1198
  $class = get_class( $callback['function'][0] );
1199
  $access = '->';
1203
  }
1204
 
1205
  $callback['name'] = $class . $access . $callback['function'][1] . '()';
 
1206
  } elseif ( is_object( $callback['function'] ) ) {
 
1207
  if ( is_a( $callback['function'], 'Closure' ) ) {
1208
  $callback['name'] = 'Closure';
1209
  } else {
1210
  $class = get_class( $callback['function'] );
1211
  $callback['name'] = $class . '->__invoke()';
1212
  }
 
1213
  } else {
 
1214
  $callback['name'] = $callback['function'] . '()';
 
1215
  }
1216
 
1217
  return $callback;
1218
 
1219
  }
1220
 
1221
+ public static function output_callback( array $callback ) {
1222
+ global $wp_filesystem;
1223
+
1224
+ $plugins = $wp_filesystem->wp_plugins_dir();
1225
+ $qm = $plugins . 'query-monitor/query-monitor.php';
1226
+ $html = plugin_dir_path( $qm ) . 'output/Html.php';
1227
+
1228
+ // If Query Monitor is installed, use its rich callback output:
1229
+ if ( class_exists( 'QueryMonitor' ) && file_exists( $html ) ) {
1230
+ require_once $html;
1231
+
1232
+ if ( class_exists( 'QM_Output_Html' ) ) {
1233
+ return QM_Output_Html::output_filename(
1234
+ $callback['callback']['name'],
1235
+ $callback['callback']['file'],
1236
+ $callback['callback']['line']
1237
+ );
1238
+ }
1239
+ }
1240
+
1241
+ return $callback['callback']['name'];
1242
+ }
1243
+
1244
  /**
1245
  * Pretty-prints the difference in two times.
1246
  *
1292
  // x days, xx hours
1293
  // so there's only two bits of calculation below:
1294
 
1295
+ $j = count( $chunks );
1296
+
1297
  // step one: the first chunk
1298
+ for ( $i = 0; $i < $j; $i++ ) {
1299
  $seconds = $chunks[ $i ][0];
1300
  $name = $chunks[ $i ][1];
1301
 
1302
  // finding the biggest chunk (if the chunk fits, break)
1303
+ $count = floor( $since / $seconds );
1304
+ if ( $count ) {
1305
  break;
1306
  }
1307
  }
1313
  if ( $i + 1 < $j ) {
1314
  $seconds2 = $chunks[ $i + 1 ][0];
1315
  $name2 = $chunks[ $i + 1 ][1];
1316
+ $count2 = floor( ( $since - ( $seconds * $count ) ) / $seconds2 );
1317
+ if ( $count2 ) {
1318
  // add to output var
1319
  $output .= ' ' . sprintf( translate_nooped_plural( $name2, $count2, 'wp-crontrol' ), $count2 );
1320
  }
1323
  return $output;
1324
  }
1325
 
1326
+ /**
1327
+ * Returns the schedule display name for a given interval.
1328
+ *
1329
+ * Falls back to the time interval if no corresponding schedule exists.
1330
+ *
1331
+ * @param int $interval An interval of time.
1332
+ * @return string The interval display name.
1333
+ */
1334
+ protected function get_schedule_name( $interval ) {
1335
+ $schedules = $this->get_schedules();
1336
+
1337
+ foreach ( $schedules as $schedule ) {
1338
+ if ( $interval === $schedule['interval'] ) {
1339
+ return $schedule['display'];
1340
+ }
1341
+ }
1342
+
1343
+ return $this->interval( $interval );
1344
+ }
1345
+
1346
+ /**
1347
+ * Enqueues the editor UI that's used for the PHP cron event code editor.
1348
+ */
1349
+ public function enqueue_code_editor() {
1350
+ if ( ! function_exists( 'wp_enqueue_code_editor' ) ) {
1351
+ return;
1352
+ }
1353
+ if ( ! current_user_can( 'edit_files' ) ) {
1354
+ return;
1355
+ }
1356
+
1357
+ $settings = wp_enqueue_code_editor( array(
1358
+ 'type' => 'text/x-php',
1359
+ ) );
1360
+
1361
+ if ( false === $settings ) {
1362
+ return;
1363
+ }
1364
+
1365
+ wp_add_inline_script( 'code-editor', sprintf(
1366
+ 'jQuery( function( $ ) {
1367
+ if ( $( "#hookcode" ).length ) {
1368
+ wp.codeEditor.initialize( "hookcode", %s );
1369
+ }
1370
+ } );',
1371
+ wp_json_encode( $settings )
1372
+ ) );
1373
+ }
1374
+
1375
+ /**
1376
+ * Filters the list of query arguments which get removed from admin area URLs in WordPress.
1377
+ *
1378
+ * @link https://core.trac.wordpress.org/ticket/23367
1379
+ *
1380
+ * @param string[] $args List of removable query arguments.
1381
+ * @return string[] Updated list of removable query arguments.
1382
+ */
1383
+ public function filter_removable_query_args( array $args ) {
1384
+ return array_merge( $args, array(
1385
+ 'crontrol_message',
1386
+ 'crontrol_name',
1387
+ ) );
1388
+ }
1389
+
1390
  public static function init() {
1391
 
1392
  static $instance = null;