WP Crontrol - Version 1.10.0

Version Description

Download this release

Release Info

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

Code changes from version 1.9.1 to 1.10.0

css/wp-crontrol.css CHANGED
@@ -49,6 +49,17 @@ table.wp-list-table {
49
  border-color: #dc3232;
50
  }
51
 
 
 
 
 
 
 
 
 
 
 
 
52
  #crontrol-hash-message {
53
  display: none;
54
  }
49
  border-color: #dc3232;
50
  }
51
 
52
+ .wp-list-table .column-crontrol_icon .dashicons,
53
+ .wp-list-table .check-column .dashicons {
54
+ margin-left: 6px;
55
+ font-size: 14px;
56
+ color: #aaa;
57
+ }
58
+
59
+ .wp-list-table .column-crontrol_icon .dashicons {
60
+ margin-top: 2px;
61
+ }
62
+
63
  #crontrol-hash-message {
64
  display: none;
65
  }
js/wp-crontrol.js CHANGED
@@ -4,14 +4,8 @@
4
  * @package wp-crontrol
5
  */
6
 
7
- const header = document.getElementById( 'crontrol-header' );
8
- const wpbody = document.getElementById( 'wpbody-content' );
9
  let hashtimer = null;
10
 
11
- if ( header && wpbody ) {
12
- wpbody.prepend( header );
13
- }
14
-
15
  if ( window.wpCrontrol && window.wpCrontrol.eventsHash && window.wpCrontrol.eventsHashInterval ) {
16
  hashtimer = setInterval( crontrolCheckHash, ( 1000 * window.wpCrontrol.eventsHashInterval ) );
17
  }
