WP Crontrol - Version 1.9.0

Version Description

Download this release

Release Info

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

Code changes from version 1.8.5 to 1.9.0

SECURITY.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you discover a security issue in WP Crontrol, please report it to [the security program on HackerOne](https://hackerone.com/johnblackbourn). Do not report security issues on GitHub or the WordPress.org support forums. Thank you.
css/wp-crontrol.css CHANGED
@@ -1,4 +1,3 @@
1
-
2
  @keyframes crontrol-rotating {
3
  from {
4
  transform: rotate(0deg);
@@ -91,3 +90,12 @@ table.wp-list-table {
91
  .row-actions .in-use {
92
  color: #666;
93
  }
 
 
 
 
 
 
 
 
 
 
1
  @keyframes crontrol-rotating {
2
  from {
3
  transform: rotate(0deg);
90
  .row-actions .in-use {
91
  color: #666;
92
  }
93
+
94
+ .form-field input[type="number"] {
95
+ width: 100px;
96
+ }
97
+
98
+ .crontrol-edit-event-standard .crontrol-event-php,
99
+ .crontrol-edit-event-php .crontrol-event-standard {
100
+ display: none;
101
+ }
js/wp-crontrol.js CHANGED
@@ -34,3 +34,26 @@ function crontrolCheckHash() {
34
  }
35
  } );
36
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  }
35
  } );
36
  }
37
+
38
+ jQuery(function($){
39
+ $('#next_run_date_local_custom_date,#next_run_date_local_custom_time').on('change', function() {
40
+ $('#next_run_date_local_custom').prop('checked',true);
41
+ });
42
+
43
+ if ( $('input[value="new_php_cron"]').length ) {
44
+ $('input[value="new_cron"]').on('click',function(){
45
+ $('.crontrol-edit-event').removeClass('crontrol-edit-event-php').addClass('crontrol-edit-event-standard');
46
+ $('#hookname').attr('required',true);
47
+ });
48
+ $('input[value="new_php_cron"]').on('click',function(){
49
+ $('.crontrol-edit-event').removeClass('crontrol-edit-event-standard').addClass('crontrol-edit-event-php');
50
+ $('#hookname').attr('required',false);
51
+ if ( ! $('#hookcode').hasClass('crontrol-editor-initialized') ) {
52
+ wp.codeEditor.initialize( 'hookcode', window.wpCrontrol.codeEditor );
53
+ }
54
+ $('#hookcode').addClass('crontrol-editor-initialized');
55
+ });
56
+ } else if ( $('#hookcode').length ) {
57
+ wp.codeEditor.initialize( 'hookcode', window.wpCrontrol.codeEditor );
58
+ }
59
+ });
readme.md CHANGED
@@ -1,44 +1,104 @@
1
- # WP Crontrol #
2
 
3
  Contributors: johnbillion, scompt
4
  Tags: cron, wp-cron, crontrol, debug
5
  Requires at least: 4.1
6
- Tested up to: 5.4
7
- Stable tag: 1.8.5
8
  Requires PHP: 5.3
 
9
 
10
- WP Crontrol lets you view and control what's happening in the WP-Cron system.
11
 
12
- ## Description ##
13
 
14
- WP Crontrol lets you view and control what's happening in the WP-Cron system. From the admin screens you can:
15
 
16
  * View all cron events along with their arguments, recurrence, callback functions, and when they are next due.
17
  * Edit, delete, and immediately run any cron events.
18
  * Add new cron events.
19
  * Bulk delete cron events.
20
- * Add, edit, and remove custom cron schedules.
21
 
22
- 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).
23
 
24
- ### Usage ###
25
 
26
  1. Go to the `Tools → Cron Events` menu to manage cron events.
27
  2. Go to the `Settings → Cron Schedules` menu to manage cron schedules.
28
 
29
- ## Frequently Asked Questions ##
30
 
31
- ### What's the use of adding new cron schedules? ###
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  Cron schedules are used by WordPress and plugins for scheduling events to be executed at regular intervals. Intervals must be provided by the WordPress core or a plugin in order to be used. As an example, many backup plugins provide support for periodic backups. In order to do a weekly backup, a weekly cron schedule must be entered into WP Crontrol first and then a backup plugin can take advantage of it as an interval.
34
 
35
- ### How do I create a new cron event? ###
36
 
37
  There are two steps to getting a functioning cron event that executes regularly. The first step is telling WordPress about the hook. This is the part that WP Crontrol was created to provide. The second step is calling a function when your hook is executed.
38
 
39
  *Step One: Adding the hook*
40
 
41
- In the Tools → Cron Events admin panel, click on the "Add Cron Event" tab and enter the details of the hook. You're best off having a hookname that conforms to normal PHP variable naming conventions. The event schedule is how often your hook will be executed. If you don't see a good interval, then add one in the Settings → Cron Schedules admin panel.
42
 
43
  *Step Two: Writing the function*
44
 
@@ -52,15 +112,15 @@ The next step is to write your function. Here's a simple example:
52
  wp_mail( 'hello@example.com', 'WP Crontrol', 'WP Crontrol rocks!' );
53
  }
54
 
55
- ### How do I create a new PHP cron event? ###
56
 
57
- In the Tools → Cron Events admin panel, click on the "Add PHP Cron Event" tab. In the form that appears, enter the schedule and next run time in the boxes. The event schedule is how often your event will be executed. If you don't see a good interval, then add one in the Settings → Cron Schedules admin panel. In the "Hook code" area, enter the PHP code that should be run when your cron event is executed. You don't need to provide the PHP opening tag (`<?php`).
58
 
59
- ### Which users can manage cron events and schedules? ###
60
 
61
  Only users with the `manage_options` capability can manage cron events and schedules. By default, only Administrators have this capability.
62
 
63
- ### Which users can manage PHP cron events? Is this dangerous? ###
64
 
65
  Only users with the `edit_files` capability can manage PHP cron events. This means if a user cannot edit files on the site (eg. through the Plugin Editor or Theme Editor) then they cannot edit or add a PHP cron event. By default, only Administrators have this capability, and with Multisite enabled only Super Admins have this capability.
66
 
