Version Description
Download this release
Release Info
Developer | johnbillion |
Plugin | WP Crontrol |
Version | 1.15.0 |
Comparing to | |
See all releases |
Code changes from version 1.14.0 to 1.15.0
- composer.json +93 -0
- css/wp-crontrol.css +32 -7
- readme.md +79 -67
- src/bootstrap.php +2210 -0
- src/event-list-table.php +70 -21
- src/event.php +90 -8
- src/schedule-list-table.php +6 -6
- src/schedule.php +4 -4
- vendor/autoload.php +7 -0
- vendor/composer/ClassLoader.php +572 -0
- vendor/composer/InstalledVersions.php +350 -0
- vendor/composer/LICENSE +21 -0
- vendor/composer/autoload_classmap.php +13 -0
- vendor/composer/autoload_namespaces.php +9 -0
- vendor/composer/autoload_psr4.php +9 -0
- vendor/composer/autoload_real.php +48 -0
- vendor/composer/autoload_static.php +23 -0
- vendor/composer/installed.json +5 -0
- vendor/composer/installed.php +23 -0
- vendor/composer/platform_check.php +26 -0
- wp-crontrol.php +12 -2064
composer.json
ADDED
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "johnbillion/wp-crontrol",
|
3 |
+
"description": "WP Crontrol lets you view and control what's happening in the WP-Cron system.",
|
4 |
+
"homepage": "https://github.com/johnbillion/wp-crontrol/",
|
5 |
+
"license": "GPL-2.0-or-later",
|
6 |
+
"type": "wordpress-plugin",
|
7 |
+
"authors": [
|
8 |
+
{
|
9 |
+
"name": "John Blackbourn",
|
10 |
+
"homepage": "https://johnblackbourn.com/"
|
11 |
+
},
|
12 |
+
{
|
13 |
+
"name": "Edward Dale",
|
14 |
+
"homepage": "http://scompt.com/"
|
15 |
+
}
|
16 |
+
],
|
17 |
+
"config": {
|
18 |
+
"sort-packages": true,
|
19 |
+
"preferred-install": "dist",
|
20 |
+
"prepend-autoloader": false,
|
21 |
+
"classmap-authoritative": true,
|
22 |
+
"allow-plugins": {
|
23 |
+
"composer/installers": true,
|
24 |
+
"dealerdirect/phpcodesniffer-composer-installer": true,
|
25 |
+
"roots/wordpress-core-installer": true
|
26 |
+
}
|
27 |
+
},
|
28 |
+
"require-dev": {
|
29 |
+
"codeception/module-asserts": "^1.0",
|
30 |
+
"codeception/module-filesystem": "^1.0",
|
31 |
+
"codeception/module-webdriver": "^1.0",
|
32 |
+
"codeception/util-universalframework": "^1.0",
|
33 |
+
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
|
34 |
+
"lucatume/wp-browser": "^3.0",
|
35 |
+
"phpcompatibility/phpcompatibility-wp": "2.1.2",
|
36 |
+
"phpstan/phpstan": "^1.7",
|
37 |
+
"phpunit/phpunit": "^9.0",
|
38 |
+
"roots/wordpress-core-installer": "^1.0.0",
|
39 |
+
"roots/wordpress-full": "*",
|
40 |
+
"szepeviktor/phpstan-wordpress": "^1.0",
|
41 |
+
"wp-coding-standards/wpcs": "2.3.0"
|
42 |
+
},
|
43 |
+
"require": {
|
44 |
+
"php": ">=5.6",
|
45 |
+
"composer/installers": "^1.0 || ^2.0"
|
46 |
+
},
|
47 |
+
"autoload": {
|
48 |
+
"classmap": [
|
49 |
+
"src"
|
50 |
+
]
|
51 |
+
},
|
52 |
+
"extra": {
|
53 |
+
"wordpress-install-dir": "tests/wordpress"
|
54 |
+
},
|
55 |
+
"scripts": {
|
56 |
+
"test:cs": [
|
57 |
+
"phpcs -nps --colors --report-code --report-summary --report-width=80 --cache=tests/cache/phpcs --basepath=./ --standard=phpcs53.xml",
|
58 |
+
"phpcs -nps --colors --report-code --report-summary --report-width=80 --cache=tests/cache/phpcs --basepath=./ ."
|
59 |
+
],
|
60 |
+
"test:cs2pr": [
|
61 |
+
"phpcs -nsq --report=checkstyle --cache=tests/cache/phpcs . | cs2pr"
|
62 |
+
],
|
63 |
+
"test:analyze": [
|
64 |
+
"phpstan analyze --memory-limit=1024M"
|
65 |
+
],
|
66 |
+
"test:start": [
|
67 |
+
"docker compose up -d"
|
68 |
+
],
|
69 |
+
"test:acceptance": [
|
70 |
+
"bin/test.sh"
|
71 |
+
],
|
72 |
+
"test:stop": [
|
73 |
+
"docker compose down"
|
74 |
+
],
|
75 |
+
"test": [
|
76 |
+
"composer validate --strict",
|
77 |
+
"@test:cs",
|
78 |
+
"@test:analyze",
|
79 |
+
"@test:acceptance"
|
80 |
+
]
|
81 |
+
},
|
82 |
+
"support": {
|
83 |
+
"issues": "https://github.com/johnbillion/wp-crontrol/issues",
|
84 |
+
"forum": "https://wordpress.org/support/plugin/wp-crontrol",
|
85 |
+
"source": "https://github.com/johnbillion/wp-crontrol"
|
86 |
+
},
|
87 |
+
"funding": [
|
88 |
+
{
|
89 |
+
"type": "github",
|
90 |
+
"url": "https://github.com/sponsors/johnbillion"
|
91 |
+
}
|
92 |
+
]
|
93 |
+
}
|
css/wp-crontrol.css
CHANGED
@@ -28,6 +28,7 @@ table.wp-list-table {
|
|
28 |
.wp-list-table code {
|
29 |
background: transparent;
|
30 |
padding: 0;
|
|
|
31 |
}
|
32 |
|
33 |
.wp-list-table td.column-crontrol_status {
|
@@ -42,31 +43,43 @@ table.wp-list-table {
|
|
42 |
.wp-list-table tr.crontrol-no-action th,
|
43 |
.wp-list-table tr.crontrol-stalled th,
|
44 |
.wp-list-table tr.crontrol-warning th {
|
45 |
-
border-color: #
|
46 |
}
|
47 |
|
48 |
.wp-list-table tr.crontrol-error th {
|
49 |
-
border-color: #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: #
|
57 |
}
|
58 |
|
59 |
.wp-list-table .column-crontrol_icon .dashicons {
|
60 |
margin-top: 2px;
|
61 |
}
|
62 |
|
|
|
|
|
|
|
|
|
63 |
#crontrol-hash-message {
|
64 |
display: none;
|
65 |
}
|
66 |
|
67 |
.status-crontrol-complete,
|
68 |
.wp-list-table tr.crontrol-complete td.column-crontrol_status {
|
69 |
-
color: #
|
70 |
}
|
71 |
|
72 |
.status-crontrol-no-action,
|
@@ -75,7 +88,7 @@ table.wp-list-table {
|
|
75 |
.wp-list-table tr.crontrol-no-action td.column-crontrol_status,
|
76 |
.wp-list-table tr.crontrol-stalled td.column-crontrol_status,
|
77 |
.wp-list-table tr.crontrol-warning td.column-crontrol_status {
|
78 |
-
color: #
|
79 |
}
|
80 |
|
81 |
.status-crontrol-emergency,
|
@@ -83,7 +96,19 @@ table.wp-list-table {
|
|
83 |
.status-crontrol-critical,
|
84 |
.status-crontrol-error,
|
85 |
.wp-list-table tr.crontrol-error td.column-crontrol_status {
|
86 |
-
color: #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
}
|
88 |
|
89 |
.wp-list-table .column-crontrol_https,
|
@@ -99,7 +124,7 @@ table.wp-list-table {
|
|
99 |
}
|
100 |
|
101 |
.row-actions .crontrol-in-use {
|
102 |
-
color: #
|
103 |
}
|
104 |
|
105 |
.form-field input[type="number"] {
|
28 |
.wp-list-table code {
|
29 |
background: transparent;
|
30 |
padding: 0;
|
31 |
+
font-size: 12px;
|
32 |
}
|
33 |
|
34 |
.wp-list-table td.column-crontrol_status {
|
43 |
.wp-list-table tr.crontrol-no-action th,
|
44 |
.wp-list-table tr.crontrol-stalled th,
|
45 |
.wp-list-table tr.crontrol-warning th {
|
46 |
+
border-color: #dba617;
|
47 |
}
|
48 |
|
49 |
.wp-list-table tr.crontrol-error th {
|
50 |
+
border-color: #d63638;
|
51 |
+
}
|
52 |
+
|
53 |
+
.wp-list-table tr.crontrol-paused th {
|
54 |
+
border-color: #8c8f94;
|
55 |
+
}
|
56 |
+
|
57 |
+
.wp-list-table tr.crontrol-paused:not(.crontrol-no-action) .column-crontrol_actions {
|
58 |
+
text-decoration: line-through;
|
59 |
}
|
60 |
|
61 |
.wp-list-table .column-crontrol_icon .dashicons,
|
62 |
.wp-list-table .check-column .dashicons {
|
63 |
margin-left: 6px;
|
64 |
font-size: 14px;
|
65 |
+
color: #a7aaad;
|
66 |
}
|
67 |
|
68 |
.wp-list-table .column-crontrol_icon .dashicons {
|
69 |
margin-top: 2px;
|
70 |
}
|
71 |
|
72 |
+
.wp-list-table .qm-icon-edit {
|
73 |
+
display: none;
|
74 |
+
}
|
75 |
+
|
76 |
#crontrol-hash-message {
|
77 |
display: none;
|
78 |
}
|
79 |
|
80 |
.status-crontrol-complete,
|
81 |
.wp-list-table tr.crontrol-complete td.column-crontrol_status {
|
82 |
+
color: #00a32a;
|
83 |
}
|
84 |
|
85 |
.status-crontrol-no-action,
|
88 |
.wp-list-table tr.crontrol-no-action td.column-crontrol_status,
|
89 |
.wp-list-table tr.crontrol-stalled td.column-crontrol_status,
|
90 |
.wp-list-table tr.crontrol-warning td.column-crontrol_status {
|
91 |
+
color: #bd8600;
|
92 |
}
|
93 |
|
94 |
.status-crontrol-emergency,
|
96 |
.status-crontrol-critical,
|
97 |
.status-crontrol-error,
|
98 |
.wp-list-table tr.crontrol-error td.column-crontrol_status {
|
99 |
+
color: #d63638;
|
100 |
+
}
|
101 |
+
|
102 |
+
.status-crontrol-paused .dashicons {
|
103 |
+
background: #646970;
|
104 |
+
border-radius: 50%;
|
105 |
+
color: #fff;
|
106 |
+
font-size: 14px;
|
107 |
+
height: 14px;
|
108 |
+
line-height: 14px;
|
109 |
+
margin-top: 2px;
|
110 |
+
padding: 1px;
|
111 |
+
width: 14px;
|
112 |
}
|
113 |
|
114 |
.wp-list-table .column-crontrol_https,
|
124 |
}
|
125 |
|
126 |
.row-actions .crontrol-in-use {
|
127 |
+
color: #646970;
|
128 |
}
|
129 |
|
130 |
.form-field input[type="number"] {
|
readme.md
CHANGED
@@ -3,9 +3,9 @@
|
|
3 |
Contributors: johnbillion, scompt
|
4 |
Tags: cron, wp-cron, crontrol, debug
|
5 |
Requires at least: 4.2
|
6 |
-
Tested up to: 6.
|
7 |
-
Stable tag: 1.
|
8 |
-
Requires PHP: 5.
|
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.
|
@@ -15,13 +15,13 @@ WP Crontrol enables you to view and control what's happening in the WP-Cron syst
|
|
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
|
19 |
* Add new cron events.
|
20 |
* Bulk delete cron events.
|
21 |
* Add and remove custom cron schedules.
|
22 |
-
* Export cron event lists as a CSV file.
|
23 |
|
24 |
-
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
|
25 |
|
26 |
### Usage
|
27 |
|
@@ -51,7 +51,7 @@ Yes, it's actively tested and working up to PHP 8.1.
|
|
51 |
|
52 |
### Why do some cron events reappear shortly after I delete them?
|
53 |
|
54 |
-
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.
|
55 |
|
56 |
### Is it safe to delete cron events?
|
57 |
|
@@ -63,6 +63,18 @@ If the event shows "None" as its action then it's usually safe to delete. Please
|
|
63 |
|
64 |
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.
|
65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
### What does it mean when "None" is shown for the Action of a cron event?
|
67 |
|
68 |
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.
|
@@ -87,7 +99,7 @@ Please see the "Which users can manage PHP cron events?" FAQ for information abo
|
|
87 |
|
88 |
### Can I export a list of cron events?
|
89 |
|
90 |
-
Yes, a CSV file of the event list can be exported via the "Export" button on the cron event listing screen. This file can be opened in any spreadsheet application.
|
91 |
|
92 |
### Can I see a historical log of all the cron events that ran on my site?
|
93 |
|
@@ -141,7 +153,7 @@ Therefore, the user access level required to execute arbitrary PHP code does not
|
|
141 |
|
142 |
### Are any WP-CLI commands available?
|
143 |
|
144 |
-
The cron commands which were previously included in WP Crontrol are now part of WP-CLI
|
145 |
|
146 |
### Who took the photo in the plugin header image?
|
147 |
|
@@ -157,6 +169,13 @@ The photo was taken by <a href="https://www.flickr.com/photos/michaelpardo/21453
|
|
157 |
|
158 |
## Changelog ##
|
159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
### 1.14.0 ###
|
161 |
|
162 |
* Reverts the changes introduced in version 1.13 while I look into the problem with the deployment process for wordpress.org
|
@@ -171,35 +190,35 @@ The photo was taken by <a href="https://www.flickr.com/photos/michaelpardo/21453
|
|
171 |
|
172 |
### 1.13.0 ###
|
173 |
|
174 |
-
* Introduces the ability to pause and resume cron events from the event listing screen; see the FAQ for full details
|
175 |
-
* Implements an autoloader to reduce memory usage
|
176 |
* Bumps the minimum supported version of PHP to 5.6
|
177 |
|
178 |
### 1.12.1 ###
|
179 |
|
180 |
-
* Corrects an issue where an invalid hook callback isn't always identified
|
181 |
* Various code quality improvements
|
182 |
|
183 |
### 1.12.0 ###
|
184 |
|
185 |
-
* Fix the PHP cron event management.
|
186 |
* More "namespacing" of query variables to avoid conflicts with other cron management plugins.
|
187 |
|
188 |
### 1.11.0 ###
|
189 |
|
190 |
-
* Introduced an `Export` feature to the event listing screen for exporting the list of events as a CSV file.
|
191 |
-
* Added the timezone offset to the date displayed for events that are due to run after the next DST change, for extra clarity.
|
192 |
-
* Introduced the `crontrol/filter-types` and `crontrol/filtered-events` filters for adjusting the available event filters on the event listing screen.
|
193 |
-
* Lots of code quality improvements (thanks, PHPStan!).
|
194 |
|
195 |
|
196 |
### 1.10.0 ###
|
197 |
|
198 |
-
* Support for more granular cron-related error messages in WordPress 5.7
|
199 |
-
* Several accessibility improvements
|
200 |
-
* 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)
|
201 |
-
* More clarity around events and schedules that are built in to WordPress core
|
202 |
-
* Add a Help tab with links to the wiki and FAQs
|
203 |
|
204 |
|
205 |
### 1.9.1 ###
|
@@ -208,12 +227,12 @@ The photo was taken by <a href="https://www.flickr.com/photos/michaelpardo/21453
|
|
208 |
|
209 |
### 1.9.0 ###
|
210 |
|
211 |
-
* Add filters and sorting to the event listing screen. Props @yuriipavlov.
|
212 |
-
* Replace the "Add New" tabs with a more standard "Add New" button on the cron event listing page.
|
213 |
-
* Switch back to using browser-native controls for the date and time inputs.
|
214 |
-
* Add an error message when trying to edit a non-existent event.
|
215 |
-
* Introduce an informational message which appears when there are events that have missed their schedule.
|
216 |
-
* Fire actions when cron events and schedules are added, updated, and deleted.
|
217 |
|
218 |
|
219 |
### 1.8.5 ###
|
@@ -222,7 +241,7 @@ The photo was taken by <a href="https://www.flickr.com/photos/michaelpardo/21453
|
|
222 |
|
223 |
### 1.8.4 ###
|
224 |
|
225 |
-
* Add a warning message if the default timezone has been changed. <a href="https://github.com/johnbillion/wp-crontrol/wiki/PHP-default-timezone-is-not-set-to-UTC">More information</a>.
|
226 |
* Fixed string being passed to `strtotime()` function when the `Now` option is chosen when adding or editing an event.
|
227 |
|
228 |
### 1.8.3 ###
|
@@ -231,27 +250,27 @@ The photo was taken by <a href="https://www.flickr.com/photos/michaelpardo/21453
|
|
231 |
|
232 |
### 1.8.2 ###
|
233 |
|
234 |
-
* Bypass the duplicate event check when manually running an event. This allows an event to manually run even if it's due within ten minutes or if it's overdue.
|
235 |
-
* Force only one event to fire when manually running a cron event.
|
236 |
-
* Introduce polling of the events list in order to show a warning when the event listing screen is out of date.
|
237 |
-
* Add a warning for cron schedules which are shorter than `WP_CRON_LOCK_TIMEOUT`.
|
238 |
-
* Add the Site Health check event to the list of persistent core hooks.
|
239 |
|
240 |
|
241 |
### 1.8.1 ###
|
242 |
|
243 |
-
* Fix the bottom bulk action menu on the event listing screen.
|
244 |
* Make the timezone more prominent when adding or editing a cron event.
|
245 |
|
246 |
### 1.8.0 ###
|
247 |
|
248 |
-
* Searching and pagination for cron events
|
249 |
-
* Ability to delete all cron events with a given hook
|
250 |
-
* More accurate response messages when managing events (in WordPress 5.1+)
|
251 |
-
* Visual warnings for events without actions, and PHP events with syntax errors
|
252 |
-
* Timezone-related clarifications and fixes
|
253 |
-
* A more unified UI
|
254 |
-
* Modernised codebase
|
255 |
|
256 |
|
257 |
### 1.7.1 ###
|
@@ -260,16 +279,16 @@ The photo was taken by <a href="https://www.flickr.com/photos/michaelpardo/21453
|
|
260 |
|
261 |
### 1.7.0 ###
|
262 |
|
263 |
-
* Remove the `date` and `time` inputs and replace with a couple of preset options and a plain text field. Fixes #24 .
|
264 |
-
* Ensure the schedule name is always correct when multiple schedules exist with the same interval. Add error handling. Fixes #25.
|
265 |
-
* Re-introduce the display of the current site time.
|
266 |
-
* Use a more appropriate HTTP response code for unauthorised request errors.
|
267 |
|
268 |
|
269 |
### 1.6.2 ###
|
270 |
|
271 |
-
* Remove the ability to delete a PHP cron event if the user cannot edit files.
|
272 |
-
* Remove the `Edit` link for PHP cron events when the user cannot edit the event.
|
273 |
* Avoid a PHP notice due to an undefined variable when adding a new cron event.
|
274 |
|
275 |
### 1.6.1 ###
|
@@ -278,22 +297,22 @@ The photo was taken by <a href="https://www.flickr.com/photos/michaelpardo/21453
|
|
278 |
|
279 |
### 1.6 ###
|
280 |
|
281 |
-
* Introduce bulk deletion of cron events. Yay!
|
282 |
-
* Show the schedule name instead of the schedule interval next to each event.
|
283 |
-
* Add core's new `delete_expired_transients` event to the list of core events.
|
284 |
-
* Don't allow custom cron schedules to be deleted if they're in use.
|
285 |
-
* Add links between the Events and Schedules admin screens.
|
286 |
-
* Add syntax highlighting to the PHP code editor for a PHP cron event.
|
287 |
-
* Styling fixes for events with many arguments or long arguments.
|
288 |
-
* Improvements to help text.
|
289 |
-
* Remove usage of `create_function()`.
|
290 |
* Fix some translator comments, improve i18n, improve coding standards.
|
291 |
|
292 |
### 1.5.0 ###
|
293 |
|
294 |
-
* Show the hooked actions for each cron event.
|
295 |
-
* Don't show the `Delete` link for core's built-in cron events, as they get re-populated immediately.
|
296 |
-
* Correct the success message after adding or editing PHP cron events.
|
297 |
* Correct the translations directory name.
|
298 |
|
299 |
### 1.4 ###
|
@@ -331,10 +350,3 @@ The photo was taken by <a href="https://www.flickr.com/photos/michaelpardo/21453
|
|
331 |
- Added `wp crontrol run-event` and `wp crontrol delete-event` WP-CLI commands
|
332 |
- Clarify language regarding hooks/entries/events
|
333 |
|
334 |
-
|
335 |
-
### 1.2.1 ###
|
336 |
-
|
337 |
-
- Correctly display the local time when listing cron events
|
338 |
-
- Remove a PHP notice
|
339 |
-
- Pass the WP-Cron spawn check through the same filter as the actual spawner
|
340 |
-
|
3 |
Contributors: johnbillion, scompt
|
4 |
Tags: cron, wp-cron, crontrol, debug
|
5 |
Requires at least: 4.2
|
6 |
+
Tested up to: 6.1
|
7 |
+
Stable tag: 1.15.0
|
8 |
+
Requires PHP: 5.6
|
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.
|
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, pause, resume, and immediately run cron events.
|
19 |
* Add new cron events.
|
20 |
* Bulk delete cron events.
|
21 |
* Add and remove custom cron schedules.
|
22 |
+
* Export and download cron event lists as a CSV file.
|
23 |
|
24 |
+
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 helpful warning message if it detects any problems with your cron system.
|
25 |
|
26 |
### Usage
|
27 |
|
51 |
|
52 |
### Why do some cron events reappear shortly after I delete them?
|
53 |
|
54 |
+
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. To get around this you can instead use the "Pause" option for the event which means it'll remain in place but won't perform any action when it runs.
|
55 |
|
56 |
### Is it safe to delete cron events?
|
57 |
|
63 |
|
64 |
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.
|
65 |
|
66 |
+
If you don't want these events to run, you can "Pause" them instead.
|
67 |
+
|
68 |
+
### What happens when I pause an event?
|
69 |
+
|
70 |
+
Pausing an event will disable all actions attached to the event's hook. The event itself will remain in place and will run according to its schedule, but all actions attached to its hook will be disabled. This renders the event inoperative but keeps it scheduled so as to remain fully compatible with events which would otherwise get automatically rescheduled when they're missing.
|
71 |
+
|
72 |
+
As pausing an event actually pauses its hook, all events that use the same hook will be paused or resumed when pausing and resuming an event. This is much more useful and reliable than pausing individual events separately.
|
73 |
+
|
74 |
+
### What happens when I resume an event?
|
75 |
+
|
76 |
+
Resuming an event re-enables all actions attached to the event's hook. All events that use the same hook will be resumed.
|
77 |
+
|
78 |
### What does it mean when "None" is shown for the Action of a cron event?
|
79 |
|
80 |
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.
|
99 |
|
100 |
### Can I export a list of cron events?
|
101 |
|
102 |
+
Yes, a CSV file of the event list can be exported and downloaded via the "Export" button on the cron event listing screen. This file can be opened in any spreadsheet application.
|
103 |
|
104 |
### Can I see a historical log of all the cron events that ran on my site?
|
105 |
|
153 |
|
154 |
### Are any WP-CLI commands available?
|
155 |
|
156 |
+
The cron commands which were previously included in WP Crontrol are now part of WP-CLI itself. See `wp help cron` for more info.
|
157 |
|
158 |
### Who took the photo in the plugin header image?
|
159 |
|
169 |
|
170 |
## Changelog ##
|
171 |
|
172 |
+
### 1.15.0 ###
|
173 |
+
|
174 |
+
* Introduces the ability to pause and resume cron events from the event listing screen; see the FAQ for full details
|
175 |
+
* Adds the site time to the cron event editing screen
|
176 |
+
* Implements an autoloader to reduce memory usage
|
177 |
+
* Bumps the minimum supported version of PHP to 5.6
|
178 |
+
|
179 |
### 1.14.0 ###
|
180 |
|
181 |
* Reverts the changes introduced in version 1.13 while I look into the problem with the deployment process for wordpress.org
|
190 |
|
191 |
### 1.13.0 ###
|
192 |
|
193 |
+
* Introduces the ability to pause and resume cron events from the event listing screen; see the FAQ for full details
|
194 |
+
* Implements an autoloader to reduce memory usage
|
195 |
* Bumps the minimum supported version of PHP to 5.6
|
196 |
|
197 |
### 1.12.1 ###
|
198 |
|
199 |
+
* Corrects an issue where an invalid hook callback isn't always identified
|
200 |
* Various code quality improvements
|
201 |
|
202 |
### 1.12.0 ###
|
203 |
|
204 |
+
* Fix the PHP cron event management.
|
205 |
* More "namespacing" of query variables to avoid conflicts with other cron management plugins.
|
206 |
|
207 |
### 1.11.0 ###
|
208 |
|
209 |
+
* Introduced an `Export` feature to the event listing screen for exporting the list of events as a CSV file.
|
210 |
+
* Added the timezone offset to the date displayed for events that are due to run after the next DST change, for extra clarity.
|
211 |
+
* Introduced the `crontrol/filter-types` and `crontrol/filtered-events` filters for adjusting the available event filters on the event listing screen.
|
212 |
+
* Lots of code quality improvements (thanks, PHPStan!).
|
213 |
|
214 |
|
215 |
### 1.10.0 ###
|
216 |
|
217 |
+
* Support for more granular cron-related error messages in WordPress 5.7
|
218 |
+
* Several accessibility improvements
|
219 |
+
* 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)
|
220 |
+
* More clarity around events and schedules that are built in to WordPress core
|
221 |
+
* Add a Help tab with links to the wiki and FAQs
|
222 |
|
223 |
|
224 |
### 1.9.1 ###
|
227 |
|
228 |
### 1.9.0 ###
|
229 |
|
230 |
+
* Add filters and sorting to the event listing screen. Props @yuriipavlov.
|
231 |
+
* Replace the "Add New" tabs with a more standard "Add New" button on the cron event listing page.
|
232 |
+
* Switch back to using browser-native controls for the date and time inputs.
|
233 |
+
* Add an error message when trying to edit a non-existent event.
|
234 |
+
* Introduce an informational message which appears when there are events that have missed their schedule.
|
235 |
+
* Fire actions when cron events and schedules are added, updated, and deleted.
|
236 |
|
237 |
|
238 |
### 1.8.5 ###
|
241 |
|
242 |
### 1.8.4 ###
|
243 |
|
244 |
+
* Add a warning message if the default timezone has been changed. <a href="https://github.com/johnbillion/wp-crontrol/wiki/PHP-default-timezone-is-not-set-to-UTC">More information</a>.
|
245 |
* Fixed string being passed to `strtotime()` function when the `Now` option is chosen when adding or editing an event.
|
246 |
|
247 |
### 1.8.3 ###
|
250 |
|
251 |
### 1.8.2 ###
|
252 |
|
253 |
+
* Bypass the duplicate event check when manually running an event. This allows an event to manually run even if it's due within ten minutes or if it's overdue.
|
254 |
+
* Force only one event to fire when manually running a cron event.
|
255 |
+
* Introduce polling of the events list in order to show a warning when the event listing screen is out of date.
|
256 |
+
* Add a warning for cron schedules which are shorter than `WP_CRON_LOCK_TIMEOUT`.
|
257 |
+
* Add the Site Health check event to the list of persistent core hooks.
|
258 |
|
259 |
|
260 |
### 1.8.1 ###
|
261 |
|
262 |
+
* Fix the bottom bulk action menu on the event listing screen.
|
263 |
* Make the timezone more prominent when adding or editing a cron event.
|
264 |
|
265 |
### 1.8.0 ###
|
266 |
|
267 |
+
* Searching and pagination for cron events
|
268 |
+
* Ability to delete all cron events with a given hook
|
269 |
+
* More accurate response messages when managing events (in WordPress 5.1+)
|
270 |
+
* Visual warnings for events without actions, and PHP events with syntax errors
|
271 |
+
* Timezone-related clarifications and fixes
|
272 |
+
* A more unified UI
|
273 |
+
* Modernised codebase
|
274 |
|
275 |
|
276 |
### 1.7.1 ###
|
279 |
|
280 |
### 1.7.0 ###
|
281 |
|
282 |
+
* Remove the `date` and `time` inputs and replace with a couple of preset options and a plain text field. Fixes #24 .
|
283 |
+
* Ensure the schedule name is always correct when multiple schedules exist with the same interval. Add error handling. Fixes #25.
|
284 |
+
* Re-introduce the display of the current site time.
|
285 |
+
* Use a more appropriate HTTP response code for unauthorised request errors.
|
286 |
|
287 |
|
288 |
### 1.6.2 ###
|
289 |
|
290 |
+
* Remove the ability to delete a PHP cron event if the user cannot edit files.
|
291 |
+
* Remove the `Edit` link for PHP cron events when the user cannot edit the event.
|
292 |
* Avoid a PHP notice due to an undefined variable when adding a new cron event.
|
293 |
|
294 |
### 1.6.1 ###
|
297 |
|
298 |
### 1.6 ###
|
299 |
|
300 |
+
* Introduce bulk deletion of cron events. Yay!
|
301 |
+
* Show the schedule name instead of the schedule interval next to each event.
|
302 |
+
* Add core's new `delete_expired_transients` event to the list of core events.
|
303 |
+
* Don't allow custom cron schedules to be deleted if they're in use.
|
304 |
+
* Add links between the Events and Schedules admin screens.
|
305 |
+
* Add syntax highlighting to the PHP code editor for a PHP cron event.
|
306 |
+
* Styling fixes for events with many arguments or long arguments.
|
307 |
+
* Improvements to help text.
|
308 |
+
* Remove usage of `create_function()`.
|
309 |
* Fix some translator comments, improve i18n, improve coding standards.
|
310 |
|
311 |
### 1.5.0 ###
|
312 |
|
313 |
+
* Show the hooked actions for each cron event.
|
314 |
+
* Don't show the `Delete` link for core's built-in cron events, as they get re-populated immediately.
|
315 |
+
* Correct the success message after adding or editing PHP cron events.
|
316 |
* Correct the translations directory name.
|
317 |
|
318 |
### 1.4 ###
|
350 |
- Added `wp crontrol run-event` and `wp crontrol delete-event` WP-CLI commands
|
351 |
- Clarify language regarding hooks/entries/events
|
352 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/bootstrap.php
ADDED
@@ -0,0 +1,2210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Functions related to bootstrapping WP Crontrol.
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace Crontrol;
|
7 |
+
|
8 |
+
use Crontrol\Event\Table;
|
9 |
+
use stdClass;
|
10 |
+
use WP_Error;
|
11 |
+
|
12 |
+
const TRANSIENT = 'crontrol-message-%d';
|
13 |
+
const PAUSED_OPTION = 'wp_crontrol_paused';
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Hook onto all of the actions and filters needed by the plugin.
|
17 |
+
*
|
18 |
+
* @return void
|
19 |
+
*/
|
20 |
+
function init_hooks() {
|
21 |
+
$plugin_file = plugin_basename( PLUGIN_FILE );
|
22 |
+
|
23 |
+
add_action( 'init', __NAMESPACE__ . '\action_init' );
|
24 |
+
add_action( 'init', __NAMESPACE__ . '\action_handle_posts' );
|
25 |
+
add_action( 'admin_menu', __NAMESPACE__ . '\action_admin_menu' );
|
26 |
+
add_action( 'wp_ajax_crontrol_checkhash', __NAMESPACE__ . '\ajax_check_events_hash' );
|
27 |
+
add_filter( "plugin_action_links_{$plugin_file}", __NAMESPACE__ . '\plugin_action_links', 10, 4 );
|
28 |
+
add_filter( "network_admin_plugin_action_links_{$plugin_file}", __NAMESPACE__ . '\network_plugin_action_links' );
|
29 |
+
add_filter( 'removable_query_args', __NAMESPACE__ . '\filter_removable_query_args' );
|
30 |
+
add_filter( 'pre_unschedule_event', __NAMESPACE__ . '\maybe_clear_doing_cron' );
|
31 |
+
add_filter( 'plugin_row_meta', __NAMESPACE__ . '\filter_plugin_row_meta', 10, 2 );
|
32 |
+
|
33 |
+
add_action( 'load-tools_page_crontrol_admin_manage_page', __NAMESPACE__ . '\setup_manage_page' );
|
34 |
+
|
35 |
+
add_filter( 'cron_schedules', __NAMESPACE__ . '\filter_cron_schedules' );
|
36 |
+
add_action( 'crontrol_cron_job', __NAMESPACE__ . '\action_php_cron_event' );
|
37 |
+
add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' );
|
38 |
+
add_action( 'crontrol/tab-header', __NAMESPACE__ . '\show_cron_status', 20 );
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Sets an error message to show to the current user after a redirect.
|
43 |
+
*
|
44 |
+
* @param string $message The error message text.
|
45 |
+
* @return bool Whether the message was saved.
|
46 |
+
*/
|
47 |
+
function set_message( $message ) {
|
48 |
+
$key = sprintf(
|
49 |
+
TRANSIENT,
|
50 |
+
get_current_user_id()
|
51 |
+
);
|
52 |
+
return set_transient( $key, $message, 60 );
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Gets the error message to show to the current user after a redirect.
|
57 |
+
*
|
58 |
+
* @return string The error message text.
|
59 |
+
*/
|
60 |
+
function get_message() {
|
61 |
+
$key = sprintf(
|
62 |
+
TRANSIENT,
|
63 |
+
get_current_user_id()
|
64 |
+
);
|
65 |
+
return get_transient( $key );
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Filters the array of row meta for each plugin in the Plugins list table.
|
70 |
+
*
|
71 |
+
* @param array<int,string> $plugin_meta An array of the plugin row's meta data.
|
72 |
+
* @param string $plugin_file Path to the plugin file relative to the plugins directory.
|
73 |
+
* @return array<int,string> An array of the plugin row's meta data.
|
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 |
+
*
|
92 |
+
* @return void
|
93 |
+
*/
|
94 |
+
function action_init() {
|
95 |
+
load_plugin_textdomain( 'wp-crontrol', false, dirname( plugin_basename( PLUGIN_FILE ) ) . '/languages' );
|
96 |
+
|
97 |
+
/** @var array<string, true>|false $paused */
|
98 |
+
$paused = get_option( PAUSED_OPTION, array() );
|
99 |
+
|
100 |
+
if ( is_array( $paused ) ) {
|
101 |
+
foreach ( $paused as $hook => $value ) {
|
102 |
+
add_action( $hook, __NAMESPACE__ . '\\pauser', -99999 );
|
103 |
+
}
|
104 |
+
}
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* @return void
|
109 |
+
*/
|
110 |
+
function pauser() {
|
111 |
+
remove_all_actions( current_filter() );
|
112 |
+
}
|
113 |
+
|
114 |
+
/**
|
115 |
+
* Handles any POSTs and GETs made by the plugin. Run using the 'init' action.
|
116 |
+
*
|
117 |
+
* @return void
|
118 |
+
*/
|
119 |
+
function action_handle_posts() {
|
120 |
+
$request = new Request();
|
121 |
+
|
122 |
+
if ( isset( $_POST['crontrol_action'] ) && ( 'new_cron' === $_POST['crontrol_action'] ) ) {
|
123 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
124 |
+
wp_die( esc_html__( 'You are not allowed to add new cron events.', 'wp-crontrol' ), 401 );
|
125 |
+
}
|
126 |
+
check_admin_referer( 'crontrol-new-cron' );
|
127 |
+
|
128 |
+
$cr = $request->init( wp_unslash( $_POST ) );
|
129 |
+
|
130 |
+
if ( 'crontrol_cron_job' === $cr->hookname && ! current_user_can( 'edit_files' ) ) {
|
131 |
+
wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 );
|
132 |
+
}
|
133 |
+
$args = json_decode( $cr->args, true );
|
134 |
+
|
135 |
+
if ( empty( $args ) || ! is_array( $args ) ) {
|
136 |
+
$args = array();
|
137 |
+
}
|
138 |
+
|
139 |
+
$next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local;
|
140 |
+
|
141 |
+
add_filter( 'schedule_event', function( $event ) {
|
142 |
+
if ( ! $event ) {
|
143 |
+
return $event;
|
144 |
+
}
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Fires after a new cron event is added.
|
148 |
+
*
|
149 |
+
* @param stdClass $event {
|
150 |
+
* An object containing the event's data.
|
151 |
+
*
|
152 |
+
* @type string $hook Action hook to execute when the event is run.
|
153 |
+
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
154 |
+
* @type string|false $schedule How often the event should subsequently recur.
|
155 |
+
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
156 |
+
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
157 |
+
* }
|
158 |
+
*/
|
159 |
+
do_action( 'crontrol/added_new_event', $event );
|
160 |
+
|
161 |
+
return $event;
|
162 |
+
}, 99 );
|
163 |
+
|
164 |
+
$added = Event\add( $next_run_local, $cr->schedule, $cr->hookname, $args );
|
165 |
+
|
166 |
+
$redirect = array(
|
167 |
+
'page' => 'crontrol_admin_manage_page',
|
168 |
+
'crontrol_message' => '5',
|
169 |
+
'crontrol_name' => rawurlencode( $cr->hookname ),
|
170 |
+
);
|
171 |
+
|
172 |
+
if ( is_wp_error( $added ) ) {
|
173 |
+
set_message( $added->get_error_message() );
|
174 |
+
$redirect['crontrol_message'] = 'error';
|
175 |
+
}
|
176 |
+
|
177 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
178 |
+
exit;
|
179 |
+
|
180 |
+
} elseif ( isset( $_POST['crontrol_action'] ) && ( 'new_php_cron' === $_POST['crontrol_action'] ) ) {
|
181 |
+
if ( ! current_user_can( 'edit_files' ) ) {
|
182 |
+
wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 );
|
183 |
+
}
|
184 |
+
check_admin_referer( 'crontrol-new-cron' );
|
185 |
+
|
186 |
+
$cr = $request->init( wp_unslash( $_POST ) );
|
187 |
+
|
188 |
+
$next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local;
|
189 |
+
$args = array(
|
190 |
+
'code' => $cr->hookcode,
|
191 |
+
'name' => $cr->eventname,
|
192 |
+
);
|
193 |
+
|
194 |
+
add_filter( 'schedule_event', function( $event ) {
|
195 |
+
if ( ! $event ) {
|
196 |
+
return $event;
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* Fires after a new PHP cron event is added.
|
201 |
+
*
|
202 |
+
* @param stdClass $event {
|
203 |
+
* An object containing the event's data.
|
204 |
+
*
|
205 |
+
* @type string $hook Action hook to execute when the event is run.
|
206 |
+
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
207 |
+
* @type string|false $schedule How often the event should subsequently recur.
|
208 |
+
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
209 |
+
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
210 |
+
* }
|
211 |
+
*/
|
212 |
+
do_action( 'crontrol/added_new_php_event', $event );
|
213 |
+
|
214 |
+
return $event;
|
215 |
+
}, 99 );
|
216 |
+
|
217 |
+
$added = Event\add( $next_run_local, $cr->schedule, 'crontrol_cron_job', $args );
|
218 |
+
|
219 |
+
$hookname = ( ! empty( $cr->eventname ) ) ? $cr->eventname : __( 'PHP Cron', 'wp-crontrol' );
|
220 |
+
$redirect = array(
|
221 |
+
'page' => 'crontrol_admin_manage_page',
|
222 |
+
'crontrol_message' => '5',
|
223 |
+
'crontrol_name' => rawurlencode( $hookname ),
|
224 |
+
);
|
225 |
+
|
226 |
+
if ( is_wp_error( $added ) ) {
|
227 |
+
set_message( $added->get_error_message() );
|
228 |
+
$redirect['crontrol_message'] = 'error';
|
229 |
+
}
|
230 |
+
|
231 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
232 |
+
exit;
|
233 |
+
|
234 |
+
} elseif ( isset( $_POST['crontrol_action'] ) && ( 'edit_cron' === $_POST['crontrol_action'] ) ) {
|
235 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
236 |
+
wp_die( esc_html__( 'You are not allowed to edit cron events.', 'wp-crontrol' ), 401 );
|
237 |
+
}
|
238 |
+
|
239 |
+
$cr = $request->init( wp_unslash( $_POST ) );
|
240 |
+
|
241 |
+
check_admin_referer( "crontrol-edit-cron_{$cr->original_hookname}_{$cr->original_sig}_{$cr->original_next_run_utc}" );
|
242 |
+
|
243 |
+
if ( 'crontrol_cron_job' === $cr->hookname && ! current_user_can( 'edit_files' ) ) {
|
244 |
+
wp_die( esc_html__( 'You are not allowed to edit PHP cron events.', 'wp-crontrol' ), 401 );
|
245 |
+
}
|
246 |
+
|
247 |
+
$args = json_decode( $cr->args, true );
|
248 |
+
|
249 |
+
if ( empty( $args ) || ! is_array( $args ) ) {
|
250 |
+
$args = array();
|
251 |
+
}
|
252 |
+
|
253 |
+
$redirect = array(
|
254 |
+
'page' => 'crontrol_admin_manage_page',
|
255 |
+
'crontrol_message' => '4',
|
256 |
+
'crontrol_name' => rawurlencode( $cr->hookname ),
|
257 |
+
);
|
258 |
+
|
259 |
+
$original = Event\get_single( $cr->original_hookname, $cr->original_sig, $cr->original_next_run_utc );
|
260 |
+
|
261 |
+
if ( is_wp_error( $original ) ) {
|
262 |
+
set_message( $original->get_error_message() );
|
263 |
+
$redirect['crontrol_message'] = 'error';
|
264 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
265 |
+
exit;
|
266 |
+
}
|
267 |
+
|
268 |
+
$deleted = Event\delete( $cr->original_hookname, $cr->original_sig, $cr->original_next_run_utc );
|
269 |
+
|
270 |
+
if ( is_wp_error( $deleted ) ) {
|
271 |
+
set_message( $deleted->get_error_message() );
|
272 |
+
$redirect['crontrol_message'] = 'error';
|
273 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
274 |
+
exit;
|
275 |
+
}
|
276 |
+
|
277 |
+
$next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local;
|
278 |
+
|
279 |
+
/**
|
280 |
+
* Modifies an event before it is scheduled.
|
281 |
+
*
|
282 |
+
* @param stdClass|false $event An object containing the new event's data, or boolean false.
|
283 |
+
*/
|
284 |
+
add_filter( 'schedule_event', function( $event ) use ( $original ) {
|
285 |
+
if ( ! $event ) {
|
286 |
+
return $event;
|
287 |
+
}
|
288 |
+
|
289 |
+
/**
|
290 |
+
* Fires after a cron event is edited.
|
291 |
+
*
|
292 |
+
* @param stdClass $event {
|
293 |
+
* An object containing the new event's data.
|
294 |
+
*
|
295 |
+
* @type string $hook Action hook to execute when the event is run.
|
296 |
+
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
297 |
+
* @type string|false $schedule How often the event should subsequently recur.
|
298 |
+
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
299 |
+
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
300 |
+
* }
|
301 |
+
* @param stdClass $original {
|
302 |
+
* An object containing the original 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 mixed[] $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 |
+
*/
|
311 |
+
do_action( 'crontrol/edited_event', $event, $original );
|
312 |
+
|
313 |
+
return $event;
|
314 |
+
}, 99 );
|
315 |
+
|
316 |
+
$added = Event\add( $next_run_local, $cr->schedule, $cr->hookname, $args );
|
317 |
+
|
318 |
+
if ( is_wp_error( $added ) ) {
|
319 |
+
set_message( $added->get_error_message() );
|
320 |
+
$redirect['crontrol_message'] = 'error';
|
321 |
+
}
|
322 |
+
|
323 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
324 |
+
exit;
|
325 |
+
|
326 |
+
} elseif ( isset( $_POST['crontrol_action'] ) && ( 'edit_php_cron' === $_POST['crontrol_action'] ) ) {
|
327 |
+
if ( ! current_user_can( 'edit_files' ) ) {
|
328 |
+
wp_die( esc_html__( 'You are not allowed to edit PHP cron events.', 'wp-crontrol' ), 401 );
|
329 |
+
}
|
330 |
+
|
331 |
+
$cr = $request->init( wp_unslash( $_POST ) );
|
332 |
+
|
333 |
+
check_admin_referer( "crontrol-edit-cron_{$cr->original_hookname}_{$cr->original_sig}_{$cr->original_next_run_utc}" );
|
334 |
+
$args = array(
|
335 |
+
'code' => $cr->hookcode,
|
336 |
+
'name' => $cr->eventname,
|
337 |
+
);
|
338 |
+
$hookname = ( ! empty( $cr->eventname ) ) ? $cr->eventname : __( 'PHP Cron', 'wp-crontrol' );
|
339 |
+
$redirect = array(
|
340 |
+
'page' => 'crontrol_admin_manage_page',
|
341 |
+
'crontrol_message' => '4',
|
342 |
+
'crontrol_name' => rawurlencode( $hookname ),
|
343 |
+
);
|
344 |
+
|
345 |
+
$original = Event\get_single( $cr->original_hookname, $cr->original_sig, $cr->original_next_run_utc );
|
346 |
+
|
347 |
+
if ( is_wp_error( $original ) ) {
|
348 |
+
set_message( $original->get_error_message() );
|
349 |
+
$redirect['crontrol_message'] = 'error';
|
350 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
351 |
+
exit;
|
352 |
+
}
|
353 |
+
|
354 |
+
$deleted = Event\delete( $cr->original_hookname, $cr->original_sig, $cr->original_next_run_utc );
|
355 |
+
|
356 |
+
if ( is_wp_error( $deleted ) ) {
|
357 |
+
set_message( $deleted->get_error_message() );
|
358 |
+
$redirect['crontrol_message'] = 'error';
|
359 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
360 |
+
exit;
|
361 |
+
}
|
362 |
+
|
363 |
+
$next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local;
|
364 |
+
|
365 |
+
/**
|
366 |
+
* Modifies an event before it is scheduled.
|
367 |
+
*
|
368 |
+
* @param stdClass|false $event An object containing the new event's data, or boolean false.
|
369 |
+
*/
|
370 |
+
add_filter( 'schedule_event', function( $event ) use ( $original ) {
|
371 |
+
if ( ! $event ) {
|
372 |
+
return $event;
|
373 |
+
}
|
374 |
+
|
375 |
+
/**
|
376 |
+
* Fires after a PHP cron event is edited.
|
377 |
+
*
|
378 |
+
* @param stdClass $event {
|
379 |
+
* An object containing the new event's data.
|
380 |
+
*
|
381 |
+
* @type string $hook Action hook to execute when the event is run.
|
382 |
+
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
383 |
+
* @type string|false $schedule How often the event should subsequently recur.
|
384 |
+
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
385 |
+
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
386 |
+
* }
|
387 |
+
* @param stdClass $original {
|
388 |
+
* An object containing the original event's data.
|
389 |
+
*
|
390 |
+
* @type string $hook Action hook to execute when the event is run.
|
391 |
+
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
392 |
+
* @type string|false $schedule How often the event should subsequently recur.
|
393 |
+
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
394 |
+
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
395 |
+
* }
|
396 |
+
*/
|
397 |
+
do_action( 'crontrol/edited_php_event', $event, $original );
|
398 |
+
|
399 |
+
return $event;
|
400 |
+
}, 99 );
|
401 |
+
|
402 |
+
$added = Event\add( $next_run_local, $cr->schedule, 'crontrol_cron_job', $args );
|
403 |
+
|
404 |
+
if ( is_wp_error( $added ) ) {
|
405 |
+
set_message( $added->get_error_message() );
|
406 |
+
$redirect['crontrol_message'] = 'error';
|
407 |
+
}
|
408 |
+
|
409 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
410 |
+
exit;
|
411 |
+
|
412 |
+
} elseif ( isset( $_POST['crontrol_new_schedule'] ) ) {
|
413 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
414 |
+
wp_die( esc_html__( 'You are not allowed to add new cron schedules.', 'wp-crontrol' ), 401 );
|
415 |
+
}
|
416 |
+
check_admin_referer( 'crontrol-new-schedule' );
|
417 |
+
$name = wp_unslash( $_POST['crontrol_schedule_internal_name'] );
|
418 |
+
$interval = absint( $_POST['crontrol_schedule_interval'] );
|
419 |
+
$display = wp_unslash( $_POST['crontrol_schedule_display_name'] );
|
420 |
+
|
421 |
+
Schedule\add( $name, $interval, $display );
|
422 |
+
$redirect = array(
|
423 |
+
'page' => 'crontrol_admin_options_page',
|
424 |
+
'crontrol_message' => '3',
|
425 |
+
'crontrol_name' => rawurlencode( $name ),
|
426 |
+
);
|
427 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
|
428 |
+
exit;
|
429 |
+
|
430 |
+
} elseif ( isset( $_GET['crontrol_action'] ) && 'delete-schedule' === $_GET['crontrol_action'] ) {
|
431 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
432 |
+
wp_die( esc_html__( 'You are not allowed to delete cron schedules.', 'wp-crontrol' ), 401 );
|
433 |
+
}
|
434 |
+
$schedule = wp_unslash( $_GET['crontrol_id'] );
|
435 |
+
check_admin_referer( "crontrol-delete-schedule_{$schedule}" );
|
436 |
+
Schedule\delete( $schedule );
|
437 |
+
$redirect = array(
|
438 |
+
'page' => 'crontrol_admin_options_page',
|
439 |
+
'crontrol_message' => '2',
|
440 |
+
'crontrol_name' => rawurlencode( $schedule ),
|
441 |
+
);
|
442 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
|
443 |
+
exit;
|
444 |
+
|
445 |
+
} elseif ( ( isset( $_POST['action'] ) && 'crontrol_delete_crons' === $_POST['action'] ) || ( isset( $_POST['action2'] ) && 'crontrol_delete_crons' === $_POST['action2'] ) ) {
|
446 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
447 |
+
wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
|
448 |
+
}
|
449 |
+
check_admin_referer( 'bulk-crontrol-events' );
|
450 |
+
|
451 |
+
if ( empty( $_POST['crontrol_delete'] ) ) {
|
452 |
+
return;
|
453 |
+
}
|
454 |
+
|
455 |
+
/**
|
456 |
+
* @var array<string,array<string,string>>
|
457 |
+
*/
|
458 |
+
$delete = (array) wp_unslash( $_POST['crontrol_delete'] );
|
459 |
+
$deleted = 0;
|
460 |
+
|
461 |
+
foreach ( $delete as $next_run_utc => $events ) {
|
462 |
+
foreach ( (array) $events as $hook => $sig ) {
|
463 |
+
if ( 'crontrol_cron_job' === $hook && ! current_user_can( 'edit_files' ) ) {
|
464 |
+
continue;
|
465 |
+
}
|
466 |
+
|
467 |
+
$event = Event\get_single( urldecode( $hook ), $sig, $next_run_utc );
|
468 |
+
$deleted = Event\delete( urldecode( $hook ), $sig, $next_run_utc );
|
469 |
+
|
470 |
+
if ( ! is_wp_error( $deleted ) ) {
|
471 |
+
$deleted++;
|
472 |
+
|
473 |
+
/** This action is documented in wp-crontrol.php */
|
474 |
+
do_action( 'crontrol/deleted_event', $event );
|
475 |
+
}
|
476 |
+
}
|
477 |
+
}
|
478 |
+
|
479 |
+
$redirect = array(
|
480 |
+
'page' => 'crontrol_admin_manage_page',
|
481 |
+
'crontrol_name' => $deleted,
|
482 |
+
'crontrol_message' => '9',
|
483 |
+
);
|
484 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
485 |
+
exit;
|
486 |
+
|
487 |
+
} elseif ( isset( $_GET['crontrol_action'] ) && 'delete-cron' === $_GET['crontrol_action'] ) {
|
488 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
489 |
+
wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
|
490 |
+
}
|
491 |
+
$hook = wp_unslash( $_GET['crontrol_id'] );
|
492 |
+
$sig = wp_unslash( $_GET['crontrol_sig'] );
|
493 |
+
$next_run_utc = wp_unslash( $_GET['crontrol_next_run_utc'] );
|
494 |
+
check_admin_referer( "crontrol-delete-cron_{$hook}_{$sig}_{$next_run_utc}" );
|
495 |
+
|
496 |
+
if ( 'crontrol_cron_job' === $hook && ! current_user_can( 'edit_files' ) ) {
|
497 |
+
wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
|
498 |
+
}
|
499 |
+
|
500 |
+
$redirect = array(
|
501 |
+
'page' => 'crontrol_admin_manage_page',
|
502 |
+
'crontrol_message' => '6',
|
503 |
+
'crontrol_name' => rawurlencode( $hook ),
|
504 |
+
);
|
505 |
+
|
506 |
+
$event = Event\get_single( $hook, $sig, $next_run_utc );
|
507 |
+
|
508 |
+
if ( is_wp_error( $event ) ) {
|
509 |
+
set_message( $event->get_error_message() );
|
510 |
+
$redirect['crontrol_message'] = 'error';
|
511 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
512 |
+
exit;
|
513 |
+
}
|
514 |
+
|
515 |
+
$deleted = Event\delete( $hook, $sig, $next_run_utc );
|
516 |
+
|
517 |
+
if ( is_wp_error( $deleted ) ) {
|
518 |
+
set_message( $deleted->get_error_message() );
|
519 |
+
$redirect['crontrol_message'] = 'error';
|
520 |
+
} else {
|
521 |
+
/**
|
522 |
+
* Fires after a cron event is deleted.
|
523 |
+
*
|
524 |
+
* @param stdClass $event {
|
525 |
+
* An object containing the event's data.
|
526 |
+
*
|
527 |
+
* @type string $hook Action hook to execute when the event is run.
|
528 |
+
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
529 |
+
* @type string|false $schedule How often the event should subsequently recur.
|
530 |
+
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
531 |
+
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
532 |
+
* }
|
533 |
+
*/
|
534 |
+
do_action( 'crontrol/deleted_event', $event );
|
535 |
+
}
|
536 |
+
|
537 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
538 |
+
exit;
|
539 |
+
|
540 |
+
} elseif ( isset( $_GET['crontrol_action'] ) && 'delete-hook' === $_GET['crontrol_action'] ) {
|
541 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
542 |
+
wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
|
543 |
+
}
|
544 |
+
$hook = wp_unslash( $_GET['crontrol_id'] );
|
545 |
+
$deleted = false;
|
546 |
+
check_admin_referer( "crontrol-delete-hook_{$hook}" );
|
547 |
+
|
548 |
+
if ( 'crontrol_cron_job' === $hook ) {
|
549 |
+
wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
|
550 |
+
}
|
551 |
+
|
552 |
+
if ( function_exists( 'wp_unschedule_hook' ) ) {
|
553 |
+
/** @var int|false */
|
554 |
+
$deleted = wp_unschedule_hook( $hook );
|
555 |
+
}
|
556 |
+
|
557 |
+
if ( 0 === $deleted ) {
|
558 |
+
$redirect = array(
|
559 |
+
'page' => 'crontrol_admin_manage_page',
|
560 |
+
'crontrol_message' => '3',
|
561 |
+
'crontrol_name' => rawurlencode( $hook ),
|
562 |
+
);
|
563 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
564 |
+
exit;
|
565 |
+
} elseif ( $deleted ) {
|
566 |
+
/**
|
567 |
+
* Fires after all cron events with the given hook are deleted.
|
568 |
+
*
|
569 |
+
* @param string $hook The hook name.
|
570 |
+
* @param int $deleted The number of events that were deleted.
|
571 |
+
*/
|
572 |
+
do_action( 'crontrol/deleted_all_with_hook', $hook, $deleted );
|
573 |
+
|
574 |
+
$redirect = array(
|
575 |
+
'page' => 'crontrol_admin_manage_page',
|
576 |
+
'crontrol_message' => '2',
|
577 |
+
'crontrol_name' => rawurlencode( $hook ),
|
578 |
+
);
|
579 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
580 |
+
exit;
|
581 |
+
} else {
|
582 |
+
$redirect = array(
|
583 |
+
'page' => 'crontrol_admin_manage_page',
|
584 |
+
'crontrol_message' => '7',
|
585 |
+
'crontrol_name' => rawurlencode( $hook ),
|
586 |
+
);
|
587 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
588 |
+
exit;
|
589 |
+
}
|
590 |
+
} elseif ( isset( $_GET['crontrol_action'] ) && 'run-cron' === $_GET['crontrol_action'] ) {
|
591 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
592 |
+
wp_die( esc_html__( 'You are not allowed to run cron events.', 'wp-crontrol' ), 401 );
|
593 |
+
}
|
594 |
+
$hook = wp_unslash( $_GET['crontrol_id'] );
|
595 |
+
$sig = wp_unslash( $_GET['crontrol_sig'] );
|
596 |
+
check_admin_referer( "crontrol-run-cron_{$hook}_{$sig}" );
|
597 |
+
|
598 |
+
$ran = Event\run( $hook, $sig );
|
599 |
+
|
600 |
+
$redirect = array(
|
601 |
+
'page' => 'crontrol_admin_manage_page',
|
602 |
+
'crontrol_message' => '1',
|
603 |
+
'crontrol_name' => rawurlencode( $hook ),
|
604 |
+
);
|
605 |
+
|
606 |
+
if ( is_wp_error( $ran ) ) {
|
607 |
+
$set = set_message( $ran->get_error_message() );
|
608 |
+
|
609 |
+
// If we can't store the error message in a transient, just display it.
|
610 |
+
if ( ! $set ) {
|
611 |
+
wp_die(
|
612 |
+
esc_html( $ran->get_error_message() ),
|
613 |
+
'',
|
614 |
+
array(
|
615 |
+
'response' => 500,
|
616 |
+
'back_link' => true,
|
617 |
+
)
|
618 |
+
);
|
619 |
+
}
|
620 |
+
$redirect['crontrol_message'] = 'error';
|
621 |
+
}
|
622 |
+
|
623 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
624 |
+
exit;
|
625 |
+
} elseif ( isset( $_GET['crontrol_action'] ) && 'pause-hook' === $_GET['crontrol_action'] ) {
|
626 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
627 |
+
wp_die( esc_html__( 'You are not allowed to pause or resume cron events.', 'wp-crontrol' ), 401 );
|
628 |
+
}
|
629 |
+
|
630 |
+
$hook = wp_unslash( $_GET['crontrol_id'] );
|
631 |
+
check_admin_referer( "crontrol-pause-hook_{$hook}" );
|
632 |
+
|
633 |
+
$paused = Event\pause( $hook );
|
634 |
+
|
635 |
+
$redirect = array(
|
636 |
+
'page' => 'crontrol_admin_manage_page',
|
637 |
+
'crontrol_message' => '11',
|
638 |
+
'crontrol_name' => rawurlencode( $hook ),
|
639 |
+
);
|
640 |
+
|
641 |
+
if ( is_wp_error( $paused ) ) {
|
642 |
+
$set = set_message( $paused->get_error_message() );
|
643 |
+
|
644 |
+
// If we can't store the error message in a transient, just display it.
|
645 |
+
if ( ! $set ) {
|
646 |
+
wp_die(
|
647 |
+
esc_html( $paused->get_error_message() ),
|
648 |
+
'',
|
649 |
+
array(
|
650 |
+
'response' => 500,
|
651 |
+
'back_link' => true,
|
652 |
+
)
|
653 |
+
);
|
654 |
+
}
|
655 |
+
$redirect['crontrol_message'] = 'error';
|
656 |
+
} else {
|
657 |
+
/**
|
658 |
+
* Fires after a cron event hook is paused.
|
659 |
+
*
|
660 |
+
* @param string $hook The event hook name.
|
661 |
+
*/
|
662 |
+
do_action( 'crontrol/paused_hook', $hook );
|
663 |
+
}
|
664 |
+
|
665 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
666 |
+
exit;
|
667 |
+
} elseif ( isset( $_GET['crontrol_action'] ) && 'resume-hook' === $_GET['crontrol_action'] ) {
|
668 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
669 |
+
wp_die( esc_html__( 'You are not allowed to pause or resume cron events.', 'wp-crontrol' ), 401 );
|
670 |
+
}
|
671 |
+
|
672 |
+
$hook = wp_unslash( $_GET['crontrol_id'] );
|
673 |
+
check_admin_referer( "crontrol-resume-hook_{$hook}" );
|
674 |
+
|
675 |
+
$resumed = Event\resume( $hook );
|
676 |
+
|
677 |
+
$redirect = array(
|
678 |
+
'page' => 'crontrol_admin_manage_page',
|
679 |
+
'crontrol_message' => '12',
|
680 |
+
'crontrol_name' => rawurlencode( $hook ),
|
681 |
+
);
|
682 |
+
|
683 |
+
if ( is_wp_error( $resumed ) ) {
|
684 |
+
$set = set_message( $resumed->get_error_message() );
|
685 |
+
|
686 |
+
// If we can't store the error message in a transient, just display it.
|
687 |
+
if ( ! $set ) {
|
688 |
+
wp_die(
|
689 |
+
esc_html( $resumed->get_error_message() ),
|
690 |
+
'',
|
691 |
+
array(
|
692 |
+
'response' => 500,
|
693 |
+
'back_link' => true,
|
694 |
+
)
|
695 |
+
);
|
696 |
+
}
|
697 |
+
$redirect['crontrol_message'] = 'error';
|
698 |
+
} else {
|
699 |
+
/**
|
700 |
+
* Fires after a paused cron event hook is resumed.
|
701 |
+
*
|
702 |
+
* @param string $hook The event hook name.
|
703 |
+
*/
|
704 |
+
do_action( 'crontrol/resumed_hook', $hook );
|
705 |
+
}
|
706 |
+
|
707 |
+
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
708 |
+
exit;
|
709 |
+
} elseif ( isset( $_POST['crontrol_action'] ) && 'export-event-csv' === $_POST['crontrol_action'] ) {
|
710 |
+
check_admin_referer( 'crontrol-export-event-csv', 'crontrol_nonce' );
|
711 |
+
|
712 |
+
$type = isset( $_POST['crontrol_hooks_type'] ) ? $_POST['crontrol_hooks_type'] : 'all';
|
713 |
+
$headers = array(
|
714 |
+
'hook',
|
715 |
+
'arguments',
|
716 |
+
'next_run',
|
717 |
+
'next_run_gmt',
|
718 |
+
'action',
|
719 |
+
'recurrence',
|
720 |
+
'interval',
|
721 |
+
);
|
722 |
+
$filename = sprintf(
|
723 |
+
'cron-events-%s-%s.csv',
|
724 |
+
$type,
|
725 |
+
gmdate( 'Y-m-d-H.i.s' )
|
726 |
+
);
|
727 |
+
$csv = fopen( 'php://output', 'w' );
|
728 |
+
|
729 |
+
if ( false === $csv ) {
|
730 |
+
wp_die( esc_html__( 'Could not save CSV file.', 'wp-crontrol' ) );
|
731 |
+
}
|
732 |
+
|
733 |
+
$events = Table::get_filtered_events( Event\get() );
|
734 |
+
|
735 |
+
header( 'Content-Type: text/csv; charset=utf-8' );
|
736 |
+
header(
|
737 |
+
sprintf(
|
738 |
+
'Content-Disposition: attachment; filename="%s"',
|
739 |
+
esc_attr( $filename )
|
740 |
+
)
|
741 |
+
);
|
742 |
+
|
743 |
+
fputcsv( $csv, $headers );
|
744 |
+
|
745 |
+
if ( isset( $events[ $type ] ) ) {
|
746 |
+
foreach ( $events[ $type ] as $event ) {
|
747 |
+
$next_run_local = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $event->timestamp ), 'c' );
|
748 |
+
$next_run_utc = gmdate( 'c', $event->timestamp );
|
749 |
+
$hook_callbacks = \Crontrol\get_hook_callbacks( $event->hook );
|
750 |
+
|
751 |
+
if ( 'crontrol_cron_job' === $event->hook ) {
|
752 |
+
$args = __( 'PHP Code', 'wp-crontrol' );
|
753 |
+
} elseif ( empty( $event->args ) ) {
|
754 |
+
$args = '';
|
755 |
+
} else {
|
756 |
+
$args = \Crontrol\json_output( $event->args, false );
|
757 |
+
}
|
758 |
+
|
759 |
+
if ( 'crontrol_cron_job' === $event->hook ) {
|
760 |
+
$action = __( 'WP Crontrol', 'wp-crontrol' );
|
761 |
+
} else {
|
762 |
+
$callbacks = array();
|
763 |
+
|
764 |
+
foreach ( $hook_callbacks as $callback ) {
|
765 |
+
$callbacks[] = $callback['callback']['name'];
|
766 |
+
}
|
767 |
+
|
768 |
+
$action = implode( ',', $callbacks );
|
769 |
+
}
|
770 |
+
|
771 |
+
if ( $event->schedule ) {
|
772 |
+
$recurrence = Event\get_schedule_name( $event );
|
773 |
+
if ( is_wp_error( $recurrence ) ) {
|
774 |
+
$recurrence = $recurrence->get_error_message();
|
775 |
+
}
|
776 |
+
} else {
|
777 |
+
$recurrence = __( 'Non-repeating', 'wp-crontrol' );
|
778 |
+
}
|
779 |
+
|
780 |
+
$row = array(
|
781 |
+
$event->hook,
|
782 |
+
$args,
|
783 |
+
$next_run_local,
|
784 |
+
$next_run_utc,
|
785 |
+
$action,
|
786 |
+
$recurrence,
|
787 |
+
(int) $event->interval,
|
788 |
+
);
|
789 |
+
fputcsv( $csv, $row );
|
790 |
+
}
|
791 |
+
}
|
792 |
+
|
793 |
+
fclose( $csv );
|
794 |
+
|
795 |
+
exit;
|
796 |
+
}
|
797 |
+
}
|
798 |
+
|
799 |
+
/**
|
800 |
+
* Adds options & management pages to the admin menu.
|
801 |
+
*
|
802 |
+
* Run using the 'admin_menu' action.
|
803 |
+
*
|
804 |
+
* @return void
|
805 |
+
*/
|
806 |
+
function action_admin_menu() {
|
807 |
+
$schedules = add_options_page(
|
808 |
+
esc_html__( 'Cron Schedules', 'wp-crontrol' ),
|
809 |
+
esc_html__( 'Cron Schedules', 'wp-crontrol' ),
|
810 |
+
'manage_options',
|
811 |
+
'crontrol_admin_options_page',
|
812 |
+
__NAMESPACE__ . '\admin_options_page'
|
813 |
+
);
|
814 |
+
$events = add_management_page(
|
815 |
+
esc_html__( 'Cron Events', 'wp-crontrol' ),
|
816 |
+
esc_html__( 'Cron Events', 'wp-crontrol' ),
|
817 |
+
'manage_options',
|
818 |
+
'crontrol_admin_manage_page',
|
819 |
+
__NAMESPACE__ . '\admin_manage_page'
|
820 |
+
);
|
821 |
+
|
822 |
+
add_action( "load-{$schedules}", __NAMESPACE__ . '\admin_help_tab' );
|
823 |
+
add_action( "load-{$events}", __NAMESPACE__ . '\admin_help_tab' );
|
824 |
+
}
|
825 |
+
|
826 |
+
/**
|
827 |
+
* Adds a Help tab with links to help resources.
|
828 |
+
*
|
829 |
+
* @return void
|
830 |
+
*/
|
831 |
+
function admin_help_tab() {
|
832 |
+
$screen = get_current_screen();
|
833 |
+
|
834 |
+
if ( ! $screen ) {
|
835 |
+
return;
|
836 |
+
}
|
837 |
+
|
838 |
+
$content = '<p>' . __( 'There are several places to get help with issues relating to WP-Cron:', 'wp-crontrol' ) . '</p>';
|
839 |
+
$content .= '<ul>';
|
840 |
+
$content .= '<li>';
|
841 |
+
$content .= sprintf(
|
842 |
+
/* translators: %s: URL to the documentation */
|
843 |
+
__( '<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' ),
|
844 |
+
'https://github.com/johnbillion/wp-crontrol/wiki'
|
845 |
+
);
|
846 |
+
$content .= '</li>';
|
847 |
+
$content .= '<li>';
|
848 |
+
$content .= sprintf(
|
849 |
+
/* translators: %s: URL to the documentation */
|
850 |
+
__( '<a href="%s">Read the Frequently Asked Questions (FAQ)</a> which cover many common questions and answers.', 'wp-crontrol' ),
|
851 |
+
'https://wordpress.org/plugins/wp-crontrol/faq/'
|
852 |
+
);
|
853 |
+
$content .= '</li>';
|
854 |
+
$content .= '<li>';
|
855 |
+
$content .= sprintf(
|
856 |
+
/* translators: %s: URL to the documentation */
|
857 |
+
__( '<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' ),
|
858 |
+
'https://developer.wordpress.org/plugins/cron/'
|
859 |
+
);
|
860 |
+
$content .= '</ul>';
|
861 |
+
|
862 |
+
$screen->add_help_tab(
|
863 |
+
array(
|
864 |
+
'id' => 'crontrol-help',
|
865 |
+
'title' => __( 'Help', 'wp-crontrol' ),
|
866 |
+
'content' => $content,
|
867 |
+
)
|
868 |
+
);
|
869 |
+
}
|
870 |
+
|
871 |
+
/**
|
872 |
+
* Adds items to the plugin's action links on the Plugins listing screen.
|
873 |
+
*
|
874 |
+
* @param array<string,string> $actions Array of action links.
|
875 |
+
* @param string $plugin_file Path to the plugin file relative to the plugins directory.
|
876 |
+
* @param mixed[] $plugin_data An array of plugin data.
|
877 |
+
* @param string $context The plugin context.
|
878 |
+
* @return array<string,string> Array of action links.
|
879 |
+
*/
|
880 |
+
function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) {
|
881 |
+
$new = array(
|
882 |
+
'crontrol-events' => sprintf(
|
883 |
+
'<a href="%s">%s</a>',
|
884 |
+
esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page' ) ),
|
885 |
+
esc_html__( 'Events', 'wp-crontrol' )
|
886 |
+
),
|
887 |
+
'crontrol-schedules' => sprintf(
|
888 |
+
'<a href="%s">%s</a>',
|
889 |
+
esc_url( admin_url( 'options-general.php?page=crontrol_admin_options_page' ) ),
|
890 |
+
esc_html__( 'Schedules', 'wp-crontrol' )
|
891 |
+
),
|
892 |
+
'crontrol-help' => sprintf(
|
893 |
+
'<a href="%s">%s</a>',
|
894 |
+
'https://github.com/johnbillion/wp-crontrol/wiki',
|
895 |
+
esc_html__( 'Help', 'wp-crontrol' )
|
896 |
+
),
|
897 |
+
);
|
898 |
+
|
899 |
+
return array_merge( $new, $actions );
|
900 |
+
}
|
901 |
+
|
902 |
+
/**
|
903 |
+
* Adds items to the plugin's action links on the Network Admin -> Plugins listing screen.
|
904 |
+
*
|
905 |
+
* @param array<string,string> $actions Array of action links.
|
906 |
+
* @return array<string,string> Array of action links.
|
907 |
+
*/
|
908 |
+
function network_plugin_action_links( $actions ) {
|
909 |
+
$new = array(
|
910 |
+
'crontrol-help' => sprintf(
|
911 |
+
'<a href="%s">%s</a>',
|
912 |
+
'https://github.com/johnbillion/wp-crontrol/wiki',
|
913 |
+
esc_html__( 'Help', 'wp-crontrol' )
|
914 |
+
),
|
915 |
+
);
|
916 |
+
|
917 |
+
return array_merge( $new, $actions );
|
918 |
+
}
|
919 |
+
|
920 |
+
/**
|
921 |
+
* Gives WordPress the plugin's set of cron schedules.
|
922 |
+
*
|
923 |
+
* Called by the `cron_schedules` filter.
|
924 |
+
*
|
925 |
+
* @param array<string,array<string,(int|string)>> $scheds Array of cron schedule arrays. Usually empty.
|
926 |
+
* @return array<string,array<string,(int|string)>> Array of modified cron schedule arrays.
|
927 |
+
*/
|
928 |
+
function filter_cron_schedules( array $scheds ) {
|
929 |
+
$new_scheds = get_option( 'crontrol_schedules', array() );
|
930 |
+
|
931 |
+
if ( ! is_array( $new_scheds ) ) {
|
932 |
+
return $scheds;
|
933 |
+
}
|
934 |
+
|
935 |
+
return array_merge( $new_scheds, $scheds );
|
936 |
+
}
|
937 |
+
|
938 |
+
/**
|
939 |
+
* Displays the options page for the plugin.
|
940 |
+
*
|
941 |
+
* @return void
|
942 |
+
*/
|
943 |
+
function admin_options_page() {
|
944 |
+
$messages = array(
|
945 |
+
'2' => array(
|
946 |
+
/* translators: 1: The name of the cron schedule. */
|
947 |
+
__( 'Deleted the cron schedule %s.', 'wp-crontrol' ),
|
948 |
+
'success',
|
949 |
+
),
|
950 |
+
'3' => array(
|
951 |
+
/* translators: 1: The name of the cron schedule. */
|
952 |
+
__( 'Added the cron schedule %s.', 'wp-crontrol' ),
|
953 |
+
'success',
|
954 |
+
),
|
955 |
+
);
|
956 |
+
if ( isset( $_GET['crontrol_message'] ) && isset( $_GET['crontrol_name'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
|
957 |
+
$hook = wp_unslash( $_GET['crontrol_name'] );
|
958 |
+
$message = wp_unslash( $_GET['crontrol_message'] );
|
959 |
+
|
960 |
+
printf(
|
961 |
+
'<div id="crontrol-message" class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>',
|
962 |
+
esc_attr( $messages[ $message ][1] ),
|
963 |
+
sprintf(
|
964 |
+
esc_html( $messages[ $message ][0] ),
|
965 |
+
'<strong>' . esc_html( $hook ) . '</strong>'
|
966 |
+
)
|
967 |
+
);
|
968 |
+
}
|
969 |
+
|
970 |
+
$table = new Schedule_List_Table();
|
971 |
+
|
972 |
+
$table->prepare_items();
|
973 |
+
|
974 |
+
?>
|
975 |
+
<div class="wrap">
|
976 |
+
|
977 |
+
<?php do_tabs(); ?>
|
978 |
+
|
979 |
+
<h1><?php esc_html_e( 'Cron Schedules', 'wp-crontrol' ); ?></h1>
|
980 |
+
|
981 |
+
<?php $table->views(); ?>
|
982 |
+
|
983 |
+
<div id="col-container" class="wp-clearfix">
|
984 |
+
<div id="col-left">
|
985 |
+
<div class="col-wrap">
|
986 |
+
<div class="form-wrap">
|
987 |
+
<h2><?php esc_html_e( 'Add Cron Schedule', 'wp-crontrol' ); ?></h2>
|
988 |
+
<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>
|
989 |
+
<form method="post" action="options-general.php?page=crontrol_admin_options_page">
|
990 |
+
<div class="form-field form-required">
|
991 |
+
<label for="crontrol_schedule_internal_name">
|
992 |
+
<?php esc_html_e( 'Internal Name', 'wp-crontrol' ); ?>
|
993 |
+
</label>
|
994 |
+
<input type="text" value="" id="crontrol_schedule_internal_name" name="crontrol_schedule_internal_name" required/>
|
995 |
+
</div>
|
996 |
+
<div class="form-field form-required">
|
997 |
+
<label for="crontrol_schedule_interval">
|
998 |
+
<?php esc_html_e( 'Interval (seconds)', 'wp-crontrol' ); ?>
|
999 |
+
</label>
|
1000 |
+
<input type="number" value="" id="crontrol_schedule_interval" name="crontrol_schedule_interval" min="1" step="1" required/>
|
1001 |
+
</div>
|
1002 |
+
<div class="form-field form-required">
|
1003 |
+
<label for="crontrol_schedule_display_name">
|
1004 |
+
<?php esc_html_e( 'Display Name', 'wp-crontrol' ); ?>
|
1005 |
+
</label>
|
1006 |
+
<input type="text" value="" id="crontrol_schedule_display_name" name="crontrol_schedule_display_name" required/>
|
1007 |
+
</div>
|
1008 |
+
<p class="submit">
|
1009 |
+
<input type="submit" class="button button-primary" value="<?php esc_attr_e( 'Add Cron Schedule', 'wp-crontrol' ); ?>" name="crontrol_new_schedule"/>
|
1010 |
+
</p>
|
1011 |
+
<?php wp_nonce_field( 'crontrol-new-schedule' ); ?>
|
1012 |
+
</form>
|
1013 |
+
</div>
|
1014 |
+
</div>
|
1015 |
+
</div>
|
1016 |
+
<div id="col-right">
|
1017 |
+
<div class="col-wrap">
|
1018 |
+
<?php $table->display(); ?>
|
1019 |
+
</div>
|
1020 |
+
</div>
|
1021 |
+
</div>
|
1022 |
+
<?php
|
1023 |
+
}
|
1024 |
+
|
1025 |
+
/**
|
1026 |
+
* Clears the doing cron status when an event is unscheduled.
|
1027 |
+
*
|
1028 |
+
* What on earth does this function do, and why?
|
1029 |
+
*
|
1030 |
+
* Good question. The purpose of this function is to prevent other overdue cron events from firing when an event is run
|
1031 |
+
* manually with the "Run Now" action. WP Crontrol works very hard to ensure that when cron event runs manually that it
|
1032 |
+
* runs in the exact same way it would run as part of its schedule - via a properly spawned cron with a queued event in
|
1033 |
+
* place. It does this by queueing an event at time `1` (1 second into 1st January 1970) and then immediately spawning
|
1034 |
+
* cron (see the `Event\run()` function).
|
1035 |
+
*
|
1036 |
+
* The problem this causes is if other events are due then they will all run too, and this isn't desirable because if a
|
1037 |
+
* site has a large number of stuck events due to a problem with the cron runner then it's not desirable for all those
|
1038 |
+
* events to run when another is manually run. This happens because WordPress core will attempt to run all due events
|
1039 |
+
* whenever cron is spawned.
|
1040 |
+
*
|
1041 |
+
* The code in this function prevents multiple events from running by changing the value of the `doing_cron` transient
|
1042 |
+
* when an event gets unscheduled during a manual run, which prevents wp-cron.php from iterating more than one event.
|
1043 |
+
*
|
1044 |
+
* The `pre_unschedule_event` filter is used for this because it's just about the only hook available within this loop.
|
1045 |
+
*
|
1046 |
+
* Refs:
|
1047 |
+
* - https://core.trac.wordpress.org/browser/trunk/src/wp-cron.php?rev=47198&marks=127,141#L122
|
1048 |
+
*
|
1049 |
+
* @param mixed $pre The pre-flight value of the event unschedule short-circuit. Not used.
|
1050 |
+
* @return mixed The unaltered pre-flight value.
|
1051 |
+
*/
|
1052 |
+
function maybe_clear_doing_cron( $pre ) {
|
1053 |
+
if ( defined( 'DOING_CRON' ) && DOING_CRON && isset( $_GET['crontrol-single-event'] ) ) {
|
1054 |
+
delete_transient( 'doing_cron' );
|
1055 |
+
}
|
1056 |
+
|
1057 |
+
return $pre;
|
1058 |
+
}
|
1059 |
+
|
1060 |
+
/**
|
1061 |
+
* Ajax handler which outputs a hash of the current list of scheduled events.
|
1062 |
+
*
|
1063 |
+
* @return void
|
1064 |
+
*/
|
1065 |
+
function ajax_check_events_hash() {
|
1066 |
+
if ( ! current_user_can( 'manage_options' ) ) {
|
1067 |
+
wp_send_json_error( null, 403 );
|
1068 |
+
}
|
1069 |
+
|
1070 |
+
$data = json_encode( Event\get() );
|
1071 |
+
|
1072 |
+
if ( false === $data ) {
|
1073 |
+
wp_send_json_error( null, 500 );
|
1074 |
+
}
|
1075 |
+
|
1076 |
+
wp_send_json_success( md5( $data ) );
|
1077 |
+
}
|
1078 |
+
|
1079 |
+
/**
|
1080 |
+
* Gets the status of WP-Cron functionality on the site by performing a test spawn if necessary. Cached for one hour when all is well.
|
1081 |
+
*
|
1082 |
+
* @param bool $cache Whether to use the cached result from previous calls.
|
1083 |
+
* @return true|WP_Error Boolean true if the cron spawner is working as expected, or a `WP_Error` object if not.
|
1084 |
+
*/
|
1085 |
+
function test_cron_spawn( $cache = true ) {
|
1086 |
+
global $wp_version;
|
1087 |
+
|
1088 |
+
$cron_runner_plugins = array(
|
1089 |
+
'\HM\Cavalcade\Plugin\Job' => 'Cavalcade',
|
1090 |
+
'\Automattic\WP\Cron_Control\Main' => 'Cron Control',
|
1091 |
+
'\KMM\KRoN\Core' => 'KMM KRoN',
|
1092 |
+
);
|
1093 |
+
|
1094 |
+
foreach ( $cron_runner_plugins as $class => $plugin ) {
|
1095 |
+
if ( class_exists( $class ) ) {
|
1096 |
+
return new WP_Error( 'crontrol_info', sprintf(
|
1097 |
+
/* translators: 1: The name of the plugin that controls the running of cron events. */
|
1098 |
+
__( 'WP-Cron spawning is being managed by the %s plugin.', 'wp-crontrol' ),
|
1099 |
+
$plugin
|
1100 |
+
) );
|
1101 |
+
}
|
1102 |
+
}
|
1103 |
+
|
1104 |
+
if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
|
1105 |
+
return new WP_Error( 'crontrol_info', sprintf(
|
1106 |
+
/* translators: 1: The name of the PHP constant that is set. */
|
1107 |
+
__( 'The %s constant is set to true. WP-Cron spawning is disabled.', 'wp-crontrol' ),
|
1108 |
+
'DISABLE_WP_CRON'
|
1109 |
+
) );
|
1110 |
+
}
|
1111 |
+
|
1112 |
+
if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
|
1113 |
+
return new WP_Error( 'crontrol_info', sprintf(
|
1114 |
+
/* translators: 1: The name of the PHP constant that is set. */
|
1115 |
+
__( 'The %s constant is set to true.', 'wp-crontrol' ),
|
1116 |
+
'ALTERNATE_WP_CRON'
|
1117 |
+
) );
|
1118 |
+
}
|
1119 |
+
|
1120 |
+
$cached_status = get_transient( 'crontrol-cron-test-ok' );
|
1121 |
+
|
1122 |
+
if ( $cache && $cached_status ) {
|
1123 |
+
return true;
|
1124 |
+
}
|
1125 |
+
|
1126 |
+
$sslverify = version_compare( $wp_version, '4.0', '<' );
|
1127 |
+
$doing_wp_cron = sprintf( '%.22F', microtime( true ) );
|
1128 |
+
|
1129 |
+
$cron_request = apply_filters( 'cron_request', array(
|
1130 |
+
'url' => add_query_arg( 'doing_wp_cron', $doing_wp_cron, site_url( 'wp-cron.php' ) ),
|
1131 |
+
'key' => $doing_wp_cron,
|
1132 |
+
'args' => array(
|
1133 |
+
'timeout' => 3,
|
1134 |
+
'blocking' => true,
|
1135 |
+
'sslverify' => apply_filters( 'https_local_ssl_verify', $sslverify ),
|
1136 |
+
),
|
1137 |
+
) );
|
1138 |
+
|
1139 |
+
$cron_request['args']['blocking'] = true;
|
1140 |
+
|
1141 |
+
$result = wp_remote_post( $cron_request['url'], $cron_request['args'] );
|
1142 |
+
|
1143 |
+
if ( is_wp_error( $result ) ) {
|
1144 |
+
return $result;
|
1145 |
+
} elseif ( wp_remote_retrieve_response_code( $result ) >= 300 ) {
|
1146 |
+
return new WP_Error( 'unexpected_http_response_code', sprintf(
|
1147 |
+
/* translators: 1: The HTTP response code. */
|
1148 |
+
__( 'Unexpected HTTP response code: %s', 'wp-crontrol' ),
|
1149 |
+
intval( wp_remote_retrieve_response_code( $result ) )
|
1150 |
+
) );
|
1151 |
+
} else {
|
1152 |
+
set_transient( 'crontrol-cron-test-ok', 1, 3600 );
|
1153 |
+
return true;
|
1154 |
+
}
|
1155 |
+
|
1156 |
+
}
|
1157 |
+
|
1158 |
+
/**
|
1159 |
+
* Shows the status of WP-Cron functionality on the site. Only displays a message when there's a problem.
|
1160 |
+
*
|
1161 |
+
* @param string $tab The tab name.
|
1162 |
+
* @return void
|
1163 |
+
*/
|
1164 |
+
function show_cron_status( $tab ) {
|
1165 |
+
if ( 'UTC' !== date_default_timezone_get() ) {
|
1166 |
+
?>
|
1167 |
+
<div id="crontrol-timezone-warning" class="notice notice-warning">
|
1168 |
+
<?php
|
1169 |
+
printf(
|
1170 |
+
'<p>%1$s</p><p><a href="%2$s">%3$s</a></p>',
|
1171 |
+
/* translators: %s: Help page URL. */
|
1172 |
+
esc_html__( 'PHP default timezone is not set to UTC. This may cause issues with cron event timings.', 'wp-crontrol' ),
|
1173 |
+
'https://github.com/johnbillion/wp-crontrol/wiki/PHP-default-timezone-is-not-set-to-UTC',
|
1174 |
+
esc_html__( 'More information', 'wp-crontrol' )
|
1175 |
+
);
|
1176 |
+
?>
|
1177 |
+
</div>
|
1178 |
+
<?php
|
1179 |
+
}
|
1180 |
+
|
1181 |
+
$status = test_cron_spawn();
|
1182 |
+
|
1183 |
+
if ( is_wp_error( $status ) ) {
|
1184 |
+
if ( 'crontrol_info' === $status->get_error_code() ) {
|
1185 |
+
?>
|
1186 |
+
<div id="crontrol-status-notice" class="notice notice-info">
|
1187 |
+
<p><?php echo esc_html( $status->get_error_message() ); ?></p>
|
1188 |
+
</div>
|
1189 |
+
<?php
|
1190 |
+
} else {
|
1191 |
+
?>
|
1192 |
+
<div id="crontrol-status-error" class="error">
|
1193 |
+
<?php
|
1194 |
+
printf(
|
1195 |
+
'<p>%1$s</p><p><a href="%2$s">%3$s</a></p>',
|
1196 |
+
sprintf(
|
1197 |
+
/* translators: 1: Error message text. */
|
1198 |
+
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' ),
|
1199 |
+
'</p><p><strong>' . esc_html( $status->get_error_message() ) . '</strong>'
|
1200 |
+
),
|
1201 |
+
'https://github.com/johnbillion/wp-crontrol/wiki/Problems-with-spawning-a-call-to-the-WP-Cron-system',
|
1202 |
+
esc_html__( 'More information', 'wp-crontrol' )
|
1203 |
+
);
|
1204 |
+
?>
|
1205 |
+
</div>
|
1206 |
+
<?php
|
1207 |
+
}
|
1208 |
+
}
|
1209 |
+
}
|
1210 |
+
|
1211 |
+
/**
|
1212 |
+
* Get the display name for the site's timezone.
|
1213 |
+
*
|
1214 |
+
* @return string The name and UTC offset for the site's timezone.
|
1215 |
+
*/
|
1216 |
+
function get_timezone_name() {
|
1217 |
+
/** @var string */
|
1218 |
+
$timezone_string = get_option( 'timezone_string', '' );
|
1219 |
+
$gmt_offset = get_option( 'gmt_offset', 0 );
|
1220 |
+
|
1221 |
+
if ( 'UTC' === $timezone_string || ( empty( $gmt_offset ) && empty( $timezone_string ) ) ) {
|
1222 |
+
return 'UTC';
|
1223 |
+
}
|
1224 |
+
|
1225 |
+
if ( '' === $timezone_string ) {
|
1226 |
+
return get_utc_offset();
|
1227 |
+
}
|
1228 |
+
|
1229 |
+
return sprintf(
|
1230 |
+
'%s, %s',
|
1231 |
+
str_replace( '_', ' ', $timezone_string ),
|
1232 |
+
get_utc_offset()
|
1233 |
+
);
|
1234 |
+
}
|
1235 |
+
|
1236 |
+
/**
|
1237 |
+
* Returns a display value for a UTC offset.
|
1238 |
+
*
|
1239 |
+
* Examples:
|
1240 |
+
* - UTC
|
1241 |
+
* - UTC+4
|
1242 |
+
* - UTC-6
|
1243 |
+
*
|
1244 |
+
* @return string The UTC offset display value.
|
1245 |
+
*/
|
1246 |
+
function get_utc_offset() {
|
1247 |
+
$offset = get_option( 'gmt_offset', 0 );
|
1248 |
+
|
1249 |
+
if ( empty( $offset ) ) {
|
1250 |
+
return 'UTC';
|
1251 |
+
}
|
1252 |
+
|
1253 |
+
if ( 0 <= $offset ) {
|
1254 |
+
$formatted_offset = '+' . (string) $offset;
|
1255 |
+
} else {
|
1256 |
+
$formatted_offset = (string) $offset;
|
1257 |
+
}
|
1258 |
+
$formatted_offset = str_replace(
|
1259 |
+
array( '.25', '.5', '.75' ),
|
1260 |
+
array( ':15', ':30', ':45' ),
|
1261 |
+
$formatted_offset
|
1262 |
+
);
|
1263 |
+
return 'UTC' . $formatted_offset;
|
1264 |
+
}
|
1265 |
+
|
1266 |
+
/**
|
1267 |
+
* Shows the form used to add/edit cron events.
|
1268 |
+
*
|
1269 |
+
* @param bool $editing Whether the form is for the event editor.
|
1270 |
+
* @return void
|
1271 |
+
*/
|
1272 |
+
function show_cron_form( $editing ) {
|
1273 |
+
$display_args = '';
|
1274 |
+
$edit_id = null;
|
1275 |
+
$existing = false;
|
1276 |
+
|
1277 |
+
if ( $editing && ! empty( $_GET['crontrol_id'] ) ) {
|
1278 |
+
$edit_id = wp_unslash( $_GET['crontrol_id'] );
|
1279 |
+
|
1280 |
+
foreach ( Event\get() as $event ) {
|
1281 |
+
if ( $edit_id === $event->hook && intval( $_GET['crontrol_next_run_utc'] ) === $event->timestamp && $event->sig === $_GET['crontrol_sig'] ) {
|
1282 |
+
$existing = array(
|
1283 |
+
'hookname' => $event->hook,
|
1284 |
+
'next_run' => $event->timestamp, // UTC
|
1285 |
+
'schedule' => ( $event->schedule ? $event->schedule : '_oneoff' ),
|
1286 |
+
'sig' => $event->sig,
|
1287 |
+
'args' => $event->args,
|
1288 |
+
);
|
1289 |
+
break;
|
1290 |
+
}
|
1291 |
+
}
|
1292 |
+
|
1293 |
+
if ( empty( $existing ) ) {
|
1294 |
+
?>
|
1295 |
+
<div id="crontrol-event-not-found" class="notice notice-error">
|
1296 |
+
<?php
|
1297 |
+
printf(
|
1298 |
+
'<p>%1$s</p>',
|
1299 |
+
esc_html__( 'The event you are trying to edit does not exist.', 'wp-crontrol' )
|
1300 |
+
);
|
1301 |
+
?>
|
1302 |
+
</div>
|
1303 |
+
<?php
|
1304 |
+
return;
|
1305 |
+
}
|
1306 |
+
}
|
1307 |
+
|
1308 |
+
$is_editing_php = ( $existing && 'crontrol_cron_job' === $existing['hookname'] );
|
1309 |
+
|
1310 |
+
if ( $is_editing_php ) {
|
1311 |
+
$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' );
|
1312 |
+
} else {
|
1313 |
+
$helper_text = sprintf(
|
1314 |
+
/* translators: %s: A file name */
|
1315 |
+
esc_html__( 'Cron events trigger actions in your code. A cron event needs a corresponding action hook somewhere in code, e.g. the %1$s file in your theme.', 'wp-crontrol' ),
|
1316 |
+
'<code>functions.php</code>'
|
1317 |
+
);
|
1318 |
+
}
|
1319 |
+
|
1320 |
+
if ( is_array( $existing ) ) {
|
1321 |
+
$other_fields = wp_nonce_field( "crontrol-edit-cron_{$existing['hookname']}_{$existing['sig']}_{$existing['next_run']}", '_wpnonce', true, false );
|
1322 |
+
$other_fields .= sprintf( '<input name="crontrol_original_hookname" type="hidden" value="%s" />',
|
1323 |
+
esc_attr( $existing['hookname'] )
|
1324 |
+
);
|
1325 |
+
$other_fields .= sprintf( '<input name="crontrol_original_sig" type="hidden" value="%s" />',
|
1326 |
+
esc_attr( $existing['sig'] )
|
1327 |
+
);
|
1328 |
+
$other_fields .= sprintf( '<input name="crontrol_original_next_run_utc" type="hidden" value="%s" />',
|
1329 |
+
esc_attr( (string) $existing['next_run'] )
|
1330 |
+
);
|
1331 |
+
if ( ! empty( $existing['args'] ) ) {
|
1332 |
+
$display_args = wp_json_encode( $existing['args'] );
|
1333 |
+
|
1334 |
+
if ( false === $display_args ) {
|
1335 |
+
$display_args = '';
|
1336 |
+
}
|
1337 |
+
}
|
1338 |
+
$button = __( 'Update Event', 'wp-crontrol' );
|
1339 |
+
$next_run_gmt = gmdate( 'Y-m-d H:i:s', $existing['next_run'] );
|
1340 |
+
$next_run_date_local = get_date_from_gmt( $next_run_gmt, 'Y-m-d' );
|
1341 |
+
$next_run_time_local = get_date_from_gmt( $next_run_gmt, 'H:i:s' );
|
1342 |
+
} else {
|
1343 |
+
$other_fields = wp_nonce_field( 'crontrol-new-cron', '_wpnonce', true, false );
|
1344 |
+
$existing = array(
|
1345 |
+
'hookname' => '',
|
1346 |
+
'args' => array(),
|
1347 |
+
'next_run' => 'now', // UTC
|
1348 |
+
'schedule' => false,
|
1349 |
+
);
|
1350 |
+
|
1351 |
+
$button = __( 'Add Event', 'wp-crontrol' );
|
1352 |
+
$next_run_date_local = '';
|
1353 |
+
$next_run_time_local = '';
|
1354 |
+
}
|
1355 |
+
|
1356 |
+
if ( $is_editing_php ) {
|
1357 |
+
if ( ! isset( $existing['args']['code'] ) ) {
|
1358 |
+
$existing['args']['code'] = '';
|
1359 |
+
}
|
1360 |
+
if ( ! isset( $existing['args']['name'] ) ) {
|
1361 |
+
$existing['args']['name'] = '';
|
1362 |
+
}
|
1363 |
+
}
|
1364 |
+
|
1365 |
+
$can_add_php = current_user_can( 'edit_files' ) && ! $editing;
|
1366 |
+
$allowed = ( ! $is_editing_php || current_user_can( 'edit_files' ) );
|
1367 |
+
?>
|
1368 |
+
<div id="crontrol_form" class="wrap narrow">
|
1369 |
+
<?php
|
1370 |
+
if ( $allowed ) {
|
1371 |
+
if ( $editing ) {
|
1372 |
+
$heading = __( 'Edit Cron Event', 'wp-crontrol' );
|
1373 |
+
} else {
|
1374 |
+
$heading = __( 'Add Cron Event', 'wp-crontrol' );
|
1375 |
+
}
|
1376 |
+
|
1377 |
+
do_tabs();
|
1378 |
+
|
1379 |
+
printf(
|
1380 |
+
'<h1>%s</h1>',
|
1381 |
+
esc_html( $heading )
|
1382 |
+
);
|
1383 |
+
printf(
|
1384 |
+
'<p>%s</p>',
|
1385 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
1386 |
+
$helper_text
|
1387 |
+
);
|
1388 |
+
?>
|
1389 |
+
<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'; ?>">
|
1390 |
+
<?php
|
1391 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
1392 |
+
echo $other_fields;
|
1393 |
+
?>
|
1394 |
+
<table class="form-table"><tbody>
|
1395 |
+
<?php
|
1396 |
+
if ( $editing ) {
|
1397 |
+
$action = $is_editing_php ? 'edit_php_cron' : 'edit_cron';
|
1398 |
+
printf(
|
1399 |
+
'<input type="hidden" name="crontrol_action" value="%s"/>',
|
1400 |
+
esc_attr( $action )
|
1401 |
+
);
|
1402 |
+
} elseif ( $can_add_php ) {
|
1403 |
+
?>
|
1404 |
+
<tr class="hide-if-no-js">
|
1405 |
+
<th valign="top" scope="row">
|
1406 |
+
<?php esc_html_e( 'Event Type', 'wp-crontrol' ); ?>
|
1407 |
+
</th>
|
1408 |
+
<td>
|
1409 |
+
<p><label><input type="radio" name="crontrol_action" value="new_cron" checked>Standard cron event</label></p>
|
1410 |
+
<p><label><input type="radio" name="crontrol_action" value="new_php_cron">PHP cron event</label></p>
|
1411 |
+
</td>
|
1412 |
+
</tr>
|
1413 |
+
<?php
|
1414 |
+
} else {
|
1415 |
+
?>
|
1416 |
+
<input type="hidden" name="crontrol_action" value="new_cron"/>
|
1417 |
+
<?php
|
1418 |
+
}
|
1419 |
+
|
1420 |
+
if ( $is_editing_php || $can_add_php ) {
|
1421 |
+
?>
|
1422 |
+
<tr class="crontrol-event-php">
|
1423 |
+
<th valign="top" scope="row">
|
1424 |
+
<label for="crontrol_hookcode">
|
1425 |
+
<?php esc_html_e( 'PHP Code', 'wp-crontrol' ); ?>
|
1426 |
+
</label>
|
1427 |
+
</th>
|
1428 |
+
<td>
|
1429 |
+
<p class="description">
|
1430 |
+
<?php
|
1431 |
+
printf(
|
1432 |
+
/* translators: The PHP tag name */
|
1433 |
+
esc_html__( 'The opening %s tag must not be included.', 'wp-crontrol' ),
|
1434 |
+
'<code><?php</code>'
|
1435 |
+
);
|
1436 |
+
?>
|
1437 |
+
</p>
|
1438 |
+
<p><textarea class="large-text code" rows="10" cols="50" id="crontrol_hookcode" name="crontrol_hookcode"><?php echo esc_textarea( $editing ? $existing['args']['code'] : '' ); ?></textarea></p>
|
1439 |
+
<?php do_action( 'crontrol/manage/hookcode', $existing ); ?>
|
1440 |
+
</td>
|
1441 |
+
</tr>
|
1442 |
+
<tr class="crontrol-event-php">
|
1443 |
+
<th valign="top" scope="row">
|
1444 |
+
<label for="crontrol_eventname">
|
1445 |
+
<?php esc_html_e( 'Event Name (optional)', 'wp-crontrol' ); ?>
|
1446 |
+
</label>
|
1447 |
+
</th>
|
1448 |
+
<td>
|
1449 |
+
<input type="text" class="regular-text" id="crontrol_eventname" name="crontrol_eventname" value="<?php echo esc_attr( $editing ? $existing['args']['name'] : '' ); ?>"/>
|
1450 |
+
<?php do_action( 'crontrol/manage/eventname', $existing ); ?>
|
1451 |
+
</td>
|
1452 |
+
</tr>
|
1453 |
+
<?php
|
1454 |
+
}
|
1455 |
+
|
1456 |
+
if ( ! $is_editing_php ) {
|
1457 |
+
?>
|
1458 |
+
<tr class="crontrol-event-standard">
|
1459 |
+
<th valign="top" scope="row">
|
1460 |
+
<label for="crontrol_hookname">
|
1461 |
+
<?php esc_html_e( 'Hook Name', 'wp-crontrol' ); ?>
|
1462 |
+
</label>
|
1463 |
+
</th>
|
1464 |
+
<td>
|
1465 |
+
<input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" class="regular-text" id="crontrol_hookname" name="crontrol_hookname" value="<?php echo esc_attr( $existing['hookname'] ); ?>" required />
|
1466 |
+
<?php do_action( 'crontrol/manage/hookname', $existing ); ?>
|
1467 |
+
</td>
|
1468 |
+
</tr>
|
1469 |
+
<tr class="crontrol-event-standard">
|
1470 |
+
<th valign="top" scope="row">
|
1471 |
+
<label for="crontrol_args">
|
1472 |
+
<?php esc_html_e( 'Arguments (optional)', 'wp-crontrol' ); ?>
|
1473 |
+
</label>
|
1474 |
+
</th>
|
1475 |
+
<td>
|
1476 |
+
<input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" class="regular-text code" id="crontrol_args" name="crontrol_args" value="<?php echo esc_attr( $display_args ); ?>"/>
|
1477 |
+
<?php do_action( 'crontrol/manage/args', $existing ); ?>
|
1478 |
+
<p class="description">
|
1479 |
+
<?php
|
1480 |
+
printf(
|
1481 |
+
/* translators: 1, 2, and 3: Example values for an input field. */
|
1482 |
+
esc_html__( 'Use a JSON encoded array, e.g. %1$s, %2$s, or %3$s', 'wp-crontrol' ),
|
1483 |
+
'<code>[25]</code>',
|
1484 |
+
'<code>["asdf"]</code>',
|
1485 |
+
'<code>["i","want",25,"cakes"]</code>'
|
1486 |
+
);
|
1487 |
+
?>
|
1488 |
+
</p>
|
1489 |
+
</td>
|
1490 |
+
</tr>
|
1491 |
+
<?php
|
1492 |
+
}
|
1493 |
+
?>
|
1494 |
+
<tr>
|
1495 |
+
<th valign="top" scope="row">
|
1496 |
+
<label for="crontrol_next_run_date_local">
|
1497 |
+
<?php esc_html_e( 'Next Run', 'wp-crontrol' ); ?>
|
1498 |
+
</label>
|
1499 |
+
</th>
|
1500 |
+
<td>
|
1501 |
+
<ul>
|
1502 |
+
<li>
|
1503 |
+
<label>
|
1504 |
+
<input type="radio" name="crontrol_next_run_date_local" value="now" checked>
|
1505 |
+
<?php esc_html_e( 'Now', 'wp-crontrol' ); ?>
|
1506 |
+
</label>
|
1507 |
+
</li>
|
1508 |
+
<li>
|
1509 |
+
<label>
|
1510 |
+
<input type="radio" name="crontrol_next_run_date_local" value="+1 day">
|
1511 |
+
<?php esc_html_e( 'Tomorrow', 'wp-crontrol' ); ?>
|
1512 |
+
</label>
|
1513 |
+
</li>
|
1514 |
+
<li>
|
1515 |
+
<label>
|
1516 |
+
<input type="radio" name="crontrol_next_run_date_local" value="custom" id="crontrol_next_run_date_local_custom" <?php checked( $editing ); ?>>
|
1517 |
+
<?php
|
1518 |
+
printf(
|
1519 |
+
/* translators: %s: An input field for specifying a date and time */
|
1520 |
+
esc_html__( 'At: %s', 'wp-crontrol' ),
|
1521 |
+
sprintf(
|
1522 |
+
'<br>
|
1523 |
+
<input type="date" autocorrect="off" autocapitalize="off" spellcheck="false" name="crontrol_next_run_date_local_custom_date" id="crontrol_next_run_date_local_custom_date" value="%1$s" placeholder="yyyy-mm-dd" pattern="\d{4}-\d{2}-\d{2}" />
|
1524 |
+
<input type="time" autocorrect="off" autocapitalize="off" spellcheck="false" name="crontrol_next_run_date_local_custom_time" id="crontrol_next_run_date_local_custom_time" value="%2$s" step="1" placeholder="hh:mm:ss" pattern="\d{2}:\d{2}:\d{2}" />',
|
1525 |
+
esc_attr( $next_run_date_local ),
|
1526 |
+
esc_attr( $next_run_time_local )
|
1527 |
+
)
|
1528 |
+
);
|
1529 |
+
?>
|
1530 |
+
</label>
|
1531 |
+
</li>
|
1532 |
+
</ul>
|
1533 |
+
|
1534 |
+
<?php do_action( 'crontrol/manage/next_run', $existing ); ?>
|
1535 |
+
|
1536 |
+
<p class="description">
|
1537 |
+
<?php
|
1538 |
+
printf(
|
1539 |
+
/* translators: %s Timezone name. */
|
1540 |
+
esc_html__( 'Timezone: %s', 'wp-crontrol' ),
|
1541 |
+
esc_html( get_timezone_name() )
|
1542 |
+
);
|
1543 |
+
?>
|
1544 |
+
</p>
|
1545 |
+
</td>
|
1546 |
+
</tr>
|
1547 |
+
<tr>
|
1548 |
+
<th valign="top" scope="row">
|
1549 |
+
<label for="crontrol_schedule">
|
1550 |
+
<?php esc_html_e( 'Recurrence', 'wp-crontrol' ); ?>
|
1551 |
+
</label>
|
1552 |
+
</th>
|
1553 |
+
<td>
|
1554 |
+
<?php Schedule\dropdown( $existing['schedule'] ); ?>
|
1555 |
+
<?php do_action( 'crontrol/manage/schedule', $existing ); ?>
|
1556 |
+
</td>
|
1557 |
+
</tr>
|
1558 |
+
</tbody></table>
|
1559 |
+
<p class="submit">
|
1560 |
+
<input type="submit" class="button button-primary" value="<?php echo esc_attr( $button ); ?>"/>
|
1561 |
+
</p>
|
1562 |
+
<p class="description">
|
1563 |
+
<?php
|
1564 |
+
echo esc_html( sprintf(
|
1565 |
+
/* translators: 1: Date and time, 2: Timezone */
|
1566 |
+
__( 'Site time when page loaded: %1$s (%2$s)', 'wp-crontrol' ),
|
1567 |
+
date_i18n( 'Y-m-d H:i:s' ),
|
1568 |
+
get_timezone_name()
|
1569 |
+
) );
|
1570 |
+
?>
|
1571 |
+
</p>
|
1572 |
+
</form>
|
1573 |
+
<?php } else { ?>
|
1574 |
+
<div class="error inline">
|
1575 |
+
<p><?php esc_html_e( 'You cannot add, edit, or delete PHP cron events because your user account does not have the ability to edit files.', 'wp-crontrol' ); ?></p>
|
1576 |
+
</div>
|
1577 |
+
<?php } ?>
|
1578 |
+
</div>
|
1579 |
+
<?php
|
1580 |
+
}
|
1581 |
+
|
1582 |
+
/**
|
1583 |
+
* Displays the manage page for the plugin.
|
1584 |
+
*
|
1585 |
+
* @return void
|
1586 |
+
*/
|
1587 |
+
function admin_manage_page() {
|
1588 |
+
$messages = array(
|
1589 |
+
'1' => array(
|
1590 |
+
/* translators: 1: The name of the cron event. */
|
1591 |
+
__( 'Scheduled the cron event %s to run now. The original event will not be affected.', 'wp-crontrol' ),
|
1592 |
+
'success',
|
1593 |
+
),
|
1594 |
+
'2' => array(
|
1595 |
+
/* translators: 1: The name of the cron event. */
|
1596 |
+
__( 'Deleted all %s cron events.', 'wp-crontrol' ),
|
1597 |
+
'success',
|
1598 |
+
),
|
1599 |
+
'3' => array(
|
1600 |
+
/* translators: 1: The name of the cron event. */
|
1601 |
+
__( 'There are no %s cron events to delete.', 'wp-crontrol' ),
|
1602 |
+
'info',
|
1603 |
+
),
|
1604 |
+
'4' => array(
|
1605 |
+
/* translators: 1: The name of the cron event. */
|
1606 |
+
__( 'Saved the cron event %s.', 'wp-crontrol' ),
|
1607 |
+
'success',
|
1608 |
+
),
|
1609 |
+
'5' => array(
|
1610 |
+
/* translators: 1: The name of the cron event. */
|
1611 |
+
__( 'Created the cron event %s.', 'wp-crontrol' ),
|
1612 |
+
'success',
|
1613 |
+
),
|
1614 |
+
'6' => array(
|
1615 |
+
/* translators: 1: The name of the cron event. */
|
1616 |
+
__( 'Deleted the cron event %s.', 'wp-crontrol' ),
|
1617 |
+
'success',
|
1618 |
+
),
|
1619 |
+
'7' => array(
|
1620 |
+
/* translators: 1: The name of the cron event. */
|
1621 |
+
__( 'Failed to the delete the cron event %s.', 'wp-crontrol' ),
|
1622 |
+
'error',
|
1623 |
+
),
|
1624 |
+
'8' => array(
|
1625 |
+
/* translators: 1: The name of the cron event. */
|
1626 |
+
__( 'Failed to the execute the cron event %s.', 'wp-crontrol' ),
|
1627 |
+
'error',
|
1628 |
+
),
|
1629 |
+
'9' => array(
|
1630 |
+
__( 'Deleted the selected cron events.', 'wp-crontrol' ),
|
1631 |
+
'success',
|
1632 |
+
),
|
1633 |
+
'10' => array(
|
1634 |
+
/* translators: 1: The name of the cron event. */
|
1635 |
+
__( 'Failed to save the cron event %s.', 'wp-crontrol' ),
|
1636 |
+
'error',
|
1637 |
+
),
|
1638 |
+
'11' => array(
|
1639 |
+
/* translators: 1: The name of the cron event. */
|
1640 |
+
__( 'Paused the %s hook.', 'wp-crontrol' ),
|
1641 |
+
'success',
|
1642 |
+
),
|
1643 |
+
'12' => array(
|
1644 |
+
/* translators: 1: The name of the cron event. */
|
1645 |
+
__( 'Resumed the %s hook.', 'wp-crontrol' ),
|
1646 |
+
'success',
|
1647 |
+
),
|
1648 |
+
'error' => array(
|
1649 |
+
__( 'An unknown error occurred.', 'wp-crontrol' ),
|
1650 |
+
'error',
|
1651 |
+
),
|
1652 |
+
);
|
1653 |
+
|
1654 |
+
if ( isset( $_GET['crontrol_name'] ) && isset( $_GET['crontrol_message'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
|
1655 |
+
$hook = wp_unslash( $_GET['crontrol_name'] );
|
1656 |
+
$message = wp_unslash( $_GET['crontrol_message'] );
|
1657 |
+
$link = '';
|
1658 |
+
|
1659 |
+
if ( 'error' === $message ) {
|
1660 |
+
$error = get_message();
|
1661 |
+
|
1662 |
+
if ( $error ) {
|
1663 |
+
$messages['error'][0] = $error;
|
1664 |
+
}
|
1665 |
+
}
|
1666 |
+
|
1667 |
+
printf(
|
1668 |
+
'<div id="crontrol-message" class="notice notice-%1$s is-dismissible"><p>%2$s%3$s</p></div>',
|
1669 |
+
esc_attr( $messages[ $message ][1] ),
|
1670 |
+
sprintf(
|
1671 |
+
esc_html( $messages[ $message ][0] ),
|
1672 |
+
'<strong>' . esc_html( $hook ) . '</strong>'
|
1673 |
+
),
|
1674 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
1675 |
+
$link
|
1676 |
+
);
|
1677 |
+
}
|
1678 |
+
|
1679 |
+
$tabs = get_tab_states();
|
1680 |
+
$table = Event\get_list_table();
|
1681 |
+
|
1682 |
+
switch ( true ) {
|
1683 |
+
case $tabs['events']:
|
1684 |
+
?>
|
1685 |
+
<div class="wrap">
|
1686 |
+
<?php do_tabs(); ?>
|
1687 |
+
|
1688 |
+
<h1 class="wp-heading-inline"><?php esc_html_e( 'Cron Events', 'wp-crontrol' ); ?></h1>
|
1689 |
+
|
1690 |
+
<?php echo '<a href="' . esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page&crontrol_action=new-cron' ) ) . '" class="page-title-action">' . esc_html__( 'Add New', 'wp-crontrol' ) . '</a>'; ?>
|
1691 |
+
|
1692 |
+
<hr class="wp-header-end">
|
1693 |
+
|
1694 |
+
<?php $table->views(); ?>
|
1695 |
+
|
1696 |
+
<form id="posts-filter" method="get" action="tools.php">
|
1697 |
+
<input type="hidden" name="page" value="crontrol_admin_manage_page" />
|
1698 |
+
<?php $table->search_box( __( 'Search Hook Names', 'wp-crontrol' ), 'cron-event' ); ?>
|
1699 |
+
</form>
|
1700 |
+
|
1701 |
+
<form method="post" action="tools.php?page=crontrol_admin_manage_page">
|
1702 |
+
<div class="table-responsive">
|
1703 |
+
<?php $table->display(); ?>
|
1704 |
+
</div>
|
1705 |
+
</form>
|
1706 |
+
|
1707 |
+
<p class="description">
|
1708 |
+
<?php
|
1709 |
+
echo esc_html( sprintf(
|
1710 |
+
/* translators: 1: Date and time, 2: Timezone */
|
1711 |
+
__( 'Site time when page loaded: %1$s (%2$s)', 'wp-crontrol' ),
|
1712 |
+
date_i18n( 'Y-m-d H:i:s' ),
|
1713 |
+
get_timezone_name()
|
1714 |
+
) );
|
1715 |
+
?>
|
1716 |
+
</p>
|
1717 |
+
</div>
|
1718 |
+
<?php
|
1719 |
+
|
1720 |
+
break;
|
1721 |
+
|
1722 |
+
case $tabs['add-event']:
|
1723 |
+
show_cron_form( false );
|
1724 |
+
break;
|
1725 |
+
|
1726 |
+
case $tabs['edit-event']:
|
1727 |
+
show_cron_form( true );
|
1728 |
+
break;
|
1729 |
+
|
1730 |
+
}
|
1731 |
+
|
1732 |
+
}
|
1733 |
+
|
1734 |
+
/**
|
1735 |
+
* Get the states of the various cron-related tabs.
|
1736 |
+
*
|
1737 |
+
* @return array<string,bool> Array of states keyed by tab name.
|
1738 |
+
*/
|
1739 |
+
function get_tab_states() {
|
1740 |
+
$tabs = array(
|
1741 |
+
'events' => ( ! empty( $_GET['page'] ) && 'crontrol_admin_manage_page' === $_GET['page'] && empty( $_GET['crontrol_action'] ) ),
|
1742 |
+
'schedules' => ( ! empty( $_GET['page'] ) && 'crontrol_admin_options_page' === $_GET['page'] ),
|
1743 |
+
'add-event' => ( ! empty( $_GET['crontrol_action'] ) && 'new-cron' === $_GET['crontrol_action'] ),
|
1744 |
+
'edit-event' => ( ! empty( $_GET['crontrol_action'] ) && 'edit-cron' === $_GET['crontrol_action'] ),
|
1745 |
+
);
|
1746 |
+
|
1747 |
+
$tabs = apply_filters( 'crontrol/tabs', $tabs );
|
1748 |
+
|
1749 |
+
return $tabs;
|
1750 |
+
}
|
1751 |
+
|
1752 |
+
/**
|
1753 |
+
* Output the cron-related tabs if we're on a cron-related admin screen.
|
1754 |
+
*
|
1755 |
+
* @return void
|
1756 |
+
*/
|
1757 |
+
function do_tabs() {
|
1758 |
+
$tabs = get_tab_states();
|
1759 |
+
$tab = array_filter( $tabs );
|
1760 |
+
|
1761 |
+
if ( ! $tab ) {
|
1762 |
+
return;
|
1763 |
+
}
|
1764 |
+
|
1765 |
+
$tab = array_keys( $tab );
|
1766 |
+
$tab = reset( $tab );
|
1767 |
+
$links = array(
|
1768 |
+
'events' => array(
|
1769 |
+
'tools.php?page=crontrol_admin_manage_page',
|
1770 |
+
__( 'Cron Events', 'wp-crontrol' ),
|
1771 |
+
),
|
1772 |
+
'schedules' => array(
|
1773 |
+
'options-general.php?page=crontrol_admin_options_page',
|
1774 |
+
__( 'Cron Schedules', 'wp-crontrol' ),
|
1775 |
+
),
|
1776 |
+
);
|
1777 |
+
|
1778 |
+
?>
|
1779 |
+
<div id="crontrol-header">
|
1780 |
+
<nav class="nav-tab-wrapper">
|
1781 |
+
<?php
|
1782 |
+
foreach ( $links as $id => $link ) {
|
1783 |
+
if ( ! empty( $tabs[ $id ] ) ) {
|
1784 |
+
printf(
|
1785 |
+
'<a href="%s" class="nav-tab nav-tab-active">%s</a>',
|
1786 |
+
esc_url( $link[0] ),
|
1787 |
+
esc_html( $link[1] )
|
1788 |
+
);
|
1789 |
+
} else {
|
1790 |
+
printf(
|
1791 |
+
'<a href="%s" class="nav-tab">%s</a>',
|
1792 |
+
esc_url( $link[0] ),
|
1793 |
+
esc_html( $link[1] )
|
1794 |
+
);
|
1795 |
+
}
|
1796 |
+
}
|
1797 |
+
|
1798 |
+
if ( $tabs['add-event'] ) {
|
1799 |
+
printf(
|
1800 |
+
'<span class="nav-tab nav-tab-active">%s</span>',
|
1801 |
+
esc_html__( 'Add Cron Event', 'wp-crontrol' )
|
1802 |
+
);
|
1803 |
+
} elseif ( $tabs['edit-event'] ) {
|
1804 |
+
printf(
|
1805 |
+
'<span class="nav-tab nav-tab-active">%s</span>',
|
1806 |
+
esc_html__( 'Edit Cron Event', 'wp-crontrol' )
|
1807 |
+
);
|
1808 |
+
}
|
1809 |
+
?>
|
1810 |
+
</nav>
|
1811 |
+
<?php
|
1812 |
+
do_action( 'crontrol/tab-header', $tab, $tabs );
|
1813 |
+
?>
|
1814 |
+
</div>
|
1815 |
+
<?php
|
1816 |
+
}
|
1817 |
+
|
1818 |
+
/**
|
1819 |
+
* Returns an array of the callback functions that are attached to the given hook name.
|
1820 |
+
*
|
1821 |
+
* @param string $name The hook name.
|
1822 |
+
* @return array<int,array<string,mixed>> Array of callbacks attached to the hook.
|
1823 |
+
* @phpstan-return array<int,array{
|
1824 |
+
* priority: int,
|
1825 |
+
* callback: array<string,mixed>,
|
1826 |
+
* }>
|
1827 |
+
*/
|
1828 |
+
function get_hook_callbacks( $name ) {
|
1829 |
+
global $wp_filter;
|
1830 |
+
|
1831 |
+
$actions = array();
|
1832 |
+
|
1833 |
+
if ( isset( $wp_filter[ $name ] ) ) {
|
1834 |
+
// See http://core.trac.wordpress.org/ticket/17817.
|
1835 |
+
$action = $wp_filter[ $name ];
|
1836 |
+
|
1837 |
+
/**
|
1838 |
+
* @var int $priority
|
1839 |
+
*/
|
1840 |
+
foreach ( $action as $priority => $callbacks ) {
|
1841 |
+
foreach ( $callbacks as $callback ) {
|
1842 |
+
$callback = populate_callback( $callback );
|
1843 |
+
|
1844 |
+
if ( __NAMESPACE__ . '\\pauser' === $callback['function'] ) {
|
1845 |
+
continue;
|
1846 |
+
}
|
1847 |
+
|
1848 |
+
$actions[] = array(
|
1849 |
+
'priority' => $priority,
|
1850 |
+
'callback' => $callback,
|
1851 |
+
);
|
1852 |
+
}
|
1853 |
+
}
|
1854 |
+
}
|
1855 |
+
|
1856 |
+
return $actions;
|
1857 |
+
}
|
1858 |
+
|
1859 |
+
/**
|
1860 |
+
* Populates the details of the given callback function.
|
1861 |
+
*
|
1862 |
+
* @param array<string,mixed> $callback A callback entry.
|
1863 |
+
* @phpstan-param array{
|
1864 |
+
* function: string|array<int,mixed>|object,
|
1865 |
+
* accepted_args: int,
|
1866 |
+
* } $callback
|
1867 |
+
* @return array<string,mixed> The updated callback entry.
|
1868 |
+
*/
|
1869 |
+
function populate_callback( array $callback ) {
|
1870 |
+
// If Query Monitor is installed, use its rich callback analysis.
|
1871 |
+
if ( method_exists( '\QM_Util', 'populate_callback' ) ) {
|
1872 |
+
return \QM_Util::populate_callback( $callback );
|
1873 |
+
}
|
1874 |
+
|
1875 |
+
if ( is_string( $callback['function'] ) && ( false !== strpos( $callback['function'], '::' ) ) ) {
|
1876 |
+
$callback['function'] = explode( '::', $callback['function'] );
|
1877 |
+
}
|
1878 |
+
|
1879 |
+
if ( is_array( $callback['function'] ) ) {
|
1880 |
+
if ( is_object( $callback['function'][0] ) ) {
|
1881 |
+
$class = get_class( $callback['function'][0] );
|
1882 |
+
$access = '->';
|
1883 |
+
} else {
|
1884 |
+
$class = $callback['function'][0];
|
1885 |
+
$access = '::';
|
1886 |
+
}
|
1887 |
+
|
1888 |
+
$callback['name'] = $class . $access . $callback['function'][1] . '()';
|
1889 |
+
} elseif ( is_object( $callback['function'] ) ) {
|
1890 |
+
if ( is_a( $callback['function'], 'Closure' ) ) {
|
1891 |
+
$callback['name'] = 'Closure';
|
1892 |
+
} else {
|
1893 |
+
$class = get_class( $callback['function'] );
|
1894 |
+
|
1895 |
+
$callback['name'] = $class . '->__invoke()';
|
1896 |
+
}
|
1897 |
+
} else {
|
1898 |
+
$callback['name'] = $callback['function'] . '()';
|
1899 |
+
}
|
1900 |
+
|
1901 |
+
if ( ! method_exists( '\QM_Util', 'populate_callback' ) && ! is_callable( $callback['function'] ) ) {
|
1902 |
+
$callback['error'] = new WP_Error(
|
1903 |
+
'not_callable',
|
1904 |
+
sprintf(
|
1905 |
+
/* translators: %s: Function name */
|
1906 |
+
__( 'Function %s does not exist', 'wp-crontrol' ),
|
1907 |
+
$callback['name']
|
1908 |
+
)
|
1909 |
+
);
|
1910 |
+
}
|
1911 |
+
|
1912 |
+
return $callback;
|
1913 |
+
}
|
1914 |
+
|
1915 |
+
/**
|
1916 |
+
* Returns a user-friendly representation of the callback function.
|
1917 |
+
*
|
1918 |
+
* @param mixed[] $callback The callback entry.
|
1919 |
+
* @return string The displayable version of the callback name.
|
1920 |
+
*/
|
1921 |
+
function output_callback( array $callback ) {
|
1922 |
+
$qm = WP_PLUGIN_DIR . '/query-monitor/query-monitor.php';
|
1923 |
+
$html = plugin_dir_path( $qm ) . 'output/Html.php';
|
1924 |
+
|
1925 |
+
if ( ! empty( $callback['callback']['error'] ) ) {
|
1926 |
+
$return = '<code>' . $callback['callback']['name'] . '</code>';
|
1927 |
+
$return .= '<br><span class="status-crontrol-error"><span class="dashicons dashicons-warning" aria-hidden="true"></span> ';
|
1928 |
+
$return .= esc_html( $callback['callback']['error']->get_error_message() );
|
1929 |
+
$return .= '</span>';
|
1930 |
+
return $return;
|
1931 |
+
}
|
1932 |
+
|
1933 |
+
// If Query Monitor is installed, use its rich callback output.
|
1934 |
+
if ( class_exists( '\QueryMonitor' ) && file_exists( $html ) ) {
|
1935 |
+
require_once $html;
|
1936 |
+
|
1937 |
+
if ( class_exists( '\QM_Output_Html' ) ) {
|
1938 |
+
return \QM_Output_Html::output_filename(
|
1939 |
+
$callback['callback']['name'],
|
1940 |
+
$callback['callback']['file'],
|
1941 |
+
$callback['callback']['line']
|
1942 |
+
);
|
1943 |
+
}
|
1944 |
+
}
|
1945 |
+
|
1946 |
+
return '<code>' . $callback['callback']['name'] . '</code>';
|
1947 |
+
}
|
1948 |
+
|
1949 |
+
/**
|
1950 |
+
* Pretty-prints the difference in two times.
|
1951 |
+
*
|
1952 |
+
* @param int $older_date Unix timestamp.
|
1953 |
+
* @param int $newer_date Unix timestamp.
|
1954 |
+
* @return string The pretty time_since value
|
1955 |
+
* @link http://binarybonsai.com/code/timesince.txt
|
1956 |
+
*/
|
1957 |
+
function time_since( $older_date, $newer_date ) {
|
1958 |
+
return interval( $newer_date - $older_date );
|
1959 |
+
}
|
1960 |
+
|
1961 |
+
/**
|
1962 |
+
* Converts a period of time in seconds into a human-readable format representing the interval.
|
1963 |
+
*
|
1964 |
+
* Example:
|
1965 |
+
*
|
1966 |
+
* echo \Crontrol\interval( 90 );
|
1967 |
+
* // 1 minute 30 seconds
|
1968 |
+
*
|
1969 |
+
* @param int|float $since A period of time in seconds.
|
1970 |
+
* @return string An interval represented as a string.
|
1971 |
+
*/
|
1972 |
+
function interval( $since ) {
|
1973 |
+
// Array of time period chunks.
|
1974 |
+
$chunks = array(
|
1975 |
+
/* translators: 1: The number of years in an interval of time. */
|
1976 |
+
array( 60 * 60 * 24 * 365, _n_noop( '%s year', '%s years', 'wp-crontrol' ) ),
|
1977 |
+
/* translators: 1: The number of months in an interval of time. */
|
1978 |
+
array( 60 * 60 * 24 * 30, _n_noop( '%s month', '%s months', 'wp-crontrol' ) ),
|
1979 |
+
/* translators: 1: The number of weeks in an interval of time. */
|
1980 |
+
array( 60 * 60 * 24 * 7, _n_noop( '%s week', '%s weeks', 'wp-crontrol' ) ),
|
1981 |
+
/* translators: 1: The number of days in an interval of time. */
|
1982 |
+
array( 60 * 60 * 24, _n_noop( '%s day', '%s days', 'wp-crontrol' ) ),
|
1983 |
+
/* translators: 1: The number of hours in an interval of time. */
|
1984 |
+
array( 60 * 60, _n_noop( '%s hour', '%s hours', 'wp-crontrol' ) ),
|
1985 |
+
/* translators: 1: The number of minutes in an interval of time. */
|
1986 |
+
array( 60, _n_noop( '%s minute', '%s minutes', 'wp-crontrol' ) ),
|
1987 |
+
/* translators: 1: The number of seconds in an interval of time. */
|
1988 |
+
array( 1, _n_noop( '%s second', '%s seconds', 'wp-crontrol' ) ),
|
1989 |
+
);
|
1990 |
+
|
1991 |
+
if ( $since <= 0 ) {
|
1992 |
+
return __( 'now', 'wp-crontrol' );
|
1993 |
+
}
|
1994 |
+
|
1995 |
+
/**
|
1996 |
+
* We only want to output two chunks of time here, eg:
|
1997 |
+
* x years, xx months
|
1998 |
+
* x days, xx hours
|
1999 |
+
* so there's only two bits of calculation below:
|
2000 |
+
*/
|
2001 |
+
|
2002 |
+
// Step one: the first chunk.
|
2003 |
+
foreach ( array_keys( $chunks ) as $i ) {
|
2004 |
+
$seconds = $chunks[ $i ][0];
|
2005 |
+
$name = $chunks[ $i ][1];
|
2006 |
+
|
2007 |
+
// Finding the biggest chunk (if the chunk fits, break).
|
2008 |
+
$count = (int) floor( $since / $seconds );
|
2009 |
+
if ( $count ) {
|
2010 |
+
break;
|
2011 |
+
}
|
2012 |
+
}
|
2013 |
+
|
2014 |
+
// Set output var.
|
2015 |
+
$output = sprintf( translate_nooped_plural( $name, $count, 'wp-crontrol' ), $count );
|
2016 |
+
|
2017 |
+
// Step two: the second chunk.
|
2018 |
+
if ( $i + 1 < count( $chunks ) ) {
|
2019 |
+
$seconds2 = $chunks[ $i + 1 ][0];
|
2020 |
+
$name2 = $chunks[ $i + 1 ][1];
|
2021 |
+
$count2 = (int) floor( ( $since - ( $seconds * $count ) ) / $seconds2 );
|
2022 |
+
if ( $count2 ) {
|
2023 |
+
// Add to output var.
|
2024 |
+
$output .= ' ' . sprintf( translate_nooped_plural( $name2, $count2, 'wp-crontrol' ), $count2 );
|
2025 |
+
}
|
2026 |
+
}
|
2027 |
+
|
2028 |
+
return $output;
|
2029 |
+
}
|
2030 |
+
|
2031 |
+
/**
|
2032 |
+
* Sets up the Events listing screen.
|
2033 |
+
*
|
2034 |
+
* @return void
|
2035 |
+
*/
|
2036 |
+
function setup_manage_page() {
|
2037 |
+
// Initialise the list table
|
2038 |
+
Event\get_list_table();
|
2039 |
+
|
2040 |
+
// Add the initially hidden admin notice about the out of date events list
|
2041 |
+
add_action( 'admin_notices', function() {
|
2042 |
+
printf(
|
2043 |
+
'<div id="crontrol-hash-message" class="notice notice-info"><p>%s</p></div>',
|
2044 |
+
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' )
|
2045 |
+
);
|
2046 |
+
} );
|
2047 |
+
}
|
2048 |
+
|
2049 |
+
/**
|
2050 |
+
* Registers the stylesheet and JavaScript for the admin areas.
|
2051 |
+
*
|
2052 |
+
* @param string $hook_suffix The admin screen ID.
|
2053 |
+
* @return void
|
2054 |
+
*/
|
2055 |
+
function enqueue_assets( $hook_suffix ) {
|
2056 |
+
$tab = get_tab_states();
|
2057 |
+
|
2058 |
+
if ( ! array_filter( $tab ) ) {
|
2059 |
+
return;
|
2060 |
+
}
|
2061 |
+
|
2062 |
+
$ver = (string) filemtime( plugin_dir_path( PLUGIN_FILE ) . 'css/wp-crontrol.css' );
|
2063 |
+
wp_enqueue_style( 'wp-crontrol', plugin_dir_url( PLUGIN_FILE ) . 'css/wp-crontrol.css', array( 'dashicons' ), $ver );
|
2064 |
+
|
2065 |
+
$ver = (string) filemtime( plugin_dir_path( PLUGIN_FILE ) . 'js/wp-crontrol.js' );
|
2066 |
+
wp_enqueue_script( 'wp-crontrol', plugin_dir_url( PLUGIN_FILE ) . 'js/wp-crontrol.js', array( 'jquery', 'wp-a11y' ), $ver, true );
|
2067 |
+
|
2068 |
+
$vars = array();
|
2069 |
+
|
2070 |
+
if ( ! empty( $tab['events'] ) ) {
|
2071 |
+
$data = json_encode( Event\get() );
|
2072 |
+
|
2073 |
+
if ( false !== $data ) {
|
2074 |
+
$vars['eventsHash'] = md5( $data );
|
2075 |
+
$vars['eventsHashInterval'] = 20;
|
2076 |
+
}
|
2077 |
+
}
|
2078 |
+
|
2079 |
+
if ( ! empty( $tab['add-event'] ) || ! empty( $tab['edit-event'] ) ) {
|
2080 |
+
if ( function_exists( 'wp_enqueue_code_editor' ) && current_user_can( 'edit_files' ) ) {
|
2081 |
+
$settings = wp_enqueue_code_editor( array(
|
2082 |
+
'type' => 'text/x-php',
|
2083 |
+
) );
|
2084 |
+
|
2085 |
+
if ( false !== $settings ) {
|
2086 |
+
$vars['codeEditor'] = $settings;
|
2087 |
+
}
|
2088 |
+
}
|
2089 |
+
}
|
2090 |
+
|
2091 |
+
wp_localize_script( 'wp-crontrol', 'wpCrontrol', $vars );
|
2092 |
+
}
|
2093 |
+
|
2094 |
+
/**
|
2095 |
+
* Filters the list of query arguments which get removed from admin area URLs in WordPress.
|
2096 |
+
*
|
2097 |
+
* @param array<int,string> $args List of removable query arguments.
|
2098 |
+
* @return array<int,string> Updated list of removable query arguments.
|
2099 |
+
*/
|
2100 |
+
function filter_removable_query_args( array $args ) {
|
2101 |
+
return array_merge( $args, array(
|
2102 |
+
'crontrol_message',
|
2103 |
+
'crontrol_name',
|
2104 |
+
) );
|
2105 |
+
}
|
2106 |
+
|
2107 |
+
/**
|
2108 |
+
* Returns an array of cron event hooks that are persistently added by WordPress core.
|
2109 |
+
*
|
2110 |
+
* @return array<int,string> Array of hook names.
|
2111 |
+
*/
|
2112 |
+
function get_persistent_core_hooks() {
|
2113 |
+
return array(
|
2114 |
+
'wp_update_plugins', // 2.7.0
|
2115 |
+
'wp_update_themes', // 2.7.0
|
2116 |
+
'wp_version_check', // 2.7.0
|
2117 |
+
'wp_scheduled_delete', // 2.9.0
|
2118 |
+
'update_network_counts', // 3.1.0
|
2119 |
+
'wp_scheduled_auto_draft_delete', // 3.4.0
|
2120 |
+
'delete_expired_transients', // 4.9.0
|
2121 |
+
'wp_privacy_delete_old_export_files', // 4.9.6
|
2122 |
+
'recovery_mode_clean_expired_keys', // 5.2.0
|
2123 |
+
'wp_site_health_scheduled_check', // 5.4.0
|
2124 |
+
'wp_https_detection', // 5.7.0
|
2125 |
+
'wp_update_user_counts', // 6.0.0
|
2126 |
+
);
|
2127 |
+
}
|
2128 |
+
|
2129 |
+
/**
|
2130 |
+
* Returns an array of all cron event hooks that are added by WordPress core.
|
2131 |
+
*
|
2132 |
+
* @return array<int,string> Array of hook names.
|
2133 |
+
*/
|
2134 |
+
function get_all_core_hooks() {
|
2135 |
+
return array_merge(
|
2136 |
+
get_persistent_core_hooks(),
|
2137 |
+
array(
|
2138 |
+
'do_pings', // 2.1.0
|
2139 |
+
'publish_future_post', // 2.1.0
|
2140 |
+
'importer_scheduled_cleanup', // 2.5.0
|
2141 |
+
'upgrader_scheduled_cleanup', // 3.2.2
|
2142 |
+
'wp_maybe_auto_update', // 3.7.0
|
2143 |
+
'wp_split_shared_term_batch', // 4.3.0
|
2144 |
+
'wp_update_comment_type_batch', // 5.5.0
|
2145 |
+
'wp_delete_temp_updater_backups', // 5.9.0
|
2146 |
+
)
|
2147 |
+
);
|
2148 |
+
}
|
2149 |
+
|
2150 |
+
/**
|
2151 |
+
* Returns an array of cron schedules that are added by WordPress core.
|
2152 |
+
*
|
2153 |
+
* @return array<int,string> Array of schedule names.
|
2154 |
+
*/
|
2155 |
+
function get_core_schedules() {
|
2156 |
+
return array(
|
2157 |
+
'hourly',
|
2158 |
+
'twicedaily',
|
2159 |
+
'daily',
|
2160 |
+
'weekly',
|
2161 |
+
);
|
2162 |
+
}
|
2163 |
+
|
2164 |
+
/**
|
2165 |
+
* Encodes some input as JSON for output.
|
2166 |
+
*
|
2167 |
+
* @param mixed $input The input.
|
2168 |
+
* @param bool $pretty Whether to pretty print the output. Default true.
|
2169 |
+
* @return string The JSON-encoded output.
|
2170 |
+
*/
|
2171 |
+
function json_output( $input, $pretty = true ) {
|
2172 |
+
$json_options = 0;
|
2173 |
+
|
2174 |
+
if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) {
|
2175 |
+
// phpcs:ignore PHPCompatibility.Constants.NewConstants.json_unescaped_slashesFound
|
2176 |
+
$json_options |= JSON_UNESCAPED_SLASHES;
|
2177 |
+
}
|
2178 |
+
|
2179 |
+
if ( $pretty && defined( 'JSON_PRETTY_PRINT' ) ) {
|
2180 |
+
$json_options |= JSON_PRETTY_PRINT;
|
2181 |
+
}
|
2182 |
+
|
2183 |
+
$output = wp_json_encode( $input, $json_options );
|
2184 |
+
|
2185 |
+
if ( false === $output ) {
|
2186 |
+
$output = '';
|
2187 |
+
}
|
2188 |
+
|
2189 |
+
return $output;
|
2190 |
+
}
|
2191 |
+
|
2192 |
+
/**
|
2193 |
+
* Evaluates the code in a PHP cron event using eval.
|
2194 |
+
*
|
2195 |
+
* Security: Only users with the `edit_files` capability can manage PHP cron events. This means if a user cannot edit
|
2196 |
+
* files on the site (eg. through the Plugin Editor or Theme Editor) then they cannot edit or add a PHP cron event. By
|
2197 |
+
* default, only Administrators have this capability, and with Multisite enabled only Super Admins have this capability.
|
2198 |
+
*
|
2199 |
+
* If file editing has been disabled via the `DISALLOW_FILE_MODS` or `DISALLOW_FILE_EDIT` configuration constants then
|
2200 |
+
* no user will have the `edit_files` capability, which means editing or adding a PHP cron event will not be permitted.
|
2201 |
+
*
|
2202 |
+
* Therefore, the user access level required to execute arbitrary PHP code does not change with WP Crontrol activated.
|
2203 |
+
*
|
2204 |
+
* @param string $code The PHP code to evaluate.
|
2205 |
+
* @return void
|
2206 |
+
*/
|
2207 |
+
function action_php_cron_event( $code ) {
|
2208 |
+
// phpcs:ignore Squiz.PHP.Eval.Discouraged
|
2209 |
+
eval( $code );
|
2210 |
+
}
|
src/event-list-table.php
CHANGED
@@ -136,6 +136,14 @@ class Table extends \WP_List_Table {
|
|
136 |
return ( ! in_array( $event->hook, $all_core_hooks, true ) );
|
137 |
} );
|
138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
/**
|
140 |
* Filters the available filtered events on the cron event listing screen.
|
141 |
*
|
@@ -222,6 +230,7 @@ class Table extends \WP_List_Table {
|
|
222 |
'noaction' => __( 'Events with no action', 'wp-crontrol' ),
|
223 |
'core' => __( 'WordPress core events', 'wp-crontrol' ),
|
224 |
'custom' => __( 'Custom events', 'wp-crontrol' ),
|
|
|
225 |
);
|
226 |
|
227 |
/**
|
@@ -305,7 +314,7 @@ class Table extends \WP_List_Table {
|
|
305 |
$callbacks = \Crontrol\get_hook_callbacks( $event->hook );
|
306 |
|
307 |
if ( ! $callbacks ) {
|
308 |
-
$classes[] = 'crontrol-
|
309 |
} else {
|
310 |
foreach ( $callbacks as $callback ) {
|
311 |
if ( ! empty( $callback['callback']['error'] ) ) {
|
@@ -319,6 +328,10 @@ class Table extends \WP_List_Table {
|
|
319 |
$classes[] = 'crontrol-warning';
|
320 |
}
|
321 |
|
|
|
|
|
|
|
|
|
322 |
printf(
|
323 |
'<tr class="%s">',
|
324 |
esc_attr( implode( ' ', $classes ) )
|
@@ -349,24 +362,50 @@ class Table extends \WP_List_Table {
|
|
349 |
'crontrol_action' => 'edit-cron',
|
350 |
'crontrol_id' => rawurlencode( $event->hook ),
|
351 |
'crontrol_sig' => rawurlencode( $event->sig ),
|
352 |
-
'crontrol_next_run_utc' => rawurlencode( $event->
|
353 |
);
|
354 |
$link = add_query_arg( $link, admin_url( 'tools.php' ) );
|
355 |
|
356 |
$links[] = "<a href='" . esc_url( $link ) . "'>" . esc_html__( 'Edit', 'wp-crontrol' ) . '</a>';
|
357 |
}
|
358 |
|
359 |
-
$
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
|
|
368 |
|
369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
370 |
|
371 |
if ( ! in_array( $event->hook, self::$persistent_core_hooks, true ) && ( ( 'crontrol_cron_job' !== $event->hook ) || self::$can_manage_php_crons ) ) {
|
372 |
$link = array(
|
@@ -374,10 +413,10 @@ class Table extends \WP_List_Table {
|
|
374 |
'crontrol_action' => 'delete-cron',
|
375 |
'crontrol_id' => rawurlencode( $event->hook ),
|
376 |
'crontrol_sig' => rawurlencode( $event->sig ),
|
377 |
-
'crontrol_next_run_utc' => rawurlencode( $event->
|
378 |
);
|
379 |
$link = add_query_arg( $link, admin_url( 'tools.php' ) );
|
380 |
-
$link = wp_nonce_url( $link, "crontrol-delete-cron_{$event->hook}_{$event->sig}_{$event->
|
381 |
|
382 |
$links[] = "<span class='delete'><a href='" . esc_url( $link ) . "'>" . esc_html__( 'Delete', 'wp-crontrol' ) . '</a></span>';
|
383 |
}
|
@@ -413,7 +452,7 @@ class Table extends \WP_List_Table {
|
|
413 |
protected function column_cb( $event ) {
|
414 |
$id = sprintf(
|
415 |
'crontrol-delete-%1$s-%2$s-%3$s',
|
416 |
-
$event->
|
417 |
rawurlencode( $event->hook ),
|
418 |
$event->sig
|
419 |
);
|
@@ -430,7 +469,7 @@ class Table extends \WP_List_Table {
|
|
430 |
<input type="checkbox" name="crontrol_delete[%3$s][%4$s]" value="%5$s" id="%1$s">',
|
431 |
esc_attr( $id ),
|
432 |
esc_html__( 'Select this row', 'wp-crontrol' ),
|
433 |
-
esc_attr( $event->
|
434 |
esc_attr( rawurlencode( $event->hook ) ),
|
435 |
esc_attr( $event->sig )
|
436 |
);
|
@@ -455,7 +494,17 @@ class Table extends \WP_List_Table {
|
|
455 |
}
|
456 |
}
|
457 |
|
458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
459 |
}
|
460 |
|
461 |
/**
|
@@ -543,14 +592,14 @@ class Table extends \WP_List_Table {
|
|
543 |
protected function column_crontrol_next( $event ) {
|
544 |
$date_local_format = 'Y-m-d H:i:s';
|
545 |
$offset_site = get_date_from_gmt( 'now', 'P' );
|
546 |
-
$offset_event = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $event->
|
547 |
|
548 |
if ( $offset_site !== $offset_event ) {
|
549 |
$date_local_format .= ' P';
|
550 |
}
|
551 |
|
552 |
-
$date_utc = gmdate( 'c', $event->
|
553 |
-
$date_local = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $event->
|
554 |
|
555 |
$time = sprintf(
|
556 |
'<time datetime="%1$s">%2$s</time>',
|
@@ -558,7 +607,7 @@ class Table extends \WP_List_Table {
|
|
558 |
esc_html( $date_local )
|
559 |
);
|
560 |
|
561 |
-
$until = $event->
|
562 |
$late = is_late( $event );
|
563 |
|
564 |
if ( $late ) {
|
136 |
return ( ! in_array( $event->hook, $all_core_hooks, true ) );
|
137 |
} );
|
138 |
|
139 |
+
$paused = array_filter( $events, function( $event ) {
|
140 |
+
return ( is_paused( $event ) );
|
141 |
+
} );
|
142 |
+
|
143 |
+
if ( count( $paused ) > 0 ) {
|
144 |
+
$filtered['paused'] = $paused;
|
145 |
+
}
|
146 |
+
|
147 |
/**
|
148 |
* Filters the available filtered events on the cron event listing screen.
|
149 |
*
|
230 |
'noaction' => __( 'Events with no action', 'wp-crontrol' ),
|
231 |
'core' => __( 'WordPress core events', 'wp-crontrol' ),
|
232 |
'custom' => __( 'Custom events', 'wp-crontrol' ),
|
233 |
+
'paused' => __( 'Paused events', 'wp-crontrol' ),
|
234 |
);
|
235 |
|
236 |
/**
|
314 |
$callbacks = \Crontrol\get_hook_callbacks( $event->hook );
|
315 |
|
316 |
if ( ! $callbacks ) {
|
317 |
+
$classes[] = 'crontrol-no-action';
|
318 |
} else {
|
319 |
foreach ( $callbacks as $callback ) {
|
320 |
if ( ! empty( $callback['callback']['error'] ) ) {
|
328 |
$classes[] = 'crontrol-warning';
|
329 |
}
|
330 |
|
331 |
+
if ( is_paused( $event ) ) {
|
332 |
+
$classes[] = 'crontrol-paused';
|
333 |
+
}
|
334 |
+
|
335 |
printf(
|
336 |
'<tr class="%s">',
|
337 |
esc_attr( implode( ' ', $classes ) )
|
362 |
'crontrol_action' => 'edit-cron',
|
363 |
'crontrol_id' => rawurlencode( $event->hook ),
|
364 |
'crontrol_sig' => rawurlencode( $event->sig ),
|
365 |
+
'crontrol_next_run_utc' => rawurlencode( $event->timestamp ),
|
366 |
);
|
367 |
$link = add_query_arg( $link, admin_url( 'tools.php' ) );
|
368 |
|
369 |
$links[] = "<a href='" . esc_url( $link ) . "'>" . esc_html__( 'Edit', 'wp-crontrol' ) . '</a>';
|
370 |
}
|
371 |
|
372 |
+
if ( ! is_paused( $event ) ) {
|
373 |
+
$link = array(
|
374 |
+
'page' => 'crontrol_admin_manage_page',
|
375 |
+
'crontrol_action' => 'run-cron',
|
376 |
+
'crontrol_id' => rawurlencode( $event->hook ),
|
377 |
+
'crontrol_sig' => rawurlencode( $event->sig ),
|
378 |
+
'crontrol_next_run_utc' => rawurlencode( $event->timestamp ),
|
379 |
+
);
|
380 |
+
$link = add_query_arg( $link, admin_url( 'tools.php' ) );
|
381 |
+
$link = wp_nonce_url( $link, "crontrol-run-cron_{$event->hook}_{$event->sig}" );
|
382 |
|
383 |
+
$links[] = "<a href='" . esc_url( $link ) . "'>" . esc_html__( 'Run Now', 'wp-crontrol' ) . '</a>';
|
384 |
+
}
|
385 |
+
|
386 |
+
if ( is_paused( $event ) ) {
|
387 |
+
$link = array(
|
388 |
+
'page' => 'crontrol_admin_manage_page',
|
389 |
+
'crontrol_action' => 'resume-hook',
|
390 |
+
'crontrol_id' => rawurlencode( $event->hook ),
|
391 |
+
);
|
392 |
+
$link = add_query_arg( $link, admin_url( 'tools.php' ) );
|
393 |
+
$link = wp_nonce_url( $link, "crontrol-resume-hook_{$event->hook}" );
|
394 |
+
|
395 |
+
/* translators: Verb */
|
396 |
+
$links[] = "<a href='" . esc_url( $link ) . "'>" . esc_html__( 'Resume', 'wp-crontrol' ) . '</a>';
|
397 |
+
} elseif ( 'crontrol_cron_job' !== $event->hook ) {
|
398 |
+
$link = array(
|
399 |
+
'page' => 'crontrol_admin_manage_page',
|
400 |
+
'crontrol_action' => 'pause-hook',
|
401 |
+
'crontrol_id' => rawurlencode( $event->hook ),
|
402 |
+
);
|
403 |
+
$link = add_query_arg( $link, admin_url( 'tools.php' ) );
|
404 |
+
$link = wp_nonce_url( $link, "crontrol-pause-hook_{$event->hook}" );
|
405 |
+
|
406 |
+
/* translators: Verb */
|
407 |
+
$links[] = "<a href='" . esc_url( $link ) . "'>" . esc_html__( 'Pause', 'wp-crontrol' ) . '</a>';
|
408 |
+
}
|
409 |
|
410 |
if ( ! in_array( $event->hook, self::$persistent_core_hooks, true ) && ( ( 'crontrol_cron_job' !== $event->hook ) || self::$can_manage_php_crons ) ) {
|
411 |
$link = array(
|
413 |
'crontrol_action' => 'delete-cron',
|
414 |
'crontrol_id' => rawurlencode( $event->hook ),
|
415 |
'crontrol_sig' => rawurlencode( $event->sig ),
|
416 |
+
'crontrol_next_run_utc' => rawurlencode( $event->timestamp ),
|
417 |
);
|
418 |
$link = add_query_arg( $link, admin_url( 'tools.php' ) );
|
419 |
+
$link = wp_nonce_url( $link, "crontrol-delete-cron_{$event->hook}_{$event->sig}_{$event->timestamp}" );
|
420 |
|
421 |
$links[] = "<span class='delete'><a href='" . esc_url( $link ) . "'>" . esc_html__( 'Delete', 'wp-crontrol' ) . '</a></span>';
|
422 |
}
|
452 |
protected function column_cb( $event ) {
|
453 |
$id = sprintf(
|
454 |
'crontrol-delete-%1$s-%2$s-%3$s',
|
455 |
+
$event->timestamp,
|
456 |
rawurlencode( $event->hook ),
|
457 |
$event->sig
|
458 |
);
|
469 |
<input type="checkbox" name="crontrol_delete[%3$s][%4$s]" value="%5$s" id="%1$s">',
|
470 |
esc_attr( $id ),
|
471 |
esc_html__( 'Select this row', 'wp-crontrol' ),
|
472 |
+
esc_attr( $event->timestamp ),
|
473 |
esc_attr( rawurlencode( $event->hook ) ),
|
474 |
esc_attr( $event->sig )
|
475 |
);
|
494 |
}
|
495 |
}
|
496 |
|
497 |
+
$output = esc_html( $event->hook );
|
498 |
+
|
499 |
+
if ( is_paused( $event ) ) {
|
500 |
+
$output .= sprintf(
|
501 |
+
' — <strong class="status-crontrol-paused post-state"><span class="dashicons dashicons-controls-pause" aria-hidden="true"></span> %s</strong>',
|
502 |
+
/* translators: State of a cron event, adjective */
|
503 |
+
esc_html__( 'Paused', 'wp-crontrol' )
|
504 |
+
);
|
505 |
+
}
|
506 |
+
|
507 |
+
return $output;
|
508 |
}
|
509 |
|
510 |
/**
|
592 |
protected function column_crontrol_next( $event ) {
|
593 |
$date_local_format = 'Y-m-d H:i:s';
|
594 |
$offset_site = get_date_from_gmt( 'now', 'P' );
|
595 |
+
$offset_event = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $event->timestamp ), 'P' );
|
596 |
|
597 |
if ( $offset_site !== $offset_event ) {
|
598 |
$date_local_format .= ' P';
|
599 |
}
|
600 |
|
601 |
+
$date_utc = gmdate( 'c', $event->timestamp );
|
602 |
+
$date_local = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $event->timestamp ), $date_local_format );
|
603 |
|
604 |
$time = sprintf(
|
605 |
'<time datetime="%1$s">%2$s</time>',
|
607 |
esc_html( $date_local )
|
608 |
);
|
609 |
|
610 |
+
$until = $event->timestamp - time();
|
611 |
$late = is_late( $event );
|
612 |
|
613 |
if ( $late ) {
|
src/event.php
CHANGED
@@ -9,6 +9,8 @@ use stdClass;
|
|
9 |
use Crontrol\Schedule;
|
10 |
use WP_Error;
|
11 |
|
|
|
|
|
12 |
/**
|
13 |
* Executes a cron event immediately.
|
14 |
*
|
@@ -244,6 +246,72 @@ function delete( $hook, $sig, $next_run_utc ) {
|
|
244 |
return true;
|
245 |
}
|
246 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
247 |
/**
|
248 |
* Returns a flattened array of cron events.
|
249 |
*
|
@@ -264,7 +332,7 @@ function get() {
|
|
264 |
// This is a prime candidate for a Crontrol_Event class but I'm not bothering currently.
|
265 |
$events[ "$hook-$sig-$time" ] = (object) array(
|
266 |
'hook' => $hook,
|
267 |
-
'
|
268 |
'sig' => $sig,
|
269 |
'args' => $data['args'],
|
270 |
'schedule' => $data['schedule'],
|
@@ -351,7 +419,7 @@ function get_schedule_name( stdClass $event ) {
|
|
351 |
$schedules = Schedule\get();
|
352 |
|
353 |
if ( isset( $schedules[ $event->schedule ] ) ) {
|
354 |
-
return $schedules[ $event->schedule ]['display'];
|
355 |
}
|
356 |
|
357 |
return new WP_Error( 'unknown_schedule', sprintf(
|
@@ -386,11 +454,27 @@ function is_too_frequent( stdClass $event ) {
|
|
386 |
* @return bool Whether the event is late.
|
387 |
*/
|
388 |
function is_late( stdClass $event ) {
|
389 |
-
$until = $event->
|
390 |
|
391 |
return ( $until < ( 0 - ( 10 * MINUTE_IN_SECONDS ) ) );
|
392 |
}
|
393 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
394 |
/**
|
395 |
* Initialises and returns the list table for events.
|
396 |
*
|
@@ -400,8 +484,6 @@ function get_list_table() {
|
|
400 |
static $table = null;
|
401 |
|
402 |
if ( ! $table ) {
|
403 |
-
require_once __DIR__ . '/event-list-table.php';
|
404 |
-
|
405 |
$table = new Table();
|
406 |
$table->prepare_items();
|
407 |
|
@@ -434,13 +516,13 @@ function uasort_order_events( $a, $b ) {
|
|
434 |
}
|
435 |
break;
|
436 |
default:
|
437 |
-
if ( $a->
|
438 |
$compare = 0;
|
439 |
} else {
|
440 |
if ( 'desc' === $order ) {
|
441 |
-
$compare = ( $a->
|
442 |
} else {
|
443 |
-
$compare = ( $a->
|
444 |
}
|
445 |
}
|
446 |
break;
|
9 |
use Crontrol\Schedule;
|
10 |
use WP_Error;
|
11 |
|
12 |
+
use const Crontrol\PAUSED_OPTION;
|
13 |
+
|
14 |
/**
|
15 |
* Executes a cron event immediately.
|
16 |
*
|
246 |
return true;
|
247 |
}
|
248 |
|
249 |
+
/**
|
250 |
+
* Pauses a cron event.
|
251 |
+
*
|
252 |
+
* @param string $hook The hook name of the event to pause.
|
253 |
+
* @return true|WP_Error True if the pause was successful, WP_Error otherwise.
|
254 |
+
*/
|
255 |
+
function pause( $hook ) {
|
256 |
+
$paused = get_option( PAUSED_OPTION, array() );
|
257 |
+
|
258 |
+
if ( ! is_array( $paused ) ) {
|
259 |
+
$paused = array();
|
260 |
+
}
|
261 |
+
|
262 |
+
$paused[ $hook ] = true;
|
263 |
+
|
264 |
+
$result = update_option( PAUSED_OPTION, $paused, false );
|
265 |
+
|
266 |
+
if ( false === $result ) {
|
267 |
+
return new WP_Error(
|
268 |
+
'could_not_pause',
|
269 |
+
sprintf(
|
270 |
+
/* translators: 1: The name of the cron event. */
|
271 |
+
__( 'Failed to pause the cron event %s.', 'wp-crontrol' ),
|
272 |
+
$hook
|
273 |
+
)
|
274 |
+
);
|
275 |
+
}
|
276 |
+
|
277 |
+
return true;
|
278 |
+
}
|
279 |
+
|
280 |
+
/**
|
281 |
+
* Resumes a paused cron event.
|
282 |
+
*
|
283 |
+
* @param string $hook The hook name of the event to resume.
|
284 |
+
* @return true|WP_Error True if the resumption was successful, WP_Error otherwise.
|
285 |
+
*/
|
286 |
+
function resume( $hook ) {
|
287 |
+
$paused = get_option( PAUSED_OPTION );
|
288 |
+
|
289 |
+
if ( ! is_array( $paused ) || ( count( $paused ) === 0 ) ) {
|
290 |
+
return true;
|
291 |
+
}
|
292 |
+
|
293 |
+
unset( $paused[ $hook ] );
|
294 |
+
|
295 |
+
if ( count( $paused ) === 0 ) {
|
296 |
+
$result = delete_option( PAUSED_OPTION );
|
297 |
+
} else {
|
298 |
+
$result = update_option( PAUSED_OPTION, $paused, false );
|
299 |
+
}
|
300 |
+
|
301 |
+
if ( false === $result ) {
|
302 |
+
return new WP_Error(
|
303 |
+
'could_not_resume',
|
304 |
+
sprintf(
|
305 |
+
/* translators: 1: The name of the cron event. */
|
306 |
+
__( 'Failed to resume the cron event %s.', 'wp-crontrol' ),
|
307 |
+
$hook
|
308 |
+
)
|
309 |
+
);
|
310 |
+
}
|
311 |
+
|
312 |
+
return true;
|
313 |
+
}
|
314 |
+
|
315 |
/**
|
316 |
* Returns a flattened array of cron events.
|
317 |
*
|
332 |
// This is a prime candidate for a Crontrol_Event class but I'm not bothering currently.
|
333 |
$events[ "$hook-$sig-$time" ] = (object) array(
|
334 |
'hook' => $hook,
|
335 |
+
'timestamp' => $time, // UTC
|
336 |
'sig' => $sig,
|
337 |
'args' => $data['args'],
|
338 |
'schedule' => $data['schedule'],
|
419 |
$schedules = Schedule\get();
|
420 |
|
421 |
if ( isset( $schedules[ $event->schedule ] ) ) {
|
422 |
+
return isset( $schedules[ $event->schedule ]['display'] ) ? $schedules[ $event->schedule ]['display'] : $schedules[ $event->schedule ]['name'];
|
423 |
}
|
424 |
|
425 |
return new WP_Error( 'unknown_schedule', sprintf(
|
454 |
* @return bool Whether the event is late.
|
455 |
*/
|
456 |
function is_late( stdClass $event ) {
|
457 |
+
$until = $event->timestamp - time();
|
458 |
|
459 |
return ( $until < ( 0 - ( 10 * MINUTE_IN_SECONDS ) ) );
|
460 |
}
|
461 |
|
462 |
+
/**
|
463 |
+
* Determines whether an event is paused.
|
464 |
+
*
|
465 |
+
* @param stdClass $event The event.
|
466 |
+
* @return bool Whether the event is paused.
|
467 |
+
*/
|
468 |
+
function is_paused( stdClass $event ) {
|
469 |
+
$paused = get_option( PAUSED_OPTION );
|
470 |
+
|
471 |
+
if ( ! is_array( $paused ) ) {
|
472 |
+
return false;
|
473 |
+
}
|
474 |
+
|
475 |
+
return array_key_exists( $event->hook, $paused );
|
476 |
+
}
|
477 |
+
|
478 |
/**
|
479 |
* Initialises and returns the list table for events.
|
480 |
*
|
484 |
static $table = null;
|
485 |
|
486 |
if ( ! $table ) {
|
|
|
|
|
487 |
$table = new Table();
|
488 |
$table->prepare_items();
|
489 |
|
516 |
}
|
517 |
break;
|
518 |
default:
|
519 |
+
if ( $a->timestamp === $b->timestamp ) {
|
520 |
$compare = 0;
|
521 |
} else {
|
522 |
if ( 'desc' === $order ) {
|
523 |
+
$compare = ( $a->timestamp > $b->timestamp ) ? 1 : -1;
|
524 |
} else {
|
525 |
+
$compare = ( $a->timestamp < $b->timestamp ) ? 1 : -1;
|
526 |
}
|
527 |
}
|
528 |
break;
|
src/schedule-list-table.php
CHANGED
@@ -96,7 +96,7 @@ class Schedule_List_Table extends \WP_List_Table {
|
|
96 |
*
|
97 |
* @phpstan-param array{
|
98 |
* interval: int,
|
99 |
-
* display
|
100 |
* name: string,
|
101 |
* is_too_frequent: bool,
|
102 |
* } $schedule
|
@@ -139,7 +139,7 @@ class Schedule_List_Table extends \WP_List_Table {
|
|
139 |
*
|
140 |
* @phpstan-param array{
|
141 |
* interval: int,
|
142 |
-
* display
|
143 |
* name: string,
|
144 |
* is_too_frequent: bool,
|
145 |
* } $schedule
|
@@ -163,7 +163,7 @@ class Schedule_List_Table extends \WP_List_Table {
|
|
163 |
*
|
164 |
* @phpstan-param array{
|
165 |
* interval: int,
|
166 |
-
* display
|
167 |
* name: string,
|
168 |
* is_too_frequent: bool,
|
169 |
* } $schedule
|
@@ -179,7 +179,7 @@ class Schedule_List_Table extends \WP_List_Table {
|
|
179 |
*
|
180 |
* @phpstan-param array{
|
181 |
* interval: int,
|
182 |
-
* display
|
183 |
* name: string,
|
184 |
* is_too_frequent: bool,
|
185 |
* } $schedule
|
@@ -215,14 +215,14 @@ class Schedule_List_Table extends \WP_List_Table {
|
|
215 |
*
|
216 |
* @phpstan-param array{
|
217 |
* interval: int,
|
218 |
-
* display
|
219 |
* name: string,
|
220 |
* is_too_frequent: bool,
|
221 |
* } $schedule
|
222 |
* @return string The cell output.
|
223 |
*/
|
224 |
protected function column_crontrol_display( array $schedule ) {
|
225 |
-
return esc_html( $schedule['display'] );
|
226 |
}
|
227 |
|
228 |
/**
|
96 |
*
|
97 |
* @phpstan-param array{
|
98 |
* interval: int,
|
99 |
+
* display?: string,
|
100 |
* name: string,
|
101 |
* is_too_frequent: bool,
|
102 |
* } $schedule
|
139 |
*
|
140 |
* @phpstan-param array{
|
141 |
* interval: int,
|
142 |
+
* display?: string,
|
143 |
* name: string,
|
144 |
* is_too_frequent: bool,
|
145 |
* } $schedule
|
163 |
*
|
164 |
* @phpstan-param array{
|
165 |
* interval: int,
|
166 |
+
* display?: string,
|
167 |
* name: string,
|
168 |
* is_too_frequent: bool,
|
169 |
* } $schedule
|
179 |
*
|
180 |
* @phpstan-param array{
|
181 |
* interval: int,
|
182 |
+
* display?: string,
|
183 |
* name: string,
|
184 |
* is_too_frequent: bool,
|
185 |
* } $schedule
|
215 |
*
|
216 |
* @phpstan-param array{
|
217 |
* interval: int,
|
218 |
+
* display?: string,
|
219 |
* name: string,
|
220 |
* is_too_frequent: bool,
|
221 |
* } $schedule
|
222 |
* @return string The cell output.
|
223 |
*/
|
224 |
protected function column_crontrol_display( array $schedule ) {
|
225 |
+
return esc_html( isset( $schedule['display'] ) ? $schedule['display'] : $schedule['name'] );
|
226 |
}
|
227 |
|
228 |
/**
|
src/schedule.php
CHANGED
@@ -59,7 +59,7 @@ function delete( $name ) {
|
|
59 |
* @return array<string,array<string,(int|string)>> Array of cron schedule arrays.
|
60 |
* @phpstan-return array<string,array{
|
61 |
* interval: int,
|
62 |
-
* display
|
63 |
* name: string,
|
64 |
* is_too_frequent: bool,
|
65 |
* }>
|
@@ -68,7 +68,7 @@ function get() {
|
|
68 |
/**
|
69 |
* @phpstan-var array<string,array{
|
70 |
* interval: int,
|
71 |
-
* display
|
72 |
* }> $schedules
|
73 |
*/
|
74 |
$schedules = wp_get_schedules();
|
@@ -84,7 +84,7 @@ function get() {
|
|
84 |
/**
|
85 |
* @phpstan-var array<string,array{
|
86 |
* interval: int,
|
87 |
-
* display
|
88 |
* name: string,
|
89 |
* is_too_frequent: bool,
|
90 |
* }> $schedules
|
@@ -108,7 +108,7 @@ function dropdown( $current = false ) {
|
|
108 |
<?php
|
109 |
printf(
|
110 |
'%s (%s)',
|
111 |
-
esc_html( $sched_data['display'] ),
|
112 |
esc_html( $sched_name )
|
113 |
);
|
114 |
?>
|
59 |
* @return array<string,array<string,(int|string)>> Array of cron schedule arrays.
|
60 |
* @phpstan-return array<string,array{
|
61 |
* interval: int,
|
62 |
+
* display?: string,
|
63 |
* name: string,
|
64 |
* is_too_frequent: bool,
|
65 |
* }>
|
68 |
/**
|
69 |
* @phpstan-var array<string,array{
|
70 |
* interval: int,
|
71 |
+
* display?: string,
|
72 |
* }> $schedules
|
73 |
*/
|
74 |
$schedules = wp_get_schedules();
|
84 |
/**
|
85 |
* @phpstan-var array<string,array{
|
86 |
* interval: int,
|
87 |
+
* display?: string,
|
88 |
* name: string,
|
89 |
* is_too_frequent: bool,
|
90 |
* }> $schedules
|
108 |
<?php
|
109 |
printf(
|
110 |
'%s (%s)',
|
111 |
+
esc_html( isset( $sched_data['display'] ) ? $sched_data['display'] : $sched_data['name'] ),
|
112 |
esc_html( $sched_name )
|
113 |
);
|
114 |
?>
|
vendor/autoload.php
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// autoload.php @generated by Composer
|
4 |
+
|
5 |
+
require_once __DIR__ . '/composer/autoload_real.php';
|
6 |
+
|
7 |
+
return ComposerAutoloaderInitdbf095101a93cb72acf9efa892a78eaf::getLoader();
|
vendor/composer/ClassLoader.php
ADDED
@@ -0,0 +1,572 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/*
|
4 |
+
* This file is part of Composer.
|
5 |
+
*
|
6 |
+
* (c) Nils Adermann <naderman@naderman.de>
|
7 |
+
* Jordi Boggiano <j.boggiano@seld.be>
|
8 |
+
*
|
9 |
+
* For the full copyright and license information, please view the LICENSE
|
10 |
+
* file that was distributed with this source code.
|
11 |
+
*/
|
12 |
+
|
13 |
+
namespace Composer\Autoload;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
17 |
+
*
|
18 |
+
* $loader = new \Composer\Autoload\ClassLoader();
|
19 |
+
*
|
20 |
+
* // register classes with namespaces
|
21 |
+
* $loader->add('Symfony\Component', __DIR__.'/component');
|
22 |
+
* $loader->add('Symfony', __DIR__.'/framework');
|
23 |
+
*
|
24 |
+
* // activate the autoloader
|
25 |
+
* $loader->register();
|
26 |
+
*
|
27 |
+
* // to enable searching the include path (eg. for PEAR packages)
|
28 |
+
* $loader->setUseIncludePath(true);
|
29 |
+
*
|
30 |
+
* In this example, if you try to use a class in the Symfony\Component
|
31 |
+
* namespace or one of its children (Symfony\Component\Console for instance),
|
32 |
+
* the autoloader will first look for the class under the component/
|
33 |
+
* directory, and it will then fallback to the framework/ directory if not
|
34 |
+
* found before giving up.
|
35 |
+
*
|
36 |
+
* This class is loosely based on the Symfony UniversalClassLoader.
|
37 |
+
*
|
38 |
+
* @author Fabien Potencier <fabien@symfony.com>
|
39 |
+
* @author Jordi Boggiano <j.boggiano@seld.be>
|
40 |
+
* @see https://www.php-fig.org/psr/psr-0/
|
41 |
+
* @see https://www.php-fig.org/psr/psr-4/
|
42 |
+
*/
|
43 |
+
class ClassLoader
|
44 |
+
{
|
45 |
+
/** @var ?string */
|
46 |
+
private $vendorDir;
|
47 |
+
|
48 |
+
// PSR-4
|
49 |
+
/**
|
50 |
+
* @var array[]
|
51 |
+
* @psalm-var array<string, array<string, int>>
|
52 |
+
*/
|
53 |
+
private $prefixLengthsPsr4 = array();
|
54 |
+
/**
|
55 |
+
* @var array[]
|
56 |
+
* @psalm-var array<string, array<int, string>>
|
57 |
+
*/
|
58 |
+
private $prefixDirsPsr4 = array();
|
59 |
+
/**
|
60 |
+
* @var array[]
|
61 |
+
* @psalm-var array<string, string>
|
62 |
+
*/
|
63 |
+
private $fallbackDirsPsr4 = array();
|
64 |
+
|
65 |
+
// PSR-0
|
66 |
+
/**
|
67 |
+
* @var array[]
|
68 |
+
* @psalm-var array<string, array<string, string[]>>
|
69 |
+
*/
|
70 |
+
private $prefixesPsr0 = array();
|
71 |
+
/**
|
72 |
+
* @var array[]
|
73 |
+
* @psalm-var array<string, string>
|
74 |
+
*/
|
75 |
+
private $fallbackDirsPsr0 = array();
|
76 |
+
|
77 |
+
/** @var bool */
|
78 |
+
private $useIncludePath = false;
|
79 |
+
|
80 |
+
/**
|
81 |
+
* @var string[]
|
82 |
+
* @psalm-var array<string, string>
|
83 |
+
*/
|
84 |
+
private $classMap = array();
|
85 |
+
|
86 |
+
/** @var bool */
|
87 |
+
private $classMapAuthoritative = false;
|
88 |
+
|
89 |
+
/**
|
90 |
+
* @var bool[]
|
91 |
+
* @psalm-var array<string, bool>
|
92 |
+
*/
|
93 |
+
private $missingClasses = array();
|
94 |
+
|
95 |
+
/** @var ?string */
|
96 |
+
private $apcuPrefix;
|
97 |
+
|
98 |
+
/**
|
99 |
+
* @var self[]
|
100 |
+
*/
|
101 |
+
private static $registeredLoaders = array();
|
102 |
+
|
103 |
+
/**
|
104 |
+
* @param ?string $vendorDir
|
105 |
+
*/
|
106 |
+
public function __construct($vendorDir = null)
|
107 |
+
{
|
108 |
+
$this->vendorDir = $vendorDir;
|
109 |
+
}
|
110 |
+
|
111 |
+
/**
|
112 |
+
* @return string[]
|
113 |
+
*/
|
114 |
+
public function getPrefixes()
|
115 |
+
{
|
116 |
+
if (!empty($this->prefixesPsr0)) {
|
117 |
+
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
118 |
+
}
|
119 |
+
|
120 |
+
return array();
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* @return array[]
|
125 |
+
* @psalm-return array<string, array<int, string>>
|
126 |
+
*/
|
127 |
+
public function getPrefixesPsr4()
|
128 |
+
{
|
129 |
+
return $this->prefixDirsPsr4;
|
130 |
+
}
|
131 |
+
|
132 |
+
/**
|
133 |
+
* @return array[]
|
134 |
+
* @psalm-return array<string, string>
|
135 |
+
*/
|
136 |
+
public function getFallbackDirs()
|
137 |
+
{
|
138 |
+
return $this->fallbackDirsPsr0;
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* @return array[]
|
143 |
+
* @psalm-return array<string, string>
|
144 |
+
*/
|
145 |
+
public function getFallbackDirsPsr4()
|
146 |
+
{
|
147 |
+
return $this->fallbackDirsPsr4;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* @return string[] Array of classname => path
|
152 |
+
* @psalm-return array<string, string>
|
153 |
+
*/
|
154 |
+
public function getClassMap()
|
155 |
+
{
|
156 |
+
return $this->classMap;
|
157 |
+
}
|
158 |
+
|
159 |
+
/**
|
160 |
+
* @param string[] $classMap Class to filename map
|
161 |
+
* @psalm-param array<string, string> $classMap
|
162 |
+
*
|
163 |
+
* @return void
|
164 |
+
*/
|
165 |
+
public function addClassMap(array $classMap)
|
166 |
+
{
|
167 |
+
if ($this->classMap) {
|
168 |
+
$this->classMap = array_merge($this->classMap, $classMap);
|
169 |
+
} else {
|
170 |
+
$this->classMap = $classMap;
|
171 |
+
}
|
172 |
+
}
|
173 |
+
|
174 |
+
/**
|
175 |
+
* Registers a set of PSR-0 directories for a given prefix, either
|
176 |
+
* appending or prepending to the ones previously set for this prefix.
|
177 |
+
*
|
178 |
+
* @param string $prefix The prefix
|
179 |
+
* @param string[]|string $paths The PSR-0 root directories
|
180 |
+
* @param bool $prepend Whether to prepend the directories
|
181 |
+
*
|
182 |
+
* @return void
|
183 |
+
*/
|
184 |
+
public function add($prefix, $paths, $prepend = false)
|
185 |
+
{
|
186 |
+
if (!$prefix) {
|
187 |
+
if ($prepend) {
|
188 |
+
$this->fallbackDirsPsr0 = array_merge(
|
189 |
+
(array) $paths,
|
190 |
+
$this->fallbackDirsPsr0
|
191 |
+
);
|
192 |
+
} else {
|
193 |
+
$this->fallbackDirsPsr0 = array_merge(
|
194 |
+
$this->fallbackDirsPsr0,
|
195 |
+
(array) $paths
|
196 |
+
);
|
197 |
+
}
|
198 |
+
|
199 |
+
return;
|
200 |
+
}
|
201 |
+
|
202 |
+
$first = $prefix[0];
|
203 |
+
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
204 |
+
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
205 |
+
|
206 |
+
return;
|
207 |
+
}
|
208 |
+
if ($prepend) {
|
209 |
+
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
210 |
+
(array) $paths,
|
211 |
+
$this->prefixesPsr0[$first][$prefix]
|
212 |
+
);
|
213 |
+
} else {
|
214 |
+
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
215 |
+
$this->prefixesPsr0[$first][$prefix],
|
216 |
+
(array) $paths
|
217 |
+
);
|
218 |
+
}
|
219 |
+
}
|
220 |
+
|
221 |
+
/**
|
222 |
+
* Registers a set of PSR-4 directories for a given namespace, either
|
223 |
+
* appending or prepending to the ones previously set for this namespace.
|
224 |
+
*
|
225 |
+
* @param string $prefix The prefix/namespace, with trailing '\\'
|
226 |
+
* @param string[]|string $paths The PSR-4 base directories
|
227 |
+
* @param bool $prepend Whether to prepend the directories
|
228 |
+
*
|
229 |
+
* @throws \InvalidArgumentException
|
230 |
+
*
|
231 |
+
* @return void
|
232 |
+
*/
|
233 |
+
public function addPsr4($prefix, $paths, $prepend = false)
|
234 |
+
{
|
235 |
+
if (!$prefix) {
|
236 |
+
// Register directories for the root namespace.
|
237 |
+
if ($prepend) {
|
238 |
+
$this->fallbackDirsPsr4 = array_merge(
|
239 |
+
(array) $paths,
|
240 |
+
$this->fallbackDirsPsr4
|
241 |
+
);
|
242 |
+
} else {
|
243 |
+
$this->fallbackDirsPsr4 = array_merge(
|
244 |
+
$this->fallbackDirsPsr4,
|
245 |
+
(array) $paths
|
246 |
+
);
|
247 |
+
}
|
248 |
+
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
249 |
+
// Register directories for a new namespace.
|
250 |
+
$length = strlen($prefix);
|
251 |
+
if ('\\' !== $prefix[$length - 1]) {
|
252 |
+
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
253 |
+
}
|
254 |
+
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
255 |
+
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
256 |
+
} elseif ($prepend) {
|
257 |
+
// Prepend directories for an already registered namespace.
|
258 |
+
$this->prefixDirsPsr4[$prefix] = array_merge(
|
259 |
+
(array) $paths,
|
260 |
+
$this->prefixDirsPsr4[$prefix]
|
261 |
+
);
|
262 |
+
} else {
|
263 |
+
// Append directories for an already registered namespace.
|
264 |
+
$this->prefixDirsPsr4[$prefix] = array_merge(
|
265 |
+
$this->prefixDirsPsr4[$prefix],
|
266 |
+
(array) $paths
|
267 |
+
);
|
268 |
+
}
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* Registers a set of PSR-0 directories for a given prefix,
|
273 |
+
* replacing any others previously set for this prefix.
|
274 |
+
*
|
275 |
+
* @param string $prefix The prefix
|
276 |
+
* @param string[]|string $paths The PSR-0 base directories
|
277 |
+
*
|
278 |
+
* @return void
|
279 |
+
*/
|
280 |
+
public function set($prefix, $paths)
|
281 |
+
{
|
282 |
+
if (!$prefix) {
|
283 |
+
$this->fallbackDirsPsr0 = (array) $paths;
|
284 |
+
} else {
|
285 |
+
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
286 |
+
}
|
287 |
+
}
|
288 |
+
|
289 |
+
/**
|
290 |
+
* Registers a set of PSR-4 directories for a given namespace,
|
291 |
+
* replacing any others previously set for this namespace.
|
292 |
+
*
|
293 |
+
* @param string $prefix The prefix/namespace, with trailing '\\'
|
294 |
+
* @param string[]|string $paths The PSR-4 base directories
|
295 |
+
*
|
296 |
+
* @throws \InvalidArgumentException
|
297 |
+
*
|
298 |
+
* @return void
|
299 |
+
*/
|
300 |
+
public function setPsr4($prefix, $paths)
|
301 |
+
{
|
302 |
+
if (!$prefix) {
|
303 |
+
$this->fallbackDirsPsr4 = (array) $paths;
|
304 |
+
} else {
|
305 |
+
$length = strlen($prefix);
|
306 |
+
if ('\\' !== $prefix[$length - 1]) {
|
307 |
+
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
308 |
+
}
|
309 |
+
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
310 |
+
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
311 |
+
}
|
312 |
+
}
|
313 |
+
|
314 |
+
/**
|
315 |
+
* Turns on searching the include path for class files.
|
316 |
+
*
|
317 |
+
* @param bool $useIncludePath
|
318 |
+
*
|
319 |
+
* @return void
|
320 |
+
*/
|
321 |
+
public function setUseIncludePath($useIncludePath)
|
322 |
+
{
|
323 |
+
$this->useIncludePath = $useIncludePath;
|
324 |
+
}
|
325 |
+
|
326 |
+
/**
|
327 |
+
* Can be used to check if the autoloader uses the include path to check
|
328 |
+
* for classes.
|
329 |
+
*
|
330 |
+
* @return bool
|
331 |
+
*/
|
332 |
+
public function getUseIncludePath()
|
333 |
+
{
|
334 |
+
return $this->useIncludePath;
|
335 |
+
}
|
336 |
+
|
337 |
+
/**
|
338 |
+
* Turns off searching the prefix and fallback directories for classes
|
339 |
+
* that have not been registered with the class map.
|
340 |
+
*
|
341 |
+
* @param bool $classMapAuthoritative
|
342 |
+
*
|
343 |
+
* @return void
|
344 |
+
*/
|
345 |
+
public function setClassMapAuthoritative($classMapAuthoritative)
|
346 |
+
{
|
347 |
+
$this->classMapAuthoritative = $classMapAuthoritative;
|
348 |
+
}
|
349 |
+
|
350 |
+
/**
|
351 |
+
* Should class lookup fail if not found in the current class map?
|
352 |
+
*
|
353 |
+
* @return bool
|
354 |
+
*/
|
355 |
+
public function isClassMapAuthoritative()
|
356 |
+
{
|
357 |
+
return $this->classMapAuthoritative;
|
358 |
+
}
|
359 |
+
|
360 |
+
/**
|
361 |
+
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
362 |
+
*
|
363 |
+
* @param string|null $apcuPrefix
|
364 |
+
*
|
365 |
+
* @return void
|
366 |
+
*/
|
367 |
+
public function setApcuPrefix($apcuPrefix)
|
368 |
+
{
|
369 |
+
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
370 |
+
}
|
371 |
+
|
372 |
+
/**
|
373 |
+
* The APCu prefix in use, or null if APCu caching is not enabled.
|
374 |
+
*
|
375 |
+
* @return string|null
|
376 |
+
*/
|
377 |
+
public function getApcuPrefix()
|
378 |
+
{
|
379 |
+
return $this->apcuPrefix;
|
380 |
+
}
|
381 |
+
|
382 |
+
/**
|
383 |
+
* Registers this instance as an autoloader.
|
384 |
+
*
|
385 |
+
* @param bool $prepend Whether to prepend the autoloader or not
|
386 |
+
*
|
387 |
+
* @return void
|
388 |
+
*/
|
389 |
+
public function register($prepend = false)
|
390 |
+
{
|
391 |
+
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
392 |
+
|
393 |
+
if (null === $this->vendorDir) {
|
394 |
+
return;
|
395 |
+
}
|
396 |
+
|
397 |
+
if ($prepend) {
|
398 |
+
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
399 |
+
} else {
|
400 |
+
unset(self::$registeredLoaders[$this->vendorDir]);
|
401 |
+
self::$registeredLoaders[$this->vendorDir] = $this;
|
402 |
+
}
|
403 |
+
}
|
404 |
+
|
405 |
+
/**
|
406 |
+
* Unregisters this instance as an autoloader.
|
407 |
+
*
|
408 |
+
* @return void
|
409 |
+
*/
|
410 |
+
public function unregister()
|
411 |
+
{
|
412 |
+
spl_autoload_unregister(array($this, 'loadClass'));
|
413 |
+
|
414 |
+
if (null !== $this->vendorDir) {
|
415 |
+
unset(self::$registeredLoaders[$this->vendorDir]);
|
416 |
+
}
|
417 |
+
}
|
418 |
+
|
419 |
+
/**
|
420 |
+
* Loads the given class or interface.
|
421 |
+
*
|
422 |
+
* @param string $class The name of the class
|
423 |
+
* @return true|null True if loaded, null otherwise
|
424 |
+
*/
|
425 |
+
public function loadClass($class)
|
426 |
+
{
|
427 |
+
if ($file = $this->findFile($class)) {
|
428 |
+
includeFile($file);
|
429 |
+
|
430 |
+
return true;
|
431 |
+
}
|
432 |
+
|
433 |
+
return null;
|
434 |
+
}
|
435 |
+
|
436 |
+
/**
|
437 |
+
* Finds the path to the file where the class is defined.
|
438 |
+
*
|
439 |
+
* @param string $class The name of the class
|
440 |
+
*
|
441 |
+
* @return string|false The path if found, false otherwise
|
442 |
+
*/
|
443 |
+
public function findFile($class)
|
444 |
+
{
|
445 |
+
// class map lookup
|
446 |
+
if (isset($this->classMap[$class])) {
|
447 |
+
return $this->classMap[$class];
|
448 |
+
}
|
449 |
+
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
450 |
+
return false;
|
451 |
+
}
|
452 |
+
if (null !== $this->apcuPrefix) {
|
453 |
+
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
454 |
+
if ($hit) {
|
455 |
+
return $file;
|
456 |
+
}
|
457 |
+
}
|
458 |
+
|
459 |
+
$file = $this->findFileWithExtension($class, '.php');
|
460 |
+
|
461 |
+
// Search for Hack files if we are running on HHVM
|
462 |
+
if (false === $file && defined('HHVM_VERSION')) {
|
463 |
+
$file = $this->findFileWithExtension($class, '.hh');
|
464 |
+
}
|
465 |
+
|
466 |
+
if (null !== $this->apcuPrefix) {
|
467 |
+
apcu_add($this->apcuPrefix.$class, $file);
|
468 |
+
}
|
469 |
+
|
470 |
+
if (false === $file) {
|
471 |
+
// Remember that this class does not exist.
|
472 |
+
$this->missingClasses[$class] = true;
|
473 |
+
}
|
474 |
+
|
475 |
+
return $file;
|
476 |
+
}
|
477 |
+
|
478 |
+
/**
|
479 |
+
* Returns the currently registered loaders indexed by their corresponding vendor directories.
|
480 |
+
*
|
481 |
+
* @return self[]
|
482 |
+
*/
|
483 |
+
public static function getRegisteredLoaders()
|
484 |
+
{
|
485 |
+
return self::$registeredLoaders;
|
486 |
+
}
|
487 |
+
|
488 |
+
/**
|
489 |
+
* @param string $class
|
490 |
+
* @param string $ext
|
491 |
+
* @return string|false
|
492 |
+
*/
|
493 |
+
private function findFileWithExtension($class, $ext)
|
494 |
+
{
|
495 |
+
// PSR-4 lookup
|
496 |
+
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
497 |
+
|
498 |
+
$first = $class[0];
|
499 |
+
if (isset($this->prefixLengthsPsr4[$first])) {
|
500 |
+
$subPath = $class;
|
501 |
+
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
502 |
+
$subPath = substr($subPath, 0, $lastPos);
|
503 |
+
$search = $subPath . '\\';
|
504 |
+
if (isset($this->prefixDirsPsr4[$search])) {
|
505 |
+
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
506 |
+
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
507 |
+
if (file_exists($file = $dir . $pathEnd)) {
|
508 |
+
return $file;
|
509 |
+
}
|
510 |
+
}
|
511 |
+
}
|
512 |
+
}
|
513 |
+
}
|
514 |
+
|
515 |
+
// PSR-4 fallback dirs
|
516 |
+
foreach ($this->fallbackDirsPsr4 as $dir) {
|
517 |
+
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
518 |
+
return $file;
|
519 |
+
}
|
520 |
+
}
|
521 |
+
|
522 |
+
// PSR-0 lookup
|
523 |
+
if (false !== $pos = strrpos($class, '\\')) {
|
524 |
+
// namespaced class name
|
525 |
+
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
526 |
+
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
527 |
+
} else {
|
528 |
+
// PEAR-like class name
|
529 |
+
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
530 |
+
}
|
531 |
+
|
532 |
+
if (isset($this->prefixesPsr0[$first])) {
|
533 |
+
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
534 |
+
if (0 === strpos($class, $prefix)) {
|
535 |
+
foreach ($dirs as $dir) {
|
536 |
+
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
537 |
+
return $file;
|
538 |
+
}
|
539 |
+
}
|
540 |
+
}
|
541 |
+
}
|
542 |
+
}
|
543 |
+
|
544 |
+
// PSR-0 fallback dirs
|
545 |
+
foreach ($this->fallbackDirsPsr0 as $dir) {
|
546 |
+
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
547 |
+
return $file;
|
548 |
+
}
|
549 |
+
}
|
550 |
+
|
551 |
+
// PSR-0 include paths.
|
552 |
+
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
553 |
+
return $file;
|
554 |
+
}
|
555 |
+
|
556 |
+
return false;
|
557 |
+
}
|
558 |
+
}
|
559 |
+
|
560 |
+
/**
|
561 |
+
* Scope isolated include.
|
562 |
+
*
|
563 |
+
* Prevents access to $this/self from included files.
|
564 |
+
*
|
565 |
+
* @param string $file
|
566 |
+
* @return void
|
567 |
+
* @private
|
568 |
+
*/
|
569 |
+
function includeFile($file)
|
570 |
+
{
|
571 |
+
include $file;
|
572 |
+
}
|
vendor/composer/InstalledVersions.php
ADDED
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/*
|
4 |
+
* This file is part of Composer.
|
5 |
+
*
|
6 |
+
* (c) Nils Adermann <naderman@naderman.de>
|
7 |
+
* Jordi Boggiano <j.boggiano@seld.be>
|
8 |
+
*
|
9 |
+
* For the full copyright and license information, please view the LICENSE
|
10 |
+
* file that was distributed with this source code.
|
11 |
+
*/
|
12 |
+
|
13 |
+
namespace Composer;
|
14 |
+
|
15 |
+
use Composer\Autoload\ClassLoader;
|
16 |
+
use Composer\Semver\VersionParser;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* This class is copied in every Composer installed project and available to all
|
20 |
+
*
|
21 |
+
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
|
22 |
+
*
|
23 |
+
* To require its presence, you can require `composer-runtime-api ^2.0`
|
24 |
+
*/
|
25 |
+
class InstalledVersions
|
26 |
+
{
|
27 |
+
/**
|
28 |
+
* @var mixed[]|null
|
29 |
+
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
|
30 |
+
*/
|
31 |
+
private static $installed;
|
32 |
+
|
33 |
+
/**
|
34 |
+
* @var bool|null
|
35 |
+
*/
|
36 |
+
private static $canGetVendors;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* @var array[]
|
40 |
+
* @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
|
41 |
+
*/
|
42 |
+
private static $installedByVendor = array();
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Returns a list of all package names which are present, either by being installed, replaced or provided
|
46 |
+
*
|
47 |
+
* @return string[]
|
48 |
+
* @psalm-return list<string>
|
49 |
+
*/
|
50 |
+
public static function getInstalledPackages()
|
51 |
+
{
|
52 |
+
$packages = array();
|
53 |
+
foreach (self::getInstalled() as $installed) {
|
54 |
+
$packages[] = array_keys($installed['versions']);
|
55 |
+
}
|
56 |
+
|
57 |
+
if (1 === \count($packages)) {
|
58 |
+
return $packages[0];
|
59 |
+
}
|
60 |
+
|
61 |
+
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Returns a list of all package names with a specific type e.g. 'library'
|
66 |
+
*
|
67 |
+
* @param string $type
|
68 |
+
* @return string[]
|
69 |
+
* @psalm-return list<string>
|
70 |
+
*/
|
71 |
+
public static function getInstalledPackagesByType($type)
|
72 |
+
{
|
73 |
+
$packagesByType = array();
|
74 |
+
|
75 |
+
foreach (self::getInstalled() as $installed) {
|
76 |
+
foreach ($installed['versions'] as $name => $package) {
|
77 |
+
if (isset($package['type']) && $package['type'] === $type) {
|
78 |
+
$packagesByType[] = $name;
|
79 |
+
}
|
80 |
+
}
|
81 |
+
}
|
82 |
+
|
83 |
+
return $packagesByType;
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Checks whether the given package is installed
|
88 |
+
*
|
89 |
+
* This also returns true if the package name is provided or replaced by another package
|
90 |
+
*
|
91 |
+
* @param string $packageName
|
92 |
+
* @param bool $includeDevRequirements
|
93 |
+
* @return bool
|
94 |
+
*/
|
95 |
+
public static function isInstalled($packageName, $includeDevRequirements = true)
|
96 |
+
{
|
97 |
+
foreach (self::getInstalled() as $installed) {
|
98 |
+
if (isset($installed['versions'][$packageName])) {
|
99 |
+
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
|
100 |
+
}
|
101 |
+
}
|
102 |
+
|
103 |
+
return false;
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* Checks whether the given package satisfies a version constraint
|
108 |
+
*
|
109 |
+
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
|
110 |
+
*
|
111 |
+
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
|
112 |
+
*
|
113 |
+
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
|
114 |
+
* @param string $packageName
|
115 |
+
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
|
116 |
+
* @return bool
|
117 |
+
*/
|
118 |
+
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
119 |
+
{
|
120 |
+
$constraint = $parser->parseConstraints($constraint);
|
121 |
+
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
122 |
+
|
123 |
+
return $provided->matches($constraint);
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Returns a version constraint representing all the range(s) which are installed for a given package
|
128 |
+
*
|
129 |
+
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
|
130 |
+
* whether a given version of a package is installed, and not just whether it exists
|
131 |
+
*
|
132 |
+
* @param string $packageName
|
133 |
+
* @return string Version constraint usable with composer/semver
|
134 |
+
*/
|
135 |
+
public static function getVersionRanges($packageName)
|
136 |
+
{
|
137 |
+
foreach (self::getInstalled() as $installed) {
|
138 |
+
if (!isset($installed['versions'][$packageName])) {
|
139 |
+
continue;
|
140 |
+
}
|
141 |
+
|
142 |
+
$ranges = array();
|
143 |
+
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
144 |
+
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
145 |
+
}
|
146 |
+
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
147 |
+
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
148 |
+
}
|
149 |
+
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
150 |
+
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
151 |
+
}
|
152 |
+
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
153 |
+
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
154 |
+
}
|
155 |
+
|
156 |
+
return implode(' || ', $ranges);
|
157 |
+
}
|
158 |
+
|
159 |
+
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
160 |
+
}
|
161 |
+
|
162 |
+
/**
|
163 |
+
* @param string $packageName
|
164 |
+
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
165 |
+
*/
|
166 |
+
public static function getVersion($packageName)
|
167 |
+
{
|
168 |
+
foreach (self::getInstalled() as $installed) {
|
169 |
+
if (!isset($installed['versions'][$packageName])) {
|
170 |
+
continue;
|
171 |
+
}
|
172 |
+
|
173 |
+
if (!isset($installed['versions'][$packageName]['version'])) {
|
174 |
+
return null;
|
175 |
+
}
|
176 |
+
|
177 |
+
return $installed['versions'][$packageName]['version'];
|
178 |
+
}
|
179 |
+
|
180 |
+
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
181 |
+
}
|
182 |
+
|
183 |
+
/**
|
184 |
+
* @param string $packageName
|
185 |
+
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
186 |
+
*/
|
187 |
+
public static function getPrettyVersion($packageName)
|
188 |
+
{
|
189 |
+
foreach (self::getInstalled() as $installed) {
|
190 |
+
if (!isset($installed['versions'][$packageName])) {
|
191 |
+
continue;
|
192 |
+
}
|
193 |
+
|
194 |
+
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
195 |
+
return null;
|
196 |
+
}
|
197 |
+
|
198 |
+
return $installed['versions'][$packageName]['pretty_version'];
|
199 |
+
}
|
200 |
+
|
201 |
+
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
202 |
+
}
|
203 |
+
|
204 |
+
/**
|
205 |
+
* @param string $packageName
|
206 |
+
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
|
207 |
+
*/
|
208 |
+
public static function getReference($packageName)
|
209 |
+
{
|
210 |
+
foreach (self::getInstalled() as $installed) {
|
211 |
+
if (!isset($installed['versions'][$packageName])) {
|
212 |
+
continue;
|
213 |
+
}
|
214 |
+
|
215 |
+
if (!isset($installed['versions'][$packageName]['reference'])) {
|
216 |
+
return null;
|
217 |
+
}
|
218 |
+
|
219 |
+
return $installed['versions'][$packageName]['reference'];
|
220 |
+
}
|
221 |
+
|
222 |
+
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* @param string $packageName
|
227 |
+
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
|
228 |
+
*/
|
229 |
+
public static function getInstallPath($packageName)
|
230 |
+
{
|
231 |
+
foreach (self::getInstalled() as $installed) {
|
232 |
+
if (!isset($installed['versions'][$packageName])) {
|
233 |
+
continue;
|
234 |
+
}
|
235 |
+
|
236 |
+
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
237 |
+
}
|
238 |
+
|
239 |
+
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
240 |
+
}
|
241 |
+
|
242 |
+
/**
|
243 |
+
* @return array
|
244 |
+
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
|
245 |
+
*/
|
246 |
+
public static function getRootPackage()
|
247 |
+
{
|
248 |
+
$installed = self::getInstalled();
|
249 |
+
|
250 |
+
return $installed[0]['root'];
|
251 |
+
}
|
252 |
+
|
253 |
+
/**
|
254 |
+
* Returns the raw installed.php data for custom implementations
|
255 |
+
*
|
256 |
+
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
|
257 |
+
* @return array[]
|
258 |
+
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
|
259 |
+
*/
|
260 |
+
public static function getRawData()
|
261 |
+
{
|
262 |
+
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
263 |
+
|
264 |
+
if (null === self::$installed) {
|
265 |
+
// only require the installed.php file if this file is loaded from its dumped location,
|
266 |
+
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
267 |
+
if (substr(__DIR__, -8, 1) !== 'C') {
|
268 |
+
self::$installed = include __DIR__ . '/installed.php';
|
269 |
+
} else {
|
270 |
+
self::$installed = array();
|
271 |
+
}
|
272 |
+
}
|
273 |
+
|
274 |
+
return self::$installed;
|
275 |
+
}
|
276 |
+
|
277 |
+
/**
|
278 |
+
* Returns the raw data of all installed.php which are currently loaded for custom implementations
|
279 |
+
*
|
280 |
+
* @return array[]
|
281 |
+
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
|
282 |
+
*/
|
283 |
+
public static function getAllRawData()
|
284 |
+
{
|
285 |
+
return self::getInstalled();
|
286 |
+
}
|
287 |
+
|
288 |
+
/**
|
289 |
+
* Lets you reload the static array from another file
|
290 |
+
*
|
291 |
+
* This is only useful for complex integrations in which a project needs to use
|
292 |
+
* this class but then also needs to execute another project's autoloader in process,
|
293 |
+
* and wants to ensure both projects have access to their version of installed.php.
|
294 |
+
*
|
295 |
+
* A typical case would be PHPUnit, where it would need to make sure it reads all
|
296 |
+
* the data it needs from this class, then call reload() with
|
297 |
+
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
|
298 |
+
* the project in which it runs can then also use this class safely, without
|
299 |
+
* interference between PHPUnit's dependencies and the project's dependencies.
|
300 |
+
*
|
301 |
+
* @param array[] $data A vendor/composer/installed.php data set
|
302 |
+
* @return void
|
303 |
+
*
|
304 |
+
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
|
305 |
+
*/
|
306 |
+
public static function reload($data)
|
307 |
+
{
|
308 |
+
self::$installed = $data;
|
309 |
+
self::$installedByVendor = array();
|
310 |
+
}
|
311 |
+
|
312 |
+
/**
|
313 |
+
* @return array[]
|
314 |
+
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
|
315 |
+
*/
|
316 |
+
private static function getInstalled()
|
317 |
+
{
|
318 |
+
if (null === self::$canGetVendors) {
|
319 |
+
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
320 |
+
}
|
321 |
+
|
322 |
+
$installed = array();
|
323 |
+
|
324 |
+
if (self::$canGetVendors) {
|
325 |
+
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
326 |
+
if (isset(self::$installedByVendor[$vendorDir])) {
|
327 |
+
$installed[] = self::$installedByVendor[$vendorDir];
|
328 |
+
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
329 |
+
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
|
330 |
+
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
331 |
+
self::$installed = $installed[count($installed) - 1];
|
332 |
+
}
|
333 |
+
}
|
334 |
+
}
|
335 |
+
}
|
336 |
+
|
337 |
+
if (null === self::$installed) {
|
338 |
+
// only require the installed.php file if this file is loaded from its dumped location,
|
339 |
+
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
340 |
+
if (substr(__DIR__, -8, 1) !== 'C') {
|
341 |
+
self::$installed = require __DIR__ . '/installed.php';
|
342 |
+
} else {
|
343 |
+
self::$installed = array();
|
344 |
+
}
|
345 |
+
}
|
346 |
+
$installed[] = self::$installed;
|
347 |
+
|
348 |
+
return $installed;
|
349 |
+
}
|
350 |
+
}
|
vendor/composer/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
Copyright (c) Nils Adermann, Jordi Boggiano
|
3 |
+
|
4 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5 |
+
of this software and associated documentation files (the "Software"), to deal
|
6 |
+
in the Software without restriction, including without limitation the rights
|
7 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8 |
+
copies of the Software, and to permit persons to whom the Software is furnished
|
9 |
+
to do so, subject to the following conditions:
|
10 |
+
|
11 |
+
The above copyright notice and this permission notice shall be included in all
|
12 |
+
copies or substantial portions of the Software.
|
13 |
+
|
14 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20 |
+
THE SOFTWARE.
|
21 |
+
|
vendor/composer/autoload_classmap.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// autoload_classmap.php @generated by Composer
|
4 |
+
|
5 |
+
$vendorDir = dirname(dirname(__FILE__));
|
6 |
+
$baseDir = dirname($vendorDir);
|
7 |
+
|
8 |
+
return array(
|
9 |
+
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
10 |
+
'Crontrol\\Event\\Table' => $baseDir . '/src/event-list-table.php',
|
11 |
+
'Crontrol\\Request' => $baseDir . '/src/request.php',
|
12 |
+
'Crontrol\\Schedule_List_Table' => $baseDir . '/src/schedule-list-table.php',
|
13 |
+
);
|
vendor/composer/autoload_namespaces.php
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// autoload_namespaces.php @generated by Composer
|
4 |
+
|
5 |
+
$vendorDir = dirname(dirname(__FILE__));
|
6 |
+
$baseDir = dirname($vendorDir);
|
7 |
+
|
8 |
+
return array(
|
9 |
+
);
|
vendor/composer/autoload_psr4.php
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// autoload_psr4.php @generated by Composer
|
4 |
+
|
5 |
+
$vendorDir = dirname(dirname(__FILE__));
|
6 |
+
$baseDir = dirname($vendorDir);
|
7 |
+
|
8 |
+
return array(
|
9 |
+
);
|
vendor/composer/autoload_real.php
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// autoload_real.php @generated by Composer
|
4 |
+
|
5 |
+
class ComposerAutoloaderInitdbf095101a93cb72acf9efa892a78eaf
|
6 |
+
{
|
7 |
+
private static $loader;
|
8 |
+
|
9 |
+
public static function loadClassLoader($class)
|
10 |
+
{
|
11 |
+
if ('Composer\Autoload\ClassLoader' === $class) {
|
12 |
+
require __DIR__ . '/ClassLoader.php';
|
13 |
+
}
|
14 |
+
}
|
15 |
+
|
16 |
+
/**
|
17 |
+
* @return \Composer\Autoload\ClassLoader
|
18 |
+
*/
|
19 |
+
public static function getLoader()
|
20 |
+
{
|
21 |
+
if (null !== self::$loader) {
|
22 |
+
return self::$loader;
|
23 |
+
}
|
24 |
+
|
25 |
+
require __DIR__ . '/platform_check.php';
|
26 |
+
|
27 |
+
spl_autoload_register(array('ComposerAutoloaderInitdbf095101a93cb72acf9efa892a78eaf', 'loadClassLoader'), true, false);
|
28 |
+
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
|
29 |
+
spl_autoload_unregister(array('ComposerAutoloaderInitdbf095101a93cb72acf9efa892a78eaf', 'loadClassLoader'));
|
30 |
+
|
31 |
+
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
32 |
+
if ($useStaticLoader) {
|
33 |
+
require __DIR__ . '/autoload_static.php';
|
34 |
+
|
35 |
+
call_user_func(\Composer\Autoload\ComposerStaticInitdbf095101a93cb72acf9efa892a78eaf::getInitializer($loader));
|
36 |
+
} else {
|
37 |
+
$classMap = require __DIR__ . '/autoload_classmap.php';
|
38 |
+
if ($classMap) {
|
39 |
+
$loader->addClassMap($classMap);
|
40 |
+
}
|
41 |
+
}
|
42 |
+
|
43 |
+
$loader->setClassMapAuthoritative(true);
|
44 |
+
$loader->register(false);
|
45 |
+
|
46 |
+
return $loader;
|
47 |
+
}
|
48 |
+
}
|
vendor/composer/autoload_static.php
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// autoload_static.php @generated by Composer
|
4 |
+
|
5 |
+
namespace Composer\Autoload;
|
6 |
+
|
7 |
+
class ComposerStaticInitdbf095101a93cb72acf9efa892a78eaf
|
8 |
+
{
|
9 |
+
public static $classMap = array (
|
10 |
+
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
11 |
+
'Crontrol\\Event\\Table' => __DIR__ . '/../..' . '/src/event-list-table.php',
|
12 |
+
'Crontrol\\Request' => __DIR__ . '/../..' . '/src/request.php',
|
13 |
+
'Crontrol\\Schedule_List_Table' => __DIR__ . '/../..' . '/src/schedule-list-table.php',
|
14 |
+
);
|
15 |
+
|
16 |
+
public static function getInitializer(ClassLoader $loader)
|
17 |
+
{
|
18 |
+
return \Closure::bind(function () use ($loader) {
|
19 |
+
$loader->classMap = ComposerStaticInitdbf095101a93cb72acf9efa892a78eaf::$classMap;
|
20 |
+
|
21 |
+
}, null, ClassLoader::class);
|
22 |
+
}
|
23 |
+
}
|
vendor/composer/installed.json
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"packages": [],
|
3 |
+
"dev": false,
|
4 |
+
"dev-package-names": []
|
5 |
+
}
|
vendor/composer/installed.php
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php return array(
|
2 |
+
'root' => array(
|
3 |
+
'pretty_version' => 'dev-release',
|
4 |
+
'version' => 'dev-release',
|
5 |
+
'type' => 'wordpress-plugin',
|
6 |
+
'install_path' => __DIR__ . '/../../',
|
7 |
+
'aliases' => array(),
|
8 |
+
'reference' => '1d6304ed14bdca543e49832dcbf9542826b54cef',
|
9 |
+
'name' => 'johnbillion/wp-crontrol',
|
10 |
+
'dev' => false,
|
11 |
+
),
|
12 |
+
'versions' => array(
|
13 |
+
'johnbillion/wp-crontrol' => array(
|
14 |
+
'pretty_version' => 'dev-release',
|
15 |
+
'version' => 'dev-release',
|
16 |
+
'type' => 'wordpress-plugin',
|
17 |
+
'install_path' => __DIR__ . '/../../',
|
18 |
+
'aliases' => array(),
|
19 |
+
'reference' => '1d6304ed14bdca543e49832dcbf9542826b54cef',
|
20 |
+
'dev_requirement' => false,
|
21 |
+
),
|
22 |
+
),
|
23 |
+
);
|
vendor/composer/platform_check.php
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// platform_check.php @generated by Composer
|
4 |
+
|
5 |
+
$issues = array();
|
6 |
+
|
7 |
+
if (!(PHP_VERSION_ID >= 50600)) {
|
8 |
+
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.';
|
9 |
+
}
|
10 |
+
|
11 |
+
if ($issues) {
|
12 |
+
if (!headers_sent()) {
|
13 |
+
header('HTTP/1.1 500 Internal Server Error');
|
14 |
+
}
|
15 |
+
if (!ini_get('display_errors')) {
|
16 |
+
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
17 |
+
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
18 |
+
} elseif (!headers_sent()) {
|
19 |
+
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
20 |
+
}
|
21 |
+
}
|
22 |
+
trigger_error(
|
23 |
+
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
24 |
+
E_USER_ERROR
|
25 |
+
);
|
26 |
+
}
|
wp-crontrol.php
CHANGED
@@ -5,10 +5,10 @@
|
|
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 |
* Text Domain: wp-crontrol
|
10 |
* Domain Path: /languages/
|
11 |
-
* Requires PHP: 5.
|
12 |
* License: GPL v2 or later
|
13 |
*
|
14 |
* LICENSE
|
@@ -29,2082 +29,30 @@
|
|
29 |
* @copyright Copyright 2008 Edward Dale, 2012-2022 John Blackbourn
|
30 |
* @license http://www.gnu.org/licenses/gpl.txt GPL 2.0
|
31 |
* @link https://wordpress.org/plugins/wp-crontrol/
|
32 |
-
* @since 0.2
|
33 |
*/
|
34 |
|
35 |
namespace Crontrol;
|
36 |
|
37 |
-
|
38 |
-
use stdClass;
|
39 |
-
use WP_Error;
|
40 |
|
41 |
if ( ! defined( 'ABSPATH' ) ) {
|
42 |
exit;
|
43 |
}
|
44 |
|
45 |
-
|
46 |
-
|
47 |
-
require_once __DIR__ . '/src/schedule.php';
|
48 |
-
|
49 |
-
const TRANSIENT = 'crontrol-message-%d';
|
50 |
-
|
51 |
-
/**
|
52 |
-
* Hook onto all of the actions and filters needed by the plugin.
|
53 |
-
*
|
54 |
-
* @return void
|
55 |
-
*/
|
56 |
-
function init_hooks() {
|
57 |
-
$plugin_file = plugin_basename( __FILE__ );
|
58 |
-
|
59 |
-
add_action( 'init', __NAMESPACE__ . '\action_init' );
|
60 |
-
add_action( 'init', __NAMESPACE__ . '\action_handle_posts' );
|
61 |
-
add_action( 'admin_menu', __NAMESPACE__ . '\action_admin_menu' );
|
62 |
-
add_action( 'wp_ajax_crontrol_checkhash', __NAMESPACE__ . '\ajax_check_events_hash' );
|
63 |
-
add_filter( "plugin_action_links_{$plugin_file}", __NAMESPACE__ . '\plugin_action_links', 10, 4 );
|
64 |
-
add_filter( 'removable_query_args', __NAMESPACE__ . '\filter_removable_query_args' );
|
65 |
-
add_filter( 'pre_unschedule_event', __NAMESPACE__ . '\maybe_clear_doing_cron' );
|
66 |
-
add_filter( 'plugin_row_meta', __NAMESPACE__ . '\filter_plugin_row_meta', 10, 4 );
|
67 |
-
|
68 |
-
add_action( 'load-tools_page_crontrol_admin_manage_page', __NAMESPACE__ . '\setup_manage_page' );
|
69 |
-
|
70 |
-
add_filter( 'cron_schedules', __NAMESPACE__ . '\filter_cron_schedules' );
|
71 |
-
add_action( 'crontrol_cron_job', __NAMESPACE__ . '\action_php_cron_event' );
|
72 |
-
add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' );
|
73 |
-
add_action( 'crontrol/tab-header', __NAMESPACE__ . '\show_cron_status', 20 );
|
74 |
-
}
|
75 |
-
|
76 |
-
/**
|
77 |
-
* Sets an error message to show to the current user after a redirect.
|
78 |
-
*
|
79 |
-
* @param string $message The error message text.
|
80 |
-
* @return bool Whether the message was saved.
|
81 |
-
*/
|
82 |
-
function set_message( $message ) {
|
83 |
-
$key = sprintf(
|
84 |
-
TRANSIENT,
|
85 |
-
get_current_user_id()
|
86 |
-
);
|
87 |
-
return set_transient( $key, $message, 60 );
|
88 |
-
}
|
89 |
-
|
90 |
-
/**
|
91 |
-
* Gets the error message to show to the current user after a redirect.
|
92 |
-
*
|
93 |
-
* @return string The error message text.
|
94 |
-
*/
|
95 |
-
function get_message() {
|
96 |
-
$key = sprintf(
|
97 |
-
TRANSIENT,
|
98 |
-
get_current_user_id()
|
99 |
-
);
|
100 |
-
return get_transient( $key );
|
101 |
-
}
|
102 |
-
|
103 |
-
/**
|
104 |
-
* Filters the array of row meta for each plugin in the Plugins list table.
|
105 |
-
*
|
106 |
-
* @param array<int,string> $plugin_meta An array of the plugin row's meta data.
|
107 |
-
* @param string $plugin_file Path to the plugin file relative to the plugins directory.
|
108 |
-
* @return array<int,string> An array of the plugin row's meta data.
|
109 |
-
*/
|
110 |
-
function filter_plugin_row_meta( array $plugin_meta, $plugin_file ) {
|
111 |
-
if ( 'wp-crontrol/wp-crontrol.php' !== $plugin_file ) {
|
112 |
-
return $plugin_meta;
|
113 |
-
}
|
114 |
-
|
115 |
-
$plugin_meta[] = sprintf(
|
116 |
-
'<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>',
|
117 |
-
'https://github.com/sponsors/johnbillion',
|
118 |
-
esc_html_x( 'Sponsor', 'verb', 'wp-crontrol' )
|
119 |
-
);
|
120 |
-
|
121 |
-
return $plugin_meta;
|
122 |
-
}
|
123 |
-
|
124 |
-
/**
|
125 |
-
* Run using the 'init' action.
|
126 |
-
*
|
127 |
-
* @return void
|
128 |
-
*/
|
129 |
-
function action_init() {
|
130 |
-
load_plugin_textdomain( 'wp-crontrol', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
|
131 |
-
}
|
132 |
-
|
133 |
-
/**
|
134 |
-
* Handles any POSTs made by the plugin. Run using the 'init' action.
|
135 |
-
*
|
136 |
-
* @return void
|
137 |
-
*/
|
138 |
-
function action_handle_posts() {
|
139 |
-
$request = new Request();
|
140 |
-
|
141 |
-
if ( isset( $_POST['crontrol_action'] ) && ( 'new_cron' === $_POST['crontrol_action'] ) ) {
|
142 |
-
if ( ! current_user_can( 'manage_options' ) ) {
|
143 |
-
wp_die( esc_html__( 'You are not allowed to add new cron events.', 'wp-crontrol' ), 401 );
|
144 |
-
}
|
145 |
-
check_admin_referer( 'crontrol-new-cron' );
|
146 |
-
|
147 |
-
$cr = $request->init( wp_unslash( $_POST ) );
|
148 |
-
|
149 |
-
if ( 'crontrol_cron_job' === $cr->hookname && ! current_user_can( 'edit_files' ) ) {
|
150 |
-
wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 );
|
151 |
-
}
|
152 |
-
$args = json_decode( $cr->args, true );
|
153 |
-
|
154 |
-
if ( empty( $args ) || ! is_array( $args ) ) {
|
155 |
-
$args = array();
|
156 |
-
}
|
157 |
-
|
158 |
-
$next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local;
|
159 |
-
|
160 |
-
add_filter( 'schedule_event', function( $event ) {
|
161 |
-
if ( ! $event ) {
|
162 |
-
return $event;
|
163 |
-
}
|
164 |
-
|
165 |
-
/**
|
166 |
-
* Fires after a new cron event is added.
|
167 |
-
*
|
168 |
-
* @param stdClass $event {
|
169 |
-
* An object containing the event's data.
|
170 |
-
*
|
171 |
-
* @type string $hook Action hook to execute when the event is run.
|
172 |
-
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
173 |
-
* @type string|false $schedule How often the event should subsequently recur.
|
174 |
-
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
175 |
-
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
176 |
-
* }
|
177 |
-
*/
|
178 |
-
do_action( 'crontrol/added_new_event', $event );
|
179 |
-
|
180 |
-
return $event;
|
181 |
-
}, 99 );
|
182 |
-
|
183 |
-
$added = Event\add( $next_run_local, $cr->schedule, $cr->hookname, $args );
|
184 |
-
|
185 |
-
$redirect = array(
|
186 |
-
'page' => 'crontrol_admin_manage_page',
|
187 |
-
'crontrol_message' => '5',
|
188 |
-
'crontrol_name' => rawurlencode( $cr->hookname ),
|
189 |
-
);
|
190 |
-
|
191 |
-
if ( is_wp_error( $added ) ) {
|
192 |
-
set_message( $added->get_error_message() );
|
193 |
-
$redirect['crontrol_message'] = 'error';
|
194 |
-
}
|
195 |
-
|
196 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
197 |
-
exit;
|
198 |
-
|
199 |
-
} elseif ( isset( $_POST['crontrol_action'] ) && ( 'new_php_cron' === $_POST['crontrol_action'] ) ) {
|
200 |
-
if ( ! current_user_can( 'edit_files' ) ) {
|
201 |
-
wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 );
|
202 |
-
}
|
203 |
-
check_admin_referer( 'crontrol-new-cron' );
|
204 |
-
|
205 |
-
$cr = $request->init( wp_unslash( $_POST ) );
|
206 |
-
|
207 |
-
$next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local;
|
208 |
-
$args = array(
|
209 |
-
'code' => $cr->hookcode,
|
210 |
-
'name' => $cr->eventname,
|
211 |
-
);
|
212 |
-
|
213 |
-
add_filter( 'schedule_event', function( $event ) {
|
214 |
-
if ( ! $event ) {
|
215 |
-
return $event;
|
216 |
-
}
|
217 |
-
|
218 |
-
/**
|
219 |
-
* Fires after a new PHP cron event is added.
|
220 |
-
*
|
221 |
-
* @param stdClass $event {
|
222 |
-
* An object containing the event's data.
|
223 |
-
*
|
224 |
-
* @type string $hook Action hook to execute when the event is run.
|
225 |
-
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
226 |
-
* @type string|false $schedule How often the event should subsequently recur.
|
227 |
-
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
228 |
-
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
229 |
-
* }
|
230 |
-
*/
|
231 |
-
do_action( 'crontrol/added_new_php_event', $event );
|
232 |
-
|
233 |
-
return $event;
|
234 |
-
}, 99 );
|
235 |
-
|
236 |
-
$added = Event\add( $next_run_local, $cr->schedule, 'crontrol_cron_job', $args );
|
237 |
-
|
238 |
-
$hookname = ( ! empty( $cr->eventname ) ) ? $cr->eventname : __( 'PHP Cron', 'wp-crontrol' );
|
239 |
-
$redirect = array(
|
240 |
-
'page' => 'crontrol_admin_manage_page',
|
241 |
-
'crontrol_message' => '5',
|
242 |
-
'crontrol_name' => rawurlencode( $hookname ),
|
243 |
-
);
|
244 |
-
|
245 |
-
if ( is_wp_error( $added ) ) {
|
246 |
-
set_message( $added->get_error_message() );
|
247 |
-
$redirect['crontrol_message'] = 'error';
|
248 |
-
}
|
249 |
-
|
250 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
251 |
-
exit;
|
252 |
-
|
253 |
-
} elseif ( isset( $_POST['crontrol_action'] ) && ( 'edit_cron' === $_POST['crontrol_action'] ) ) {
|
254 |
-
if ( ! current_user_can( 'manage_options' ) ) {
|
255 |
-
wp_die( esc_html__( 'You are not allowed to edit cron events.', 'wp-crontrol' ), 401 );
|
256 |
-
}
|
257 |
-
|
258 |
-
$cr = $request->init( wp_unslash( $_POST ) );
|
259 |
-
|
260 |
-
check_admin_referer( "crontrol-edit-cron_{$cr->original_hookname}_{$cr->original_sig}_{$cr->original_next_run_utc}" );
|
261 |
-
|
262 |
-
if ( 'crontrol_cron_job' === $cr->hookname && ! current_user_can( 'edit_files' ) ) {
|
263 |
-
wp_die( esc_html__( 'You are not allowed to edit PHP cron events.', 'wp-crontrol' ), 401 );
|
264 |
-
}
|
265 |
-
|
266 |
-
$args = json_decode( $cr->args, true );
|
267 |
-
|
268 |
-
if ( empty( $args ) || ! is_array( $args ) ) {
|
269 |
-
$args = array();
|
270 |
-
}
|
271 |
-
|
272 |
-
$redirect = array(
|
273 |
-
'page' => 'crontrol_admin_manage_page',
|
274 |
-
'crontrol_message' => '4',
|
275 |
-
'crontrol_name' => rawurlencode( $cr->hookname ),
|
276 |
-
);
|
277 |
-
|
278 |
-
$original = Event\get_single( $cr->original_hookname, $cr->original_sig, $cr->original_next_run_utc );
|
279 |
-
|
280 |
-
if ( is_wp_error( $original ) ) {
|
281 |
-
set_message( $original->get_error_message() );
|
282 |
-
$redirect['crontrol_message'] = 'error';
|
283 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
284 |
-
exit;
|
285 |
-
}
|
286 |
-
|
287 |
-
$deleted = Event\delete( $cr->original_hookname, $cr->original_sig, $cr->original_next_run_utc );
|
288 |
-
|
289 |
-
if ( is_wp_error( $deleted ) ) {
|
290 |
-
set_message( $deleted->get_error_message() );
|
291 |
-
$redirect['crontrol_message'] = 'error';
|
292 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
293 |
-
exit;
|
294 |
-
}
|
295 |
-
|
296 |
-
$next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local;
|
297 |
-
|
298 |
-
/**
|
299 |
-
* Modifies an event before it is scheduled.
|
300 |
-
*
|
301 |
-
* @param stdClass|false $event An object containing the new event's data, or boolean false.
|
302 |
-
*/
|
303 |
-
add_filter( 'schedule_event', function( $event ) use ( $original ) {
|
304 |
-
if ( ! $event ) {
|
305 |
-
return $event;
|
306 |
-
}
|
307 |
-
|
308 |
-
/**
|
309 |
-
* Fires after a cron event is edited.
|
310 |
-
*
|
311 |
-
* @param stdClass $event {
|
312 |
-
* An object containing the new event's data.
|
313 |
-
*
|
314 |
-
* @type string $hook Action hook to execute when the event is run.
|
315 |
-
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
316 |
-
* @type string|false $schedule How often the event should subsequently recur.
|
317 |
-
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
318 |
-
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
319 |
-
* }
|
320 |
-
* @param stdClass $original {
|
321 |
-
* An object containing the original event's data.
|
322 |
-
*
|
323 |
-
* @type string $hook Action hook to execute when the event is run.
|
324 |
-
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
325 |
-
* @type string|false $schedule How often the event should subsequently recur.
|
326 |
-
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
327 |
-
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
328 |
-
* }
|
329 |
-
*/
|
330 |
-
do_action( 'crontrol/edited_event', $event, $original );
|
331 |
-
|
332 |
-
return $event;
|
333 |
-
}, 99 );
|
334 |
-
|
335 |
-
$added = Event\add( $next_run_local, $cr->schedule, $cr->hookname, $args );
|
336 |
-
|
337 |
-
if ( is_wp_error( $added ) ) {
|
338 |
-
set_message( $added->get_error_message() );
|
339 |
-
$redirect['crontrol_message'] = 'error';
|
340 |
-
}
|
341 |
-
|
342 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
343 |
-
exit;
|
344 |
-
|
345 |
-
} elseif ( isset( $_POST['crontrol_action'] ) && ( 'edit_php_cron' === $_POST['crontrol_action'] ) ) {
|
346 |
-
if ( ! current_user_can( 'edit_files' ) ) {
|
347 |
-
wp_die( esc_html__( 'You are not allowed to edit PHP cron events.', 'wp-crontrol' ), 401 );
|
348 |
-
}
|
349 |
-
|
350 |
-
$cr = $request->init( wp_unslash( $_POST ) );
|
351 |
-
|
352 |
-
check_admin_referer( "crontrol-edit-cron_{$cr->original_hookname}_{$cr->original_sig}_{$cr->original_next_run_utc}" );
|
353 |
-
$args = array(
|
354 |
-
'code' => $cr->hookcode,
|
355 |
-
'name' => $cr->eventname,
|
356 |
-
);
|
357 |
-
$hookname = ( ! empty( $cr->eventname ) ) ? $cr->eventname : __( 'PHP Cron', 'wp-crontrol' );
|
358 |
-
$redirect = array(
|
359 |
-
'page' => 'crontrol_admin_manage_page',
|
360 |
-
'crontrol_message' => '4',
|
361 |
-
'crontrol_name' => rawurlencode( $hookname ),
|
362 |
-
);
|
363 |
-
|
364 |
-
$original = Event\get_single( $cr->original_hookname, $cr->original_sig, $cr->original_next_run_utc );
|
365 |
-
|
366 |
-
if ( is_wp_error( $original ) ) {
|
367 |
-
set_message( $original->get_error_message() );
|
368 |
-
$redirect['crontrol_message'] = 'error';
|
369 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
370 |
-
exit;
|
371 |
-
}
|
372 |
-
|
373 |
-
$deleted = Event\delete( $cr->original_hookname, $cr->original_sig, $cr->original_next_run_utc );
|
374 |
-
|
375 |
-
if ( is_wp_error( $deleted ) ) {
|
376 |
-
set_message( $deleted->get_error_message() );
|
377 |
-
$redirect['crontrol_message'] = 'error';
|
378 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
379 |
-
exit;
|
380 |
-
}
|
381 |
-
|
382 |
-
$next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local;
|
383 |
-
|
384 |
-
/**
|
385 |
-
* Modifies an event before it is scheduled.
|
386 |
-
*
|
387 |
-
* @param stdClass|false $event An object containing the new event's data, or boolean false.
|
388 |
-
*/
|
389 |
-
add_filter( 'schedule_event', function( $event ) use ( $original ) {
|
390 |
-
if ( ! $event ) {
|
391 |
-
return $event;
|
392 |
-
}
|
393 |
-
|
394 |
-
/**
|
395 |
-
* Fires after a PHP cron event is edited.
|
396 |
-
*
|
397 |
-
* @param stdClass $event {
|
398 |
-
* An object containing the new event's data.
|
399 |
-
*
|
400 |
-
* @type string $hook Action hook to execute when the event is run.
|
401 |
-
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
402 |
-
* @type string|false $schedule How often the event should subsequently recur.
|
403 |
-
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
404 |
-
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
405 |
-
* }
|
406 |
-
* @param stdClass $original {
|
407 |
-
* An object containing the original event's data.
|
408 |
-
*
|
409 |
-
* @type string $hook Action hook to execute when the event is run.
|
410 |
-
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
411 |
-
* @type string|false $schedule How often the event should subsequently recur.
|
412 |
-
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
413 |
-
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
414 |
-
* }
|
415 |
-
*/
|
416 |
-
do_action( 'crontrol/edited_php_event', $event, $original );
|
417 |
-
|
418 |
-
return $event;
|
419 |
-
}, 99 );
|
420 |
-
|
421 |
-
$added = Event\add( $next_run_local, $cr->schedule, 'crontrol_cron_job', $args );
|
422 |
-
|
423 |
-
if ( is_wp_error( $added ) ) {
|
424 |
-
set_message( $added->get_error_message() );
|
425 |
-
$redirect['crontrol_message'] = 'error';
|
426 |
-
}
|
427 |
-
|
428 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
429 |
-
exit;
|
430 |
-
|
431 |
-
} elseif ( isset( $_POST['crontrol_new_schedule'] ) ) {
|
432 |
-
if ( ! current_user_can( 'manage_options' ) ) {
|
433 |
-
wp_die( esc_html__( 'You are not allowed to add new cron schedules.', 'wp-crontrol' ), 401 );
|
434 |
-
}
|
435 |
-
check_admin_referer( 'crontrol-new-schedule' );
|
436 |
-
$name = wp_unslash( $_POST['crontrol_schedule_internal_name'] );
|
437 |
-
$interval = absint( $_POST['crontrol_schedule_interval'] );
|
438 |
-
$display = wp_unslash( $_POST['crontrol_schedule_display_name'] );
|
439 |
-
|
440 |
-
Schedule\add( $name, $interval, $display );
|
441 |
-
$redirect = array(
|
442 |
-
'page' => 'crontrol_admin_options_page',
|
443 |
-
'crontrol_message' => '3',
|
444 |
-
'crontrol_name' => rawurlencode( $name ),
|
445 |
-
);
|
446 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
|
447 |
-
exit;
|
448 |
-
|
449 |
-
} elseif ( isset( $_GET['crontrol_action'] ) && 'delete-schedule' === $_GET['crontrol_action'] ) {
|
450 |
-
if ( ! current_user_can( 'manage_options' ) ) {
|
451 |
-
wp_die( esc_html__( 'You are not allowed to delete cron schedules.', 'wp-crontrol' ), 401 );
|
452 |
-
}
|
453 |
-
$schedule = wp_unslash( $_GET['crontrol_id'] );
|
454 |
-
check_admin_referer( "crontrol-delete-schedule_{$schedule}" );
|
455 |
-
Schedule\delete( $schedule );
|
456 |
-
$redirect = array(
|
457 |
-
'page' => 'crontrol_admin_options_page',
|
458 |
-
'crontrol_message' => '2',
|
459 |
-
'crontrol_name' => rawurlencode( $schedule ),
|
460 |
-
);
|
461 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'options-general.php' ) ) );
|
462 |
-
exit;
|
463 |
-
|
464 |
-
} elseif ( ( isset( $_POST['action'] ) && 'crontrol_delete_crons' === $_POST['action'] ) || ( isset( $_POST['action2'] ) && 'crontrol_delete_crons' === $_POST['action2'] ) ) {
|
465 |
-
if ( ! current_user_can( 'manage_options' ) ) {
|
466 |
-
wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
|
467 |
-
}
|
468 |
-
check_admin_referer( 'bulk-crontrol-events' );
|
469 |
-
|
470 |
-
if ( empty( $_POST['crontrol_delete'] ) ) {
|
471 |
-
return;
|
472 |
-
}
|
473 |
-
|
474 |
-
/**
|
475 |
-
* @var array<string,array<string,string>>
|
476 |
-
*/
|
477 |
-
$delete = (array) wp_unslash( $_POST['crontrol_delete'] );
|
478 |
-
$deleted = 0;
|
479 |
-
|
480 |
-
foreach ( $delete as $next_run_utc => $events ) {
|
481 |
-
foreach ( (array) $events as $hook => $sig ) {
|
482 |
-
if ( 'crontrol_cron_job' === $hook && ! current_user_can( 'edit_files' ) ) {
|
483 |
-
continue;
|
484 |
-
}
|
485 |
-
|
486 |
-
$event = Event\get_single( urldecode( $hook ), $sig, $next_run_utc );
|
487 |
-
$deleted = Event\delete( urldecode( $hook ), $sig, $next_run_utc );
|
488 |
-
|
489 |
-
if ( ! is_wp_error( $deleted ) ) {
|
490 |
-
$deleted++;
|
491 |
-
|
492 |
-
/** This action is documented in wp-crontrol.php */
|
493 |
-
do_action( 'crontrol/deleted_event', $event );
|
494 |
-
}
|
495 |
-
}
|
496 |
-
}
|
497 |
-
|
498 |
-
$redirect = array(
|
499 |
-
'page' => 'crontrol_admin_manage_page',
|
500 |
-
'crontrol_name' => $deleted,
|
501 |
-
'crontrol_message' => '9',
|
502 |
-
);
|
503 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
504 |
-
exit;
|
505 |
-
|
506 |
-
} elseif ( isset( $_GET['crontrol_action'] ) && 'delete-cron' === $_GET['crontrol_action'] ) {
|
507 |
-
if ( ! current_user_can( 'manage_options' ) ) {
|
508 |
-
wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
|
509 |
-
}
|
510 |
-
$hook = wp_unslash( $_GET['crontrol_id'] );
|
511 |
-
$sig = wp_unslash( $_GET['crontrol_sig'] );
|
512 |
-
$next_run_utc = wp_unslash( $_GET['crontrol_next_run_utc'] );
|
513 |
-
check_admin_referer( "crontrol-delete-cron_{$hook}_{$sig}_{$next_run_utc}" );
|
514 |
-
|
515 |
-
if ( 'crontrol_cron_job' === $hook && ! current_user_can( 'edit_files' ) ) {
|
516 |
-
wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
|
517 |
-
}
|
518 |
-
|
519 |
-
$redirect = array(
|
520 |
-
'page' => 'crontrol_admin_manage_page',
|
521 |
-
'crontrol_message' => '6',
|
522 |
-
'crontrol_name' => rawurlencode( $hook ),
|
523 |
-
);
|
524 |
-
|
525 |
-
$event = Event\get_single( $hook, $sig, $next_run_utc );
|
526 |
-
|
527 |
-
if ( is_wp_error( $event ) ) {
|
528 |
-
set_message( $event->get_error_message() );
|
529 |
-
$redirect['crontrol_message'] = 'error';
|
530 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
531 |
-
exit;
|
532 |
-
}
|
533 |
-
|
534 |
-
$deleted = Event\delete( $hook, $sig, $next_run_utc );
|
535 |
-
|
536 |
-
if ( is_wp_error( $deleted ) ) {
|
537 |
-
set_message( $deleted->get_error_message() );
|
538 |
-
$redirect['crontrol_message'] = 'error';
|
539 |
-
} else {
|
540 |
-
/**
|
541 |
-
* Fires after a cron event is deleted.
|
542 |
-
*
|
543 |
-
* @param stdClass $event {
|
544 |
-
* An object containing the event's data.
|
545 |
-
*
|
546 |
-
* @type string $hook Action hook to execute when the event is run.
|
547 |
-
* @type int $timestamp Unix timestamp (UTC) for when to next run the event.
|
548 |
-
* @type string|false $schedule How often the event should subsequently recur.
|
549 |
-
* @type mixed[] $args Array containing each separate argument to pass to the hook's callback function.
|
550 |
-
* @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
|
551 |
-
* }
|
552 |
-
*/
|
553 |
-
do_action( 'crontrol/deleted_event', $event );
|
554 |
-
}
|
555 |
-
|
556 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
557 |
-
exit;
|
558 |
-
|
559 |
-
} elseif ( isset( $_GET['crontrol_action'] ) && 'delete-hook' === $_GET['crontrol_action'] ) {
|
560 |
-
if ( ! current_user_can( 'manage_options' ) ) {
|
561 |
-
wp_die( esc_html__( 'You are not allowed to delete cron events.', 'wp-crontrol' ), 401 );
|
562 |
-
}
|
563 |
-
$hook = wp_unslash( $_GET['crontrol_id'] );
|
564 |
-
$deleted = false;
|
565 |
-
check_admin_referer( "crontrol-delete-hook_{$hook}" );
|
566 |
-
|
567 |
-
if ( 'crontrol_cron_job' === $hook ) {
|
568 |
-
wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 401 );
|
569 |
-
}
|
570 |
-
|
571 |
-
if ( function_exists( 'wp_unschedule_hook' ) ) {
|
572 |
-
/** @var int|false */
|
573 |
-
$deleted = wp_unschedule_hook( $hook );
|
574 |
-
}
|
575 |
-
|
576 |
-
if ( 0 === $deleted ) {
|
577 |
-
$redirect = array(
|
578 |
-
'page' => 'crontrol_admin_manage_page',
|
579 |
-
'crontrol_message' => '3',
|
580 |
-
'crontrol_name' => rawurlencode( $hook ),
|
581 |
-
);
|
582 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
583 |
-
exit;
|
584 |
-
} elseif ( $deleted ) {
|
585 |
-
/**
|
586 |
-
* Fires after all cron events with the given hook are deleted.
|
587 |
-
*
|
588 |
-
* @param string $hook The hook name.
|
589 |
-
* @param int $deleted The number of events that were deleted.
|
590 |
-
*/
|
591 |
-
do_action( 'crontrol/deleted_all_with_hook', $hook, $deleted );
|
592 |
-
|
593 |
-
$redirect = array(
|
594 |
-
'page' => 'crontrol_admin_manage_page',
|
595 |
-
'crontrol_message' => '2',
|
596 |
-
'crontrol_name' => rawurlencode( $hook ),
|
597 |
-
);
|
598 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
599 |
-
exit;
|
600 |
-
} else {
|
601 |
-
$redirect = array(
|
602 |
-
'page' => 'crontrol_admin_manage_page',
|
603 |
-
'crontrol_message' => '7',
|
604 |
-
'crontrol_name' => rawurlencode( $hook ),
|
605 |
-
);
|
606 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
607 |
-
exit;
|
608 |
-
}
|
609 |
-
} elseif ( isset( $_GET['crontrol_action'] ) && 'run-cron' === $_GET['crontrol_action'] ) {
|
610 |
-
if ( ! current_user_can( 'manage_options' ) ) {
|
611 |
-
wp_die( esc_html__( 'You are not allowed to run cron events.', 'wp-crontrol' ), 401 );
|
612 |
-
}
|
613 |
-
$hook = wp_unslash( $_GET['crontrol_id'] );
|
614 |
-
$sig = wp_unslash( $_GET['crontrol_sig'] );
|
615 |
-
check_admin_referer( "crontrol-run-cron_{$hook}_{$sig}" );
|
616 |
-
|
617 |
-
$ran = Event\run( $hook, $sig );
|
618 |
-
|
619 |
-
$redirect = array(
|
620 |
-
'page' => 'crontrol_admin_manage_page',
|
621 |
-
'crontrol_message' => '1',
|
622 |
-
'crontrol_name' => rawurlencode( $hook ),
|
623 |
-
);
|
624 |
-
|
625 |
-
if ( is_wp_error( $ran ) ) {
|
626 |
-
$set = set_message( $ran->get_error_message() );
|
627 |
-
|
628 |
-
// If we can't store the error message in a transient, just display it.
|
629 |
-
if ( ! $set ) {
|
630 |
-
wp_die(
|
631 |
-
esc_html( $ran->get_error_message() ),
|
632 |
-
'',
|
633 |
-
array(
|
634 |
-
'response' => 500,
|
635 |
-
'back_link' => true,
|
636 |
-
)
|
637 |
-
);
|
638 |
-
}
|
639 |
-
$redirect['crontrol_message'] = 'error';
|
640 |
-
}
|
641 |
-
|
642 |
-
wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) );
|
643 |
-
exit;
|
644 |
-
} elseif ( isset( $_POST['crontrol_action'] ) && 'export-event-csv' === $_POST['crontrol_action'] ) {
|
645 |
-
check_admin_referer( 'crontrol-export-event-csv', 'crontrol_nonce' );
|
646 |
-
|
647 |
-
require_once __DIR__ . '/src/event-list-table.php';
|
648 |
-
|
649 |
-
$type = isset( $_POST['crontrol_hooks_type'] ) ? $_POST['crontrol_hooks_type'] : 'all';
|
650 |
-
$headers = array(
|
651 |
-
'hook',
|
652 |
-
'arguments',
|
653 |
-
'next_run',
|
654 |
-
'next_run_gmt',
|
655 |
-
'action',
|
656 |
-
'recurrence',
|
657 |
-
'interval',
|
658 |
-
);
|
659 |
-
$filename = sprintf(
|
660 |
-
'cron-events-%s-%s.csv',
|
661 |
-
$type,
|
662 |
-
gmdate( 'Y-m-d-H.i.s' )
|
663 |
-
);
|
664 |
-
$csv = fopen( 'php://output', 'w' );
|
665 |
-
|
666 |
-
if ( false === $csv ) {
|
667 |
-
wp_die( esc_html__( 'Could not save CSV file.', 'wp-crontrol' ) );
|
668 |
-
}
|
669 |
-
|
670 |
-
$events = Table::get_filtered_events( Event\get() );
|
671 |
-
|
672 |
-
header( 'Content-Type: text/csv; charset=utf-8' );
|
673 |
-
header(
|
674 |
-
sprintf(
|
675 |
-
'Content-Disposition: attachment; filename="%s"',
|
676 |
-
esc_attr( $filename )
|
677 |
-
)
|
678 |
-
);
|
679 |
-
|
680 |
-
fputcsv( $csv, $headers );
|
681 |
-
|
682 |
-
if ( isset( $events[ $type ] ) ) {
|
683 |
-
foreach ( $events[ $type ] as $event ) {
|
684 |
-
$next_run_local = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $event->time ), 'c' );
|
685 |
-
$next_run_utc = gmdate( 'c', $event->time );
|
686 |
-
$hook_callbacks = \Crontrol\get_hook_callbacks( $event->hook );
|
687 |
-
|
688 |
-
if ( 'crontrol_cron_job' === $event->hook ) {
|
689 |
-
$args = __( 'PHP Code', 'wp-crontrol' );
|
690 |
-
} elseif ( empty( $event->args ) ) {
|
691 |
-
$args = '';
|
692 |
-
} else {
|
693 |
-
$args = \Crontrol\json_output( $event->args, false );
|
694 |
-
}
|
695 |
-
|
696 |
-
if ( 'crontrol_cron_job' === $event->hook ) {
|
697 |
-
$action = __( 'WP Crontrol', 'wp-crontrol' );
|
698 |
-
} else {
|
699 |
-
$callbacks = array();
|
700 |
-
|
701 |
-
foreach ( $hook_callbacks as $callback ) {
|
702 |
-
$callbacks[] = $callback['callback']['name'];
|
703 |
-
}
|
704 |
-
|
705 |
-
$action = implode( ',', $callbacks );
|
706 |
-
}
|
707 |
-
|
708 |
-
if ( $event->schedule ) {
|
709 |
-
$recurrence = Event\get_schedule_name( $event );
|
710 |
-
if ( is_wp_error( $recurrence ) ) {
|
711 |
-
$recurrence = $recurrence->get_error_message();
|
712 |
-
}
|
713 |
-
} else {
|
714 |
-
$recurrence = __( 'Non-repeating', 'wp-crontrol' );
|
715 |
-
}
|
716 |
-
|
717 |
-
$row = array(
|
718 |
-
$event->hook,
|
719 |
-
$args,
|
720 |
-
$next_run_local,
|
721 |
-
$next_run_utc,
|
722 |
-
$action,
|
723 |
-
$recurrence,
|
724 |
-
(int) $event->interval,
|
725 |
-
);
|
726 |
-
fputcsv( $csv, $row );
|
727 |
-
}
|
728 |
-
}
|
729 |
-
|
730 |
-
fclose( $csv );
|
731 |
-
|
732 |
-
exit;
|
733 |
-
}
|
734 |
}
|
735 |
|
736 |
-
|
737 |
-
* Adds options & management pages to the admin menu.
|
738 |
-
*
|
739 |
-
* Run using the 'admin_menu' action.
|
740 |
-
*
|
741 |
-
* @return void
|
742 |
-
*/
|
743 |
-
function action_admin_menu() {
|
744 |
-
$schedules = add_options_page(
|
745 |
-
esc_html__( 'Cron Schedules', 'wp-crontrol' ),
|
746 |
-
esc_html__( 'Cron Schedules', 'wp-crontrol' ),
|
747 |
-
'manage_options',
|
748 |
-
'crontrol_admin_options_page',
|
749 |
-
__NAMESPACE__ . '\admin_options_page'
|
750 |
-
);
|
751 |
-
$events = add_management_page(
|
752 |
-
esc_html__( 'Cron Events', 'wp-crontrol' ),
|
753 |
-
esc_html__( 'Cron Events', 'wp-crontrol' ),
|
754 |
-
'manage_options',
|
755 |
-
'crontrol_admin_manage_page',
|
756 |
-
__NAMESPACE__ . '\admin_manage_page'
|
757 |
-
);
|
758 |
|
759 |
-
|
760 |
-
|
761 |
}
|
762 |
|
763 |
-
|
764 |
-
|
765 |
-
|
766 |
-
|
767 |
-
*/
|
768 |
-
function admin_help_tab() {
|
769 |
-
$screen = get_current_screen();
|
770 |
-
|
771 |
-
if ( ! $screen ) {
|
772 |
-
return;
|
773 |
-
}
|
774 |
-
|
775 |
-
$content = '<p>' . __( 'There are several places to get help with issues relating to WP-Cron:', 'wp-crontrol' ) . '</p>';
|
776 |
-
$content .= '<ul>';
|
777 |
-
$content .= '<li>';
|
778 |
-
$content .= sprintf(
|
779 |
-
/* translators: %s: URL to the documentation */
|
780 |
-
__( '<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' ),
|
781 |
-
'https://github.com/johnbillion/wp-crontrol/wiki'
|
782 |
-
);
|
783 |
-
$content .= '</li>';
|
784 |
-
$content .= '<li>';
|
785 |
-
$content .= sprintf(
|
786 |
-
/* translators: %s: URL to the documentation */
|
787 |
-
__( '<a href="%s">Read the Frequently Asked Questions (FAQ)</a> which cover many common questions and answers.', 'wp-crontrol' ),
|
788 |
-
'https://wordpress.org/plugins/wp-crontrol/faq/'
|
789 |
-
);
|
790 |
-
$content .= '</li>';
|
791 |
-
$content .= '<li>';
|
792 |
-
$content .= sprintf(
|
793 |
-
/* translators: %s: URL to the documentation */
|
794 |
-
__( '<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' ),
|
795 |
-
'https://developer.wordpress.org/plugins/cron/'
|
796 |
-
);
|
797 |
-
$content .= '</ul>';
|
798 |
-
|
799 |
-
$screen->add_help_tab(
|
800 |
-
array(
|
801 |
-
'id' => 'crontrol-help',
|
802 |
-
'title' => __( 'Help', 'wp-crontrol' ),
|
803 |
-
'content' => $content,
|
804 |
-
)
|
805 |
-
);
|
806 |
-
}
|
807 |
-
|
808 |
-
/**
|
809 |
-
* Adds items to the plugin's action links on the Plugins listing screen.
|
810 |
-
*
|
811 |
-
* @param array<string,string> $actions Array of action links.
|
812 |
-
* @param string $plugin_file Path to the plugin file relative to the plugins directory.
|
813 |
-
* @param mixed[] $plugin_data An array of plugin data.
|
814 |
-
* @param string $context The plugin context.
|
815 |
-
* @return array<string,string> Array of action links.
|
816 |
-
*/
|
817 |
-
function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) {
|
818 |
-
$new = array(
|
819 |
-
'crontrol-events' => sprintf(
|
820 |
-
'<a href="%s">%s</a>',
|
821 |
-
esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page' ) ),
|
822 |
-
esc_html__( 'Events', 'wp-crontrol' )
|
823 |
-
),
|
824 |
-
'crontrol-schedules' => sprintf(
|
825 |
-
'<a href="%s">%s</a>',
|
826 |
-
esc_url( admin_url( 'options-general.php?page=crontrol_admin_options_page' ) ),
|
827 |
-
esc_html__( 'Schedules', 'wp-crontrol' )
|
828 |
-
),
|
829 |
-
'crontrol-help' => sprintf(
|
830 |
-
'<a href="%s">%s</a>',
|
831 |
-
'https://github.com/johnbillion/wp-crontrol/wiki',
|
832 |
-
esc_html__( 'Help', 'wp-crontrol' )
|
833 |
-
),
|
834 |
-
);
|
835 |
-
|
836 |
-
return array_merge( $new, $actions );
|
837 |
-
}
|
838 |
-
|
839 |
-
/**
|
840 |
-
* Gives WordPress the plugin's set of cron schedules.
|
841 |
-
*
|
842 |
-
* Called by the `cron_schedules` filter.
|
843 |
-
*
|
844 |
-
* @param array<string,array<string,(int|string)>> $scheds Array of cron schedule arrays. Usually empty.
|
845 |
-
* @return array<string,array<string,(int|string)>> Array of modified cron schedule arrays.
|
846 |
-
*/
|
847 |
-
function filter_cron_schedules( array $scheds ) {
|
848 |
-
$new_scheds = get_option( 'crontrol_schedules', array() );
|
849 |
-
|
850 |
-
if ( ! is_array( $new_scheds ) ) {
|
851 |
-
return $scheds;
|
852 |
-
}
|
853 |
-
|
854 |
-
return array_merge( $new_scheds, $scheds );
|
855 |
-
}
|
856 |
-
|
857 |
-
/**
|
858 |
-
* Displays the options page for the plugin.
|
859 |
-
*
|
860 |
-
* @return void
|
861 |
-
*/
|
862 |
-
function admin_options_page() {
|
863 |
-
$messages = array(
|
864 |
-
'2' => array(
|
865 |
-
/* translators: 1: The name of the cron schedule. */
|
866 |
-
__( 'Deleted the cron schedule %s.', 'wp-crontrol' ),
|
867 |
-
'success',
|
868 |
-
),
|
869 |
-
'3' => array(
|
870 |
-
/* translators: 1: The name of the cron schedule. */
|
871 |
-
__( 'Added the cron schedule %s.', 'wp-crontrol' ),
|
872 |
-
'success',
|
873 |
-
),
|
874 |
-
);
|
875 |
-
if ( isset( $_GET['crontrol_message'] ) && isset( $_GET['crontrol_name'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
|
876 |
-
$hook = wp_unslash( $_GET['crontrol_name'] );
|
877 |
-
$message = wp_unslash( $_GET['crontrol_message'] );
|
878 |
-
|
879 |
-
printf(
|
880 |
-
'<div id="crontrol-message" class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>',
|
881 |
-
esc_attr( $messages[ $message ][1] ),
|
882 |
-
sprintf(
|
883 |
-
esc_html( $messages[ $message ][0] ),
|
884 |
-
'<strong>' . esc_html( $hook ) . '</strong>'
|
885 |
-
)
|
886 |
-
);
|
887 |
-
}
|
888 |
-
|
889 |
-
require_once __DIR__ . '/src/schedule-list-table.php';
|
890 |
-
|
891 |
-
$table = new Schedule_List_Table();
|
892 |
-
|
893 |
-
$table->prepare_items();
|
894 |
-
|
895 |
-
?>
|
896 |
-
<div class="wrap">
|
897 |
-
|
898 |
-
<?php do_tabs(); ?>
|
899 |
-
|
900 |
-
<h1><?php esc_html_e( 'Cron Schedules', 'wp-crontrol' ); ?></h1>
|
901 |
-
|
902 |
-
<?php $table->views(); ?>
|
903 |
-
|
904 |
-
<div id="col-container" class="wp-clearfix">
|
905 |
-
<div id="col-left">
|
906 |
-
<div class="col-wrap">
|
907 |
-
<div class="form-wrap">
|
908 |
-
<h2><?php esc_html_e( 'Add Cron Schedule', 'wp-crontrol' ); ?></h2>
|
909 |
-
<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>
|
910 |
-
<form method="post" action="options-general.php?page=crontrol_admin_options_page">
|
911 |
-
<div class="form-field form-required">
|
912 |
-
<label for="crontrol_schedule_internal_name">
|
913 |
-
<?php esc_html_e( 'Internal Name', 'wp-crontrol' ); ?>
|
914 |
-
</label>
|
915 |
-
<input type="text" value="" id="crontrol_schedule_internal_name" name="crontrol_schedule_internal_name" required/>
|
916 |
-
</div>
|
917 |
-
<div class="form-field form-required">
|
918 |
-
<label for="crontrol_schedule_interval">
|
919 |
-
<?php esc_html_e( 'Interval (seconds)', 'wp-crontrol' ); ?>
|
920 |
-
</label>
|
921 |
-
<input type="number" value="" id="crontrol_schedule_interval" name="crontrol_schedule_interval" min="1" step="1" required/>
|
922 |
-
</div>
|
923 |
-
<div class="form-field form-required">
|
924 |
-
<label for="crontrol_schedule_display_name">
|
925 |
-
<?php esc_html_e( 'Display Name', 'wp-crontrol' ); ?>
|
926 |
-
</label>
|
927 |
-
<input type="text" value="" id="crontrol_schedule_display_name" name="crontrol_schedule_display_name" required/>
|
928 |
-
</div>
|
929 |
-
<p class="submit">
|
930 |
-
<input type="submit" class="button button-primary" value="<?php esc_attr_e( 'Add Cron Schedule', 'wp-crontrol' ); ?>" name="crontrol_new_schedule"/>
|
931 |
-
</p>
|
932 |
-
<?php wp_nonce_field( 'crontrol-new-schedule' ); ?>
|
933 |
-
</form>
|
934 |
-
</div>
|
935 |
-
</div>
|
936 |
-
</div>
|
937 |
-
<div id="col-right">
|
938 |
-
<div class="col-wrap">
|
939 |
-
<?php $table->display(); ?>
|
940 |
-
</div>
|
941 |
-
</div>
|
942 |
-
</div>
|
943 |
-
<?php
|
944 |
-
}
|
945 |
-
|
946 |
-
/**
|
947 |
-
* Clears the doing cron status when an event is unscheduled.
|
948 |
-
*
|
949 |
-
* What on earth does this function do, and why?
|
950 |
-
*
|
951 |
-
* Good question. The purpose of this function is to prevent other overdue cron events from firing when an event is run
|
952 |
-
* manually with the "Run Now" action. WP Crontrol works very hard to ensure that when cron event runs manually that it
|
953 |
-
* runs in the exact same way it would run as part of its schedule - via a properly spawned cron with a queued event in
|
954 |
-
* place. It does this by queueing an event at time `1` (1 second into 1st January 1970) and then immediately spawning
|
955 |
-
* cron (see the `Event\run()` function).
|
956 |
-
*
|
957 |
-
* The problem this causes is if other events are due then they will all run too, and this isn't desirable because if a
|
958 |
-
* site has a large number of stuck events due to a problem with the cron runner then it's not desirable for all those
|
959 |
-
* events to run when another is manually run. This happens because WordPress core will attempt to run all due events
|
960 |
-
* whenever cron is spawned.
|
961 |
-
*
|
962 |
-
* The code in this function prevents multiple events from running by changing the value of the `doing_cron` transient
|
963 |
-
* when an event gets unscheduled during a manual run, which prevents wp-cron.php from iterating more than one event.
|
964 |
-
*
|
965 |
-
* The `pre_unschedule_event` filter is used for this because it's just about the only hook available within this loop.
|
966 |
-
*
|
967 |
-
* Refs:
|
968 |
-
* - https://core.trac.wordpress.org/browser/trunk/src/wp-cron.php?rev=47198&marks=127,141#L122
|
969 |
-
*
|
970 |
-
* @param mixed $pre The pre-flight value of the event unschedule short-circuit. Not used.
|
971 |
-
* @return mixed The unaltered pre-flight value.
|
972 |
-
*/
|
973 |
-
function maybe_clear_doing_cron( $pre ) {
|
974 |
-
if ( defined( 'DOING_CRON' ) && DOING_CRON && isset( $_GET['crontrol-single-event'] ) ) {
|
975 |
-
delete_transient( 'doing_cron' );
|
976 |
-
}
|
977 |
-
|
978 |
-
return $pre;
|
979 |
-
}
|
980 |
-
|
981 |
-
/**
|
982 |
-
* Ajax handler which outputs a hash of the current list of scheduled events.
|
983 |
-
*
|
984 |
-
* @return void
|
985 |
-
*/
|
986 |
-
function ajax_check_events_hash() {
|
987 |
-
if ( ! current_user_can( 'manage_options' ) ) {
|
988 |
-
wp_send_json_error( null, 403 );
|
989 |
-
}
|
990 |
-
|
991 |
-
$data = json_encode( Event\get() );
|
992 |
-
|
993 |
-
if ( false === $data ) {
|
994 |
-
wp_send_json_error( null, 500 );
|
995 |
-
}
|
996 |
-
|
997 |
-
wp_send_json_success( md5( $data ) );
|
998 |
-
}
|
999 |
-
|
1000 |
-
/**
|
1001 |
-
* Gets the status of WP-Cron functionality on the site by performing a test spawn if necessary. Cached for one hour when all is well.
|
1002 |
-
*
|
1003 |
-
* @param bool $cache Whether to use the cached result from previous calls.
|
1004 |
-
* @return true|WP_Error Boolean true if the cron spawner is working as expected, or a `WP_Error` object if not.
|
1005 |
-
*/
|
1006 |
-
function test_cron_spawn( $cache = true ) {
|
1007 |
-
global $wp_version;
|
1008 |
-
|
1009 |
-
$cron_runner_plugins = array(
|
1010 |
-
'\HM\Cavalcade\Plugin\Job' => 'Cavalcade',
|
1011 |
-
'\Automattic\WP\Cron_Control\Main' => 'Cron Control',
|
1012 |
-
'\KMM\KRoN\Core' => 'KMM KRoN',
|
1013 |
-
);
|
1014 |
-
|
1015 |
-
foreach ( $cron_runner_plugins as $class => $plugin ) {
|
1016 |
-
if ( class_exists( $class ) ) {
|
1017 |
-
return new WP_Error( 'crontrol_info', sprintf(
|
1018 |
-
/* translators: 1: The name of the plugin that controls the running of cron events. */
|
1019 |
-
__( 'WP-Cron spawning is being managed by the %s plugin.', 'wp-crontrol' ),
|
1020 |
-
$plugin
|
1021 |
-
) );
|
1022 |
-
}
|
1023 |
-
}
|
1024 |
-
|
1025 |
-
if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
|
1026 |
-
return new WP_Error( 'crontrol_info', sprintf(
|
1027 |
-
/* translators: 1: The name of the PHP constant that is set. */
|
1028 |
-
__( 'The %s constant is set to true. WP-Cron spawning is disabled.', 'wp-crontrol' ),
|
1029 |
-
'DISABLE_WP_CRON'
|
1030 |
-
) );
|
1031 |
-
}
|
1032 |
-
|
1033 |
-
if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
|
1034 |
-
return new WP_Error( 'crontrol_info', sprintf(
|
1035 |
-
/* translators: 1: The name of the PHP constant that is set. */
|
1036 |
-
__( 'The %s constant is set to true.', 'wp-crontrol' ),
|
1037 |
-
'ALTERNATE_WP_CRON'
|
1038 |
-
) );
|
1039 |
-
}
|
1040 |
-
|
1041 |
-
$cached_status = get_transient( 'crontrol-cron-test-ok' );
|
1042 |
-
|
1043 |
-
if ( $cache && $cached_status ) {
|
1044 |
-
return true;
|
1045 |
-
}
|
1046 |
-
|
1047 |
-
$sslverify = version_compare( $wp_version, '4.0', '<' );
|
1048 |
-
$doing_wp_cron = sprintf( '%.22F', microtime( true ) );
|
1049 |
-
|
1050 |
-
$cron_request = apply_filters( 'cron_request', array(
|
1051 |
-
'url' => add_query_arg( 'doing_wp_cron', $doing_wp_cron, site_url( 'wp-cron.php' ) ),
|
1052 |
-
'key' => $doing_wp_cron,
|
1053 |
-
'args' => array(
|
1054 |
-
'timeout' => 3,
|
1055 |
-
'blocking' => true,
|
1056 |
-
'sslverify' => apply_filters( 'https_local_ssl_verify', $sslverify ),
|
1057 |
-
),
|
1058 |
-
) );
|
1059 |
-
|
1060 |
-
$cron_request['args']['blocking'] = true;
|
1061 |
-
|
1062 |
-
$result = wp_remote_post( $cron_request['url'], $cron_request['args'] );
|
1063 |
-
|
1064 |
-
if ( is_wp_error( $result ) ) {
|
1065 |
-
return $result;
|
1066 |
-
} elseif ( wp_remote_retrieve_response_code( $result ) >= 300 ) {
|
1067 |
-
return new WP_Error( 'unexpected_http_response_code', sprintf(
|
1068 |
-
/* translators: 1: The HTTP response code. */
|
1069 |
-
__( 'Unexpected HTTP response code: %s', 'wp-crontrol' ),
|
1070 |
-
intval( wp_remote_retrieve_response_code( $result ) )
|
1071 |
-
) );
|
1072 |
-
} else {
|
1073 |
-
set_transient( 'crontrol-cron-test-ok', 1, 3600 );
|
1074 |
-
return true;
|
1075 |
-
}
|
1076 |
-
|
1077 |
-
}
|
1078 |
-
|
1079 |
-
/**
|
1080 |
-
* Shows the status of WP-Cron functionality on the site. Only displays a message when there's a problem.
|
1081 |
-
*
|
1082 |
-
* @param string $tab The tab name.
|
1083 |
-
* @return void
|
1084 |
-
*/
|
1085 |
-
function show_cron_status( $tab ) {
|
1086 |
-
if ( 'UTC' !== date_default_timezone_get() ) {
|
1087 |
-
?>
|
1088 |
-
<div id="crontrol-timezone-warning" class="notice notice-warning">
|
1089 |
-
<?php
|
1090 |
-
printf(
|
1091 |
-
'<p>%1$s</p><p><a href="%2$s">%3$s</a></p>',
|
1092 |
-
/* translators: %s: Help page URL. */
|
1093 |
-
esc_html__( 'PHP default timezone is not set to UTC. This may cause issues with cron event timings.', 'wp-crontrol' ),
|
1094 |
-
'https://github.com/johnbillion/wp-crontrol/wiki/PHP-default-timezone-is-not-set-to-UTC',
|
1095 |
-
esc_html__( 'More information', 'wp-crontrol' )
|
1096 |
-
);
|
1097 |
-
?>
|
1098 |
-
</div>
|
1099 |
-
<?php
|
1100 |
-
}
|
1101 |
-
|
1102 |
-
$status = test_cron_spawn();
|
1103 |
-
|
1104 |
-
if ( is_wp_error( $status ) ) {
|
1105 |
-
if ( 'crontrol_info' === $status->get_error_code() ) {
|
1106 |
-
?>
|
1107 |
-
<div id="crontrol-status-notice" class="notice notice-info">
|
1108 |
-
<p><?php echo esc_html( $status->get_error_message() ); ?></p>
|
1109 |
-
</div>
|
1110 |
-
<?php
|
1111 |
-
} else {
|
1112 |
-
?>
|
1113 |
-
<div id="crontrol-status-error" class="error">
|
1114 |
-
<?php
|
1115 |
-
printf(
|
1116 |
-
'<p>%1$s</p><p><a href="%2$s">%3$s</a></p>',
|
1117 |
-
sprintf(
|
1118 |
-
/* translators: 1: Error message text. */
|
1119 |
-
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' ),
|
1120 |
-
'</p><p><strong>' . esc_html( $status->get_error_message() ) . '</strong>'
|
1121 |
-
),
|
1122 |
-
'https://github.com/johnbillion/wp-crontrol/wiki/Problems-with-spawning-a-call-to-the-WP-Cron-system',
|
1123 |
-
esc_html__( 'More information', 'wp-crontrol' )
|
1124 |
-
);
|
1125 |
-
?>
|
1126 |
-
</div>
|
1127 |
-
<?php
|
1128 |
-
}
|
1129 |
-
}
|
1130 |
-
}
|
1131 |
-
|
1132 |
-
/**
|
1133 |
-
* Get the display name for the site's timezone.
|
1134 |
-
*
|
1135 |
-
* @return string The name and UTC offset for the site's timezone.
|
1136 |
-
*/
|
1137 |
-
function get_timezone_name() {
|
1138 |
-
/** @var string */
|
1139 |
-
$timezone_string = get_option( 'timezone_string', '' );
|
1140 |
-
$gmt_offset = get_option( 'gmt_offset', 0 );
|
1141 |
-
|
1142 |
-
if ( 'UTC' === $timezone_string || ( empty( $gmt_offset ) && empty( $timezone_string ) ) ) {
|
1143 |
-
return 'UTC';
|
1144 |
-
}
|
1145 |
-
|
1146 |
-
if ( '' === $timezone_string ) {
|
1147 |
-
return get_utc_offset();
|
1148 |
-
}
|
1149 |
-
|
1150 |
-
return sprintf(
|
1151 |
-
'%s, %s',
|
1152 |
-
str_replace( '_', ' ', $timezone_string ),
|
1153 |
-
get_utc_offset()
|
1154 |
-
);
|
1155 |
-
}
|
1156 |
-
|
1157 |
-
/**
|
1158 |
-
* Returns a display value for a UTC offset.
|
1159 |
-
*
|
1160 |
-
* Examples:
|
1161 |
-
* - UTC
|
1162 |
-
* - UTC+4
|
1163 |
-
* - UTC-6
|
1164 |
-
*
|
1165 |
-
* @return string The UTC offset display value.
|
1166 |
-
*/
|
1167 |
-
function get_utc_offset() {
|
1168 |
-
$offset = get_option( 'gmt_offset', 0 );
|
1169 |
-
|
1170 |
-
if ( empty( $offset ) ) {
|
1171 |
-
return 'UTC';
|
1172 |
-
}
|
1173 |
-
|
1174 |
-
if ( 0 <= $offset ) {
|
1175 |
-
$formatted_offset = '+' . (string) $offset;
|
1176 |
-
} else {
|
1177 |
-
$formatted_offset = (string) $offset;
|
1178 |
-
}
|
1179 |
-
$formatted_offset = str_replace(
|
1180 |
-
array( '.25', '.5', '.75' ),
|
1181 |
-
array( ':15', ':30', ':45' ),
|
1182 |
-
$formatted_offset
|
1183 |
-
);
|
1184 |
-
return 'UTC' . $formatted_offset;
|
1185 |
-
}
|
1186 |
-
|
1187 |
-
/**
|
1188 |
-
* Shows the form used to add/edit cron events.
|
1189 |
-
*
|
1190 |
-
* @param bool $editing Whether the form is for the event editor.
|
1191 |
-
* @return void
|
1192 |
-
*/
|
1193 |
-
function show_cron_form( $editing ) {
|
1194 |
-
$display_args = '';
|
1195 |
-
$edit_id = null;
|
1196 |
-
$existing = false;
|
1197 |
-
|
1198 |
-
if ( $editing && ! empty( $_GET['crontrol_id'] ) ) {
|
1199 |
-
$edit_id = wp_unslash( $_GET['crontrol_id'] );
|
1200 |
-
|
1201 |
-
foreach ( Event\get() as $event ) {
|
1202 |
-
if ( $edit_id === $event->hook && intval( $_GET['crontrol_next_run_utc'] ) === $event->time && $event->sig === $_GET['crontrol_sig'] ) {
|
1203 |
-
$existing = array(
|
1204 |
-
'hookname' => $event->hook,
|
1205 |
-
'next_run' => $event->time, // UTC
|
1206 |
-
'schedule' => ( $event->schedule ? $event->schedule : '_oneoff' ),
|
1207 |
-
'sig' => $event->sig,
|
1208 |
-
'args' => $event->args,
|
1209 |
-
);
|
1210 |
-
break;
|
1211 |
-
}
|
1212 |
-
}
|
1213 |
-
|
1214 |
-
if ( empty( $existing ) ) {
|
1215 |
-
?>
|
1216 |
-
<div id="crontrol-event-not-found" class="notice notice-error">
|
1217 |
-
<?php
|
1218 |
-
printf(
|
1219 |
-
'<p>%1$s</p>',
|
1220 |
-
esc_html__( 'The event you are trying to edit does not exist.', 'wp-crontrol' )
|
1221 |
-
);
|
1222 |
-
?>
|
1223 |
-
</div>
|
1224 |
-
<?php
|
1225 |
-
return;
|
1226 |
-
}
|
1227 |
-
}
|
1228 |
-
|
1229 |
-
$is_editing_php = ( $existing && 'crontrol_cron_job' === $existing['hookname'] );
|
1230 |
-
|
1231 |
-
if ( $is_editing_php ) {
|
1232 |
-
$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' );
|
1233 |
-
} else {
|
1234 |
-
$helper_text = sprintf(
|
1235 |
-
/* translators: %s: A file name */
|
1236 |
-
esc_html__( 'Cron events trigger actions in your code. A cron event needs a corresponding action hook somewhere in code, e.g. the %1$s file in your theme.', 'wp-crontrol' ),
|
1237 |
-
'<code>functions.php</code>'
|
1238 |
-
);
|
1239 |
-
}
|
1240 |
-
|
1241 |
-
if ( is_array( $existing ) ) {
|
1242 |
-
$other_fields = wp_nonce_field( "crontrol-edit-cron_{$existing['hookname']}_{$existing['sig']}_{$existing['next_run']}", '_wpnonce', true, false );
|
1243 |
-
$other_fields .= sprintf( '<input name="crontrol_original_hookname" type="hidden" value="%s" />',
|
1244 |
-
esc_attr( $existing['hookname'] )
|
1245 |
-
);
|
1246 |
-
$other_fields .= sprintf( '<input name="crontrol_original_sig" type="hidden" value="%s" />',
|
1247 |
-
esc_attr( $existing['sig'] )
|
1248 |
-
);
|
1249 |
-
$other_fields .= sprintf( '<input name="crontrol_original_next_run_utc" type="hidden" value="%s" />',
|
1250 |
-
esc_attr( (string) $existing['next_run'] )
|
1251 |
-
);
|
1252 |
-
if ( ! empty( $existing['args'] ) ) {
|
1253 |
-
$display_args = wp_json_encode( $existing['args'] );
|
1254 |
-
|
1255 |
-
if ( false === $display_args ) {
|
1256 |
-
$display_args = '';
|
1257 |
-
}
|
1258 |
-
}
|
1259 |
-
$button = __( 'Update Event', 'wp-crontrol' );
|
1260 |
-
$next_run_gmt = gmdate( 'Y-m-d H:i:s', $existing['next_run'] );
|
1261 |
-
$next_run_date_local = get_date_from_gmt( $next_run_gmt, 'Y-m-d' );
|
1262 |
-
$next_run_time_local = get_date_from_gmt( $next_run_gmt, 'H:i:s' );
|
1263 |
-
} else {
|
1264 |
-
$other_fields = wp_nonce_field( 'crontrol-new-cron', '_wpnonce', true, false );
|
1265 |
-
$existing = array(
|
1266 |
-
'hookname' => '',
|
1267 |
-
'args' => array(),
|
1268 |
-
'next_run' => 'now', // UTC
|
1269 |
-
'schedule' => false,
|
1270 |
-
);
|
1271 |
-
|
1272 |
-
$button = __( 'Add Event', 'wp-crontrol' );
|
1273 |
-
$next_run_date_local = '';
|
1274 |
-
$next_run_time_local = '';
|
1275 |
-
}
|
1276 |
-
|
1277 |
-
if ( $is_editing_php ) {
|
1278 |
-
if ( ! isset( $existing['args']['code'] ) ) {
|
1279 |
-
$existing['args']['code'] = '';
|
1280 |
-
}
|
1281 |
-
if ( ! isset( $existing['args']['name'] ) ) {
|
1282 |
-
$existing['args']['name'] = '';
|
1283 |
-
}
|
1284 |
-
}
|
1285 |
-
|
1286 |
-
$can_add_php = current_user_can( 'edit_files' ) && ! $editing;
|
1287 |
-
$allowed = ( ! $is_editing_php || current_user_can( 'edit_files' ) );
|
1288 |
-
?>
|
1289 |
-
<div id="crontrol_form" class="wrap narrow">
|
1290 |
-
<?php
|
1291 |
-
if ( $allowed ) {
|
1292 |
-
if ( $editing ) {
|
1293 |
-
$heading = __( 'Edit Cron Event', 'wp-crontrol' );
|
1294 |
-
} else {
|
1295 |
-
$heading = __( 'Add Cron Event', 'wp-crontrol' );
|
1296 |
-
}
|
1297 |
-
|
1298 |
-
do_tabs();
|
1299 |
-
|
1300 |
-
printf(
|
1301 |
-
'<h1>%s</h1>',
|
1302 |
-
esc_html( $heading )
|
1303 |
-
);
|
1304 |
-
printf(
|
1305 |
-
'<p>%s</p>',
|
1306 |
-
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
1307 |
-
$helper_text
|
1308 |
-
);
|
1309 |
-
?>
|
1310 |
-
<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'; ?>">
|
1311 |
-
<?php
|
1312 |
-
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
1313 |
-
echo $other_fields;
|
1314 |
-
?>
|
1315 |
-
<table class="form-table"><tbody>
|
1316 |
-
<?php
|
1317 |
-
if ( $editing ) {
|
1318 |
-
$action = $is_editing_php ? 'edit_php_cron' : 'edit_cron';
|
1319 |
-
printf(
|
1320 |
-
'<input type="hidden" name="crontrol_action" value="%s"/>',
|
1321 |
-
esc_attr( $action )
|
1322 |
-
);
|
1323 |
-
} elseif ( $can_add_php ) {
|
1324 |
-
?>
|
1325 |
-
<tr class="hide-if-no-js">
|
1326 |
-
<th valign="top" scope="row">
|
1327 |
-
<?php esc_html_e( 'Event Type', 'wp-crontrol' ); ?>
|
1328 |
-
</th>
|
1329 |
-
<td>
|
1330 |
-
<p><label><input type="radio" name="crontrol_action" value="new_cron" checked>Standard cron event</label></p>
|
1331 |
-
<p><label><input type="radio" name="crontrol_action" value="new_php_cron">PHP cron event</label></p>
|
1332 |
-
</td>
|
1333 |
-
</tr>
|
1334 |
-
<?php
|
1335 |
-
} else {
|
1336 |
-
?>
|
1337 |
-
<input type="hidden" name="crontrol_action" value="new_cron"/>
|
1338 |
-
<?php
|
1339 |
-
}
|
1340 |
-
|
1341 |
-
if ( $is_editing_php || $can_add_php ) {
|
1342 |
-
?>
|
1343 |
-
<tr class="crontrol-event-php">
|
1344 |
-
<th valign="top" scope="row">
|
1345 |
-
<label for="crontrol_hookcode">
|
1346 |
-
<?php esc_html_e( 'PHP Code', 'wp-crontrol' ); ?>
|
1347 |
-
</label>
|
1348 |
-
</th>
|
1349 |
-
<td>
|
1350 |
-
<p class="description">
|
1351 |
-
<?php
|
1352 |
-
printf(
|
1353 |
-
/* translators: The PHP tag name */
|
1354 |
-
esc_html__( 'The opening %s tag must not be included.', 'wp-crontrol' ),
|
1355 |
-
'<code><?php</code>'
|
1356 |
-
);
|
1357 |
-
?>
|
1358 |
-
</p>
|
1359 |
-
<p><textarea class="large-text code" rows="10" cols="50" id="crontrol_hookcode" name="crontrol_hookcode"><?php echo esc_textarea( $editing ? $existing['args']['code'] : '' ); ?></textarea></p>
|
1360 |
-
<?php do_action( 'crontrol/manage/hookcode', $existing ); ?>
|
1361 |
-
</td>
|
1362 |
-
</tr>
|
1363 |
-
<tr class="crontrol-event-php">
|
1364 |
-
<th valign="top" scope="row">
|
1365 |
-
<label for="crontrol_eventname">
|
1366 |
-
<?php esc_html_e( 'Event Name (optional)', 'wp-crontrol' ); ?>
|
1367 |
-
</label>
|
1368 |
-
</th>
|
1369 |
-
<td>
|
1370 |
-
<input type="text" class="regular-text" id="crontrol_eventname" name="crontrol_eventname" value="<?php echo esc_attr( $editing ? $existing['args']['name'] : '' ); ?>"/>
|
1371 |
-
<?php do_action( 'crontrol/manage/eventname', $existing ); ?>
|
1372 |
-
</td>
|
1373 |
-
</tr>
|
1374 |
-
<?php
|
1375 |
-
}
|
1376 |
-
|
1377 |
-
if ( ! $is_editing_php ) {
|
1378 |
-
?>
|
1379 |
-
<tr class="crontrol-event-standard">
|
1380 |
-
<th valign="top" scope="row">
|
1381 |
-
<label for="crontrol_hookname">
|
1382 |
-
<?php esc_html_e( 'Hook Name', 'wp-crontrol' ); ?>
|
1383 |
-
</label>
|
1384 |
-
</th>
|
1385 |
-
<td>
|
1386 |
-
<input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" class="regular-text" id="crontrol_hookname" name="crontrol_hookname" value="<?php echo esc_attr( $existing['hookname'] ); ?>" required />
|
1387 |
-
<?php do_action( 'crontrol/manage/hookname', $existing ); ?>
|
1388 |
-
</td>
|
1389 |
-
</tr>
|
1390 |
-
<tr class="crontrol-event-standard">
|
1391 |
-
<th valign="top" scope="row">
|
1392 |
-
<label for="crontrol_args">
|
1393 |
-
<?php esc_html_e( 'Arguments (optional)', 'wp-crontrol' ); ?>
|
1394 |
-
</label>
|
1395 |
-
</th>
|
1396 |
-
<td>
|
1397 |
-
<input type="text" autocorrect="off" autocapitalize="off" spellcheck="false" class="regular-text code" id="crontrol_args" name="crontrol_args" value="<?php echo esc_attr( $display_args ); ?>"/>
|
1398 |
-
<?php do_action( 'crontrol/manage/args', $existing ); ?>
|
1399 |
-
<p class="description">
|
1400 |
-
<?php
|
1401 |
-
printf(
|
1402 |
-
/* translators: 1, 2, and 3: Example values for an input field. */
|
1403 |
-
esc_html__( 'Use a JSON encoded array, e.g. %1$s, %2$s, or %3$s', 'wp-crontrol' ),
|
1404 |
-
'<code>[25]</code>',
|
1405 |
-
'<code>["asdf"]</code>',
|
1406 |
-
'<code>["i","want",25,"cakes"]</code>'
|
1407 |
-
);
|
1408 |
-
?>
|
1409 |
-
</p>
|
1410 |
-
</td>
|
1411 |
-
</tr>
|
1412 |
-
<?php
|
1413 |
-
}
|
1414 |
-
?>
|
1415 |
-
<tr>
|
1416 |
-
<th valign="top" scope="row">
|
1417 |
-
<label for="crontrol_next_run_date_local">
|
1418 |
-
<?php esc_html_e( 'Next Run', 'wp-crontrol' ); ?>
|
1419 |
-
</label>
|
1420 |
-
</th>
|
1421 |
-
<td>
|
1422 |
-
<ul>
|
1423 |
-
<li>
|
1424 |
-
<label>
|
1425 |
-
<input type="radio" name="crontrol_next_run_date_local" value="now" checked>
|
1426 |
-
<?php esc_html_e( 'Now', 'wp-crontrol' ); ?>
|
1427 |
-
</label>
|
1428 |
-
</li>
|
1429 |
-
<li>
|
1430 |
-
<label>
|
1431 |
-
<input type="radio" name="crontrol_next_run_date_local" value="+1 day">
|
1432 |
-
<?php esc_html_e( 'Tomorrow', 'wp-crontrol' ); ?>
|
1433 |
-
</label>
|
1434 |
-
</li>
|
1435 |
-
<li>
|
1436 |
-
<label>
|
1437 |
-
<input type="radio" name="crontrol_next_run_date_local" value="custom" id="crontrol_next_run_date_local_custom" <?php checked( $editing ); ?>>
|
1438 |
-
<?php
|
1439 |
-
printf(
|
1440 |
-
/* translators: %s: An input field for specifying a date and time */
|
1441 |
-
esc_html__( 'At: %s', 'wp-crontrol' ),
|
1442 |
-
sprintf(
|
1443 |
-
'<br>
|
1444 |
-
<input type="date" autocorrect="off" autocapitalize="off" spellcheck="false" name="crontrol_next_run_date_local_custom_date" id="crontrol_next_run_date_local_custom_date" value="%1$s" placeholder="yyyy-mm-dd" pattern="\d{4}-\d{2}-\d{2}" />
|
1445 |
-
<input type="time" autocorrect="off" autocapitalize="off" spellcheck="false" name="crontrol_next_run_date_local_custom_time" id="crontrol_next_run_date_local_custom_time" value="%2$s" step="1" placeholder="hh:mm:ss" pattern="\d{2}:\d{2}:\d{2}" />',
|
1446 |
-
esc_attr( $next_run_date_local ),
|
1447 |
-
esc_attr( $next_run_time_local )
|
1448 |
-
)
|
1449 |
-
);
|
1450 |
-
?>
|
1451 |
-
</label>
|
1452 |
-
</li>
|
1453 |
-
</ul>
|
1454 |
-
|
1455 |
-
<?php do_action( 'crontrol/manage/next_run', $existing ); ?>
|
1456 |
-
|
1457 |
-
<p class="description">
|
1458 |
-
<?php
|
1459 |
-
printf(
|
1460 |
-
/* translators: %s Timezone name. */
|
1461 |
-
esc_html__( 'Timezone: %s', 'wp-crontrol' ),
|
1462 |
-
esc_html( get_timezone_name() )
|
1463 |
-
);
|
1464 |
-
?>
|
1465 |
-
</p>
|
1466 |
-
</td>
|
1467 |
-
</tr>
|
1468 |
-
<tr>
|
1469 |
-
<th valign="top" scope="row">
|
1470 |
-
<label for="crontrol_schedule">
|
1471 |
-
<?php esc_html_e( 'Recurrence', 'wp-crontrol' ); ?>
|
1472 |
-
</label>
|
1473 |
-
</th>
|
1474 |
-
<td>
|
1475 |
-
<?php Schedule\dropdown( $existing['schedule'] ); ?>
|
1476 |
-
<?php do_action( 'crontrol/manage/schedule', $existing ); ?>
|
1477 |
-
</td>
|
1478 |
-
</tr>
|
1479 |
-
</tbody></table>
|
1480 |
-
<p class="submit">
|
1481 |
-
<input type="submit" class="button button-primary" value="<?php echo esc_attr( $button ); ?>"/>
|
1482 |
-
</p>
|
1483 |
-
</form>
|
1484 |
-
<?php } else { ?>
|
1485 |
-
<div class="error inline">
|
1486 |
-
<p><?php esc_html_e( 'You cannot add, edit, or delete PHP cron events because your user account does not have the ability to edit files.', 'wp-crontrol' ); ?></p>
|
1487 |
-
</div>
|
1488 |
-
<?php } ?>
|
1489 |
-
</div>
|
1490 |
-
<?php
|
1491 |
-
}
|
1492 |
-
|
1493 |
-
/**
|
1494 |
-
* Displays the manage page for the plugin.
|
1495 |
-
*
|
1496 |
-
* @return void
|
1497 |
-
*/
|
1498 |
-
function admin_manage_page() {
|
1499 |
-
$messages = array(
|
1500 |
-
'1' => array(
|
1501 |
-
/* translators: 1: The name of the cron event. */
|
1502 |
-
__( 'Scheduled the cron event %s to run now. The original event will not be affected.', 'wp-crontrol' ),
|
1503 |
-
'success',
|
1504 |
-
),
|
1505 |
-
'2' => array(
|
1506 |
-
/* translators: 1: The name of the cron event. */
|
1507 |
-
__( 'Deleted all %s cron events.', 'wp-crontrol' ),
|
1508 |
-
'success',
|
1509 |
-
),
|
1510 |
-
'3' => array(
|
1511 |
-
/* translators: 1: The name of the cron event. */
|
1512 |
-
__( 'There are no %s cron events to delete.', 'wp-crontrol' ),
|
1513 |
-
'info',
|
1514 |
-
),
|
1515 |
-
'4' => array(
|
1516 |
-
/* translators: 1: The name of the cron event. */
|
1517 |
-
__( 'Saved the cron event %s.', 'wp-crontrol' ),
|
1518 |
-
'success',
|
1519 |
-
),
|
1520 |
-
'5' => array(
|
1521 |
-
/* translators: 1: The name of the cron event. */
|
1522 |
-
__( 'Created the cron event %s.', 'wp-crontrol' ),
|
1523 |
-
'success',
|
1524 |
-
),
|
1525 |
-
'6' => array(
|
1526 |
-
/* translators: 1: The name of the cron event. */
|
1527 |
-
__( 'Deleted the cron event %s.', 'wp-crontrol' ),
|
1528 |
-
'success',
|
1529 |
-
),
|
1530 |
-
'7' => array(
|
1531 |
-
/* translators: 1: The name of the cron event. */
|
1532 |
-
__( 'Failed to the delete the cron event %s.', 'wp-crontrol' ),
|
1533 |
-
'error',
|
1534 |
-
),
|
1535 |
-
'8' => array(
|
1536 |
-
/* translators: 1: The name of the cron event. */
|
1537 |
-
__( 'Failed to the execute the cron event %s.', 'wp-crontrol' ),
|
1538 |
-
'error',
|
1539 |
-
),
|
1540 |
-
'9' => array(
|
1541 |
-
__( 'Deleted the selected cron events.', 'wp-crontrol' ),
|
1542 |
-
'success',
|
1543 |
-
),
|
1544 |
-
'10' => array(
|
1545 |
-
/* translators: 1: The name of the cron event. */
|
1546 |
-
__( 'Failed to save the cron event %s.', 'wp-crontrol' ),
|
1547 |
-
'error',
|
1548 |
-
),
|
1549 |
-
'error' => array(
|
1550 |
-
__( 'An unknown error occurred.', 'wp-crontrol' ),
|
1551 |
-
'error',
|
1552 |
-
),
|
1553 |
-
);
|
1554 |
-
|
1555 |
-
if ( isset( $_GET['crontrol_name'] ) && isset( $_GET['crontrol_message'] ) && isset( $messages[ $_GET['crontrol_message'] ] ) ) {
|
1556 |
-
$hook = wp_unslash( $_GET['crontrol_name'] );
|
1557 |
-
$message = wp_unslash( $_GET['crontrol_message'] );
|
1558 |
-
$link = '';
|
1559 |
-
|
1560 |
-
if ( 'error' === $message ) {
|
1561 |
-
$error = get_message();
|
1562 |
-
|
1563 |
-
if ( $error ) {
|
1564 |
-
$messages['error'][0] = $error;
|
1565 |
-
}
|
1566 |
-
}
|
1567 |
-
|
1568 |
-
printf(
|
1569 |
-
'<div id="crontrol-message" class="notice notice-%1$s is-dismissible"><p>%2$s%3$s</p></div>',
|
1570 |
-
esc_attr( $messages[ $message ][1] ),
|
1571 |
-
sprintf(
|
1572 |
-
esc_html( $messages[ $message ][0] ),
|
1573 |
-
'<strong>' . esc_html( $hook ) . '</strong>'
|
1574 |
-
),
|
1575 |
-
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
1576 |
-
$link
|
1577 |
-
);
|
1578 |
-
}
|
1579 |
-
|
1580 |
-
$tabs = get_tab_states();
|
1581 |
-
$table = Event\get_list_table();
|
1582 |
-
|
1583 |
-
switch ( true ) {
|
1584 |
-
case $tabs['events']:
|
1585 |
-
?>
|
1586 |
-
<div class="wrap">
|
1587 |
-
<?php do_tabs(); ?>
|
1588 |
-
|
1589 |
-
<h1 class="wp-heading-inline"><?php esc_html_e( 'Cron Events', 'wp-crontrol' ); ?></h1>
|
1590 |
-
|
1591 |
-
<?php echo '<a href="' . esc_url( admin_url( 'tools.php?page=crontrol_admin_manage_page&crontrol_action=new-cron' ) ) . '" class="page-title-action">' . esc_html__( 'Add New', 'wp-crontrol' ) . '</a>'; ?>
|
1592 |
-
|
1593 |
-
<hr class="wp-header-end">
|
1594 |
-
|
1595 |
-
<?php $table->views(); ?>
|
1596 |
-
|
1597 |
-
<form id="posts-filter" method="get" action="tools.php">
|
1598 |
-
<input type="hidden" name="page" value="crontrol_admin_manage_page" />
|
1599 |
-
<?php $table->search_box( __( 'Search Hook Names', 'wp-crontrol' ), 'cron-event' ); ?>
|
1600 |
-
</form>
|
1601 |
-
|
1602 |
-
<form method="post" action="tools.php?page=crontrol_admin_manage_page">
|
1603 |
-
<div class="table-responsive">
|
1604 |
-
<?php $table->display(); ?>
|
1605 |
-
</div>
|
1606 |
-
</form>
|
1607 |
-
|
1608 |
-
<p>
|
1609 |
-
<?php
|
1610 |
-
echo esc_html( sprintf(
|
1611 |
-
/* translators: 1: Date and time, 2: Timezone */
|
1612 |
-
__( 'Site time: %1$s (%2$s)', 'wp-crontrol' ),
|
1613 |
-
date_i18n( 'Y-m-d H:i:s' ),
|
1614 |
-
get_timezone_name()
|
1615 |
-
) );
|
1616 |
-
?>
|
1617 |
-
</p>
|
1618 |
-
</div>
|
1619 |
-
<?php
|
1620 |
-
|
1621 |
-
break;
|
1622 |
-
|
1623 |
-
case $tabs['add-event']:
|
1624 |
-
show_cron_form( false );
|
1625 |
-
break;
|
1626 |
-
|
1627 |
-
case $tabs['edit-event']:
|
1628 |
-
show_cron_form( true );
|
1629 |
-
break;
|
1630 |
-
|
1631 |
-
}
|
1632 |
-
|
1633 |
-
}
|
1634 |
-
|
1635 |
-
/**
|
1636 |
-
* Get the states of the various cron-related tabs.
|
1637 |
-
*
|
1638 |
-
* @return array<string,bool> Array of states keyed by tab name.
|
1639 |
-
*/
|
1640 |
-
function get_tab_states() {
|
1641 |
-
$tabs = array(
|
1642 |
-
'events' => ( ! empty( $_GET['page'] ) && 'crontrol_admin_manage_page' === $_GET['page'] && empty( $_GET['crontrol_action'] ) ),
|
1643 |
-
'schedules' => ( ! empty( $_GET['page'] ) && 'crontrol_admin_options_page' === $_GET['page'] ),
|
1644 |
-
'add-event' => ( ! empty( $_GET['crontrol_action'] ) && 'new-cron' === $_GET['crontrol_action'] ),
|
1645 |
-
'edit-event' => ( ! empty( $_GET['crontrol_action'] ) && 'edit-cron' === $_GET['crontrol_action'] ),
|
1646 |
-
);
|
1647 |
-
|
1648 |
-
$tabs = apply_filters( 'crontrol/tabs', $tabs );
|
1649 |
-
|
1650 |
-
return $tabs;
|
1651 |
-
}
|
1652 |
-
|
1653 |
-
/**
|
1654 |
-
* Output the cron-related tabs if we're on a cron-related admin screen.
|
1655 |
-
*
|
1656 |
-
* @return void
|
1657 |
-
*/
|
1658 |
-
function do_tabs() {
|
1659 |
-
$tabs = get_tab_states();
|
1660 |
-
$tab = array_filter( $tabs );
|
1661 |
-
|
1662 |
-
if ( ! $tab ) {
|
1663 |
-
return;
|
1664 |
-
}
|
1665 |
-
|
1666 |
-
$tab = array_keys( $tab );
|
1667 |
-
$tab = reset( $tab );
|
1668 |
-
$links = array(
|
1669 |
-
'events' => array(
|
1670 |
-
'tools.php?page=crontrol_admin_manage_page',
|
1671 |
-
__( 'Cron Events', 'wp-crontrol' ),
|
1672 |
-
),
|
1673 |
-
'schedules' => array(
|
1674 |
-
'options-general.php?page=crontrol_admin_options_page',
|
1675 |
-
__( 'Cron Schedules', 'wp-crontrol' ),
|
1676 |
-
),
|
1677 |
-
);
|
1678 |
-
|
1679 |
-
?>
|
1680 |
-
<div id="crontrol-header">
|
1681 |
-
<nav class="nav-tab-wrapper">
|
1682 |
-
<?php
|
1683 |
-
foreach ( $links as $id => $link ) {
|
1684 |
-
if ( ! empty( $tabs[ $id ] ) ) {
|
1685 |
-
printf(
|
1686 |
-
'<a href="%s" class="nav-tab nav-tab-active">%s</a>',
|
1687 |
-
esc_url( $link[0] ),
|
1688 |
-
esc_html( $link[1] )
|
1689 |
-
);
|
1690 |
-
} else {
|
1691 |
-
printf(
|
1692 |
-
'<a href="%s" class="nav-tab">%s</a>',
|
1693 |
-
esc_url( $link[0] ),
|
1694 |
-
esc_html( $link[1] )
|
1695 |
-
);
|
1696 |
-
}
|
1697 |
-
}
|
1698 |
-
|
1699 |
-
if ( $tabs['add-event'] ) {
|
1700 |
-
printf(
|
1701 |
-
'<span class="nav-tab nav-tab-active">%s</span>',
|
1702 |
-
esc_html__( 'Add Cron Event', 'wp-crontrol' )
|
1703 |
-
);
|
1704 |
-
} elseif ( $tabs['edit-event'] ) {
|
1705 |
-
printf(
|
1706 |
-
'<span class="nav-tab nav-tab-active">%s</span>',
|
1707 |
-
esc_html__( 'Edit Cron Event', 'wp-crontrol' )
|
1708 |
-
);
|
1709 |
-
}
|
1710 |
-
?>
|
1711 |
-
</nav>
|
1712 |
-
<?php
|
1713 |
-
do_action( 'crontrol/tab-header', $tab, $tabs );
|
1714 |
-
?>
|
1715 |
-
</div>
|
1716 |
-
<?php
|
1717 |
-
}
|
1718 |
-
|
1719 |
-
/**
|
1720 |
-
* Returns an array of the callback functions that are attached to the given hook name.
|
1721 |
-
*
|
1722 |
-
* @param string $name The hook name.
|
1723 |
-
* @return array<int,array<string,mixed>> Array of callbacks attached to the hook.
|
1724 |
-
* @phpstan-return array<int,array{
|
1725 |
-
* priority: int,
|
1726 |
-
* callback: array<string,mixed>,
|
1727 |
-
* }>
|
1728 |
-
*/
|
1729 |
-
function get_hook_callbacks( $name ) {
|
1730 |
-
global $wp_filter;
|
1731 |
-
|
1732 |
-
$actions = array();
|
1733 |
-
|
1734 |
-
if ( isset( $wp_filter[ $name ] ) ) {
|
1735 |
-
// See http://core.trac.wordpress.org/ticket/17817.
|
1736 |
-
$action = $wp_filter[ $name ];
|
1737 |
-
|
1738 |
-
/**
|
1739 |
-
* @var int $priority
|
1740 |
-
*/
|
1741 |
-
foreach ( $action as $priority => $callbacks ) {
|
1742 |
-
foreach ( $callbacks as $callback ) {
|
1743 |
-
$callback = populate_callback( $callback );
|
1744 |
-
|
1745 |
-
$actions[] = array(
|
1746 |
-
'priority' => $priority,
|
1747 |
-
'callback' => $callback,
|
1748 |
-
);
|
1749 |
-
}
|
1750 |
-
}
|
1751 |
-
}
|
1752 |
-
|
1753 |
-
return $actions;
|
1754 |
-
}
|
1755 |
-
|
1756 |
-
/**
|
1757 |
-
* Populates the details of the given callback function.
|
1758 |
-
*
|
1759 |
-
* @param array<string,mixed> $callback A callback entry.
|
1760 |
-
* @phpstan-param array{
|
1761 |
-
* function: string|array<int,mixed>|object,
|
1762 |
-
* accepted_args: int,
|
1763 |
-
* } $callback
|
1764 |
-
* @return array<string,mixed> The updated callback entry.
|
1765 |
-
*/
|
1766 |
-
function populate_callback( array $callback ) {
|
1767 |
-
// If Query Monitor is installed, use its rich callback analysis.
|
1768 |
-
if ( method_exists( '\QM_Util', 'populate_callback' ) ) {
|
1769 |
-
return \QM_Util::populate_callback( $callback );
|
1770 |
-
}
|
1771 |
-
|
1772 |
-
if ( is_string( $callback['function'] ) && ( false !== strpos( $callback['function'], '::' ) ) ) {
|
1773 |
-
$callback['function'] = explode( '::', $callback['function'] );
|
1774 |
-
}
|
1775 |
-
|
1776 |
-
if ( is_array( $callback['function'] ) ) {
|
1777 |
-
if ( is_object( $callback['function'][0] ) ) {
|
1778 |
-
$class = get_class( $callback['function'][0] );
|
1779 |
-
$access = '->';
|
1780 |
-
} else {
|
1781 |
-
$class = $callback['function'][0];
|
1782 |
-
$access = '::';
|
1783 |
-
}
|
1784 |
-
|
1785 |
-
$callback['name'] = $class . $access . $callback['function'][1] . '()';
|
1786 |
-
} elseif ( is_object( $callback['function'] ) ) {
|
1787 |
-
if ( is_a( $callback['function'], 'Closure' ) ) {
|
1788 |
-
$callback['name'] = 'Closure';
|
1789 |
-
} else {
|
1790 |
-
$class = get_class( $callback['function'] );
|
1791 |
-
|
1792 |
-
$callback['name'] = $class . '->__invoke()';
|
1793 |
-
}
|
1794 |
-
} else {
|
1795 |
-
$callback['name'] = $callback['function'] . '()';
|
1796 |
-
}
|
1797 |
-
|
1798 |
-
if ( ! method_exists( '\QM_Util', 'populate_callback' ) && ! is_callable( $callback['function'] ) ) {
|
1799 |
-
$callback['error'] = new WP_Error(
|
1800 |
-
'not_callable',
|
1801 |
-
sprintf(
|
1802 |
-
/* translators: %s: Function name */
|
1803 |
-
__( 'Function %s does not exist', 'wp-crontrol' ),
|
1804 |
-
$callback['name']
|
1805 |
-
)
|
1806 |
-
);
|
1807 |
-
}
|
1808 |
-
|
1809 |
-
return $callback;
|
1810 |
-
}
|
1811 |
-
|
1812 |
-
/**
|
1813 |
-
* Returns a user-friendly representation of the callback function.
|
1814 |
-
*
|
1815 |
-
* @param mixed[] $callback The callback entry.
|
1816 |
-
* @return string The displayable version of the callback name.
|
1817 |
-
*/
|
1818 |
-
function output_callback( array $callback ) {
|
1819 |
-
$qm = WP_PLUGIN_DIR . '/query-monitor/query-monitor.php';
|
1820 |
-
$html = plugin_dir_path( $qm ) . 'output/Html.php';
|
1821 |
-
|
1822 |
-
if ( ! empty( $callback['callback']['error'] ) ) {
|
1823 |
-
$return = '<code>' . $callback['callback']['name'] . '</code>';
|
1824 |
-
$return .= '<br><span class="status-crontrol-error"><span class="dashicons dashicons-warning" aria-hidden="true"></span> ';
|
1825 |
-
$return .= esc_html( $callback['callback']['error']->get_error_message() );
|
1826 |
-
$return .= '</span>';
|
1827 |
-
return $return;
|
1828 |
-
}
|
1829 |
-
|
1830 |
-
// If Query Monitor is installed, use its rich callback output.
|
1831 |
-
if ( class_exists( '\QueryMonitor' ) && file_exists( $html ) ) {
|
1832 |
-
require_once $html;
|
1833 |
-
|
1834 |
-
if ( class_exists( '\QM_Output_Html' ) ) {
|
1835 |
-
return \QM_Output_Html::output_filename(
|
1836 |
-
$callback['callback']['name'],
|
1837 |
-
$callback['callback']['file'],
|
1838 |
-
$callback['callback']['line']
|
1839 |
-
);
|
1840 |
-
}
|
1841 |
-
}
|
1842 |
-
|
1843 |
-
return '<code>' . $callback['callback']['name'] . '</code>';
|
1844 |
-
}
|
1845 |
-
|
1846 |
-
/**
|
1847 |
-
* Pretty-prints the difference in two times.
|
1848 |
-
*
|
1849 |
-
* @param int $older_date Unix timestamp.
|
1850 |
-
* @param int $newer_date Unix timestamp.
|
1851 |
-
* @return string The pretty time_since value
|
1852 |
-
* @link http://binarybonsai.com/code/timesince.txt
|
1853 |
-
*/
|
1854 |
-
function time_since( $older_date, $newer_date ) {
|
1855 |
-
return interval( $newer_date - $older_date );
|
1856 |
-
}
|
1857 |
-
|
1858 |
-
/**
|
1859 |
-
* Converts a period of time in seconds into a human-readable format representing the interval.
|
1860 |
-
*
|
1861 |
-
* Example:
|
1862 |
-
*
|
1863 |
-
* echo \Crontrol\interval( 90 );
|
1864 |
-
* // 1 minute 30 seconds
|
1865 |
-
*
|
1866 |
-
* @param int|float $since A period of time in seconds.
|
1867 |
-
* @return string An interval represented as a string.
|
1868 |
-
*/
|
1869 |
-
function interval( $since ) {
|
1870 |
-
// Array of time period chunks.
|
1871 |
-
$chunks = array(
|
1872 |
-
/* translators: 1: The number of years in an interval of time. */
|
1873 |
-
array( 60 * 60 * 24 * 365, _n_noop( '%s year', '%s years', 'wp-crontrol' ) ),
|
1874 |
-
/* translators: 1: The number of months in an interval of time. */
|
1875 |
-
array( 60 * 60 * 24 * 30, _n_noop( '%s month', '%s months', 'wp-crontrol' ) ),
|
1876 |
-
/* translators: 1: The number of weeks in an interval of time. */
|
1877 |
-
array( 60 * 60 * 24 * 7, _n_noop( '%s week', '%s weeks', 'wp-crontrol' ) ),
|
1878 |
-
/* translators: 1: The number of days in an interval of time. */
|
1879 |
-
array( 60 * 60 * 24, _n_noop( '%s day', '%s days', 'wp-crontrol' ) ),
|
1880 |
-
/* translators: 1: The number of hours in an interval of time. */
|
1881 |
-
array( 60 * 60, _n_noop( '%s hour', '%s hours', 'wp-crontrol' ) ),
|
1882 |
-
/* translators: 1: The number of minutes in an interval of time. */
|
1883 |
-
array( 60, _n_noop( '%s minute', '%s minutes', 'wp-crontrol' ) ),
|
1884 |
-
/* translators: 1: The number of seconds in an interval of time. */
|
1885 |
-
array( 1, _n_noop( '%s second', '%s seconds', 'wp-crontrol' ) ),
|
1886 |
-
);
|
1887 |
-
|
1888 |
-
if ( $since <= 0 ) {
|
1889 |
-
return __( 'now', 'wp-crontrol' );
|
1890 |
-
}
|
1891 |
-
|
1892 |
-
/**
|
1893 |
-
* We only want to output two chunks of time here, eg:
|
1894 |
-
* x years, xx months
|
1895 |
-
* x days, xx hours
|
1896 |
-
* so there's only two bits of calculation below:
|
1897 |
-
*/
|
1898 |
-
|
1899 |
-
// Step one: the first chunk.
|
1900 |
-
foreach ( array_keys( $chunks ) as $i ) {
|
1901 |
-
$seconds = $chunks[ $i ][0];
|
1902 |
-
$name = $chunks[ $i ][1];
|
1903 |
-
|
1904 |
-
// Finding the biggest chunk (if the chunk fits, break).
|
1905 |
-
$count = (int) floor( $since / $seconds );
|
1906 |
-
if ( $count ) {
|
1907 |
-
break;
|
1908 |
-
}
|
1909 |
-
}
|
1910 |
-
|
1911 |
-
// Set output var.
|
1912 |
-
$output = sprintf( translate_nooped_plural( $name, $count, 'wp-crontrol' ), $count );
|
1913 |
-
|
1914 |
-
// Step two: the second chunk.
|
1915 |
-
if ( $i + 1 < count( $chunks ) ) {
|
1916 |
-
$seconds2 = $chunks[ $i + 1 ][0];
|
1917 |
-
$name2 = $chunks[ $i + 1 ][1];
|
1918 |
-
$count2 = (int) floor( ( $since - ( $seconds * $count ) ) / $seconds2 );
|
1919 |
-
if ( $count2 ) {
|
1920 |
-
// Add to output var.
|
1921 |
-
$output .= ' ' . sprintf( translate_nooped_plural( $name2, $count2, 'wp-crontrol' ), $count2 );
|
1922 |
-
}
|
1923 |
-
}
|
1924 |
-
|
1925 |
-
return $output;
|
1926 |
-
}
|
1927 |
-
|
1928 |
-
/**
|
1929 |
-
* Sets up the Events listing screen.
|
1930 |
-
*
|
1931 |
-
* @return void
|
1932 |
-
*/
|
1933 |
-
function setup_manage_page() {
|
1934 |
-
// Initialise the list table
|
1935 |
-
Event\get_list_table();
|
1936 |
-
|
1937 |
-
// Add the initially hidden admin notice about the out of date events list
|
1938 |
-
add_action( 'admin_notices', function() {
|
1939 |
-
printf(
|
1940 |
-
'<div id="crontrol-hash-message" class="notice notice-info"><p>%s</p></div>',
|
1941 |
-
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' )
|
1942 |
-
);
|
1943 |
-
} );
|
1944 |
-
}
|
1945 |
-
|
1946 |
-
/**
|
1947 |
-
* Registers the stylesheet and JavaScript for the admin areas.
|
1948 |
-
*
|
1949 |
-
* @param string $hook_suffix The admin screen ID.
|
1950 |
-
* @return void
|
1951 |
-
*/
|
1952 |
-
function enqueue_assets( $hook_suffix ) {
|
1953 |
-
$tab = get_tab_states();
|
1954 |
-
|
1955 |
-
if ( ! array_filter( $tab ) ) {
|
1956 |
-
return;
|
1957 |
-
}
|
1958 |
-
|
1959 |
-
$ver = (string) filemtime( plugin_dir_path( __FILE__ ) . 'css/wp-crontrol.css' );
|
1960 |
-
wp_enqueue_style( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'css/wp-crontrol.css', array( 'dashicons' ), $ver );
|
1961 |
-
|
1962 |
-
$ver = (string) filemtime( plugin_dir_path( __FILE__ ) . 'js/wp-crontrol.js' );
|
1963 |
-
wp_enqueue_script( 'wp-crontrol', plugin_dir_url( __FILE__ ) . 'js/wp-crontrol.js', array( 'jquery', 'wp-a11y' ), $ver, true );
|
1964 |
-
|
1965 |
-
$vars = array();
|
1966 |
-
|
1967 |
-
if ( ! empty( $tab['events'] ) ) {
|
1968 |
-
$data = json_encode( Event\get() );
|
1969 |
-
|
1970 |
-
if ( false !== $data ) {
|
1971 |
-
$vars['eventsHash'] = md5( $data );
|
1972 |
-
$vars['eventsHashInterval'] = 20;
|
1973 |
-
}
|
1974 |
-
}
|
1975 |
-
|
1976 |
-
if ( ! empty( $tab['add-event'] ) || ! empty( $tab['edit-event'] ) ) {
|
1977 |
-
if ( function_exists( 'wp_enqueue_code_editor' ) && current_user_can( 'edit_files' ) ) {
|
1978 |
-
$settings = wp_enqueue_code_editor( array(
|
1979 |
-
'type' => 'text/x-php',
|
1980 |
-
) );
|
1981 |
-
|
1982 |
-
if ( false !== $settings ) {
|
1983 |
-
$vars['codeEditor'] = $settings;
|
1984 |
-
}
|
1985 |
-
}
|
1986 |
-
}
|
1987 |
-
|
1988 |
-
wp_localize_script( 'wp-crontrol', 'wpCrontrol', $vars );
|
1989 |
-
}
|
1990 |
-
|
1991 |
-
/**
|
1992 |
-
* Filters the list of query arguments which get removed from admin area URLs in WordPress.
|
1993 |
-
*
|
1994 |
-
* @param array<int,string> $args List of removable query arguments.
|
1995 |
-
* @return array<int,string> Updated list of removable query arguments.
|
1996 |
-
*/
|
1997 |
-
function filter_removable_query_args( array $args ) {
|
1998 |
-
return array_merge( $args, array(
|
1999 |
-
'crontrol_message',
|
2000 |
-
'crontrol_name',
|
2001 |
-
) );
|
2002 |
-
}
|
2003 |
-
|
2004 |
-
/**
|
2005 |
-
* Returns an array of cron event hooks that are persistently added by WordPress core.
|
2006 |
-
*
|
2007 |
-
* @return array<int,string> Array of hook names.
|
2008 |
-
*/
|
2009 |
-
function get_persistent_core_hooks() {
|
2010 |
-
return array(
|
2011 |
-
'wp_update_plugins', // 2.7.0
|
2012 |
-
'wp_update_themes', // 2.7.0
|
2013 |
-
'wp_version_check', // 2.7.0
|
2014 |
-
'wp_scheduled_delete', // 2.9.0
|
2015 |
-
'update_network_counts', // 3.1.0
|
2016 |
-
'wp_scheduled_auto_draft_delete', // 3.4.0
|
2017 |
-
'delete_expired_transients', // 4.9.0
|
2018 |
-
'wp_privacy_delete_old_export_files', // 4.9.6
|
2019 |
-
'recovery_mode_clean_expired_keys', // 5.2.0
|
2020 |
-
'wp_site_health_scheduled_check', // 5.4.0
|
2021 |
-
'wp_https_detection', // 5.7.0
|
2022 |
-
'wp_update_user_counts', // 6.0.0
|
2023 |
-
);
|
2024 |
-
}
|
2025 |
-
|
2026 |
-
/**
|
2027 |
-
* Returns an array of all cron event hooks that are added by WordPress core.
|
2028 |
-
*
|
2029 |
-
* @return array<int,string> Array of hook names.
|
2030 |
-
*/
|
2031 |
-
function get_all_core_hooks() {
|
2032 |
-
return array_merge(
|
2033 |
-
get_persistent_core_hooks(),
|
2034 |
-
array(
|
2035 |
-
'do_pings', // 2.1.0
|
2036 |
-
'publish_future_post', // 2.1.0
|
2037 |
-
'importer_scheduled_cleanup', // 2.5.0
|
2038 |
-
'upgrader_scheduled_cleanup', // 3.2.2
|
2039 |
-
'wp_maybe_auto_update', // 3.7.0
|
2040 |
-
'wp_split_shared_term_batch', // 4.3.0
|
2041 |
-
'wp_update_comment_type_batch', // 5.5.0
|
2042 |
-
'wp_delete_temp_updater_backups', // 5.9.0
|
2043 |
-
)
|
2044 |
-
);
|
2045 |
-
}
|
2046 |
-
|
2047 |
-
/**
|
2048 |
-
* Returns an array of cron schedules that are added by WordPress core.
|
2049 |
-
*
|
2050 |
-
* @return array<int,string> Array of schedule names.
|
2051 |
-
*/
|
2052 |
-
function get_core_schedules() {
|
2053 |
-
return array(
|
2054 |
-
'hourly',
|
2055 |
-
'twicedaily',
|
2056 |
-
'daily',
|
2057 |
-
'weekly',
|
2058 |
-
);
|
2059 |
-
}
|
2060 |
-
|
2061 |
-
/**
|
2062 |
-
* Encodes some input as JSON for output.
|
2063 |
-
*
|
2064 |
-
* @param mixed $input The input.
|
2065 |
-
* @param bool $pretty Whether to pretty print the output. Default true.
|
2066 |
-
* @return string The JSON-encoded output.
|
2067 |
-
*/
|
2068 |
-
function json_output( $input, $pretty = true ) {
|
2069 |
-
$json_options = 0;
|
2070 |
-
|
2071 |
-
if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) {
|
2072 |
-
// phpcs:ignore PHPCompatibility.Constants.NewConstants.json_unescaped_slashesFound
|
2073 |
-
$json_options |= JSON_UNESCAPED_SLASHES;
|
2074 |
-
}
|
2075 |
-
|
2076 |
-
if ( $pretty && defined( 'JSON_PRETTY_PRINT' ) ) {
|
2077 |
-
$json_options |= JSON_PRETTY_PRINT;
|
2078 |
-
}
|
2079 |
-
|
2080 |
-
$output = wp_json_encode( $input, $json_options );
|
2081 |
-
|
2082 |
-
if ( false === $output ) {
|
2083 |
-
$output = '';
|
2084 |
-
}
|
2085 |
-
|
2086 |
-
return $output;
|
2087 |
-
}
|
2088 |
-
|
2089 |
-
/**
|
2090 |
-
* Evaluates the code in a PHP cron event using eval.
|
2091 |
-
*
|
2092 |
-
* Security: Only users with the `edit_files` capability can manage PHP cron events. This means if a user cannot edit
|
2093 |
-
* files on the site (eg. through the Plugin Editor or Theme Editor) then they cannot edit or add a PHP cron event. By
|
2094 |
-
* default, only Administrators have this capability, and with Multisite enabled only Super Admins have this capability.
|
2095 |
-
*
|
2096 |
-
* If file editing has been disabled via the `DISALLOW_FILE_MODS` or `DISALLOW_FILE_EDIT` configuration constants then
|
2097 |
-
* no user will have the `edit_files` capability, which means editing or adding a PHP cron event will not be permitted.
|
2098 |
-
*
|
2099 |
-
* Therefore, the user access level required to execute arbitrary PHP code does not change with WP Crontrol activated.
|
2100 |
-
*
|
2101 |
-
* @param string $code The PHP code to evaluate.
|
2102 |
-
* @return void
|
2103 |
-
*/
|
2104 |
-
function action_php_cron_event( $code ) {
|
2105 |
-
// phpcs:ignore Squiz.PHP.Eval.Discouraged
|
2106 |
-
eval( $code );
|
2107 |
-
}
|
2108 |
|
2109 |
// Get this show on the road.
|
2110 |
init_hooks();
|
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.15.0
|
9 |
* Text Domain: wp-crontrol
|
10 |
* Domain Path: /languages/
|
11 |
+
* Requires PHP: 5.6
|
12 |
* License: GPL v2 or later
|
13 |
*
|
14 |
* LICENSE
|
29 |
* @copyright Copyright 2008 Edward Dale, 2012-2022 John Blackbourn
|
30 |
* @license http://www.gnu.org/licenses/gpl.txt GPL 2.0
|
31 |
* @link https://wordpress.org/plugins/wp-crontrol/
|
|
|
32 |
*/
|
33 |
|
34 |
namespace Crontrol;
|
35 |
|
36 |
+
const PLUGIN_FILE = __FILE__;
|
|
|
|
|
37 |
|
38 |
if ( ! defined( 'ABSPATH' ) ) {
|
39 |
exit;
|
40 |
}
|
41 |
|
42 |
+
if ( ! version_compare( PHP_VERSION, '5.6', '>=' ) ) {
|
43 |
+
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
}
|
45 |
|
46 |
+
$autoload = __DIR__ . '/vendor/autoload.php';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
|
48 |
+
if ( ! file_exists( $autoload ) ) {
|
49 |
+
return;
|
50 |
}
|
51 |
|
52 |
+
require_once $autoload;
|
53 |
+
require_once __DIR__ . '/src/bootstrap.php';
|
54 |
+
require_once __DIR__ . '/src/event.php';
|
55 |
+
require_once __DIR__ . '/src/schedule.php';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
// Get this show on the road.
|
58 |
init_hooks();
|