@@ -28,6 +22,10 @@ function crontrolCheckHash() {
28
  if ( response.success && response.data && response.data !== window.wpCrontrol.eventsHash ) {
29
  jQuery( '#crontrol-hash-message' ).slideDown();
30
 
 
 
 
 
31
  if ( hashtimer ) {
32
  clearInterval( hashtimer );
33
  }
4
  * @package wp-crontrol
5
  */
6
 
 
 
7
  let hashtimer = null;
8
 
 
 
 
 
9
  if ( window.wpCrontrol && window.wpCrontrol.eventsHash && window.wpCrontrol.eventsHashInterval ) {
10
  hashtimer = setInterval( crontrolCheckHash, ( 1000 * window.wpCrontrol.eventsHashInterval ) );
11
  }
22
  if ( response.success && response.data && response.data !== window.wpCrontrol.eventsHash ) {
23
  jQuery( '#crontrol-hash-message' ).slideDown();
24
 
25
+ if ( wp && wp.a11y && wp.a11y.speak ) {
26
+ wp.a11y.speak( jQuery( '#crontrol-hash-message' ).text() );
27
+ }
28
+
29
  if ( hashtimer ) {
30
  clearInterval( hashtimer );
31
  }
readme.md CHANGED
@@ -2,9 +2,9 @@
2
 
3
  Contributors: johnbillion, scompt
4
  Tags: cron, wp-cron, crontrol, debug
5
- Requires at least: 4.1
6
- Tested up to: 5.6
7
- Stable tag: 1.9.1
8
  Requires PHP: 5.3
9
  Donate link: https://github.com/sponsors/johnbillion
10
 
@@ -42,7 +42,7 @@ Yes.
42
 
43
  ### I get the error "There was a problem spawning a call to the WP-Cron system on your site". How do I fix this?
44
 
45
- If this error is persistent then you should contact your web host for support. It usually means the HTTP connection that runs cron events on your site is failing for some reason.
46
 
47
  ### Why do some cron events miss their schedule?
48
 
@@ -74,15 +74,15 @@ You can change the time and recurrence of a cron event by clicking the "Edit" li
74
 
75
  ### How can I create a cron event that requests a URL?
76
 
77
- From the "Add PHP Cron Event" tab, create a cron event that includes PHP that fetches the URL using the WordPress HTTP API. For example:
78
 
79
  wp_remote_get( 'http://example.com' );
80
 
81
- ### Why do changes that I make to some cron events not get saved?
82
 
83
- Unfortunately WordPress core doesn't expose all the errors that can occur in its cron event system, so a plugin such as WP Crontrol can't always tell you if or what went wrong when saving cron events.
84
 
85
- You should try deactivating your other plugins one by one to see if one is causing a problem. Otherwise, I'm afraid I don't have much useful information for you. Hopefully a future version of WordPress will provide better error handling for its cron event system.
86
 
87
  ### Can I see a historical log of all the cron events that ran on my site?
88
 
@@ -138,10 +138,19 @@ The cron commands which were previously included in WP Crontrol are now part of
138
 
139
  2. New cron events can be added<br>![](.wordpress-org/screenshot-2.png)
140
 
141
- 3. New cron schedules can be added, giving plugin developers more options when scheduling events<br>![](.wordpress-org/screenshot-2.png)
142
 
143
  ## Changelog ##
144
 
 
 
 
 
 
 
 
 
 
145
  ### 1.9.1 ###
146
 
147
  * Fix the adding of new cron events when `DISALLOW_FILE_EDIT` is true.
2
 
3
  Contributors: johnbillion, scompt
4
  Tags: cron, wp-cron, crontrol, debug
5
+ Requires at least: 4.2
6
+ Tested up to: 5.8
7
+ Stable tag: 1.10.0
8
  Requires PHP: 5.3
9
  Donate link: https://github.com/sponsors/johnbillion
10
 
42
 
43
  ### I get the error "There was a problem spawning a call to the WP-Cron system on your site". How do I fix this?
44
 
45
+ [You can read all about problems spwaning WP-Cron here](https://github.com/johnbillion/wp-crontrol/wiki/Problems-with-spawning-a-call-to-the-WP-Cron-system).
46
 
47
  ### Why do some cron events miss their schedule?
48
 
74
 
75
  ### How can I create a cron event that requests a URL?
76
 
77
+ From the Tools Cron Events → Add New screen, create a PHP cron event that includes PHP that fetches the URL using the WordPress HTTP API. For example:
78
 
79
  wp_remote_get( 'http://example.com' );
80
 
81
+ Please see the "Which users can manage PHP cron events?" FAQ for information about which users can create PHP cron events.
82
 
83
+ ### Why do changes that I make to some cron events not get saved?
84
 
85
+ [You can read all about problems with editing cron events here](https://github.com/johnbillion/wp-crontrol/wiki/Problems-adding-or-editing-WP-Cron-events).
86
 
87
  ### Can I see a historical log of all the cron events that ran on my site?
88
 
138
 
139
  2. New cron events can be added<br>![](.wordpress-org/screenshot-2.png)
140
 
141
+ 3. New cron schedules can be added, giving plugin developers more options when scheduling events<br>![](.wordpress-org/screenshot-3.png)
142
 
143
  ## Changelog ##
144
 
145
+ ### 1.10.0 ###
146
+
147
+ * Support for more granular cron-related error messages in WordPress 5.7
148
+ * Several accessibility improvements
149
+ * Warning for events that are attached to [a schedule that is too frequent](https://github.com/johnbillion/wp-crontrol/wiki/This-interval-is-less-than-the-WP_CRON_LOCK_TIMEOUT-constant)
150
+ * More clarity around events and schedules that are built in to WordPress core
151
+ * Add a Help tab with links to the wiki and FAQs
152
+
153
+
154
  ### 1.9.1 ###
155
 
156
  * Fix the adding of new cron events when `DISALLOW_FILE_EDIT` is true.
src/event-list-table.php CHANGED
@@ -37,6 +37,11 @@ class Table extends \WP_List_Table {
37
  */
38
  protected static $count_by_hook;
39
 
 
 
 
 
 
40
  protected $all_events = array();
41
 
42
  /**
@@ -201,10 +206,10 @@ class Table extends \WP_List_Table {
201
  $hooks_type = ( ! empty( $_GET['hooks_type'] ) ? $_GET['hooks_type'] : 'all' );
202
 
203
  $types = array(
204
- 'all' => __( 'All hooks', 'wp-crontrol' ),
205
- 'noaction' => __( 'Hooks with no action', 'wp-crontrol' ),
206
- 'core' => __( 'WordPress core hooks', 'wp-crontrol' ),
207
- 'custom' => __( 'Custom hooks', 'wp-crontrol' ),
208
  );
209
 
210
  $url = admin_url( 'tools.php?page=crontrol_admin_manage_page' );
@@ -253,7 +258,7 @@ class Table extends \WP_List_Table {
253
  }
254
  }
255
 
256
- if ( is_late( $event ) ) {
257
  $classes[] = 'crontrol-warning';
258
  }
259
 
@@ -346,23 +351,35 @@ class Table extends \WP_List_Table {
346
  * Outputs the checkbox cell of a table row.
347
  *
348
  * @param stdClass $event The cron event for the current row.
 
349
  */
350
  protected function column_cb( $event ) {
351
- if ( ! in_array( $event->hook, self::$persistent_core_hooks, true ) && ( ( 'crontrol_cron_job' !== $event->hook ) || self::$can_edit_files ) ) {
352
- ?>
353
- <label class="screen-reader-text" for="">
354
- <?php printf( esc_html__( 'Select this row', 'wp-crontrol' ) ); ?>
355
- </label>
356
- <?php
357
- printf(
358
- '<input type="checkbox" name="delete[%1$s][%2$s]" value="%3$s">',
359
- esc_attr( $event->time ),
360
- esc_attr( rawurlencode( $event->hook ) ),
361
- esc_attr( $event->sig )
362
- );
363
- ?>
364
- <?php
 
 
 
 
 
 
 
 
 
365
  }
 
 
366
  }
367
 
368
  /**
@@ -518,6 +535,17 @@ class Table extends \WP_List_Table {
518
  '<span class="status-crontrol-error"><span class="dashicons dashicons-warning" aria-hidden="true"></span> %s</span>',
519
  esc_html( $schedule_name->get_error_message() )
520
  );
 
 
 
 
 
 
 
 
 
 
 
521
  } else {
522
  return esc_html( $schedule_name );
523
  }
37
  */
38
  protected static $count_by_hook;
39
 
40
+ /**
41
+ * Array of all cron events.
42
+ *
43
+ * @var object[] Array of event objects.
44
+ */
45
  protected $all_events = array();
46
 
47
  /**
206
  $hooks_type = ( ! empty( $_GET['hooks_type'] ) ? $_GET['hooks_type'] : 'all' );
207
 
208
  $types = array(
209
+ 'all' => __( 'All events', 'wp-crontrol' ),
210
+ 'noaction' => __( 'Events with no action', 'wp-crontrol' ),
211
+ 'core' => __( 'WordPress core events', 'wp-crontrol' ),
212
+ 'custom' => __( 'Custom events', 'wp-crontrol' ),
213
  );
214
 
215
  $url = admin_url( 'tools.php?page=crontrol_admin_manage_page' );
258
  }
259
  }
260
 
261
+ if ( is_late( $event ) || is_too_frequent( $event ) ) {
262
  $classes[] = 'crontrol-warning';
263
  }
264
 
351
  * Outputs the checkbox cell of a table row.
352
  *
353
  * @param stdClass $event The cron event for the current row.
354
+ * @return string The cell output.
355
  */
356
  protected function column_cb( $event ) {
357
+ $id = sprintf(
358
+ 'wp-crontrol-delete-%1$s-%2$s-%3$s',
359
+ $event->time,
360
+ rawurlencode( $event->hook ),
361
+ $event->sig
362
+ );
363
+
364
+ if ( in_array( $event->hook, self::$persistent_core_hooks, true ) ) {
365
+ return sprintf(
366
+ '<span class="dashicons dashicons-wordpress" aria-hidden="true"></span>
367
+ <span class="screen-reader-text">%s</span>',
368
+ esc_html__( 'This is a WordPress core event and cannot be deleted', 'wp-crontrol' )
369
+ );
370
+ } elseif ( ( 'crontrol_cron_job' !== $event->hook ) || self::$can_edit_files ) {
371
+ return sprintf(
372
+ '<label class="screen-reader-text" for="%1$s">%2$s</label>
373
+ <input type="checkbox" name="delete[%3$s][%4$s]" value="%5$s" id="%1$s">',
374
+ esc_attr( $id ),
375
+ esc_html__( 'Select this row', 'wp-crontrol' ),
376
+ esc_attr( $event->time ),
377
+ esc_attr( rawurlencode( $event->hook ) ),
378
+ esc_attr( $event->sig )
379
+ );
380
  }
381
+
382
+ return '';
383
  }
384
 
385
  /**
535
  '<span class="status-crontrol-error"><span class="dashicons dashicons-warning" aria-hidden="true"></span> %s</span>',
536
  esc_html( $schedule_name->get_error_message() )
537
  );
538
+ } elseif ( is_too_frequent( $event ) ) {
539
+ return sprintf(
540
+ '%1$s<span class="status-crontrol-warning"><br><span class="dashicons dashicons-warning" aria-hidden="true"></span> %2$s</span>',
541
+ esc_html( $schedule_name ),
542
+ sprintf(
543
+ /* translators: 1: The name of the configuration constant, 2: The value of the configuration constant */
544
+ esc_html__( 'This interval is less than the %1$s constant which is set to %2$s seconds. Events that use it may not run on time.', 'wp-crontrol' ),
545
+ '<code>WP_CRON_LOCK_TIMEOUT</code>',
546
+ intval( WP_CRON_LOCK_TIMEOUT )
547
+ )
548
+ );
549
  } else {
550
  return esc_html( $schedule_name );
551
  }
src/event.php CHANGED
@@ -18,7 +18,7 @@ use WP_Error;
18
  *
19
  * @param string $hookname The hook name of the cron event to run.
20
  * @param string $sig The cron event signature.
21
- * @return bool Whether the execution was successful.
22
  */
23
  function run( $hookname, $sig ) {
24
  $crons = _get_cron_array();
@@ -34,7 +34,7 @@ function run( $hookname, $sig ) {
34
  delete_transient( 'doing_cron' );
35
  $scheduled = force_schedule_single_event( $hookname, $event->args ); // UTC
36
 
37
- if ( false === $scheduled ) {
38
  return $scheduled;
39
  }
40
 
@@ -48,7 +48,7 @@ function run( $hookname, $sig ) {
48
  sleep( 1 );
49
 
50
  /**
51
- * Fires after a cron event is ran manually.
52
  *
53
  * @param object $event {
54
  * An object containing the event's data.
@@ -65,7 +65,15 @@ function run( $hookname, $sig ) {
65
  return true;
66
  }
67
  }
68
- return false;
 
 
 
 
 
 
 
 
69
  }
70
 
71
  /**
@@ -75,7 +83,7 @@ function run( $hookname, $sig ) {
75
  *
76
  * @param string $hook Action hook to execute when the event is run.
77
  * @param array $args Optional. Array containing each separate argument to pass to the hook's callback function.
78
- * @return bool True if event successfully scheduled. False for failure.
79
  */
80
  function force_schedule_single_event( $hook, $args = array() ) {
81
  $event = (object) array(
@@ -93,7 +101,21 @@ function force_schedule_single_event( $hook, $args = array() ) {
93
  );
94
  uksort( $crons, 'strnatcasecmp' );
95
 
96
- return _set_cron_array( $crons );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  }
98
 
99
  /**
@@ -103,13 +125,16 @@ function force_schedule_single_event( $hook, $args = array() ) {
103
  * @param string $schedule The recurrence of the cron event.
104
  * @param string $hook The name of the hook to execute.
105
  * @param array $args Arguments to add to the cron event.
106
- * @return bool Whether the addition was successful.
107
  */
108
  function add( $next_run_local, $schedule, $hook, array $args ) {
109
  $next_run_local = strtotime( $next_run_local, current_time( 'timestamp' ) );
110
 
111
  if ( false === $next_run_local ) {
112
- return false;
 
 
 
113
  }
114
 
115
  $next_run_utc = get_gmt_from_date( gmdate( 'Y-m-d H:i:s', $next_run_local ), 'U' );
@@ -133,10 +158,35 @@ function add( $next_run_local, $schedule, $hook, array $args ) {
133
  }
134
 
135
  if ( '_oneoff' === $schedule ) {
136
- return ( false !== wp_schedule_single_event( $next_run_utc, $hook, $args ) );
137
  } else {
138
- return ( false !== wp_schedule_event( $next_run_utc, $schedule, $hook, $args ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  }
 
 
140
  }
141
 
142
  /**
@@ -145,21 +195,40 @@ function add( $next_run_local, $schedule, $hook, array $args ) {
145
  * @param string $hook The hook name of the event to delete.
146
  * @param string $sig The cron event signature.
147
  * @param string $next_run_utc The UTC time that the event would be run at.
148
- * @return bool Whether the deletion was successful.
149
  */
150
  function delete( $hook, $sig, $next_run_utc ) {
151
  $event = get_single( $hook, $sig, $next_run_utc );
152
 
153
- if ( ! $event ) {
154
- return false;
155
  }
156
 
157
- $unscheduled = wp_unschedule_event( $event->timestamp, $event->hook, $event->args );
158
 
159
- if ( false === $unscheduled ) {
 
 
 
 
 
 
 
 
160
  return $unscheduled;
161
  }
162
 
 
 
 
 
 
 
 
 
 
 
 
163
  return true;
164
  }
165
 
@@ -204,9 +273,10 @@ function get() {
204
  /**
205
  * Gets a single cron event.
206
  *
207
- * @param string $hook The hook name of the event.
208
  * @param string $sig The event signature.
209
  * @param string $next_run_utc The UTC time that the event would be run at.
 
210
  */
211
  function get_single( $hook, $sig, $next_run_utc ) {
212
  $crons = _get_cron_array();
@@ -221,7 +291,14 @@ function get_single( $hook, $sig, $next_run_utc ) {
221
  return $event;
222
  }
223
 
224
- return null;
 
 
 
 
 
 
 
225
  }
226
 
227
  /**
@@ -270,6 +347,22 @@ function get_schedule_name( stdClass $event ) {
270
  ) );
271
  }
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  /**
274
  * Determines whether an event is late.
275
  *
18
  *
19
  * @param string $hookname The hook name of the cron event to run.
20
  * @param string $sig The cron event signature.
21
+ * @return true|WP_Error True if the execution was successful, WP_Error if not.
22
  */
23
  function run( $hookname, $sig ) {
24
  $crons = _get_cron_array();
34
  delete_transient( 'doing_cron' );
35
  $scheduled = force_schedule_single_event( $hookname, $event->args ); // UTC
36
 
37
+ if ( is_wp_error( $scheduled ) ) {
38
  return $scheduled;
39
  }
40
 
48
  sleep( 1 );
49
 
50
  /**
51
+ * Fires after a cron event is scheduled to run manually.
52
  *
53
  * @param object $event {
54
  * An object containing the event's data.
65
  return true;
66
  }
67
  }
68
+
69
+ return new WP_Error(
70
+ 'not_found',
71
+ sprintf(
72
+ /* translators: 1: The name of the cron event. */
73
+ __( 'The cron event %s could not be found.', 'wp-crontrol' ),
74
+ $hookname
75
+ )
76
+ );
77
  }
78
 
79
  /**
83
  *
84
  * @param string $hook Action hook to execute when the event is run.
85
  * @param array $args Optional. Array containing each separate argument to pass to the hook's callback function.
86
+ * @return true|WP_Error True if event successfully scheduled. WP_Error on failure.
87
  */
88
  function force_schedule_single_event( $hook, $args = array() ) {
89
  $event = (object) array(
101
  );
102
  uksort( $crons, 'strnatcasecmp' );
103
 
104
+ $result = _set_cron_array( $crons );
105
+
106
+ // Not using the WP_Error from `_set_cron_array()` here so we can provide a more specific error message.
107
+ if ( false === $result ) {
108
+ return new WP_Error(
109
+ 'could_not_add',
110
+ sprintf(
111
+ /* translators: 1: The name of the cron event. */
112
+ __( 'Failed to schedule the cron event %s.', 'wp-crontrol' ),
113
+ $hook
114
+ )
115
+ );
116
+ }
117
+
118
+ return true;
119
  }
120
 
121
  /**
125
  * @param string $schedule The recurrence of the cron event.
126
  * @param string $hook The name of the hook to execute.
127
  * @param array $args Arguments to add to the cron event.
128
+ * @return true|WP_error True if the addition was successful, WP_Error otherwise.
129
  */
130
  function add( $next_run_local, $schedule, $hook, array $args ) {
131
  $next_run_local = strtotime( $next_run_local, current_time( 'timestamp' ) );
132
 
133
  if ( false === $next_run_local ) {
134
+ return new WP_Error(
135
+ 'invalid_timestamp',
136
+ __( 'Invalid timestamp provided.', 'wp-crontrol' )
137
+ );
138
  }
139
 
140
  $next_run_utc = get_gmt_from_date( gmdate( 'Y-m-d H:i:s', $next_run_local ), 'U' );
158
  }
159
 
160
  if ( '_oneoff' === $schedule ) {
161
+ $result = wp_schedule_single_event( $next_run_utc, $hook, $args, true );
162
  } else {
163
+ $result = wp_schedule_event( $next_run_utc, $schedule, $hook, $args, true );
164
+ }
165
+
166
+ /**
167
+ * Possible return values of `wp_schedule_*()` as called above:
168
+ *
169
+ * - 5.7+ Success: true, Failure: WP_Error
170
+ * - 5.1+ Success: true, Failure: false
171
+ * - <5.1 Success: null, Failure: false
172
+ */
173
+
174
+ if ( is_wp_error( $result ) ) {
175
+ return $result;
176
+ }
177
+
178
+ if ( false === $result ) {
179
+ return new WP_Error(
180
+ 'could_not_add',
181
+ sprintf(
182
+ /* translators: 1: The name of the cron event. */
183
+ __( 'Failed to schedule the cron event %s.', 'wp-crontrol' ),
184
+ $hook
185
+ )
186
+ );
187
  }
188
+
189
+ return true;
190
  }
191
 
192
  /**
195
  * @param string $hook The hook name of the event to delete.
196
  * @param string $sig The cron event signature.
197
  * @param string $next_run_utc The UTC time that the event would be run at.
198
+ * @return true|WP_Error True if the deletion was successful, WP_Error otherwise.
199
  */
200
  function delete( $hook, $sig, $next_run_utc ) {
201
  $event = get_single( $hook, $sig, $next_run_utc );
202
 
203
+ if ( is_wp_error( $event ) ) {
204
+ return $event;
205
  }
206
 
207
+ $unscheduled = wp_unschedule_event( $event->timestamp, $event->hook, $event->args, true );
208
 
209
+ /**
210
+ * Possible return values of `wp_unschedule_*()` as called above:
211
+ *
212
+ * - 5.7+ Success: true, Failure: WP_Error
213
+ * - 5.1+ Success: true, Failure: false
214
+ * - <5.1 Success: null, Failure: false
215
+ */
216
+
217
+ if ( is_wp_error( $unscheduled ) ) {
218
  return $unscheduled;
219
  }
220
 
221
+ if ( false === $unscheduled ) {
222
+ return new WP_Error(
223
+ 'could_not_delete',
224
+ sprintf(
225
+ /* translators: 1: The name of the cron event. */
226
+ __( 'Failed to the delete the cron event %s.', 'wp-crontrol' ),
227
+ $hook
228
+ )
229
+ );
230
+ }
231
+
232
  return true;
233
  }
234
 
273
  /**
274
  * Gets a single cron event.
275
  *
276
+ * @param string $hook The hook name of the event.
277
  * @param string $sig The event signature.
278
  * @param string $next_run_utc The UTC time that the event would be run at.
279
+ * @return object|WP_Error A cron event object, or a WP_Error if it's not found.
280
  */
281
  function get_single( $hook, $sig, $next_run_utc ) {
282
  $crons = _get_cron_array();
291
  return $event;
292
  }
293
 
294
+ return new WP_Error(
295
+ 'not_found',
296
+ sprintf(
297
+ /* translators: 1: The name of the cron event. */
298
+ __( 'The cron event %s could not be found.', 'wp-crontrol' ),
299
+ $hook
300
+ )
301
+ );
302
  }
303
 
304
  /**
347
  ) );
348
  }
349
 
350
+ /**
351
+ * Determines whether the schedule for an event means it runs too frequently to be reliable.
352
+ *
353
+ * @param stdClass $event A WP-Cron event.
354
+ * @return bool Whether the event scheduled is too frequent.
355
+ */
356
+ function is_too_frequent( stdClass $event ) {
357
+ $schedules = Schedule\get();
358
+
359
+ if ( ! isset( $schedules[ $event->schedule ] ) ) {
360
+ return false;
361
+ }
362
+
363
+ return $schedules[ $event->schedule ]['is_too_frequent'];
364
+ }
365
+
366
  /**
367
  * Determines whether an event is late.
368
  *
src/schedule-list-table.php CHANGED
@@ -40,6 +40,15 @@ class Schedule_List_Table extends \WP_List_Table {
40
  ) );
41
  }
42
 
 
 
 
 
 
 
 
 
 
43
  /**
44
  * Prepares the list table items and arguments.
45
  */
@@ -66,6 +75,7 @@ class Schedule_List_Table extends \WP_List_Table {
66
  */
67
  public function get_columns() {
68
  return array(
 
69
  'crontrol_name' => __( 'Internal Name', 'wp-crontrol' ),
70
  'crontrol_interval' => __( 'Interval', 'wp-crontrol' ),
71
  'crontrol_display' => __( 'Display Name', 'wp-crontrol' ),
@@ -117,6 +127,24 @@ class Schedule_List_Table extends \WP_List_Table {
117
  return $this->row_actions( $links );
118
  }
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  /**
121
  * Returns the output for the schdule name cell of a table row.
122
  *
@@ -140,12 +168,12 @@ class Schedule_List_Table extends \WP_List_Table {
140
  esc_html( interval( $schedule['interval'] ) )
141
  );
142
 
143
- if ( $schedule['interval'] < WP_CRON_LOCK_TIMEOUT ) {
144
  $interval .= sprintf(
145
  '<span class="status-crontrol-warning"><br><span class="dashicons dashicons-warning" aria-hidden="true"></span> %s</span>',
146
  sprintf(
147
  /* translators: 1: The name of the configuration constant, 2: The value of the configuration constant */
148
- esc_html__( 'This interval is less than the %1$s constant which is set to %2$s. Events that use it may not run on time.', 'wp-crontrol' ),
149
  '<code>WP_CRON_LOCK_TIMEOUT</code>',
150
  intval( WP_CRON_LOCK_TIMEOUT )
151
  )
40
  ) );
41
  }
42
 
43
+ /**
44
+ * Gets the name of the primary column.
45
+ *
46
+ * @return string The name of the primary column.
47
+ */
48
+ protected function get_primary_column_name() {
49
+ return 'crontrol_name';
50
+ }
51
+
52
  /**
53
  * Prepares the list table items and arguments.
54
  */
75
  */
76
  public function get_columns() {
77
  return array(
78
+ 'crontrol_icon' => '',
79
  'crontrol_name' => __( 'Internal Name', 'wp-crontrol' ),
80
  'crontrol_interval' => __( 'Interval', 'wp-crontrol' ),
81
  'crontrol_display' => __( 'Display Name', 'wp-crontrol' ),
127
  return $this->row_actions( $links );
128
  }
129
 
130
+ /**
131
+ * Returns the output for the icon cell of a table row.
132
+ *
133
+ * @param array $schedule The schedule for the current row.
134
+ * @return string The cell output.
135
+ */
136
+ protected function column_crontrol_icon( array $schedule ) {
137
+ if ( in_array( $schedule['name'], self::$core_schedules, true ) ) {
138
+ return sprintf(
139
+ '<span class="dashicons dashicons-wordpress" aria-hidden="true"></span>
140
+ <span class="screen-reader-text">%s</span>',
141
+ esc_html__( 'This is a WordPress core schedule and cannot be deleted', 'wp-crontrol' )
142
+ );
143
+ }
144
+
145
+ return '';
146
+ }
147
+
148
  /**
149
  * Returns the output for the schdule name cell of a table row.
150
  *
168
  esc_html( interval( $schedule['interval'] ) )
169
  );
170
 
171
+ if ( $schedule['is_too_frequent'] ) {
172
  $interval .= sprintf(
173
  '<span class="status-crontrol-warning"><br><span class="dashicons dashicons-warning" aria-hidden="true"></span> %s</span>',
174
  sprintf(
175
  /* translators: 1: The name of the configuration constant, 2: The value of the configuration constant */
176
+ esc_html__( 'This interval is less than the %1$s constant which is set to %2$s seconds. Events that use it may not run on time.', 'wp-crontrol' ),
177
  '<code>WP_CRON_LOCK_TIMEOUT</code>',
178
  intval( WP_CRON_LOCK_TIMEOUT )
179
  )
src/schedule.php CHANGED
@@ -64,6 +64,7 @@ function get() {
64
 
65
  array_walk( $schedules, function( array &$schedule, $name ) {
66
  $schedule['name'] = $name;
 
67
  } );
68
 
69
  return $schedules;
@@ -72,7 +73,7 @@ function get() {
72
  /**
73
  * Displays a dropdown filled with the possible schedules, including non-repeating.
74
  *
75
- * @param bool $current The currently selected schedule.
76
  */
77
  function dropdown( $current = false ) {
78
  $schedules = get();
64
 
65
  array_walk( $schedules, function( array &$schedule, $name ) {
66
  $schedule['name'] = $name;
67
+ $schedule['is_too_frequent'] = ( $schedule['interval'] < WP_CRON_LOCK_TIMEOUT );
68
  } );
69
 
70
  return $schedules;
73
  /**
74
  * Displays a dropdown filled with the possible schedules, including non-repeating.
75
  *
76
+ * @param string|false $current The currently selected schedule, or false for none.
77
  */
78
  function dropdown( $current = false ) {
79
  $schedules = get();
wp-crontrol.php CHANGED
@@ -5,7 +5,7 @@
5
  * Description: WP Crontrol enables you to view and control what's happening in the WP-Cron system.
6
  * Author: John Blackbourn & crontributors
7
  * Author URI: https://github.com/johnbillion/wp-crontrol/graphs/contributors
8
- * Version: 1.9.1
9
  * Text Domain: wp-crontrol
10
  * Domain Path: /languages/
11
  * Requires PHP: 5.3.6
@@ -41,6 +41,8 @@ defined( 'ABSPATH' ) || die();
41
  require_once __DIR__ . '/src/event.php';
42
  require_once __DIR__ . '/src/schedule.php';
43
 
 
 
44
  /**
45
  * Hook onto all of the actions and filters needed by the plugin.
46
  */
@@ -53,7 +55,6 @@ function init_hooks() {
53
  add_action( 'wp_ajax_crontrol_checkhash', __NAMESPACE__ . '\ajax_check_events_hash' );
54
  add_filter( "plugin_action_links_{$plugin_file}", __NAMESPACE__ . '\plugin_action_links', 10, 4 );
55
  add_filter( 'removable_query_args', __NAMESPACE__ . '\filter_removable_query_args' );
56
- add_filter( 'in_admin_header', __NAMESPACE__ . '\do_tabs' );
57
  add_filter( 'pre_unschedule_event', __NAMESPACE__ . '\maybe_clear_doing_cron' );
58
  add_filter( 'plugin_row_meta', __NAMESPACE__ . '\filter_plugin_row_meta', 10, 4 );
59
 
@@ -65,6 +66,33 @@ function init_hooks() {
65
  add_action( 'crontrol/tab-header', __NAMESPACE__ . '\show_cron_status', 20 );
66
  }
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  /**
69
  * Filters the array of row meta for each plugin in the Plugins list table.
70
  *
@@ -145,8 +173,9 @@ function action_handle_posts() {
145
  'crontrol_name' => rawurlencode( $in_hookname ),
146
  );
147
 
148
- if ( false === $added ) {
149
- $redirect['crontrol_message'] = '10';
 
150
  }
151
 
152
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
@@ -196,8 +225,9 @@ function action_handle_posts() {
196
  'crontrol_name' => rawurlencode( $hookname ),
197
  );
198
 
199
- if ( false === $added ) {
200
- $redirect['crontrol_message'] = '10';
 
201
  }
202
 
203
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
@@ -221,8 +251,29 @@ function action_handle_posts() {
221
  $in_args = array();
222
  }
223
 
 
 
 
 
 
 
224
  $original = Event\get_single( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
225
- Event\delete( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
  $next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom_date . ' ' . $in_next_run_date_local_custom_time : $in_next_run_date_local;
228
 
@@ -260,14 +311,9 @@ function action_handle_posts() {
260
 
261
  $added = Event\add( $next_run_local, $in_schedule, $in_hookname, $in_args );
262
 
263
- $redirect = array(
264
- 'page' => 'crontrol_admin_manage_page',
265
- 'crontrol_message' => '4',
266
- 'crontrol_name' => rawurlencode( $in_hookname ),
267
- );
268
-
269
- if ( false === $added ) {
270
- $redirect['crontrol_message'] = '10';
271
  }
272
 
273
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
@@ -284,9 +330,30 @@ function action_handle_posts() {
284
  'code' => $in_hookcode,
285
  'name' => $in_eventname,
286
  );
 
 
 
 
 
 
287
 
288
  $original = Event\get_single( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
289
- Event\delete( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  $next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom_date . ' ' . $in_next_run_date_local_custom_time : $in_next_run_date_local;
292
 
@@ -324,15 +391,9 @@ function action_handle_posts() {
324
 
325
  $added = Event\add( $next_run_local, $in_schedule, 'crontrol_cron_job', $args );
326
 
327
- $hookname = ( ! empty( $in_eventname ) ) ? $in_eventname : __( 'PHP Cron', 'wp-crontrol' );
328
- $redirect = array(
329
- 'page' => 'crontrol_admin_manage_page',
330
- 'crontrol_message' => '4',
331
- 'crontrol_name' => rawurlencode( $hookname ),
332
- );
333
-
334
- if ( false === $added ) {
335
- $redirect['crontrol_message'] = '10';
336
  }
337
 
338
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
@@ -344,34 +405,9 @@ function action_handle_posts() {
344
  }
345
  check_admin_referer( 'new-sched' );
346
  $name = wp_unslash( $_POST['internal_name'] );
347
- $interval = wp_unslash( $_POST['interval'] );
348
  $display = wp_unslash( $_POST['display_name'] );
349
 
350
- // The user entered something that wasn't a number.
351
- // Try to convert it with strtotime.
352
- if ( ! is_numeric( $interval ) ) {
353
- $now = time();
354
- $future = strtotime( $interval, $now );
355
- if ( false === $future || $now > $future ) {
356
- $redirect = array(
357
- 'page' => 'crontrol_admin_options_page',
358
- 'crontrol_message' => '7',
359
- 'crontrol_name' => rawurlencode( $interval ),
360
- );
361
- wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
362
- exit;
363
- }
364
- $interval = $future - $now;
365
- } elseif ( $interval <= 0 ) {
366
- $redirect = array(
367
- 'page' => 'crontrol_admin_options_page',
368
- 'crontrol_message' => '7',
369
- 'crontrol_name' => rawurlencode( $interval ),
370
- );
371
- wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
372
- exit;
373
- }
374
-
375
  Schedule\add( $name, $interval, $display );
376
  $redirect = array(
377
  'page' => 'crontrol_admin_options_page',
@@ -416,8 +452,9 @@ function action_handle_posts() {
416
  }
417
 
418
  $event = Event\get_single( urldecode( $hook ), $sig, $next_run_utc );
 
419
 
420
- if ( Event\delete( urldecode( $hook ), $sig, $next_run_utc ) ) {
421
  $deleted++;
422
 
423
  /** This action is documented in wp-crontrol.php */
@@ -447,16 +484,26 @@ function action_handle_posts() {
447
  wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
448
  }
449
 
450
- $event = Event\get_single( $hook, $sig, $next_run_utc );
451
- $deleted = Event\delete( $hook, $sig, $next_run_utc );
452
  $redirect = array(
453
  'page' => 'crontrol_admin_manage_page',
454
  'crontrol_message' => '6',
455
  'crontrol_name' => rawurlencode( $hook ),
456
  );
457
 
458
- if ( false === $deleted ) {
459
- $redirect['crontrol_message'] = '7';
 
 
 
 
 
 
 
 
 
 
 
 
460
  } else {
461
  /**
462
  * Fires after a cron event is deleted.
@@ -542,8 +589,9 @@ function action_handle_posts() {
542
  'crontrol_name' => rawurlencode( $hook ),
543
  );
544
 
545
- if ( false === $ran ) {
546
- $redirect['crontrol_message'] = '8';
 
547
  }
548
 
549
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
@@ -557,8 +605,50 @@ function action_handle_posts() {
557
  * Run using the 'admin_menu' action.
558
  */
559
  function action_admin_menu() {
560
- add_options_page( esc_html__( 'Cron Schedules', 'wp-crontrol' ), esc_html__( 'Cron Schedules', 'wp-crontrol' ), 'manage_options', 'crontrol_admin_options_page', __NAMESPACE__ . '\admin_options_page' );
561
- add_management_page( esc_html__( 'Cron Events', 'wp-crontrol' ), esc_html__( 'Cron Events', 'wp-crontrol' ), 'manage_options', 'crontrol_admin_manage_page', __NAMESPACE__ . '\admin_manage_page' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  }
563
 
564
  /**
@@ -582,6 +672,11 @@ function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) {
582
  esc_url( admin_url( 'options-general.php?page=crontrol_admin_options_page' ) ),
583
  esc_html__( 'Schedules', 'wp-crontrol' )
584
  ),
 
 
 
 
 
585
  );
586
 
587
  return array_merge( $new, $actions );
@@ -597,6 +692,11 @@ function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) {
597
  */
598
  function filter_cron_schedules( array $scheds ) {
599
  $new_scheds = get_option( 'crontrol_schedules', array() );
 
 
 
 
 
600
  return array_merge( $new_scheds, $scheds );
601
  }
602
 
@@ -615,11 +715,6 @@ function admin_options_page() {
615
  __( 'Added the cron schedule %s.', 'wp-crontrol' ),
616
  'success',
617
  ),
618
- '7' => array(
619
- /* translators: 1: The name of the cron schedule. */
620
- __( 'Cron schedule not added because there was a problem parsing %s.', 'wp-crontrol' ),
621
- 'error',
622
- ),
623
  );
624
  if ( isset( $_GET['crontrol_message'] ) && isset( $_GET['crontrol_name'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
625
  $hook = wp_unslash( $_GET['crontrol_name'] );
@@ -644,6 +739,8 @@ function admin_options_page() {
644
  ?>
645
  <div class="wrap">
646
 
 
 
647
  <h1><?php esc_html_e( 'Cron Schedules', 'wp-crontrol' ); ?></h1>
648
 
649
  <?php $table->views(); ?>
@@ -1046,6 +1143,8 @@ function show_cron_form( $editing ) {
1046
  $heading = __( 'Add Cron Event', 'wp-crontrol' );
1047
  }
1048
 
 
 
1049
  printf(
1050
  '<h1>%s</h1>',
1051
  esc_html( $heading )
@@ -1238,63 +1337,54 @@ function admin_manage_page() {
1238
  $messages = array(
1239
  '1' => array(
1240
  /* translators: 1: The name of the cron event. */
1241
- __( 'Scheduled the cron event %s to run now.', 'wp-crontrol' ),
1242
  'success',
1243
- true,
1244
  ),
1245
  '2' => array(
1246
  /* translators: 1: The name of the cron event. */
1247
  __( 'Deleted all %s cron events.', 'wp-crontrol' ),
1248
  'success',
1249
- false,
1250
  ),
1251
  '3' => array(
1252
  /* translators: 1: The name of the cron event. */
1253
  __( 'There are no %s cron events to delete.', 'wp-crontrol' ),
1254
  'info',
1255
- false,
1256
  ),
1257
  '4' => array(
1258
  /* translators: 1: The name of the cron event. */
1259
  __( 'Saved the cron event %s.', 'wp-crontrol' ),
1260
  'success',
1261
- false,
1262
  ),
1263
  '5' => array(
1264
  /* translators: 1: The name of the cron event. */
1265
  __( 'Created the cron event %s.', 'wp-crontrol' ),
1266
  'success',
1267
- false,
1268
  ),
1269
  '6' => array(
1270
  /* translators: 1: The name of the cron event. */
1271
  __( 'Deleted the cron event %s.', 'wp-crontrol' ),
1272
  'success',
1273
- false,
1274
  ),
1275
  '7' => array(
1276
  /* translators: 1: The name of the cron event. */
1277
  __( 'Failed to the delete the cron event %s.', 'wp-crontrol' ),
1278
  'error',
1279
- false,
1280
  ),
1281
  '8' => array(
1282
  /* translators: 1: The name of the cron event. */
1283
  __( 'Failed to the execute the cron event %s.', 'wp-crontrol' ),
1284
  'error',
1285
- false,
1286
  ),
1287
  '9' => array(
1288
  __( 'Deleted the selected cron events.', 'wp-crontrol' ),
1289
  'success',
1290
- false,
1291
  ),
1292
  '10' => array(
1293
  /* translators: 1: The name of the cron event. */
1294
  __( 'Failed to save the cron event %s.', 'wp-crontrol' ),
1295
  'error',
1296
- false,
1297
  ),
 
1298
  );
1299
 
1300
  if ( isset( $_GET['crontrol_name'] ) && isset( $_GET['crontrol_message'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
@@ -1302,6 +1392,13 @@ function admin_manage_page() {
1302
  $message = wp_unslash( $_GET['crontrol_message'] );
1303
  $link = '';
1304
 
 
 
 
 
 
 
 
1305
  printf(
1306
  '<div id="crontrol-message" class="notice notice-%1$s is-dismissible"><p>%2$s%3$s</p></div>',
1307
  esc_attr( $messages[ $message ][1] ),
@@ -1321,6 +1418,8 @@ function admin_manage_page() {
1321
  case $tabs['events']:
1322
  ?>
1323
  <div class="wrap">
 
 
1324
  <h1 class="wp-heading-inline"><?php esc_html_e( 'Cron Events', 'wp-crontrol' ); ?></h1>
1325
 
1326
  <?php echo '<a href="' . esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page&action=new-cron' ) ) . '" class="page-title-action">' . esc_html__( 'Add New', 'wp-crontrol' ) . '</a>'; ?>
@@ -1643,7 +1742,7 @@ function setup_manage_page() {
1643
  // Add the initially hidden admin notice about the out of date events list
1644
  add_action( 'admin_notices', function() {
1645
  printf(
1646
- '<div id="crontrol-hash-message" class="notice notice-warning"><p>%s</p></div>',
1647
  esc_html__( 'The scheduled cron events have changed since you first opened this page. Reload the page to see the up to date list.', 'wp-crontrol' )
1648
  );
1649
  } );
@@ -1665,7 +1764,7 @@ function enqueue_assets( $hook_suffix ) {
1665
  wp_enqueue_style( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'css/wp-crontrol.css', array( 'dashicons' ), $ver );
1666
 
1667
  $ver = filemtime( plugin_dir_path( __FILE__ ) . 'js/wp-crontrol.js' );
1668
- wp_enqueue_script( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'js/wp-crontrol.js', array( 'jquery' ), $ver, true );
1669
 
1670
  $vars = array();
1671
 
@@ -1712,6 +1811,7 @@ function get_persistent_core_hooks() {
1712
  'delete_expired_transients',
1713
  'recovery_mode_clean_expired_keys',
1714
  'update_network_counts',
 
1715
  'wp_privacy_delete_old_export_files',
1716
  'wp_scheduled_auto_draft_delete',
1717
  'wp_scheduled_delete',
5
  * Description: WP Crontrol enables you to view and control what's happening in the WP-Cron system.
6
  * Author: John Blackbourn & crontributors
7
  * Author URI: https://github.com/johnbillion/wp-crontrol/graphs/contributors
8
+ * Version: 1.10.0
9
  * Text Domain: wp-crontrol
10
  * Domain Path: /languages/
11
  * Requires PHP: 5.3.6
41
  require_once __DIR__ . '/src/event.php';
42
  require_once __DIR__ . '/src/schedule.php';
43
 
44
+ const TRANSIENT = 'crontrol-message-%d';
45
+
46
  /**
47
  * Hook onto all of the actions and filters needed by the plugin.
48
  */
55
  add_action( 'wp_ajax_crontrol_checkhash', __NAMESPACE__ . '\ajax_check_events_hash' );
56
  add_filter( "plugin_action_links_{$plugin_file}", __NAMESPACE__ . '\plugin_action_links', 10, 4 );
57
  add_filter( 'removable_query_args', __NAMESPACE__ . '\filter_removable_query_args' );
 
58
  add_filter( 'pre_unschedule_event', __NAMESPACE__ . '\maybe_clear_doing_cron' );
59
  add_filter( 'plugin_row_meta', __NAMESPACE__ . '\filter_plugin_row_meta', 10, 4 );
60
 
66
  add_action( 'crontrol/tab-header', __NAMESPACE__ . '\show_cron_status', 20 );
67
  }
68
 
69
+ /**
70
+ * Sets an error message to show to the current user after a redirect.
71
+ *
72
+ * @param string $message The error message text.
73
+ * @return bool Whether the message was saved.
74
+ */
75
+ function set_message( $message ) {
76
+ $key = sprintf(
77
+ TRANSIENT,
78
+ get_current_user_id()
79
+ );
80
+ return set_transient( $key, $message, 60 );
81
+ }
82
+
83
+ /**
84
+ * Gets the error message to show to the current user after a redirect.
85
+ *
86
+ * @return string The error message text.
87
+ */
88
+ function get_message() {
89
+ $key = sprintf(
90
+ TRANSIENT,
91
+ get_current_user_id()
92
+ );
93
+ return get_transient( $key );
94
+ }
95
+
96
  /**
97
  * Filters the array of row meta for each plugin in the Plugins list table.
98
  *
173
  'crontrol_name' => rawurlencode( $in_hookname ),
174
  );
175
 
176
+ if ( is_wp_error( $added ) ) {
177
+ set_message( $added->get_error_message() );
178
+ $redirect['crontrol_message'] = 'error';
179
  }
180
 
181
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
225
  'crontrol_name' => rawurlencode( $hookname ),
226
  );
227
 
228
+ if ( is_wp_error( $added ) ) {
229
+ set_message( $added->get_error_message() );
230
+ $redirect['crontrol_message'] = 'error';
231
  }
232
 
233
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
251
  $in_args = array();
252
  }
253
 
254
+ $redirect = array(
255
+ 'page' => 'crontrol_admin_manage_page',
256
+ 'crontrol_message' => '4',
257
+ 'crontrol_name' => rawurlencode( $in_hookname ),
258
+ );
259
+
260
  $original = Event\get_single( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
261
+
262
+ if ( is_wp_error( $original ) ) {
263
+ set_message( $original->get_error_message() );
264
+ $redirect['crontrol_message'] = 'error';
265
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
266
+ exit;
267
+ }
268
+
269
+ $deleted = Event\delete( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
270
+
271
+ if ( is_wp_error( $deleted ) ) {
272
+ set_message( $deleted->get_error_message() );
273
+ $redirect['crontrol_message'] = 'error';
274
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
275
+ exit;
276
+ }
277
 
278
  $next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom_date . ' ' . $in_next_run_date_local_custom_time : $in_next_run_date_local;
279
 
311
 
312
  $added = Event\add( $next_run_local, $in_schedule, $in_hookname, $in_args );
313
 
314
+ if ( is_wp_error( $added ) ) {
315
+ set_message( $added->get_error_message() );
316
+ $redirect['crontrol_message'] = 'error';
 
 
 
 
 
317
  }
318
 
319
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
330
  'code' => $in_hookcode,
331
  'name' => $in_eventname,
332
  );
333
+ $hookname = ( ! empty( $in_eventname ) ) ? $in_eventname : __( 'PHP Cron', 'wp-crontrol' );
334
+ $redirect = array(
335
+ 'page' => 'crontrol_admin_manage_page',
336
+ 'crontrol_message' => '4',
337
+ 'crontrol_name' => rawurlencode( $hookname ),
338
+ );
339
 
340
  $original = Event\get_single( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
341
+
342
+ if ( is_wp_error( $original ) ) {
343
+ set_message( $original->get_error_message() );
344
+ $redirect['crontrol_message'] = 'error';
345
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
346
+ exit;
347
+ }
348
+
349
+ $deleted = Event\delete( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
350
+
351
+ if ( is_wp_error( $deleted ) ) {
352
+ set_message( $deleted->get_error_message() );
353
+ $redirect['crontrol_message'] = 'error';
354
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
355
+ exit;
356
+ }
357
 
358
  $next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom_date . ' ' . $in_next_run_date_local_custom_time : $in_next_run_date_local;
359
 
391
 
392
  $added = Event\add( $next_run_local, $in_schedule, 'crontrol_cron_job', $args );
393
 
394
+ if ( is_wp_error( $added ) ) {
395
+ set_message( $added->get_error_message() );
396
+ $redirect['crontrol_message'] = 'error';
 
 
 
 
 
 
397
  }
398
 
399
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
405
  }
406
  check_admin_referer( 'new-sched' );
407
  $name = wp_unslash( $_POST['internal_name'] );
408
+ $interval = absint( $_POST['interval'] );
409
  $display = wp_unslash( $_POST['display_name'] );
410
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  Schedule\add( $name, $interval, $display );
412
  $redirect = array(
413
  'page' => 'crontrol_admin_options_page',
452
  }
453
 
454
  $event = Event\get_single( urldecode( $hook ), $sig, $next_run_utc );
455
+ $deleted = Event\delete( urldecode( $hook ), $sig, $next_run_utc );
456
 
457
+ if ( ! is_wp_error( $deleted ) ) {
458
  $deleted++;
459
 
460
  /** This action is documented in wp-crontrol.php */
484
  wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
485
  }
486
 
 
 
487
  $redirect = array(
488
  'page' => 'crontrol_admin_manage_page',
489
  'crontrol_message' => '6',
490
  'crontrol_name' => rawurlencode( $hook ),
491
  );
492
 
493
+ $event = Event\get_single( $hook, $sig, $next_run_utc );
494
+
495
+ if ( is_wp_error( $event ) ) {
496
+ set_message( $event->get_error_message() );
497
+ $redirect['crontrol_message'] = 'error';
498
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
499
+ exit;
500
+ }
501
+
502
+ $deleted = Event\delete( $hook, $sig, $next_run_utc );
503
+
504
+ if ( is_wp_error( $deleted ) ) {
505
+ set_message( $deleted->get_error_message() );
506
+ $redirect['crontrol_message'] = 'error';
507
  } else {
508
  /**
509
  * Fires after a cron event is deleted.
589
  'crontrol_name' => rawurlencode( $hook ),
590
  );
591
 
592
+ if ( is_wp_error( $ran ) ) {
593
+ set_message( $ran->get_error_message() );
594
+ $redirect['crontrol_message'] = 'error';
595
  }
596
 
597
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
605
  * Run using the 'admin_menu' action.
606
  */
607
  function action_admin_menu() {
608
+ $schedules = add_options_page( esc_html__( 'Cron Schedules', 'wp-crontrol' ), esc_html__( 'Cron Schedules', 'wp-crontrol' ), 'manage_options', 'crontrol_admin_options_page', __NAMESPACE__ . '\admin_options_page' );
609
+ $events = add_management_page( esc_html__( 'Cron Events', 'wp-crontrol' ), esc_html__( 'Cron Events', 'wp-crontrol' ), 'manage_options', 'crontrol_admin_manage_page', __NAMESPACE__ . '\admin_manage_page' );
610
+
611
+ add_action( "load-{$schedules}", __NAMESPACE__ . '\admin_help_tab' );
612
+ add_action( "load-{$events}", __NAMESPACE__ . '\admin_help_tab' );
613
+ }
614
+
615
+ /**
616
+ * Adds a Help tab with links to help resources;
617
+ */
618
+ function admin_help_tab() {
619
+ $screen = get_current_screen();
620
+
621
+ $content = '<p>' . __( 'There are several places to get help with issues relating to WP-Cron:', 'wp-crontrol' ) . '</p>';
622
+ $content .= '<ul>';
623
+ $content .= '<li>';
624
+ $content .= sprintf(
625
+ /* translators: %s: URL to the documentation */
626
+ __( '<a href="%s">Read the WP Crontrol wiki</a> which contains information about events that have missed their schedule, problems with spawning a call to the WP-Cron system, and much more.', 'wp-crontrol' ),
627
+ 'https://github.com/johnbillion/wp-crontrol/wiki'
628
+ );
629
+ $content .= '</li>';
630
+ $content .= '<li>';
631
+ $content .= sprintf(
632
+ /* translators: %s: URL to the documentation */
633
+ __( '<a href="%s">Read the Frequently Asked Questions (FAQ)</a> which cover many common questions and answers.', 'wp-crontrol' ),
634
+ 'https://wordpress.org/plugins/wp-crontrol/faq/'
635
+ );
636
+ $content .= '</li>';
637
+ $content .= '<li>';
638
+ $content .= sprintf(
639
+ /* translators: %s: URL to the documentation */
640
+ __( '<a href="%s">Read the WordPress.org documentation on WP-Cron</a> for more technical details about the WP-Cron system for developers.', 'wp-crontrol' ),
641
+ 'https://developer.wordpress.org/plugins/cron/'
642
+ );
643
+ $content .= '</ul>';
644
+
645
+ $screen->add_help_tab(
646
+ array(
647
+ 'id' => 'crontrol-help',
648
+ 'title' => __( 'Help', 'wp-crontrol' ),
649
+ 'content' => $content,
650
+ )
651
+ );
652
  }
653
 
654
  /**
672
  esc_url( admin_url( 'options-general.php?page=crontrol_admin_options_page' ) ),
673
  esc_html__( 'Schedules', 'wp-crontrol' )
674
  ),
675
+ 'crontrol-help' => sprintf(
676
+ '<a href="%s">%s</a>',
677
+ 'https://github.com/johnbillion/wp-crontrol/wiki',
678
+ esc_html__( 'Help', 'wp-crontrol' )
679
+ ),
680
  );
681
 
682
  return array_merge( $new, $actions );
692
  */
693
  function filter_cron_schedules( array $scheds ) {
694
  $new_scheds = get_option( 'crontrol_schedules', array() );
695
+
696
+ if ( ! is_array( $new_scheds ) ) {
697
+ return $scheds;
698
+ }
699
+
700
  return array_merge( $new_scheds, $scheds );
701
  }
702
 
715
  __( 'Added the cron schedule %s.', 'wp-crontrol' ),
716
  'success',
717
  ),
 
 
 
 
 
718
  );
719
  if ( isset( $_GET['crontrol_message'] ) && isset( $_GET['crontrol_name'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
720
  $hook = wp_unslash( $_GET['crontrol_name'] );
739
  ?>
740
  <div class="wrap">
741
 
742
+ <?php do_tabs(); ?>
743
+
744
  <h1><?php esc_html_e( 'Cron Schedules', 'wp-crontrol' ); ?></h1>
745
 
746
  <?php $table->views(); ?>
1143
  $heading = __( 'Add Cron Event', 'wp-crontrol' );
1144
  }
1145
 
1146
+ do_tabs();
1147
+
1148
  printf(
1149
  '<h1>%s</h1>',
1150
  esc_html( $heading )
1337
  $messages = array(
1338
  '1' => array(
1339
  /* translators: 1: The name of the cron event. */
1340
+ __( 'Scheduled the cron event %s to run now. The original event will not be affected.', 'wp-crontrol' ),
1341
  'success',
 
1342
  ),
1343
  '2' => array(
1344
  /* translators: 1: The name of the cron event. */
1345
  __( 'Deleted all %s cron events.', 'wp-crontrol' ),
1346
  'success',
 
1347
  ),
1348
  '3' => array(
1349
  /* translators: 1: The name of the cron event. */
1350
  __( 'There are no %s cron events to delete.', 'wp-crontrol' ),
1351
  'info',
 
1352
  ),
1353
  '4' => array(
1354
  /* translators: 1: The name of the cron event. */
1355
  __( 'Saved the cron event %s.', 'wp-crontrol' ),
1356
  'success',
 
1357
  ),
1358
  '5' => array(
1359
  /* translators: 1: The name of the cron event. */
1360
  __( 'Created the cron event %s.', 'wp-crontrol' ),
1361
  'success',
 
1362
  ),
1363
  '6' => array(
1364
  /* translators: 1: The name of the cron event. */
1365
  __( 'Deleted the cron event %s.', 'wp-crontrol' ),
1366
  'success',
 
1367
  ),
1368
  '7' => array(
1369
  /* translators: 1: The name of the cron event. */
1370
  __( 'Failed to the delete the cron event %s.', 'wp-crontrol' ),
1371
  'error',
 
1372
  ),
1373
  '8' => array(
1374
  /* translators: 1: The name of the cron event. */
1375
  __( 'Failed to the execute the cron event %s.', 'wp-crontrol' ),
1376
  'error',
 
1377
  ),
1378
  '9' => array(
1379
  __( 'Deleted the selected cron events.', 'wp-crontrol' ),
1380
  'success',
 
1381
  ),
1382
  '10' => array(
1383
  /* translators: 1: The name of the cron event. */
1384
  __( 'Failed to save the cron event %s.', 'wp-crontrol' ),
1385
  'error',
 
1386
  ),
1387
+ 'error' => array(),
1388
  );
1389
 
1390
  if ( isset( $_GET['crontrol_name'] ) && isset( $_GET['crontrol_message'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
1392
  $message = wp_unslash( $_GET['crontrol_message'] );
1393
  $link = '';
1394
 
1395
+ if ( 'error' === $message ) {
1396
+ $messages['error'] = array(
1397
+ get_message(),
1398
+ 'error',
1399
+ );
1400
+ }
1401
+
1402
  printf(
1403
  '<div id="crontrol-message" class="notice notice-%1$s is-dismissible"><p>%2$s%3$s</p></div>',
1404
  esc_attr( $messages[ $message ][1] ),
1418
  case $tabs['events']:
1419
  ?>
1420
  <div class="wrap">
1421
+ <?php do_tabs(); ?>
1422
+
1423
  <h1 class="wp-heading-inline"><?php esc_html_e( 'Cron Events', 'wp-crontrol' ); ?></h1>
1424
 
1425
  <?php echo '<a href="' . esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page&action=new-cron' ) ) . '" class="page-title-action">' . esc_html__( 'Add New', 'wp-crontrol' ) . '</a>'; ?>
1742
  // Add the initially hidden admin notice about the out of date events list
1743
  add_action( 'admin_notices', function() {
1744
  printf(
1745
+ '<div id="crontrol-hash-message" class="notice notice-info"><p>%s</p></div>',
1746
  esc_html__( 'The scheduled cron events have changed since you first opened this page. Reload the page to see the up to date list.', 'wp-crontrol' )
1747
  );
1748
  } );
1764
  wp_enqueue_style( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'css/wp-crontrol.css', array( 'dashicons' ), $ver );
1765
 
1766
  $ver = filemtime( plugin_dir_path( __FILE__ ) . 'js/wp-crontrol.js' );
1767
+ wp_enqueue_script( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'js/wp-crontrol.js', array( 'jquery', 'wp-a11y' ), $ver, true );
1768
 
1769
  $vars = array();
1770
 
1811
  'delete_expired_transients',
1812
  'recovery_mode_clean_expired_keys',
1813
  'update_network_counts',
1814
+ 'wp_https_detection',
1815
  'wp_privacy_delete_old_export_files',
1816
  'wp_scheduled_auto_draft_delete',
1817
  'wp_scheduled_delete',