@@ -68,18 +128,30 @@ If file editing has been disabled via the `DISALLOW_FILE_MODS` or `DISALLOW_FILE
68
 
69
  Therefore, the user access level required to execute arbitrary PHP code does not change with WP Crontrol activated.
70
 
71
- ### Are any WP-CLI commands available? ###
72
 
73
  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.
74
 
75
- ## Screenshots ##
76
 
77
- 1. New cron events can be added, modified, deleted, and executed<br>![](.wordpress-org/screenshot-1.png)
78
 
79
- 2. New cron schedules can be added, giving plugin developers more options when scheduling events<br>![](.wordpress-org/screenshot-2.png)
 
 
80
 
81
  ## Changelog ##
82
 
 
 
 
 
 
 
 
 
 
 
83
  ### 1.8.5 ###
84
 
85
  * Fix an issue with the tabs in 1.8.4.
1
+ # WP Crontrol
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.0
8
  Requires PHP: 5.3
9
+ Donate link: https://github.com/sponsors/johnbillion
10
 
11
+ WP Crontrol enables you to view and control what's happening in the WP-Cron system.
12
 
13
+ ## Description
14
 
15
+ WP Crontrol enables you to view and control what's happening in the WP-Cron system. From the admin screens you can:
16
 
17
  * View all cron events along with their arguments, recurrence, callback functions, and when they are next due.
18
  * Edit, delete, and immediately run any cron events.
19
  * Add new cron events.
20
  * Bulk delete cron events.
21
+ * Add and remove custom cron schedules.
22
 
23
+ WP Crontrol is aware of timezones, will alert you to events that have no actions or that have missed their schedule, and 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).
24
 
25
+ ### Usage
26
 
27
  1. Go to the `Tools → Cron Events` menu to manage cron events.
28
  2. Go to the `Settings → Cron Schedules` menu to manage cron schedules.
29
 
30
+ ### Other Plugins
31
 
32
+ I maintain several other plugins for developers. Check them out:
33
+
34
+ * [Query Monitor](https://wordpress.org/plugins/query-monitor/) is the developer tools panel for WordPress.
35
+ * [User Switching](https://wordpress.org/plugins/user-switching/) provides instant switching between user accounts in WordPress.
36
+
37
+ ## Frequently Asked Questions
38
+
39
+ ### Does this plugin work with PHP 8?
40
+
41
+ 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
+
49
+ [You can read all about cron events that miss their schedule here](https://github.com/johnbillion/wp-crontrol/wiki/Cron-events-that-have-missed-their-schedule).
50
+
51
+ ### Why do some cron events reappear shortly after I delete them?
52
+
53
+ If the event is added by a plugin then the plugin most likely rescheduled the event as soon as it saw that the event was missing. Unfortunately there's nothing that WP Crontrol can do about this - you should contact the author of the related plugin and ask for advice.
54
+
55
+ ### Is it safe to delete cron events?
56
+
57
+ This depends entirely on the event. You can use your favourite search engine to search for the event name in order to find out which plugin it belongs to, and then decide whether or not to delete it.
58
+
59
+ If the event shows "None" as its action then it's usually safe to delete. Please see the other FAQs for more information about events with no action.
60
+
61
+ ### Why can't I delete some cron events?
62
+
63
+ The WordPress core software uses cron events for some of its functionality and removing these events is not possible because WordPress would immediately reschedule them if you did delete them. For this reason, WP Crontrol doesn't let you delete these persistent events from WordPress core in the first place.
64
+
65
+ ### What does it mean when "None" is shown for the Action of a cron event?
66
+
67
+ This means the cron event is scheduled to run at the specified time but there is no corresponding functionality that will be triggered when the event runs, therefore the event is useless.
68
+
69
+ This is often caused by plugins that don't clean up their cron events when you deactivate them. You can use your favourite search engine to search for the event name in order to find out which plugin it belongs to, and then decide whether or not to delete it.
70
+
71
+ ### How do I change the next run time or the recurrence of a cron event?
72
+
73
+ You can change the time and recurrence of a cron event by clicking the "Edit" link next to the event.
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
+
89
+ Not yet, but I hope to add this functionality soon.
90
+
91
+ ### What's the use of adding new cron schedules?
92
 
93
  Cron schedules are used by WordPress and plugins for scheduling events to be executed at regular intervals. Intervals must be provided by the WordPress core or a plugin in order to be used. As an example, many backup plugins provide support for periodic backups. In order to do a weekly backup, a weekly cron schedule must be entered into WP Crontrol first and then a backup plugin can take advantage of it as an interval.
94
 
95
+ ### How do I create a new cron event?
96
 
97
  There are two steps to getting a functioning cron event that executes regularly. The first step is telling WordPress about the hook. This is the part that WP Crontrol was created to provide. The second step is calling a function when your hook is executed.
98
 
99
  *Step One: Adding the hook*
100
 
101
+ In the Tools → Cron Events admin panel, click on "Add New" and enter the details of the hook. You're best off using a hook name that conforms to normal PHP variable naming conventions. The event schedule is how often your hook will be executed. If you don't see a good interval, then add one in the Settings → Cron Schedules admin panel.
102
 
103
  *Step Two: Writing the function*
104
 
112
  wp_mail( 'hello@example.com', 'WP Crontrol', 'WP Crontrol rocks!' );
113
  }
114
 
115
+ ### How do I create a new PHP cron event?
116
 
117
+ In the Tools → Cron Events admin panel, click on "Add New". In the form that appears, select "PHP Cron Event" and enter the schedule and next run time. The event schedule is how often your event will be executed. If you don't see a good interval, then add one in the Settings → Cron Schedules admin panel. In the "Hook code" area, enter the PHP code that should be run when your cron event is executed. You don't need to provide the PHP opening tag (`<?php`).
118
 
119
+ ### Which users can manage cron events and schedules?
120
 
121
  Only users with the `manage_options` capability can manage cron events and schedules. By default, only Administrators have this capability.
122
 
123
+ ### Which users can manage PHP cron events? Is this dangerous?
124
 
125
  Only users with the `edit_files` capability can manage PHP cron events. This means if a user cannot edit files on the site (eg. through the Plugin Editor or Theme Editor) then they cannot edit or add a PHP cron event. By default, only Administrators have this capability, and with Multisite enabled only Super Admins have this capability.
126
 
128
 
129
  Therefore, the user access level required to execute arbitrary PHP code does not change with WP Crontrol activated.
130
 
131
+ ### Are any WP-CLI commands available?
132
 
133
  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.
134
 
135
+ ## Screenshots
136
 
137
+ 1. Cron events can be modified, deleted, and executed<br>![](.wordpress-org/screenshot-1.png)
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.0 ###
146
+
147
+ * Add filters and sorting to the event listing screen. Props @yuriipavlov.
148
+ * Replace the "Add New" tabs with a more standard "Add New" button on the cron event listing page.
149
+ * Switch back to using browser-native controls for the date and time inputs.
150
+ * Add an error message when trying to edit a non-existent event.
151
+ * Introduce an informational message which appears when there are events that have missed their schedule.
152
+ * Fire actions when cron events and schedules are added, updated, and deleted.
153
+
154
+
155
  ### 1.8.5 ###
156
 
157
  * Fix an issue with the tabs in 1.8.4.
src/event-list-table.php CHANGED
@@ -37,6 +37,8 @@ class Table extends \WP_List_Table {
37
  */
38
  protected static $count_by_hook;
39
 
 
 
40
  /**
41
  * Constructor.
42
  */
@@ -58,6 +60,7 @@ class Table extends \WP_List_Table {
58
  self::$count_by_hook = count_by_hook();
59
 
60
  $events = get();
 
61
 
62
  if ( ! empty( $_GET['s'] ) ) {
63
  $s = sanitize_text_field( wp_unslash( $_GET['s'] ) );
@@ -67,12 +70,35 @@ class Table extends \WP_List_Table {
67
  } );
68
  }
69
 
 
 
 
 
 
 
 
 
 
70
  $count = count( $events );
71
  $per_page = 50;
72
  $offset = ( $this->get_pagenum() - 1 ) * $per_page;
73
 
74
  $this->items = array_slice( $events, $offset, $per_page );
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  $this->set_pagination_args( array(
77
  'total_items' => $count,
78
  'per_page' => $per_page,
@@ -80,6 +106,34 @@ class Table extends \WP_List_Table {
80
  ) );
81
  }
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  /**
84
  * Returns an array of column names for the table.
85
  *
@@ -100,6 +154,18 @@ class Table extends \WP_List_Table {
100
  );
101
  }
102
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  /**
104
  * Returns an array of CSS class names for the table.
105
  *
@@ -123,6 +189,39 @@ class Table extends \WP_List_Table {
123
  );
124
  }
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  /**
127
  * Generates content for a single row of the table.
128
  *
@@ -141,8 +240,17 @@ class Table extends \WP_List_Table {
141
  $classes[] = 'crontrol-error';
142
  }
143
 
144
- if ( ! \Crontrol\get_hook_callbacks( $event->hook ) ) {
 
 
145
  $classes[] = 'crontrol-warning';
 
 
 
 
 
 
 
146
  }
147
 
148
  if ( is_late( $event ) ) {
@@ -422,7 +530,11 @@ class Table extends \WP_List_Table {
422
  * Outputs a message when there are no items to show in the table.
423
  */
424
  public function no_items() {
425
- esc_html_e( 'There are currently no scheduled cron events.', 'wp-crontrol' );
 
 
 
 
426
  }
427
 
428
  }
37
  */
38
  protected static $count_by_hook;
39
 
40
+ protected $all_events = array();
41
+
42
  /**
43
  * Constructor.
44
  */
60
  self::$count_by_hook = count_by_hook();
61
 
62
  $events = get();
63
+ $this->all_events = $events;
64
 
65
  if ( ! empty( $_GET['s'] ) ) {
66
  $s = sanitize_text_field( wp_unslash( $_GET['s'] ) );
70
  } );
71
  }
72
 
73
+ if ( ! empty( $_GET['hooks_type'] ) ) {
74
+ $hooks_type = sanitize_text_field( $_GET['hooks_type'] );
75
+ $filtered = $this->get_filtered_events( $events );
76
+
77
+ if ( isset( $filtered[ $hooks_type ] ) ) {
78
+ $events = $filtered[ $hooks_type ];
79
+ }
80
+ }
81
+
82
  $count = count( $events );
83
  $per_page = 50;
84
  $offset = ( $this->get_pagenum() - 1 ) * $per_page;
85
 
86
  $this->items = array_slice( $events, $offset, $per_page );
87
 
88
+ $has_late = (bool) array_filter( array_map( __NAMESPACE__ . '\\is_late', $this->items ) );
89
+
90
+ if ( $has_late ) {
91
+ add_action( 'admin_notices', function() {
92
+ printf(
93
+ '<div id="crontrol-late-message" class="notice notice-warning"><p>%1$s</p><p><a href="%2$s">%3$s</a></p></div>',
94
+ /* translators: %s: Help page URL. */
95
+ esc_html__( 'One or more cron events have missed their schedule.', 'wp-crontrol' ),
96
+ 'https://github.com/johnbillion/wp-crontrol/wiki/Cron-events-that-have-missed-their-schedule',
97
+ esc_html__( 'More information', 'wp-crontrol' )
98
+ );
99
+ } );
100
+ }
101
+
102
  $this->set_pagination_args( array(
103
  'total_items' => $count,
104
  'per_page' => $per_page,
106
  ) );
107
  }
108
 
109
+ /**
110
+ * Returns events filtered by various parameters
111
+ *
112
+ * @param array $events The list of all events.
113
+ * @return array Array of filtered events keyed by parameter.
114
+ */
115
+ protected function get_filtered_events( array $events ) {
116
+ $all_core_hooks = \Crontrol\get_all_core_hooks();
117
+ $filtered = array(
118
+ 'all' => $events,
119
+ );
120
+
121
+ $filtered['noaction'] = array_filter( $events, function( $event ) {
122
+ $hook_callbacks = \Crontrol\get_hook_callbacks( $event->hook );
123
+ return empty( $hook_callbacks );
124
+ } );
125
+
126
+ $filtered['core'] = array_filter( $events, function( $event ) use ( $all_core_hooks ) {
127
+ return ( in_array( $event->hook, $all_core_hooks, true ) );
128
+ } );
129
+
130
+ $filtered['custom'] = array_filter( $events, function( $event ) use ( $all_core_hooks ) {
131
+ return ( ! in_array( $event->hook, $all_core_hooks, true ) );
132
+ } );
133
+
134
+ return $filtered;
135
+ }
136
+
137
  /**
138
  * Returns an array of column names for the table.
139
  *
154
  );
155
  }
156
 
157
+ /**
158
+ * Columns to make sortable.
159
+ *
160
+ * @return array
161
+ */
162
+ public function get_sortable_columns() {
163
+ return array(
164
+ 'crontrol_hook' => array( 'crontrol_hook', true ),
165
+ 'crontrol_next' => array( 'crontrol_next', false ),
166
+ );
167
+ }
168
+
169
  /**
170
  * Returns an array of CSS class names for the table.
171
  *
189
  );
190
  }
191
 
192
+ /**
193
+ * Display the list of hook types.
194
+ *
195
+ * @return string[]
196
+ */
197
+ public function get_views() {
198
+ $filtered = $this->get_filtered_events( $this->all_events );
199
+
200
+ $views = array();
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' );
211
+
212
+ foreach ( $types as $key => $type ) {
213
+ $views[ $key ] = sprintf(
214
+ '<a href="%1$s"%2$s>%3$s <span class="count">(%4$s)</span></a>',
215
+ 'all' === $key ? $url : add_query_arg( 'hooks_type', $key, $url ),
216
+ $hooks_type === $key ? ' class="current"' : '',
217
+ $type,
218
+ count( $filtered[ $key ] )
219
+ );
220
+ }
221
+
222
+ return $views;
223
+ }
224
+
225
  /**
226
  * Generates content for a single row of the table.
227
  *
240
  $classes[] = 'crontrol-error';
241
  }
242
 
243
+ $callbacks = \Crontrol\get_hook_callbacks( $event->hook );
244
+
245
+ if ( ! $callbacks ) {
246
  $classes[] = 'crontrol-warning';
247
+ } else {
248
+ foreach ( $callbacks as $callback ) {
249
+ if ( ! empty( $callback['callback']['error'] ) ) {
250
+ $classes[] = 'crontrol-error';
251
+ break;
252
+ }
253
+ }
254
  }
255
 
256
  if ( is_late( $event ) ) {
530
  * Outputs a message when there are no items to show in the table.
531
  */
532
  public function no_items() {
533
+ if ( empty( $_GET['s'] ) && empty( $_GET['hooks_type'] ) ) {
534
+ esc_html_e( 'There are currently no scheduled cron events.', 'wp-crontrol' );
535
+ } else {
536
+ esc_html_e( 'No matching cron events.', 'wp-crontrol' );
537
+ }
538
  }
539
 
540
  }
src/event.php CHANGED
@@ -24,9 +24,15 @@ function run( $hookname, $sig ) {
24
  $crons = _get_cron_array();
25
  foreach ( $crons as $time => $cron ) {
26
  if ( isset( $cron[ $hookname ][ $sig ] ) ) {
27
- $args = $cron[ $hookname ][ $sig ]['args'];
 
 
 
 
 
 
28
  delete_transient( 'doing_cron' );
29
- $scheduled = force_schedule_single_event( $hookname, $args ); // UTC
30
 
31
  if ( false === $scheduled ) {
32
  return $scheduled;
@@ -41,6 +47,21 @@ function run( $hookname, $sig ) {
41
 
42
  sleep( 1 );
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  return true;
45
  }
46
  }
@@ -80,11 +101,11 @@ function force_schedule_single_event( $hook, $args = array() ) {
80
  *
81
  * @param string $next_run_local The time that the event should be run at, in the site's timezone.
82
  * @param string $schedule The recurrence of the cron event.
83
- * @param string $hookname The name of the hook to execute.
84
  * @param array $args Arguments to add to the cron event.
85
  * @return bool Whether the addition was successful.
86
  */
87
- function add( $next_run_local, $schedule, $hookname, array $args ) {
88
  $next_run_local = strtotime( $next_run_local, current_time( 'timestamp' ) );
89
 
90
  if ( false === $next_run_local ) {
@@ -97,7 +118,7 @@ function add( $next_run_local, $schedule, $hookname, array $args ) {
97
  $args = array();
98
  }
99
 
100
- if ( 'crontrol_cron_job' === $hookname && ! empty( $args['code'] ) && class_exists( '\ParseError' ) ) {
101
  try {
102
  // phpcs:ignore Squiz.PHP.Eval.Discouraged
103
  eval( sprintf(
@@ -112,33 +133,34 @@ function add( $next_run_local, $schedule, $hookname, array $args ) {
112
  }
113
 
114
  if ( '_oneoff' === $schedule ) {
115
- return ( false !== wp_schedule_single_event( $next_run_utc, $hookname, $args ) );
116
  } else {
117
- return ( false !== wp_schedule_event( $next_run_utc, $schedule, $hookname, $args ) );
118
  }
119
  }
120
 
121
  /**
122
  * Deletes a cron event.
123
  *
124
- * @param string $to_delete The hook name of the event to delete.
125
  * @param string $sig The cron event signature.
126
  * @param string $next_run_utc The UTC time that the event would be run at.
127
  * @return bool Whether the deletion was successful.
128
  */
129
- function delete( $to_delete, $sig, $next_run_utc ) {
130
- $crons = _get_cron_array();
131
- if ( isset( $crons[ $next_run_utc ][ $to_delete ][ $sig ] ) ) {
132
- $args = $crons[ $next_run_utc ][ $to_delete ][ $sig ]['args'];
133
- $unscheduled = wp_unschedule_event( $next_run_utc, $to_delete, $args );
134
 
135
- if ( false === $unscheduled ) {
136
- return $unscheduled;
137
- }
 
 
138
 
139
- return true;
 
140
  }
141
- return false;
 
142
  }
143
 
144
  /**
@@ -174,17 +196,34 @@ function get() {
174
 
175
  // Ensure events are always returned in date descending order.
176
  // External cron runners such as Cavalcade don't guarantee events are returned in order of time.
177
- uasort( $events, function( $a, $b ) {
178
- if ( $a->time === $b->time ) {
179
- return 0;
180
- } else {
181
- return ( $a->time > $b->time ) ? 1 : -1;
182
- }
183
- } );
184
 
185
  return $events;
186
  }
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  /**
189
  * Returns an array of the number of events for each hook.
190
  *
@@ -263,3 +302,39 @@ function get_list_table() {
263
 
264
  return $table;
265
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  $crons = _get_cron_array();
25
  foreach ( $crons as $time => $cron ) {
26
  if ( isset( $cron[ $hookname ][ $sig ] ) ) {
27
+ $event = $cron[ $hookname ][ $sig ];
28
+
29
+ $event['hook'] = $hookname;
30
+ $event['timestamp'] = $time;
31
+
32
+ $event = (object) $event;
33
+
34
  delete_transient( 'doing_cron' );
35
+ $scheduled = force_schedule_single_event( $hookname, $event->args ); // UTC
36
 
37
  if ( false === $scheduled ) {
38
  return $scheduled;
47
 
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.
55
+ *
56
+ * @type string $hook Action hook to execute when the event is run.
57
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
58
+ * @type string|false $schedule How often the event should subsequently recur.
59
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
60
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
61
+ * }
62
+ */
63
+ do_action( 'crontrol/ran_event', $event );
64
+
65
  return true;
66
  }
67
  }
101
  *
102
  * @param string $next_run_local The time that the event should be run at, in the site's timezone.
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 ) {
118
  $args = array();
119
  }
120
 
121
+ if ( 'crontrol_cron_job' === $hook && ! empty( $args['code'] ) && class_exists( '\ParseError' ) ) {
122
  try {
123
  // phpcs:ignore Squiz.PHP.Eval.Discouraged
124
  eval( sprintf(
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
  /**
143
  * Deletes a cron event.
144
  *
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
 
166
  /**
196
 
197
  // Ensure events are always returned in date descending order.
198
  // External cron runners such as Cavalcade don't guarantee events are returned in order of time.
199
+ uasort( $events, 'Crontrol\Event\uasort_order_events' );
 
 
 
 
 
 
200
 
201
  return $events;
202
  }
203
 
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();
213
+ if ( isset( $crons[ $next_run_utc ][ $hook ][ $sig ] ) ) {
214
+ $event = $crons[ $next_run_utc ][ $hook ][ $sig ];
215
+
216
+ $event['hook'] = $hook;
217
+ $event['timestamp'] = $next_run_utc;
218
+
219
+ $event = (object) $event;
220
+
221
+ return $event;
222
+ }
223
+
224
+ return null;
225
+ }
226
+
227
  /**
228
  * Returns an array of the number of events for each hook.
229
  *
302
 
303
  return $table;
304
  }
305
+
306
+ /**
307
+ * Order events function.
308
+ *
309
+ * The comparison function returns an integer less than, equal to, or greater than zero if the first argument is
310
+ * considered to be respectively less than, equal to, or greater than the second.
311
+ *
312
+ * @param object $a The first event to compare.
313
+ * @param object $b The second event to compare.
314
+ * @return int
315
+ */
316
+ function uasort_order_events( $a, $b ) {
317
+ $orderby = ( ! empty( $_GET['orderby'] ) ) ? sanitize_text_field( $_GET['orderby'] ) : 'crontrol_next';
318
+ $order = ( ! empty( $_GET['order'] ) ) ? sanitize_text_field( $_GET['order'] ) : 'desc';
319
+
320
+ switch ( $orderby ) {
321
+ case 'crontrol_hook':
322
+ if ( 'desc' === $order ) {
323
+ return strcmp( $a->hook, $b->hook );
324
+ } else {
325
+ return strcmp( $b->hook, $a->hook );
326
+ }
327
+ break;
328
+ default:
329
+ if ( $a->time === $b->time ) {
330
+ return 0;
331
+ } else {
332
+ if ( 'desc' === $order ) {
333
+ return ( $a->time > $b->time ) ? 1 : -1;
334
+ } else {
335
+ return ( $a->time < $b->time ) ? 1 : -1;
336
+ }
337
+ }
338
+ break;
339
+ }
340
+ }
src/schedule-list-table.php CHANGED
@@ -66,7 +66,7 @@ class Schedule_List_Table extends \WP_List_Table {
66
  */
67
  public function get_columns() {
68
  return array(
69
- 'crontrol_name' => __( 'Name', 'wp-crontrol' ),
70
  'crontrol_interval' => __( 'Interval', 'wp-crontrol' ),
71
  'crontrol_display' => __( 'Display Name', 'wp-crontrol' ),
72
  );
@@ -78,7 +78,7 @@ class Schedule_List_Table extends \WP_List_Table {
78
  * @return string[] Array of class names.
79
  */
80
  protected function get_table_classes() {
81
- return array( 'widefat', 'striped', $this->_args['plural'] );
82
  }
83
 
84
  /**
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' ),
72
  );
78
  * @return string[] Array of class names.
79
  */
80
  protected function get_table_classes() {
81
+ return array( 'widefat', 'fixed', 'striped', 'table-view-list', $this->_args['plural'] );
82
  }
83
 
84
  /**
src/schedule.php CHANGED
@@ -22,17 +22,33 @@ function add( $name, $interval, $display ) {
22
  'display' => $display,
23
  );
24
  update_option( 'crontrol_schedules', $old_scheds );
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
  /**
28
  * Deletes a custom cron schedule.
29
  *
30
- * @param string $name The internal_name of the schedule to delete.
31
  */
32
  function delete( $name ) {
33
  $scheds = get_option( 'crontrol_schedules', array() );
34
  unset( $scheds[ $name ] );
35
  update_option( 'crontrol_schedules', $scheds );
 
 
 
 
 
 
 
36
  }
37
 
38
  /**
22
  'display' => $display,
23
  );
24
  update_option( 'crontrol_schedules', $old_scheds );
25
+
26
+ /**
27
+ * Fires after a new cron schedule is added.
28
+ *
29
+ * @param string $name The internal name of the schedule.
30
+ * @param int $interval The interval between executions of the new schedule.
31
+ * @param string $display The display name of the schedule.
32
+ */
33
+ do_action( 'crontrol/added_new_schedule', $name, $interval, $display );
34
  }
35
 
36
  /**
37
  * Deletes a custom cron schedule.
38
  *
39
+ * @param string $name The internal name of the schedule to delete.
40
  */
41
  function delete( $name ) {
42
  $scheds = get_option( 'crontrol_schedules', array() );
43
  unset( $scheds[ $name ] );
44
  update_option( 'crontrol_schedules', $scheds );
45
+
46
+ /**
47
+ * Fires after a cron schedule is deleted.
48
+ *
49
+ * @param string $name The internal name of the schedule.
50
+ */
51
+ do_action( 'crontrol/deleted_schedule', $name );
52
  }
53
 
54
  /**
wp-crontrol.php CHANGED
@@ -2,10 +2,10 @@
2
  /**
3
  * Plugin Name: WP Crontrol
4
  * Plugin URI: https://wordpress.org/plugins/wp-crontrol/
5
- * Description: WP Crontrol lets you 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.8.5
9
  * Text Domain: wp-crontrol
10
  * Domain Path: /languages/
11
  * Requires PHP: 5.3.6
@@ -55,6 +55,7 @@ function init_hooks() {
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
 
59
  add_action( 'load-tools_page_crontrol_admin_manage_page', __NAMESPACE__ . '\setup_manage_page' );
60
 
@@ -64,6 +65,27 @@ function init_hooks() {
64
  add_action( 'crontrol/tab-header', __NAMESPACE__ . '\show_cron_status', 20 );
65
  }
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  /**
68
  * Run using the 'init' action.
69
  */
@@ -75,7 +97,7 @@ function action_init() {
75
  * Handles any POSTs made by the plugin. Run using the 'init' action.
76
  */
77
  function action_handle_posts() {
78
- if ( isset( $_POST['new_cron'] ) ) {
79
  if ( ! current_user_can( 'manage_options' ) ) {
80
  wp_die( esc_html__( 'You are not allowed to add new cron events.', 'wp-crontrol' ), 401 );
81
  }
@@ -90,7 +112,30 @@ function action_handle_posts() {
90
  $in_args = array();
91
  }
92
 
93
- $next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom : $in_next_run_date_local;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
  $added = Event\add( $next_run_local, $in_schedule, $in_hookname, $in_args );
96
 
@@ -107,18 +152,41 @@ function action_handle_posts() {
107
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
108
  exit;
109
 
110
- } elseif ( isset( $_POST['new_php_cron'] ) ) {
111
  if ( ! current_user_can( 'edit_files' ) ) {
112
  wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 );
113
  }
114
  check_admin_referer( 'new-cron' );
115
  extract( wp_unslash( $_POST ), EXTR_PREFIX_ALL, 'in' );
116
- $next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom : $in_next_run_date_local;
117
  $args = array(
118
  'code' => $in_hookcode,
119
  'name' => $in_eventname,
120
  );
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  $added = Event\add( $next_run_local, $in_schedule, 'crontrol_cron_job', $args );
123
 
124
  $hookname = ( ! empty( $in_eventname ) ) ? $in_eventname : __( 'PHP Cron', 'wp-crontrol' );
@@ -135,7 +203,7 @@ function action_handle_posts() {
135
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
136
  exit;
137
 
138
- } elseif ( isset( $_POST['edit_cron'] ) ) {
139
  if ( ! current_user_can( 'manage_options' ) ) {
140
  wp_die( esc_html__( 'You are not allowed to edit cron events.', 'wp-crontrol' ), 401 );
141
  }
@@ -153,9 +221,42 @@ function action_handle_posts() {
153
  $in_args = array();
154
  }
155
 
 
156
  Event\delete( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
157
 
158
- $next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom : $in_next_run_date_local;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
  $added = Event\add( $next_run_local, $in_schedule, $in_hookname, $in_args );
161
 
@@ -172,7 +273,7 @@ function action_handle_posts() {
172
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
173
  exit;
174
 
175
- } elseif ( isset( $_POST['edit_php_cron'] ) ) {
176
  if ( ! current_user_can( 'edit_files' ) ) {
177
  wp_die( esc_html__( 'You are not allowed to edit PHP cron events.', 'wp-crontrol' ), 401 );
178
  }
@@ -184,9 +285,42 @@ function action_handle_posts() {
184
  'name' => $in_eventname,
185
  );
186
 
 
187
  Event\delete( $in_original_hookname, $in_original_sig, $in_original_next_run_utc );
188
 
189
- $next_run_local = ( 'custom' === $in_next_run_date_local ) ? $in_next_run_date_local_custom : $in_next_run_date_local;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
  $added = Event\add( $next_run_local, $in_schedule, 'crontrol_cron_job', $args );
192
 
@@ -251,13 +385,13 @@ function action_handle_posts() {
251
  if ( ! current_user_can( 'manage_options' ) ) {
252
  wp_die( esc_html__( 'You are not allowed to delete cron schedules.', 'wp-crontrol' ), 401 );
253
  }
254
- $id = wp_unslash( $_GET['id'] );
255
- check_admin_referer( "delete-sched_{$id}" );
256
- Schedule\delete( $id );
257
  $redirect = array(
258
  'page' => 'crontrol_admin_options_page',
259
  'crontrol_message' => '2',
260
- 'crontrol_name' => rawurlencode( $id ),
261
  );
262
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
263
  exit;
@@ -276,12 +410,18 @@ function action_handle_posts() {
276
  $deleted = 0;
277
 
278
  foreach ( $delete as $next_run_utc => $events ) {
279
- foreach ( $events as $id => $sig ) {
280
- if ( 'crontrol_cron_job' === $id && ! current_user_can( 'edit_files' ) ) {
281
  continue;
282
  }
283
- if ( Event\delete( urldecode( $id ), $sig, $next_run_utc ) ) {
 
 
 
284
  $deleted++;
 
 
 
285
  }
286
  }
287
  }
@@ -298,63 +438,82 @@ function action_handle_posts() {
298
  if ( ! current_user_can( 'manage_options' ) ) {
299
  wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
300
  }
301
- $id = wp_unslash( $_GET['id'] );
302
  $sig = wp_unslash( $_GET['sig'] );
303
  $next_run_utc = intval( $_GET['next_run_utc'] );
304
- check_admin_referer( "delete-cron_{$id}_{$sig}_{$next_run_utc}" );
305
 
306
- if ( 'crontrol_cron_job' === $id && ! current_user_can( 'edit_files' ) ) {
307
  wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
308
  }
309
 
310
- if ( Event\delete( $id, $sig, $next_run_utc ) ) {
311
- $redirect = array(
312
- 'page' => 'crontrol_admin_manage_page',
313
- 'crontrol_message' => '6',
314
- 'crontrol_name' => rawurlencode( $id ),
315
- );
316
- wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
317
- exit;
 
 
318
  } else {
319
- $redirect = array(
320
- 'page' => 'crontrol_admin_manage_page',
321
- 'crontrol_message' => '7',
322
- 'crontrol_name' => rawurlencode( $id ),
323
- );
324
- wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
325
- exit;
 
 
 
 
 
 
 
 
326
 
327
- };
 
328
 
329
  } elseif ( isset( $_GET['action'] ) && 'delete-hook' === $_GET['action'] ) {
330
  if ( ! current_user_can( 'manage_options' ) ) {
331
  wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
332
  }
333
- $id = wp_unslash( $_GET['id'] );
334
  $deleted = false;
335
- check_admin_referer( "delete-hook_{$id}" );
336
 
337
- if ( 'crontrol_cron_job' === $id ) {
338
  wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
339
  }
340
 
341
  if ( function_exists( 'wp_unschedule_hook' ) ) {
342
- $deleted = wp_unschedule_hook( $id );
343
  }
344
 
345
  if ( 0 === $deleted ) {
346
  $redirect = array(
347
  'page' => 'crontrol_admin_manage_page',
348
  'crontrol_message' => '3',
349
- 'crontrol_name' => rawurlencode( $id ),
350
  );
351
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
352
  exit;
353
  } elseif ( $deleted ) {
 
 
 
 
 
 
 
 
354
  $redirect = array(
355
  'page' => 'crontrol_admin_manage_page',
356
  'crontrol_message' => '2',
357
- 'crontrol_name' => rawurlencode( $id ),
358
  );
359
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
360
  exit;
@@ -362,7 +521,7 @@ function action_handle_posts() {
362
  $redirect = array(
363
  'page' => 'crontrol_admin_manage_page',
364
  'crontrol_message' => '7',
365
- 'crontrol_name' => rawurlencode( $id ),
366
  );
367
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
368
  exit;
@@ -371,26 +530,24 @@ function action_handle_posts() {
371
  if ( ! current_user_can( 'manage_options' ) ) {
372
  wp_die( esc_html__( 'You are not allowed to run cron events.', 'wp-crontrol' ), 401 );
373
  }
374
- $id = wp_unslash( $_GET['id'] );
375
  $sig = wp_unslash( $_GET['sig'] );
376
- check_admin_referer( "run-cron_{$id}_{$sig}" );
377
- if ( Event\run( $id, $sig ) ) {
378
- $redirect = array(
379
- 'page' => 'crontrol_admin_manage_page',
380
- 'crontrol_message' => '1',
381
- 'crontrol_name' => rawurlencode( $id ),
382
- );
383
- wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
384
- exit;
385
- } else {
386
- $redirect = array(
387
- 'page' => 'crontrol_admin_manage_page',
388
- 'crontrol_message' => '8',
389
- 'crontrol_name' => rawurlencode( $id ),
390
- );
391
- wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
392
- exit;
393
  }
 
 
 
394
  }
395
  }
396
 
@@ -491,32 +648,44 @@ function admin_options_page() {
491
 
492
  <?php $table->views(); ?>
493
 
494
- <div class="table-responsive">
495
- <?php $table->display(); ?>
496
- </div>
497
-
498
- <div class="wrap narrow">
499
- <h2 class="title"><?php esc_html_e( 'Add Cron Schedule', 'wp-crontrol' ); ?></h2>
500
- <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>
501
- <form method="post" action="options-general.php?page=crontrol_admin_options_page">
502
- <table class="form-table">
503
- <tbody>
504
- <tr>
505
- <th valign="top" scope="row"><label for="cron_internal_name"><?php esc_html_e( 'Internal name', 'wp-crontrol' ); ?></label></th>
506
- <td><input type="text" class="regular-text" value="" id="cron_internal_name" name="internal_name" required/></td>
507
- </tr>
508
- <tr>
509
- <th valign="top" scope="row"><label for="cron_interval"><?php esc_html_e( 'Interval (seconds)', 'wp-crontrol' ); ?></label></th>
510
- <td><input type="number" class="regular-text" value="" id="cron_interval" name="interval" min="1" step="1" required/></td>
511
- </tr>
512
- <tr>
513
- <th valign="top" scope="row"><label for="cron_display_name"><?php esc_html_e( 'Display name', 'wp-crontrol' ); ?></label></th>
514
- <td><input type="text" class="regular-text" value="" id="cron_display_name" name="display_name" required/></td>
515
- </tr>
516
- </tbody></table>
517
- <p class="submit"><input id="schedadd-submit" type="submit" class="button button-primary" value="<?php esc_attr_e( 'Add Cron Schedule', 'wp-crontrol' ); ?>" name="new_schedule"/></p>
518
- <?php wp_nonce_field( 'new-sched' ); ?>
519
- </form>
 
 
 
 
 
 
 
 
 
 
 
 
520
  </div>
521
  <?php
522
  }
@@ -670,25 +839,17 @@ function show_cron_status( $tab ) {
670
  }
671
 
672
  if ( 'UTC' !== date_default_timezone_get() ) {
673
- $string = sprintf(
674
- /* translators: %s: Help page URL. */
675
- __( 'PHP default timezone is not set to UTC. This may cause issues with cron event timings. <a href="%s">More information</a>.', 'wp-crontrol' ),
676
- 'https://github.com/johnbillion/wp-crontrol/wiki/PHP-default-timezone-is-not-set-to-UTC'
677
- );
678
  ?>
679
  <div id="crontrol-timezone-warning" class="notice notice-warning">
680
- <p>
681
- <?php
682
- echo wp_kses(
683
- $string,
684
- array(
685
- 'a' => array(
686
- 'href' => true,
687
- ),
688
- )
689
  );
690
- ?>
691
- </p>
692
  </div>
693
  <?php
694
  }
@@ -705,15 +866,18 @@ function show_cron_status( $tab ) {
705
  } else {
706
  ?>
707
  <div id="cron-status-error" class="error">
708
- <p>
709
- <?php
710
- printf(
 
711
  /* translators: 1: Error message text. */
712
  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' ),
713
- '<br><strong>' . esc_html( $status->get_error_message() ) . '</strong>'
714
- );
715
- ?>
716
- </p>
 
 
717
  </div>
718
  <?php
719
  }
@@ -778,36 +942,47 @@ function get_utc_offset() {
778
  * Shows the form used to add/edit cron events.
779
  *
780
  * @param bool $editing Whether the form is for the event editor.
781
- * @param bool $is_php Whether the form is for a PHP event.
782
  * @return void
783
  */
784
- function show_cron_form( $editing, $is_php = null ) {
785
  $display_args = '';
786
  $edit_id = null;
787
  $existing = false;
788
 
789
- if ( ! empty( $_GET['id'] ) ) {
790
  $edit_id = wp_unslash( $_GET['id'] );
791
- }
792
 
793
- foreach ( Event\get() as $event ) {
794
- if ( $edit_id === $event->hook && intval( $_GET['next_run_utc'] ) === $event->time && $event->sig === $_GET['sig'] ) {
795
- $existing = array(
796
- 'hookname' => $event->hook,
797
- 'next_run' => $event->time, // UTC
798
- 'schedule' => ( $event->schedule ? $event->schedule : '_oneoff' ),
799
- 'sig' => $event->sig,
800
- 'args' => $event->args,
801
- );
802
- break;
 
803
  }
804
- }
805
 
806
- if ( null === $is_php ) {
807
- $is_php = ( $existing && 'crontrol_cron_job' === $existing['hookname'] );
 
 
 
 
 
 
 
 
 
 
 
808
  }
809
 
810
- if ( $is_php ) {
 
 
811
  $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' );
812
  } else {
813
  $helper_text = sprintf(
@@ -831,9 +1006,11 @@ function show_cron_form( $editing, $is_php = null ) {
831
  if ( ! empty( $existing['args'] ) ) {
832
  $display_args = wp_json_encode( $existing['args'] );
833
  }
834
- $action = $is_php ? 'edit_php_cron' : 'edit_cron';
835
  $button = __( 'Update Event', 'wp-crontrol' );
836
- $next_run_date_local = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $existing['next_run'] ), 'Y-m-d H:i:s' );
 
 
837
  } else {
838
  $other_fields = wp_nonce_field( 'new-cron', '_wpnonce', true, false );
839
  $existing = array(
@@ -843,12 +1020,12 @@ function show_cron_form( $editing, $is_php = null ) {
843
  'schedule' => false,
844
  );
845
 
846
- $action = $is_php ? 'new_php_cron' : 'new_cron';
847
  $button = __( 'Add Event', 'wp-crontrol' );
848
  $next_run_date_local = '';
 
849
  }
850
 
851
- if ( $is_php ) {
852
  if ( ! isset( $existing['args']['code'] ) ) {
853
  $existing['args']['code'] = '';
854
  }
@@ -857,23 +1034,16 @@ function show_cron_form( $editing, $is_php = null ) {
857
  }
858
  }
859
 
860
- $allowed = ( ! $is_php || current_user_can( 'edit_files' ) );
 
861
  ?>
862
  <div id="crontrol_form" class="wrap narrow">
863
  <?php
864
  if ( $allowed ) {
865
  if ( $editing ) {
866
- if ( $is_php ) {
867
- $heading = __( 'Edit PHP Cron Event', 'wp-crontrol' );
868
- } else {
869
- $heading = __( 'Edit Cron Event', 'wp-crontrol' );
870
- }
871
  } else {
872
- if ( $is_php ) {
873
- $heading = __( 'Add PHP Cron Event', 'wp-crontrol' );
874
- } else {
875
- $heading = __( 'Add Cron Event', 'wp-crontrol' );
876
- }
877
  }
878
 
879
  printf(
@@ -886,15 +1056,40 @@ function show_cron_form( $editing, $is_php = null ) {
886
  $helper_text
887
  );
888
  ?>
889
- <form method="post" action="<?php echo esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page' ) ); ?>">
890
  <?php
891
  // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
892
  echo $other_fields;
893
  ?>
894
  <table class="form-table"><tbody>
895
- <?php if ( $is_php ) : ?>
896
- <tr>
897
- <th valign="top" scope="row"><label for="hookcode"><?php esc_html_e( 'PHP Code', 'wp-crontrol' ); ?></label></th>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
898
  <td>
899
  <p class="description">
900
  <?php
@@ -905,24 +1100,42 @@ function show_cron_form( $editing, $is_php = null ) {
905
  );
906
  ?>
907
  </p>
908
- <p><textarea class="large-text code" rows="10" cols="50" id="hookcode" name="hookcode"><?php echo esc_textarea( $existing['args']['code'] ); ?></textarea></p>
909
  </td>
910
  </tr>
911
- <tr>
912
- <th valign="top" scope="row"><label for="eventname"><?php esc_html_e( 'Event Name (optional)', 'wp-crontrol' ); ?></label></th>
913
- <td><input type="text" class="regular-text" id="eventname" name="eventname" value="<?php echo esc_attr( $existing['args']['name'] ); ?>"/></td>
 
 
 
 
 
 
914
  </tr>
915
- <?php else : ?>
916
- <tr>
917
- <th valign="top" scope="row"><label for="hookname"><?php esc_html_e( 'Hook Name', 'wp-crontrol' ); ?></label></th>
 
 
 
 
 
 
 
 
918
  <td>
919
  <input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" class="regular-text" id="hookname" name="hookname" value="<?php echo esc_attr( $existing['hookname'] ); ?>" required />
920
  </td>
921
  </tr>
922
- <tr>
923
- <th valign="top" scope="row"><label for="args"><?php esc_html_e( 'Arguments (optional)', 'wp-crontrol' ); ?></label></th>
 
 
 
 
924
  <td>
925
- <input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" class="regular-text" id="args" name="args" value="<?php echo esc_attr( $display_args ); ?>"/>
926
  <p class="description">
927
  <?php
928
  printf(
@@ -936,9 +1149,15 @@ function show_cron_form( $editing, $is_php = null ) {
936
  </p>
937
  </td>
938
  </tr>
939
- <?php endif; ?>
 
 
940
  <tr>
941
- <th valign="top" scope="row"><label for="next_run_date_local"><?php esc_html_e( 'Next Run', 'wp-crontrol' ); ?></label></th>
 
 
 
 
942
  <td>
943
  <ul>
944
  <li>
@@ -961,8 +1180,11 @@ function show_cron_form( $editing, $is_php = null ) {
961
  /* translators: %s: An input field for specifying a date and time */
962
  esc_html__( 'At: %s', 'wp-crontrol' ),
963
  sprintf(
964
- '<br><input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" name="next_run_date_local_custom" value="%s" class="regular-text" onfocus="jQuery(\'#next_run_date_local_custom\').prop(\'checked\',true);" />',
965
- esc_attr( $next_run_date_local )
 
 
 
966
  )
967
  );
968
  ?>
@@ -979,25 +1201,22 @@ function show_cron_form( $editing, $is_php = null ) {
979
  );
980
  ?>
981
  </p>
982
- <p class="description">
983
- <?php
984
- printf(
985
- /* translators: 1: Date/time format for an input field, 2: PHP function name. */
986
- esc_html__( 'Format: %1$s or anything accepted by %2$s', 'wp-crontrol' ),
987
- '<code>YYYY-MM-DD HH:MM:SS</code>',
988
- '<a href="https://www.php.net/manual/function.strtotime.php"><code>strtotime()</code></a>'
989
- );
990
- ?>
991
- </p>
992
  </td>
993
- </tr><tr>
994
- <th valign="top" scope="row"><label for="schedule"><?php esc_html_e( 'Recurrence', 'wp-crontrol' ); ?></label></th>
 
 
 
 
 
995
  <td>
996
  <?php Schedule\dropdown( $existing['schedule'] ); ?>
997
  </td>
998
  </tr>
999
  </tbody></table>
1000
- <p class="submit"><input type="submit" class="button button-primary" value="<?php echo esc_attr( $button ); ?>" name="<?php echo esc_attr( $action ); ?>"/></p>
 
 
1001
  </form>
1002
  <?php } else { ?>
1003
  <div class="error inline">
@@ -1098,7 +1317,11 @@ function admin_manage_page() {
1098
  case $tabs['events']:
1099
  ?>
1100
  <div class="wrap">
1101
- <h1><?php esc_html_e( 'Cron Events', 'wp-crontrol' ); ?></h1>
 
 
 
 
1102
 
1103
  <?php $table->views(); ?>
1104
 
@@ -1132,10 +1355,6 @@ function admin_manage_page() {
1132
  show_cron_form( false );
1133
  break;
1134
 
1135
- case $tabs['add-php-event']:
1136
- show_cron_form( false, true );
1137
- break;
1138
-
1139
  case $tabs['edit-event']:
1140
  show_cron_form( true );
1141
  break;
@@ -1154,7 +1373,6 @@ function get_tab_states() {
1154
  'events' => ( ! empty( $_GET['page'] ) && 'crontrol_admin_manage_page' === $_GET['page'] && empty( $_GET['action'] ) ),
1155
  'schedules' => ( ! empty( $_GET['page'] ) && 'crontrol_admin_options_page' === $_GET['page'] ),
1156
  'add-event' => ( ! empty( $_GET['action'] ) && 'new-cron' === $_GET['action'] ),
1157
- 'add-php-event' => ( ! empty( $_GET['action'] ) && 'new-php-cron' === $_GET['action'] ),
1158
  'edit-event' => ( ! empty( $_GET['action'] ) && 'edit-cron' === $_GET['action'] ),
1159
  );
1160
  }
@@ -1173,22 +1391,14 @@ function do_tabs() {
1173
  $tab = array_keys( $tab );
1174
  $tab = reset( $tab );
1175
  $links = array(
1176
- 'events' => array(
1177
  'tools.php?page=crontrol_admin_manage_page',
1178
  __( 'Cron Events', 'wp-crontrol' ),
1179
  ),
1180
- 'schedules' => array(
1181
  'options-general.php?page=crontrol_admin_options_page',
1182
  __( 'Cron Schedules', 'wp-crontrol' ),
1183
  ),
1184
- 'add-event' => array(
1185
- 'tools.php?page=crontrol_admin_manage_page&action=new-cron',
1186
- __( 'Add Cron Event', 'wp-crontrol' ),
1187
- ),
1188
- 'add-php-event' => array(
1189
- 'tools.php?page=crontrol_admin_manage_page&action=new-php-cron',
1190
- __( 'Add PHP Cron Event', 'wp-crontrol' ),
1191
- ),
1192
  );
1193
 
1194
  ?>
@@ -1211,7 +1421,12 @@ function do_tabs() {
1211
  }
1212
  }
1213
 
1214
- if ( $tabs['edit-event'] ) {
 
 
 
 
 
1215
  printf(
1216
  '<span class="nav-tab nav-tab-active">%s</span>',
1217
  esc_html__( 'Edit Cron Event', 'wp-crontrol' )
@@ -1314,7 +1529,7 @@ function output_callback( array $callback ) {
1314
  if ( class_exists( '\QM_Output_Html' ) ) {
1315
  if ( ! empty( $callback['callback']['error'] ) ) {
1316
  $return = '<code>' . $callback['callback']['name'] . '</code>';
1317
- $return .= '<br><span style="color:#c00"><span class="dashicons dashicons-warning" aria-hidden="true"></span> ';
1318
  $return .= esc_html( $callback['callback']['error']->get_error_message() );
1319
  $return .= '</span>';
1320
  return $return;
@@ -1428,30 +1643,6 @@ function setup_manage_page() {
1428
  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' )
1429
  );
1430
  } );
1431
-
1432
- if ( ! function_exists( 'wp_enqueue_code_editor' ) ) {
1433
- return;
1434
- }
1435
- if ( ! current_user_can( 'edit_files' ) ) {
1436
- return;
1437
- }
1438
-
1439
- $settings = wp_enqueue_code_editor( array(
1440
- 'type' => 'text/x-php',
1441
- ) );
1442
-
1443
- if ( false === $settings ) {
1444
- return;
1445
- }
1446
-
1447
- wp_add_inline_script( 'code-editor', sprintf(
1448
- 'jQuery( function( $ ) {
1449
- if ( $( "#hookcode" ).length ) {
1450
- wp.codeEditor.initialize( "hookcode", %s );
1451
- }
1452
- } );',
1453
- wp_json_encode( $settings )
1454
- ) );
1455
  }
1456
 
1457
  /**
@@ -1467,17 +1658,31 @@ function enqueue_assets( $hook_suffix ) {
1467
  }
1468
 
1469
  $ver = filemtime( plugin_dir_path( __FILE__ ) . 'css/wp-crontrol.css' );
1470
- wp_enqueue_style( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'css/wp-crontrol.css', array(), $ver );
1471
 
1472
  $ver = filemtime( plugin_dir_path( __FILE__ ) . 'js/wp-crontrol.js' );
1473
  wp_enqueue_script( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'js/wp-crontrol.js', array( 'jquery' ), $ver, true );
1474
 
 
 
1475
  if ( ! empty( $tab['events'] ) ) {
1476
- wp_localize_script( 'wp-crontrol', 'wpCrontrol', array(
1477
- 'eventsHash' => md5( json_encode( Event\get_list_table()->items ) ),
1478
- 'eventsHashInterval' => 20,
1479
- ) );
1480
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1481
  }
1482
 
1483
  /**
@@ -1528,6 +1733,7 @@ function get_all_core_hooks() {
1528
  'upgrader_scheduled_cleanup',
1529
  'wp_maybe_auto_update',
1530
  'wp_split_shared_term_batch',
 
1531
  )
1532
  );
1533
  }
@@ -1542,6 +1748,7 @@ function get_core_schedules() {
1542
  'hourly',
1543
  'twicedaily',
1544
  'daily',
 
1545
  );
1546
  }
1547
 
2
  /**
3
  * Plugin Name: WP Crontrol
4
  * Plugin URI: https://wordpress.org/plugins/wp-crontrol/
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.0
9
  * Text Domain: wp-crontrol
10
  * Domain Path: /languages/
11
  * Requires PHP: 5.3.6
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
 
60
  add_action( 'load-tools_page_crontrol_admin_manage_page', __NAMESPACE__ . '\setup_manage_page' );
61
 
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
+ *
71
+ * @param string[] $plugin_meta An array of the plugin's metadata.
72
+ * @param string $plugin_file Path to the plugin file relative to the plugins directory.
73
+ * @return string[] An array of the plugin's metadata.
74
+ */
75
+ function filter_plugin_row_meta( array $plugin_meta, $plugin_file ) {
76
+ if ( 'wp-crontrol/wp-crontrol.php' !== $plugin_file ) {
77
+ return $plugin_meta;
78
+ }
79
+
80
+ $plugin_meta[] = sprintf(
81
+ '<a href="%1$s"><span class="dashicons dashicons-star-filled" aria-hidden="true" style="font-size:14px;line-height:1.3"></span>%2$s</a>',
82
+ 'https://github.com/sponsors/johnbillion',
83
+ esc_html_x( 'Sponsor', 'verb', 'wp-crontrol' )
84
+ );
85
+
86
+ return $plugin_meta;
87
+ }
88
+
89
  /**
90
  * Run using the 'init' action.
91
  */
97
  * Handles any POSTs made by the plugin. Run using the 'init' action.
98
  */
99
  function action_handle_posts() {
100
+ if ( isset( $_POST['action'] ) && ( 'new_cron' === $_POST['action'] ) ) {
101
  if ( ! current_user_can( 'manage_options' ) ) {
102
  wp_die( esc_html__( 'You are not allowed to add new cron events.', 'wp-crontrol' ), 401 );
103
  }
112
  $in_args = array();
113
  }
114
 
115
+ $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;
116
+
117
+ add_filter( 'schedule_event', function( $event ) {
118
+ if ( ! $event ) {
119
+ return $event;
120
+ }
121
+
122
+ /**
123
+ * Fires after a new cron event is added.
124
+ *
125
+ * @param object $event {
126
+ * An object containing the event's data.
127
+ *
128
+ * @type string $hook Action hook to execute when the event is run.
129
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
130
+ * @type string|false $schedule How often the event should subsequently recur.
131
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
132
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
133
+ * }
134
+ */
135
+ do_action( 'crontrol/added_new_event', $event );
136
+
137
+ return $event;
138
+ }, 99 );
139
 
140
  $added = Event\add( $next_run_local, $in_schedule, $in_hookname, $in_args );
141
 
152
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
153
  exit;
154
 
155
+ } elseif ( isset( $_POST['action'] ) && ( 'new_php_cron' === $_POST['action'] ) ) {
156
  if ( ! current_user_can( 'edit_files' ) ) {
157
  wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 );
158
  }
159
  check_admin_referer( 'new-cron' );
160
  extract( wp_unslash( $_POST ), EXTR_PREFIX_ALL, 'in' );
161
+ $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;
162
  $args = array(
163
  'code' => $in_hookcode,
164
  'name' => $in_eventname,
165
  );
166
 
167
+ add_filter( 'schedule_event', function( $event ) {
168
+ if ( ! $event ) {
169
+ return $event;
170
+ }
171
+
172
+ /**
173
+ * Fires after a new PHP cron event is added.
174
+ *
175
+ * @param object $event {
176
+ * An object containing the event's data.
177
+ *
178
+ * @type string $hook Action hook to execute when the event is run.
179
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
180
+ * @type string|false $schedule How often the event should subsequently recur.
181
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
182
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
183
+ * }
184
+ */
185
+ do_action( 'crontrol/added_new_php_event', $event );
186
+
187
+ return $event;
188
+ }, 99 );
189
+
190
  $added = Event\add( $next_run_local, $in_schedule, 'crontrol_cron_job', $args );
191
 
192
  $hookname = ( ! empty( $in_eventname ) ) ? $in_eventname : __( 'PHP Cron', 'wp-crontrol' );
203
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
204
  exit;
205
 
206
+ } elseif ( isset( $_POST['action'] ) && ( 'edit_cron' === $_POST['action'] ) ) {
207
  if ( ! current_user_can( 'manage_options' ) ) {
208
  wp_die( esc_html__( 'You are not allowed to edit cron events.', 'wp-crontrol' ), 401 );
209
  }
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
+
229
+ add_filter( 'schedule_event', function( $event ) use ( $original ) {
230
+ if ( ! $event || ! $original ) {
231
+ return $event;
232
+ }
233
+
234
+ /**
235
+ * Fires after a cron event is edited.
236
+ *
237
+ * @param object $event {
238
+ * An object containing the new event's data.
239
+ *
240
+ * @type string $hook Action hook to execute when the event is run.
241
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
242
+ * @type string|false $schedule How often the event should subsequently recur.
243
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
244
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
245
+ * }
246
+ * @param object $original {
247
+ * An object containing the original event's data.
248
+ *
249
+ * @type string $hook Action hook to execute when the event is run.
250
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
251
+ * @type string|false $schedule How often the event should subsequently recur.
252
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
253
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
254
+ * }
255
+ */
256
+ do_action( 'crontrol/edited_event', $event, $original );
257
+
258
+ return $event;
259
+ }, 99 );
260
 
261
  $added = Event\add( $next_run_local, $in_schedule, $in_hookname, $in_args );
262
 
273
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
274
  exit;
275
 
276
+ } elseif ( isset( $_POST['action'] ) && ( 'edit_php_cron' === $_POST['action'] ) ) {
277
  if ( ! current_user_can( 'edit_files' ) ) {
278
  wp_die( esc_html__( 'You are not allowed to edit PHP cron events.', 'wp-crontrol' ), 401 );
279
  }
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
+
293
+ add_filter( 'schedule_event', function( $event ) use ( $original ) {
294
+ if ( ! $event || ! $original ) {
295
+ return $event;
296
+ }
297
+
298
+ /**
299
+ * Fires after a PHP cron event is edited.
300
+ *
301
+ * @param object $event {
302
+ * An object containing the new event's data.
303
+ *
304
+ * @type string $hook Action hook to execute when the event is run.
305
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
306
+ * @type string|false $schedule How often the event should subsequently recur.
307
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
308
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
309
+ * }
310
+ * @param object $original {
311
+ * An object containing the original event's data.
312
+ *
313
+ * @type string $hook Action hook to execute when the event is run.
314
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
315
+ * @type string|false $schedule How often the event should subsequently recur.
316
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
317
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
318
+ * }
319
+ */
320
+ do_action( 'crontrol/edited_php_event', $event, $original );
321
+
322
+ return $event;
323
+ }, 99 );
324
 
325
  $added = Event\add( $next_run_local, $in_schedule, 'crontrol_cron_job', $args );
326
 
385
  if ( ! current_user_can( 'manage_options' ) ) {
386
  wp_die( esc_html__( 'You are not allowed to delete cron schedules.', 'wp-crontrol' ), 401 );
387
  }
388
+ $schedule = wp_unslash( $_GET['id'] );
389
+ check_admin_referer( "delete-sched_{$schedule}" );
390
+ Schedule\delete( $schedule );
391
  $redirect = array(
392
  'page' => 'crontrol_admin_options_page',
393
  'crontrol_message' => '2',
394
+ 'crontrol_name' => rawurlencode( $schedule ),
395
  );
396
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
397
  exit;
410
  $deleted = 0;
411
 
412
  foreach ( $delete as $next_run_utc => $events ) {
413
+ foreach ( $events as $hook => $sig ) {
414
+ if ( 'crontrol_cron_job' === $hook && ! current_user_can( 'edit_files' ) ) {
415
  continue;
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 */
424
+ do_action( 'crontrol/deleted_event', $event );
425
  }
426
  }
427
  }
438
  if ( ! current_user_can( 'manage_options' ) ) {
439
  wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
440
  }
441
+ $hook = wp_unslash( $_GET['id'] );
442
  $sig = wp_unslash( $_GET['sig'] );
443
  $next_run_utc = intval( $_GET['next_run_utc'] );
444
+ check_admin_referer( "delete-cron_{$hook}_{$sig}_{$next_run_utc}" );
445
 
446
+ if ( 'crontrol_cron_job' === $hook && ! current_user_can( 'edit_files' ) ) {
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.
463
+ *
464
+ * @param object $event {
465
+ * An object containing the event's data.
466
+ *
467
+ * @type string $hook Action hook to execute when the event is run.
468
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
469
+ * @type string|false $schedule How often the event should subsequently recur.
470
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
471
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
472
+ * }
473
+ */
474
+ do_action( 'crontrol/deleted_event', $event );
475
+ }
476
 
477
+ wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
478
+ exit;
479
 
480
  } elseif ( isset( $_GET['action'] ) && 'delete-hook' === $_GET['action'] ) {
481
  if ( ! current_user_can( 'manage_options' ) ) {
482
  wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
483
  }
484
+ $hook = wp_unslash( $_GET['id'] );
485
  $deleted = false;
486
+ check_admin_referer( "delete-hook_{$hook}" );
487
 
488
+ if ( 'crontrol_cron_job' === $hook ) {
489
  wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
490
  }
491
 
492
  if ( function_exists( 'wp_unschedule_hook' ) ) {
493
+ $deleted = wp_unschedule_hook( $hook );
494
  }
495
 
496
  if ( 0 === $deleted ) {
497
  $redirect = array(
498
  'page' => 'crontrol_admin_manage_page',
499
  'crontrol_message' => '3',
500
+ 'crontrol_name' => rawurlencode( $hook ),
501
  );
502
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
503
  exit;
504
  } elseif ( $deleted ) {
505
+ /**
506
+ * Fires after all cron events with the given hook are deleted.
507
+ *
508
+ * @param string $hook The hook name.
509
+ * @param int $deleted The number of events that were deleted.
510
+ */
511
+ do_action( 'crontrol/deleted_all_with_hook', $hook, $deleted );
512
+
513
  $redirect = array(
514
  'page' => 'crontrol_admin_manage_page',
515
  'crontrol_message' => '2',
516
+ 'crontrol_name' => rawurlencode( $hook ),
517
  );
518
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
519
  exit;
521
  $redirect = array(
522
  'page' => 'crontrol_admin_manage_page',
523
  'crontrol_message' => '7',
524
+ 'crontrol_name' => rawurlencode( $hook ),
525
  );
526
  wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
527
  exit;
530
  if ( ! current_user_can( 'manage_options' ) ) {
531
  wp_die( esc_html__( 'You are not allowed to run cron events.', 'wp-crontrol' ), 401 );
532
  }
533
+ $hook = wp_unslash( $_GET['id'] );
534
  $sig = wp_unslash( $_GET['sig'] );
535
+ check_admin_referer( "run-cron_{$hook}_{$sig}" );
536
+
537
+ $ran = Event\run( $hook, $sig );
538
+
539
+ $redirect = array(
540
+ 'page' => 'crontrol_admin_manage_page',
541
+ 'crontrol_message' => '1',
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' ) ) );
550
+ exit;
551
  }
552
  }
553
 
648
 
649
  <?php $table->views(); ?>
650
 
651
+ <div id="col-container" class="wp-clearfix">
652
+ <div id="col-left">
653
+ <div class="col-wrap">
654
+ <div class="form-wrap">
655
+ <h2><?php esc_html_e( 'Add Cron Schedule', 'wp-crontrol' ); ?></h2>
656
+ <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>
657
+ <form method="post" action="options-general.php?page=crontrol_admin_options_page">
658
+ <div class="form-field form-required">
659
+ <label for="cron_internal_name">
660
+ <?php esc_html_e( 'Internal Name', 'wp-crontrol' ); ?>
661
+ </label>
662
+ <input type="text" value="" id="cron_internal_name" name="internal_name" required/>
663
+ </div>
664
+ <div class="form-field form-required">
665
+ <label for="cron_interval">
666
+ <?php esc_html_e( 'Interval (seconds)', 'wp-crontrol' ); ?>
667
+ </label>
668
+ <input type="number" value="" id="cron_interval" name="interval" min="1" step="1" required/>
669
+ </div>
670
+ <div class="form-field form-required">
671
+ <label for="cron_display_name">
672
+ <?php esc_html_e( 'Display Name', 'wp-crontrol' ); ?>
673
+ </label>
674
+ <input type="text" value="" id="cron_display_name" name="display_name" required/>
675
+ </div>
676
+ <p class="submit">
677
+ <input id="schedadd-submit" type="submit" class="button button-primary" value="<?php esc_attr_e( 'Add Cron Schedule', 'wp-crontrol' ); ?>" name="new_schedule"/>
678
+ </p>
679
+ <?php wp_nonce_field( 'new-sched' ); ?>
680
+ </form>
681
+ </div>
682
+ </div>
683
+ </div>
684
+ <div id="col-right">
685
+ <div class="col-wrap">
686
+ <?php $table->display(); ?>
687
+ </div>
688
+ </div>
689
  </div>
690
  <?php
691
  }
839
  }
840
 
841
  if ( 'UTC' !== date_default_timezone_get() ) {
 
 
 
 
 
842
  ?>
843
  <div id="crontrol-timezone-warning" class="notice notice-warning">
844
+ <?php
845
+ printf(
846
+ '<p>%1$s</p><p><a href="%2$s">%3$s</a></p>',
847
+ /* translators: %s: Help page URL. */
848
+ esc_html__( 'PHP default timezone is not set to UTC. This may cause issues with cron event timings.', 'wp-crontrol' ),
849
+ 'https://github.com/johnbillion/wp-crontrol/wiki/PHP-default-timezone-is-not-set-to-UTC',
850
+ esc_html__( 'More information', 'wp-crontrol' )
 
 
851
  );
852
+ ?>
 
853
  </div>
854
  <?php
855
  }
866
  } else {
867
  ?>
868
  <div id="cron-status-error" class="error">
869
+ <?php
870
+ printf(
871
+ '<p>%1$s</p><p><a href="%2$s">%3$s</a></p>',
872
+ sprintf(
873
  /* translators: 1: Error message text. */
874
  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' ),
875
+ '</p><p><strong>' . esc_html( $status->get_error_message() ) . '</strong>'
876
+ ),
877
+ 'https://github.com/johnbillion/wp-crontrol/wiki/Problems-with-spawning-a-call-to-the-WP-Cron-system',
878
+ esc_html__( 'More information', 'wp-crontrol' )
879
+ );
880
+ ?>
881
  </div>
882
  <?php
883
  }
942
  * Shows the form used to add/edit cron events.
943
  *
944
  * @param bool $editing Whether the form is for the event editor.
 
945
  * @return void
946
  */
947
+ function show_cron_form( $editing ) {
948
  $display_args = '';
949
  $edit_id = null;
950
  $existing = false;
951
 
952
+ if ( $editing && ! empty( $_GET['id'] ) ) {
953
  $edit_id = wp_unslash( $_GET['id'] );
 
954
 
955
+ foreach ( Event\get() as $event ) {
956
+ if ( $edit_id === $event->hook && intval( $_GET['next_run_utc'] ) === $event->time && $event->sig === $_GET['sig'] ) {
957
+ $existing = array(
958
+ 'hookname' => $event->hook,
959
+ 'next_run' => $event->time, // UTC
960
+ 'schedule' => ( $event->schedule ? $event->schedule : '_oneoff' ),
961
+ 'sig' => $event->sig,
962
+ 'args' => $event->args,
963
+ );
964
+ break;
965
+ }
966
  }
 
967
 
968
+ if ( empty( $existing ) ) {
969
+ ?>
970
+ <div id="crontrol-event-not-found" class="notice notice-error">
971
+ <?php
972
+ printf(
973
+ '<p>%1$s</p>',
974
+ esc_html__( 'The event you are trying to edit does not exist.', 'wp-crontrol' )
975
+ );
976
+ ?>
977
+ </div>
978
+ <?php
979
+ return;
980
+ }
981
  }
982
 
983
+ $is_editing_php = ( $existing && 'crontrol_cron_job' === $existing['hookname'] );
984
+
985
+ if ( $is_editing_php ) {
986
  $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' );
987
  } else {
988
  $helper_text = sprintf(
1006
  if ( ! empty( $existing['args'] ) ) {
1007
  $display_args = wp_json_encode( $existing['args'] );
1008
  }
1009
+ $action = $is_editing_php ? 'edit_php_cron' : 'edit_cron';
1010
  $button = __( 'Update Event', 'wp-crontrol' );
1011
+ $next_run_gmt = gmdate( 'Y-m-d H:i:s', $existing['next_run'] );
1012
+ $next_run_date_local = get_date_from_gmt( $next_run_gmt, 'Y-m-d' );
1013
+ $next_run_time_local = get_date_from_gmt( $next_run_gmt, 'H:i:s' );
1014
  } else {
1015
  $other_fields = wp_nonce_field( 'new-cron', '_wpnonce', true, false );
1016
  $existing = array(
1020
  'schedule' => false,
1021
  );
1022
 
 
1023
  $button = __( 'Add Event', 'wp-crontrol' );
1024
  $next_run_date_local = '';
1025
+ $next_run_time_local = '';
1026
  }
1027
 
1028
+ if ( $is_editing_php ) {
1029
  if ( ! isset( $existing['args']['code'] ) ) {
1030
  $existing['args']['code'] = '';
1031
  }
1034
  }
1035
  }
1036
 
1037
+ $can_add_php = current_user_can( 'edit_files' ) && ! $editing;
1038
+ $allowed = ( ! $is_editing_php || current_user_can( 'edit_files' ) );
1039
  ?>
1040
  <div id="crontrol_form" class="wrap narrow">
1041
  <?php
1042
  if ( $allowed ) {
1043
  if ( $editing ) {
1044
+ $heading = __( 'Edit Cron Event', 'wp-crontrol' );
 
 
 
 
1045
  } else {
1046
+ $heading = __( 'Add Cron Event', 'wp-crontrol' );
 
 
 
 
1047
  }
1048
 
1049
  printf(
1056
  $helper_text
1057
  );
1058
  ?>
1059
+ <form method="post" action="<?php echo esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page' ) ); ?>" class="crontrol-edit-event crontrol-edit-event-<?php echo ( $is_editing_php ) ? 'php' : 'standard'; ?>">
1060
  <?php
1061
  // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1062
  echo $other_fields;
1063
  ?>
1064
  <table class="form-table"><tbody>
1065
+ <?php
1066
+ if ( $editing ) {
1067
+ printf(
1068
+ '<input type="hidden" name="action" value="%s"/>',
1069
+ esc_attr( $action )
1070
+ );
1071
+ } elseif ( $can_add_php ) {
1072
+ ?>
1073
+ <tr class="hide-if-no-js">
1074
+ <th valign="top" scope="row">
1075
+ <?php esc_html_e( 'Event Type', 'wp-crontrol' ); ?>
1076
+ </th>
1077
+ <td>
1078
+ <p><label><input type="radio" name="action" value="new_cron" checked>Standard cron event</label></p>
1079
+ <p><label><input type="radio" name="action" value="new_php_cron">PHP cron event</label></p>
1080
+ </td>
1081
+ </tr>
1082
+ <?php
1083
+ }
1084
+
1085
+ if ( $is_editing_php || $can_add_php ) {
1086
+ ?>
1087
+ <tr class="crontrol-event-php">
1088
+ <th valign="top" scope="row">
1089
+ <label for="hookcode">
1090
+ <?php esc_html_e( 'PHP Code', 'wp-crontrol' ); ?>
1091
+ </label>
1092
+ </th>
1093
  <td>
1094
  <p class="description">
1095
  <?php
1100
  );
1101
  ?>
1102
  </p>
1103
+ <p><textarea class="large-text code" rows="10" cols="50" id="hookcode" name="hookcode"><?php echo esc_textarea( $editing ? $existing['args']['code'] : '' ); ?></textarea></p>
1104
  </td>
1105
  </tr>
1106
+ <tr class="crontrol-event-php">
1107
+ <th valign="top" scope="row">
1108
+ <label for="eventname">
1109
+ <?php esc_html_e( 'Event Name (optional)', 'wp-crontrol' ); ?>
1110
+ </label>
1111
+ </th>
1112
+ <td>
1113
+ <input type="text" class="regular-text" id="eventname" name="eventname" value="<?php echo esc_attr( $editing ? $existing['args']['name'] : '' ); ?>"/>
1114
+ </td>
1115
  </tr>
1116
+ <?php
1117
+ }
1118
+
1119
+ if ( ! $is_editing_php ) {
1120
+ ?>
1121
+ <tr class="crontrol-event-standard">
1122
+ <th valign="top" scope="row">
1123
+ <label for="hookname">
1124
+ <?php esc_html_e( 'Hook Name', 'wp-crontrol' ); ?>
1125
+ </label>
1126
+ </th>
1127
  <td>
1128
  <input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" class="regular-text" id="hookname" name="hookname" value="<?php echo esc_attr( $existing['hookname'] ); ?>" required />
1129
  </td>
1130
  </tr>
1131
+ <tr class="crontrol-event-standard">
1132
+ <th valign="top" scope="row">
1133
+ <label for="args">
1134
+ <?php esc_html_e( 'Arguments (optional)', 'wp-crontrol' ); ?>
1135
+ </label>
1136
+ </th>
1137
  <td>
1138
+ <input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" class="regular-text code" id="args" name="args" value="<?php echo esc_attr( $display_args ); ?>"/>
1139
  <p class="description">
1140
  <?php
1141
  printf(
1149
  </p>
1150
  </td>
1151
  </tr>
1152
+ <?php
1153
+ }
1154
+ ?>
1155
  <tr>
1156
+ <th valign="top" scope="row">
1157
+ <label for="next_run_date_local">
1158
+ <?php esc_html_e( 'Next Run', 'wp-crontrol' ); ?>
1159
+ </label>
1160
+ </th>
1161
  <td>
1162
  <ul>
1163
  <li>
1180
  /* translators: %s: An input field for specifying a date and time */
1181
  esc_html__( 'At: %s', 'wp-crontrol' ),
1182
  sprintf(
1183
+ '<br>
1184
+ <input type="date" autocorrect="off" autocapitalize="off" spellcheck="false" name="next_run_date_local_custom_date" id="next_run_date_local_custom_date" value="%1$s" placeholder="yyyy-mm-dd" pattern="\d{4}-\d{2}-\d{2}" />
1185
+ <input type="time" autocorrect="off" autocapitalize="off" spellcheck="false" name="next_run_date_local_custom_time" id="next_run_date_local_custom_time" value="%2$s" step="1" placeholder="hh:mm:ss" pattern="\d{2}:\d{2}:\d{2}" />',
1186
+ esc_attr( $next_run_date_local ),
1187
+ esc_attr( $next_run_time_local )
1188
  )
1189
  );
1190
  ?>
1201
  );
1202
  ?>
1203
  </p>
 
 
 
 
 
 
 
 
 
 
1204
  </td>
1205
+ </tr>
1206
+ <tr>
1207
+ <th valign="top" scope="row">
1208
+ <label for="schedule">
1209
+ <?php esc_html_e( 'Recurrence', 'wp-crontrol' ); ?>
1210
+ </label>
1211
+ </th>
1212
  <td>
1213
  <?php Schedule\dropdown( $existing['schedule'] ); ?>
1214
  </td>
1215
  </tr>
1216
  </tbody></table>
1217
+ <p class="submit">
1218
+ <input type="submit" class="button button-primary" value="<?php echo esc_attr( $button ); ?>"/>
1219
+ </p>
1220
  </form>
1221
  <?php } else { ?>
1222
  <div class="error inline">
1317
  case $tabs['events']:
1318
  ?>
1319
  <div class="wrap">
1320
+ <h1 class="wp-heading-inline"><?php esc_html_e( 'Cron Events', 'wp-crontrol' ); ?></h1>
1321
+
1322
+ <?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>'; ?>
1323
+
1324
+ <hr class="wp-header-end">
1325
 
1326
  <?php $table->views(); ?>
1327
 
1355
  show_cron_form( false );
1356
  break;
1357
 
 
 
 
 
1358
  case $tabs['edit-event']:
1359
  show_cron_form( true );
1360
  break;
1373
  'events' => ( ! empty( $_GET['page'] ) && 'crontrol_admin_manage_page' === $_GET['page'] && empty( $_GET['action'] ) ),
1374
  'schedules' => ( ! empty( $_GET['page'] ) && 'crontrol_admin_options_page' === $_GET['page'] ),
1375
  'add-event' => ( ! empty( $_GET['action'] ) && 'new-cron' === $_GET['action'] ),
 
1376
  'edit-event' => ( ! empty( $_GET['action'] ) && 'edit-cron' === $_GET['action'] ),
1377
  );
1378
  }
1391
  $tab = array_keys( $tab );
1392
  $tab = reset( $tab );
1393
  $links = array(
1394
+ 'events' => array(
1395
  'tools.php?page=crontrol_admin_manage_page',
1396
  __( 'Cron Events', 'wp-crontrol' ),
1397
  ),
1398
+ 'schedules' => array(
1399
  'options-general.php?page=crontrol_admin_options_page',
1400
  __( 'Cron Schedules', 'wp-crontrol' ),
1401
  ),
 
 
 
 
 
 
 
 
1402
  );
1403
 
1404
  ?>
1421
  }
1422
  }
1423
 
1424
+ if ( $tabs['add-event'] ) {
1425
+ printf(
1426
+ '<span class="nav-tab nav-tab-active">%s</span>',
1427
+ esc_html__( 'Add Cron Event', 'wp-crontrol' )
1428
+ );
1429
+ } elseif ( $tabs['edit-event'] ) {
1430
  printf(
1431
  '<span class="nav-tab nav-tab-active">%s</span>',
1432
  esc_html__( 'Edit Cron Event', 'wp-crontrol' )
1529
  if ( class_exists( '\QM_Output_Html' ) ) {
1530
  if ( ! empty( $callback['callback']['error'] ) ) {
1531
  $return = '<code>' . $callback['callback']['name'] . '</code>';
1532
+ $return .= '<br><span class="status-crontrol-error"><span class="dashicons dashicons-warning" aria-hidden="true"></span> ';
1533
  $return .= esc_html( $callback['callback']['error']->get_error_message() );
1534
  $return .= '</span>';
1535
  return $return;
1643
  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' )
1644
  );
1645
  } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1646
  }
1647
 
1648
  /**
1658
  }
1659
 
1660
  $ver = filemtime( plugin_dir_path( __FILE__ ) . 'css/wp-crontrol.css' );
1661
+ wp_enqueue_style( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'css/wp-crontrol.css', array( 'dashicons' ), $ver );
1662
 
1663
  $ver = filemtime( plugin_dir_path( __FILE__ ) . 'js/wp-crontrol.js' );
1664
  wp_enqueue_script( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'js/wp-crontrol.js', array( 'jquery' ), $ver, true );
1665
 
1666
+ $vars = array();
1667
+
1668
  if ( ! empty( $tab['events'] ) ) {
1669
+ $vars['eventsHash'] = md5( json_encode( Event\get_list_table()->items ) );
1670
+ $vars['eventsHashInterval'] = 20;
 
 
1671
  }
1672
+
1673
+ if ( ! empty( $tab['add-event'] ) || ! empty( $tab['edit-event'] ) ) {
1674
+ if ( function_exists( 'wp_enqueue_code_editor' ) && current_user_can( 'edit_files' ) ) {
1675
+ $settings = wp_enqueue_code_editor( array(
1676
+ 'type' => 'text/x-php',
1677
+ ) );
1678
+
1679
+ if ( false !== $settings ) {
1680
+ $vars['codeEditor'] = $settings;
1681
+ }
1682
+ }
1683
+ }
1684
+
1685
+ wp_localize_script( 'wp-crontrol', 'wpCrontrol', $vars );
1686
  }
1687
 
1688
  /**
1733
  'upgrader_scheduled_cleanup',
1734
  'wp_maybe_auto_update',
1735
  'wp_split_shared_term_batch',
1736
+ 'wp_update_comment_type_batch',
1737
  )
1738
  );
1739
  }
1748
  'hourly',
1749
  'twicedaily',
1750
  'daily',
1751
+ 'weekly',
1752
  );
1753
  }
1754