├── .actrc ├── .distignore ├── .editorconfig ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE ├── PULL_REQUEST_TEMPLATE ├── dependabot.yml └── workflows │ ├── code-quality.yml │ ├── regenerate-readme.yml │ └── testing.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── behat.yml ├── composer.json ├── cron-command.php ├── features ├── cron-event.feature └── cron.feature ├── phpcs.xml.dist ├── src ├── Cron_Command.php ├── Cron_Event_Command.php └── Cron_Schedule_Command.php └── wp-cli.yml /.actrc: -------------------------------------------------------------------------------- 1 | # Configuration file for nektos/act. 2 | # See https://github.com/nektos/act#configuration 3 | -P ubuntu-latest=shivammathur/node:latest 4 | -------------------------------------------------------------------------------- /.distignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git 3 | .gitignore 4 | .gitlab-ci.yml 5 | .editorconfig 6 | .travis.yml 7 | behat.yml 8 | circle.yml 9 | phpcs.xml.dist 10 | phpunit.xml.dist 11 | bin/ 12 | features/ 13 | utils/ 14 | *.zip 15 | *.tar.gz 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | # From https://github.com/WordPress/wordpress-develop/blob/trunk/.editorconfig with a couple of additions. 8 | 9 | root = true 10 | 11 | [*] 12 | charset = utf-8 13 | end_of_line = lf 14 | insert_final_newline = true 15 | trim_trailing_whitespace = true 16 | indent_style = tab 17 | 18 | [{*.yml,*.feature,.jshintrc,*.json}] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [*.md] 23 | trim_trailing_whitespace = false 24 | 25 | [{*.txt,wp-config-sample.php}] 26 | end_of_line = crlf 27 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @wp-cli/committers 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | labels: 9 | - scope:distribution 10 | - package-ecosystem: github-actions 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | open-pull-requests-limit: 10 15 | labels: 16 | - scope:distribution 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/code-quality.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality Checks 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | 10 | jobs: 11 | code-quality: 12 | uses: wp-cli/.github/.github/workflows/reusable-code-quality.yml@main 13 | -------------------------------------------------------------------------------- /.github/workflows/regenerate-readme.yml: -------------------------------------------------------------------------------- 1 | name: Regenerate README file 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | paths-ignore: 10 | - "features/**" 11 | - "README.md" 12 | 13 | jobs: 14 | regenerate-readme: 15 | uses: wp-cli/.github/.github/workflows/reusable-regenerate-readme.yml@main 16 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | - master 10 | schedule: 11 | - cron: '17 1 * * *' # Run every day on a seemly random time. 12 | 13 | jobs: 14 | test: 15 | uses: wp-cli/.github/.github/workflows/reusable-testing.yml@main 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | wp-cli.local.yml 3 | node_modules/ 4 | vendor/ 5 | *.zip 6 | *.tar.gz 7 | composer.lock 8 | *.log 9 | phpunit.xml 10 | phpcs.xml 11 | .phpcs.xml 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | We appreciate you taking the initiative to contribute to this project. 5 | 6 | Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. 7 | 8 | For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2011-2018 WP-CLI Development Group (https://github.com/wp-cli/cron-command/contributors) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wp-cli/cron-command 2 | =================== 3 | 4 | Tests, runs, and deletes WP-Cron events; manages WP-Cron schedules. 5 | 6 | [![Testing](https://github.com/wp-cli/cron-command/actions/workflows/testing.yml/badge.svg)](https://github.com/wp-cli/cron-command/actions/workflows/testing.yml) 7 | 8 | Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contributing) | [Support](#support) 9 | 10 | ## Using 11 | 12 | This package implements the following commands: 13 | 14 | ### wp cron 15 | 16 | Tests, runs, and deletes WP-Cron events; manages WP-Cron schedules. 17 | 18 | ~~~ 19 | wp cron 20 | ~~~ 21 | 22 | **EXAMPLES** 23 | 24 | # Test WP Cron spawning system 25 | $ wp cron test 26 | Success: WP-Cron spawning is working as expected. 27 | 28 | 29 | 30 | ### wp cron test 31 | 32 | Tests the WP Cron spawning system and reports back its status. 33 | 34 | ~~~ 35 | wp cron test 36 | ~~~ 37 | 38 | This command tests the spawning system by performing the following steps: 39 | 40 | * Checks to see if the `DISABLE_WP_CRON` constant is set; errors if true 41 | because WP-Cron is disabled. 42 | * Checks to see if the `ALTERNATE_WP_CRON` constant is set; warns if true. 43 | * Attempts to spawn WP-Cron over HTTP; warns if non 200 response code is 44 | returned. 45 | 46 | **EXAMPLES** 47 | 48 | # Cron test runs successfully. 49 | $ wp cron test 50 | Success: WP-Cron spawning is working as expected. 51 | 52 | 53 | 54 | ### wp cron event 55 | 56 | Schedules, runs, and deletes WP-Cron events. 57 | 58 | ~~~ 59 | wp cron event 60 | ~~~ 61 | 62 | **EXAMPLES** 63 | 64 | # Schedule a new cron event 65 | $ wp cron event schedule cron_test 66 | Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:19:16 GMT. 67 | 68 | # Run all cron events due right now 69 | $ wp cron event run --due-now 70 | Executed the cron event 'cron_test_1' in 0.01s. 71 | Executed the cron event 'cron_test_2' in 0.006s. 72 | Success: Executed a total of 2 cron events. 73 | 74 | # Delete all scheduled cron events for the given hook 75 | $ wp cron event delete cron_test 76 | Success: Deleted a total of 2 cron events. 77 | 78 | # List scheduled cron events in JSON 79 | $ wp cron event list --fields=hook,next_run --format=json 80 | [{"hook":"wp_version_check","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_plugins","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_themes","next_run":"2016-05-31 10:15:14"}] 81 | 82 | 83 | 84 | 85 | 86 | ### wp cron event delete 87 | 88 | Deletes all scheduled cron events for the given hook. 89 | 90 | ~~~ 91 | wp cron event delete [...] [--due-now] [--exclude=] [--all] 92 | ~~~ 93 | 94 | **OPTIONS** 95 | 96 | [...] 97 | One or more hooks to delete. 98 | 99 | [--due-now] 100 | Delete all hooks due right now. 101 | 102 | [--exclude=] 103 | Comma-separated list of hooks to exclude. 104 | 105 | [--all] 106 | Delete all hooks. 107 | 108 | **EXAMPLES** 109 | 110 | # Delete all scheduled cron events for the given hook 111 | $ wp cron event delete cron_test 112 | Success: Deleted a total of 2 cron events. 113 | 114 | 115 | 116 | ### wp cron event list 117 | 118 | Lists scheduled cron events. 119 | 120 | ~~~ 121 | wp cron event list [--fields=] [--=] [--field=] [--format=] 122 | ~~~ 123 | 124 | **OPTIONS** 125 | 126 | [--fields=] 127 | Limit the output to specific object fields. 128 | 129 | [--=] 130 | Filter by one or more fields. 131 | 132 | [--field=] 133 | Prints the value of a single field for each event. 134 | 135 | [--format=] 136 | Render output in a particular format. 137 | --- 138 | default: table 139 | options: 140 | - table 141 | - csv 142 | - ids 143 | - json 144 | - count 145 | - yaml 146 | --- 147 | 148 | **AVAILABLE FIELDS** 149 | 150 | These fields will be displayed by default for each cron event: 151 | * hook 152 | * next_run_gmt 153 | * next_run_relative 154 | * recurrence 155 | 156 | These fields are optionally available: 157 | * time 158 | * sig 159 | * args 160 | * schedule 161 | * interval 162 | * next_run 163 | 164 | **EXAMPLES** 165 | 166 | # List scheduled cron events 167 | $ wp cron event list 168 | +-------------------+---------------------+---------------------+------------+ 169 | | hook | next_run_gmt | next_run_relative | recurrence | 170 | +-------------------+---------------------+---------------------+------------+ 171 | | wp_version_check | 2016-05-31 22:15:13 | 11 hours 57 minutes | 12 hours | 172 | | wp_update_plugins | 2016-05-31 22:15:13 | 11 hours 57 minutes | 12 hours | 173 | | wp_update_themes | 2016-05-31 22:15:14 | 11 hours 57 minutes | 12 hours | 174 | +-------------------+---------------------+---------------------+------------+ 175 | 176 | # List scheduled cron events in JSON 177 | $ wp cron event list --fields=hook,next_run --format=json 178 | [{"hook":"wp_version_check","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_plugins","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_themes","next_run":"2016-05-31 10:15:14"}] 179 | 180 | 181 | 182 | ### wp cron event run 183 | 184 | Runs the next scheduled cron event for the given hook. 185 | 186 | ~~~ 187 | wp cron event run [...] [--due-now] [--exclude=] [--all] 188 | ~~~ 189 | 190 | **OPTIONS** 191 | 192 | [...] 193 | One or more hooks to run. 194 | 195 | [--due-now] 196 | Run all hooks due right now. 197 | 198 | [--exclude=] 199 | Comma-separated list of hooks to exclude. 200 | 201 | [--all] 202 | Run all hooks. 203 | 204 | **EXAMPLES** 205 | 206 | # Run all cron events due right now 207 | $ wp cron event run --due-now 208 | Executed the cron event 'cron_test_1' in 0.01s. 209 | Executed the cron event 'cron_test_2' in 0.006s. 210 | Success: Executed a total of 2 cron events. 211 | 212 | 213 | 214 | ### wp cron event schedule 215 | 216 | Schedules a new cron event. 217 | 218 | ~~~ 219 | wp cron event schedule [] [] [--=] 220 | ~~~ 221 | 222 | **OPTIONS** 223 | 224 | 225 | The hook name. 226 | 227 | [] 228 | A Unix timestamp or an English textual datetime description compatible with `strtotime()`. Defaults to now. 229 | 230 | [] 231 | How often the event should recur. See `wp cron schedule list` for available schedule names. Defaults to no recurrence. 232 | 233 | [--=] 234 | Arguments to pass to the hook for the event. should be a numeric key, not a string. 235 | 236 | **EXAMPLES** 237 | 238 | # Schedule a new cron event 239 | $ wp cron event schedule cron_test 240 | Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:19:16 GMT. 241 | 242 | # Schedule new cron event with hourly recurrence 243 | $ wp cron event schedule cron_test now hourly 244 | Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:20:32 GMT. 245 | 246 | # Schedule new cron event and pass arguments 247 | $ wp cron event schedule cron_test '+1 hour' --0=first-argument --1=second-argument 248 | Success: Scheduled event with hook 'cron_test' for 2016-05-31 11:21:35 GMT. 249 | 250 | 251 | 252 | ### wp cron schedule 253 | 254 | Gets WP-Cron schedules. 255 | 256 | ~~~ 257 | wp cron schedule 258 | ~~~ 259 | 260 | **EXAMPLES** 261 | 262 | # List available cron schedules 263 | $ wp cron schedule list 264 | +------------+-------------+----------+ 265 | | name | display | interval | 266 | +------------+-------------+----------+ 267 | | hourly | Once Hourly | 3600 | 268 | | twicedaily | Twice Daily | 43200 | 269 | | daily | Once Daily | 86400 | 270 | +------------+-------------+----------+ 271 | 272 | 273 | 274 | 275 | 276 | ### wp cron schedule list 277 | 278 | List available cron schedules. 279 | 280 | ~~~ 281 | wp cron schedule list [--fields=] [--field=] [--format=] 282 | ~~~ 283 | 284 | **OPTIONS** 285 | 286 | [--fields=] 287 | Limit the output to specific object fields. 288 | 289 | [--field=] 290 | Prints the value of a single field for each schedule. 291 | 292 | [--format=] 293 | Render output in a particular format. 294 | --- 295 | default: table 296 | options: 297 | - table 298 | - csv 299 | - ids 300 | - json 301 | - yaml 302 | --- 303 | 304 | **AVAILABLE FIELDS** 305 | 306 | These fields will be displayed by default for each cron schedule: 307 | 308 | * name 309 | * display 310 | * interval 311 | 312 | There are no additional fields. 313 | 314 | **EXAMPLES** 315 | 316 | # List available cron schedules 317 | $ wp cron schedule list 318 | +------------+-------------+----------+ 319 | | name | display | interval | 320 | +------------+-------------+----------+ 321 | | hourly | Once Hourly | 3600 | 322 | | twicedaily | Twice Daily | 43200 | 323 | | daily | Once Daily | 86400 | 324 | +------------+-------------+----------+ 325 | 326 | # List id of available cron schedule 327 | $ wp cron schedule list --fields=name --format=ids 328 | hourly twicedaily daily 329 | 330 | 331 | 332 | ### wp cron event unschedule 333 | 334 | Unschedules all cron events for a given hook. 335 | 336 | ~~~ 337 | wp cron event unschedule 338 | ~~~ 339 | 340 | **OPTIONS** 341 | 342 | 343 | Name of the hook for which all events should be unscheduled. 344 | 345 | **EXAMPLES** 346 | 347 | # Unschedule a cron event on given hook. 348 | $ wp cron event unschedule cron_test 349 | Success: Unscheduled 2 events for hook 'cron_test'. 350 | 351 | ## Installing 352 | 353 | This package is included with WP-CLI itself, no additional installation necessary. 354 | 355 | To install the latest version of this package over what's included in WP-CLI, run: 356 | 357 | wp package install git@github.com:wp-cli/cron-command.git 358 | 359 | ## Contributing 360 | 361 | We appreciate you taking the initiative to contribute to this project. 362 | 363 | Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. 364 | 365 | For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. 366 | 367 | ### Reporting a bug 368 | 369 | Think you’ve found a bug? We’d love for you to help us get it fixed. 370 | 371 | Before you create a new issue, you should [search existing issues](https://github.com/wp-cli/cron-command/issues?q=label%3Abug%20) to see if there’s an existing resolution to it, or if it’s already been fixed in a newer version. 372 | 373 | Once you’ve done a bit of searching and discovered there isn’t an open or fixed issue for your bug, please [create a new issue](https://github.com/wp-cli/cron-command/issues/new). Include as much detail as you can, and clear steps to reproduce if possible. For more guidance, [review our bug report documentation](https://make.wordpress.org/cli/handbook/bug-reports/). 374 | 375 | ### Creating a pull request 376 | 377 | Want to contribute a new feature? Please first [open a new issue](https://github.com/wp-cli/cron-command/issues/new) to discuss whether the feature is a good fit for the project. 378 | 379 | Once you've decided to commit the time to seeing your pull request through, [please follow our guidelines for creating a pull request](https://make.wordpress.org/cli/handbook/pull-requests/) to make sure it's a pleasant experience. See "[Setting up](https://make.wordpress.org/cli/handbook/pull-requests/#setting-up)" for details specific to working on this package locally. 380 | 381 | ## Support 382 | 383 | GitHub issues aren't for general support questions, but there are other venues you can try: https://wp-cli.org/#support 384 | 385 | 386 | *This README.md is generated dynamically from the project's codebase using `wp scaffold package-readme` ([doc](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-readme)). To suggest changes, please submit a pull request against the corresponding part of the codebase.* 387 | -------------------------------------------------------------------------------- /behat.yml: -------------------------------------------------------------------------------- 1 | default: 2 | suites: 3 | default: 4 | contexts: 5 | - WP_CLI\Tests\Context\FeatureContext 6 | paths: 7 | - features 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-cli/cron-command", 3 | "type": "wp-cli-package", 4 | "description": "Tests, runs, and deletes WP-Cron events; manages WP-Cron schedules.", 5 | "homepage": "https://github.com/wp-cli/cron-command", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Daniel Bachhuber", 10 | "email": "daniel@runcommand.io", 11 | "homepage": "https://runcommand.io" 12 | } 13 | ], 14 | "require": { 15 | "wp-cli/wp-cli": "^2.12" 16 | }, 17 | "require-dev": { 18 | "wp-cli/entity-command": "^1.3 || ^2", 19 | "wp-cli/eval-command": "^2.0", 20 | "wp-cli/server-command": "^2.0", 21 | "wp-cli/wp-cli-tests": "^4" 22 | }, 23 | "config": { 24 | "process-timeout": 7200, 25 | "sort-packages": true, 26 | "allow-plugins": { 27 | "dealerdirect/phpcodesniffer-composer-installer": true, 28 | "johnpbloch/wordpress-core-installer": true 29 | }, 30 | "lock": false 31 | }, 32 | "extra": { 33 | "branch-alias": { 34 | "dev-main": "2.x-dev" 35 | }, 36 | "bundled": true, 37 | "commands": [ 38 | "cron", 39 | "cron test", 40 | "cron event", 41 | "cron event delete", 42 | "cron event list", 43 | "cron event run", 44 | "cron event schedule", 45 | "cron schedule", 46 | "cron schedule list", 47 | "cron event unschedule" 48 | ] 49 | }, 50 | "autoload": { 51 | "classmap": [ 52 | "src/" 53 | ], 54 | "files": [ 55 | "cron-command.php" 56 | ] 57 | }, 58 | "minimum-stability": "dev", 59 | "prefer-stable": true, 60 | "scripts": { 61 | "behat": "run-behat-tests", 62 | "behat-rerun": "rerun-behat-tests", 63 | "lint": "run-linter-tests", 64 | "phpcs": "run-phpcs-tests", 65 | "phpcbf": "run-phpcbf-cleanup", 66 | "phpunit": "run-php-unit-tests", 67 | "prepare-tests": "install-package-tests", 68 | "test": [ 69 | "@lint", 70 | "@phpcs", 71 | "@phpunit", 72 | "@behat" 73 | ] 74 | }, 75 | "support": { 76 | "issues": "https://github.com/wp-cli/cron-command/issues" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cron-command.php: -------------------------------------------------------------------------------- 1 | 3600, 149 | 'display' => __( 'Every Hour' ), 150 | ); 151 | return $schedules; 152 | } 153 | ); 154 | """ 155 | 156 | When I run `wp cron event schedule wp_cli_test_event "1 hour" test_schedule` 157 | Then STDOUT should contain: 158 | """ 159 | Success: Scheduled event with hook 'wp_cli_test_event' 160 | """ 161 | 162 | When I run `wp cron event list --hook=wp_cli_test_event --fields=hook,recurrence` 163 | Then STDOUT should be a table containing rows: 164 | | hook | recurrence | 165 | | wp_cli_test_event | 1 hour | 166 | 167 | When I run `rm wp-content/mu-plugins/schedule.php` 168 | Then the return code should be 0 169 | 170 | When I run `wp cron event list --hook=wp_cli_test_event --fields=hook,recurrence` 171 | Then STDOUT should be a table containing rows: 172 | | hook | recurrence | 173 | | wp_cli_test_event | Non-repeating | 174 | 175 | Scenario: Scheduling and then running a re-occurring event 176 | When I run `wp cron event schedule wp_cli_test_event_4 now hourly` 177 | Then STDOUT should contain: 178 | """ 179 | Success: Scheduled event with hook 'wp_cli_test_event_4' 180 | """ 181 | 182 | When I run `wp cron event list --format=csv --fields=hook,recurrence` 183 | Then STDOUT should be CSV containing: 184 | | hook | recurrence | 185 | | wp_cli_test_event_4 | 1 hour | 186 | 187 | When I run `wp cron event run wp_cli_test_event_4` 188 | Then STDOUT should not be empty 189 | 190 | When I run `wp cron event list` 191 | Then STDOUT should contain: 192 | """ 193 | wp_cli_test_event_4 194 | """ 195 | 196 | Scenario: Scheduling and then deleting a recurring event 197 | When I run `wp cron event schedule wp_cli_test_event_2 now daily` 198 | Then STDOUT should contain: 199 | """ 200 | Success: Scheduled event with hook 'wp_cli_test_event_2' 201 | """ 202 | 203 | When I run `wp cron event list --format=csv --fields=hook,recurrence` 204 | Then STDOUT should be CSV containing: 205 | | hook | recurrence | 206 | | wp_cli_test_event_2 | 1 day | 207 | 208 | When I run `wp cron event delete wp_cli_test_event_2` 209 | Then STDOUT should contain: 210 | """ 211 | Success: Deleted a total of 1 cron event. 212 | """ 213 | 214 | When I run `wp cron event list` 215 | Then STDOUT should not contain: 216 | """ 217 | wp_cli_test_event_2 218 | """ 219 | 220 | Scenario: Listing cron schedules 221 | When I run `wp cron schedule list --format=csv --fields=name,interval` 222 | Then STDOUT should be CSV containing: 223 | | name | interval | 224 | | hourly | 3600 | 225 | 226 | Scenario: Testing WP-Cron 227 | Given a php.ini file: 228 | """ 229 | error_log = {RUN_DIR}/server.log 230 | log_errors = on 231 | """ 232 | And I launch in the background `wp server --host=localhost --port=8080 --config=php.ini` 233 | And a wp-content/mu-plugins/set_cron_site_url.php file: 234 | """ 235 | /dev/null || true` 264 | Then STDOUT should be empty 265 | 266 | Scenario: Run multiple cron events 267 | When I try `wp cron event run` 268 | Then STDERR should be: 269 | """ 270 | Error: Please specify one or more cron events, or use --due-now/--all. 271 | """ 272 | 273 | When I run `wp cron event run wp_version_check wp_update_plugins` 274 | Then STDOUT should contain: 275 | """ 276 | Executed the cron event 'wp_version_check' 277 | """ 278 | And STDOUT should contain: 279 | """ 280 | Executed the cron event 'wp_update_plugins' 281 | """ 282 | And STDOUT should contain: 283 | """ 284 | Success: Executed a total of 2 cron events. 285 | """ 286 | 287 | # WP throws a notice here for older versions of core. 288 | When I try `wp cron event run --all` 289 | Then STDOUT should contain: 290 | """ 291 | Executed the cron event 'wp_version_check' 292 | """ 293 | And STDOUT should contain: 294 | """ 295 | Executed the cron event 'wp_update_plugins' 296 | """ 297 | And STDOUT should contain: 298 | """ 299 | Executed the cron event 'wp_update_themes' 300 | """ 301 | And STDOUT should contain: 302 | """ 303 | Success: Executed a total of 304 | """ 305 | 306 | # Fails on WordPress 4.9 because `wp cron event run --due-now` 307 | # executes the "wp_privacy_delete_old_export_files" event there. 308 | @require-wp-5.0 309 | Scenario: Run currently scheduled events 310 | # WP throws a notice here for older versions of core. 311 | When I try `wp cron event run --all` 312 | Then STDOUT should contain: 313 | """ 314 | Executed the cron event 'wp_version_check' 315 | """ 316 | And STDOUT should contain: 317 | """ 318 | Executed the cron event 'wp_update_plugins' 319 | """ 320 | And STDOUT should contain: 321 | """ 322 | Executed the cron event 'wp_update_themes' 323 | """ 324 | And STDOUT should contain: 325 | """ 326 | Success: Executed a total of 327 | """ 328 | 329 | When I run `wp cron event run --due-now` 330 | Then STDOUT should contain: 331 | """ 332 | Executed a total of 0 cron events 333 | """ 334 | 335 | When I run `wp cron event schedule wp_cli_test_event_1 now hourly` 336 | Then STDOUT should contain: 337 | """ 338 | Success: Scheduled event with hook 'wp_cli_test_event_1' 339 | """ 340 | 341 | When I run `wp cron event run --due-now` 342 | Then STDOUT should contain: 343 | """ 344 | Executed the cron event 'wp_cli_test_event_1' 345 | """ 346 | And STDOUT should contain: 347 | """ 348 | Executed a total of 1 cron event 349 | """ 350 | 351 | When I run `wp cron event run --due-now` 352 | Then STDOUT should contain: 353 | """ 354 | Executed a total of 0 cron events 355 | """ 356 | 357 | Scenario: Don't trigger cron when ALTERNATE_WP_CRON is defined 358 | Given a alternate-wp-cron.php file: 359 | """ 360 | array( 414 | 'postindexer_secondpass_cron' => array( 415 | '40cd750bba9870f18aada2478b24840a' => array( 416 | 'schedule' => '5mins', 417 | 'args' => array(), 418 | 'interval' => 100, 419 | ), 420 | ), 421 | ), 422 | 'wp_batch_split_terms' => array( 423 | 1442323165 => array( 424 | '40cd750bba9870f18aada2478b24840a' => array( 425 | 'schedule' => false, 426 | 'args' => array() 427 | ) 428 | ) 429 | ) 430 | ); 431 | update_option( 'cron', $val ); 432 | """ 433 | And I run `wp eval-file update.php` 434 | 435 | When I try `wp cron event list` 436 | Then STDOUT should contain: 437 | """ 438 | postindexer_secondpass_cron 439 | """ 440 | And STDERR should contain: 441 | """ 442 | Warning: Ignoring incorrectly registered cron event "wp_batch_split_terms". 443 | """ 444 | 445 | Scenario: Delete multiple cron events 446 | When I run `wp cron event schedule wp_cli_test_event_1 '+1 hour 5 minutes' hourly` 447 | Then STDOUT should not be empty 448 | 449 | When I run `wp cron event schedule wp_cli_test_event_2 '+1 hour 5 minutes' hourly` 450 | Then STDOUT should not be empty 451 | 452 | When I try `wp cron event delete` 453 | Then STDERR should be: 454 | """ 455 | Error: Please specify one or more cron events, or use --due-now/--all. 456 | """ 457 | 458 | # WP throws a notice here for older versions of core. 459 | When I try `wp cron event delete --all` 460 | Then STDOUT should contain: 461 | """ 462 | Success: Deleted a total of 463 | """ 464 | 465 | When I try `wp cron event list` 466 | Then STDOUT should not contain: 467 | """ 468 | wp_cli_test_event_1 469 | """ 470 | And STDOUT should not contain: 471 | """ 472 | wp_cli_test_event_2 473 | """ 474 | 475 | When I run `wp cron event schedule wp_cli_test_event_1 now hourly` 476 | Then STDOUT should contain: 477 | """ 478 | Success: Scheduled event with hook 'wp_cli_test_event_1' 479 | """ 480 | 481 | When I run `wp cron event schedule wp_cli_test_event_2 now hourly` 482 | Then STDOUT should contain: 483 | """ 484 | Success: Scheduled event with hook 'wp_cli_test_event_2' 485 | """ 486 | 487 | When I run `wp cron event schedule wp_cli_test_event_2 '+1 hour 5 minutes' hourly` 488 | Then STDOUT should contain: 489 | """ 490 | Success: Scheduled event with hook 'wp_cli_test_event_2' 491 | """ 492 | 493 | When I run `wp cron event delete wp_cli_test_event_2 --due-now` 494 | Then STDOUT should contain: 495 | """ 496 | Deleted a total of 1 cron event 497 | """ 498 | 499 | When I try `wp cron event list` 500 | Then STDOUT should contain: 501 | """ 502 | wp_cli_test_event_2 503 | """ 504 | 505 | When I run `wp cron event list --hook=wp_cli_test_event_2 --format=count` 506 | Then STDOUT should be: 507 | """ 508 | 1 509 | """ 510 | 511 | When I run `wp cron event delete --due-now` 512 | Then STDOUT should contain: 513 | """ 514 | Success: Deleted a total of 515 | """ 516 | 517 | When I try `wp cron event list` 518 | Then STDOUT should not contain: 519 | """ 520 | wp_cli_test_event_1 521 | """ 522 | And STDOUT should contain: 523 | """ 524 | wp_cli_test_event_2 525 | """ 526 | 527 | When I run `wp cron event schedule wp_cli_test_event_1 '+1 hour 5 minutes' hourly` 528 | Then STDOUT should not be empty 529 | 530 | When I run `wp cron event schedule wp_cli_test_event_2 '+1 hour 5 minutes' hourly` 531 | Then STDOUT should not be empty 532 | 533 | When I run `wp cron event delete --all --exclude=wp_cli_test_event_1` 534 | Then STDOUT should contain: 535 | """ 536 | Success: Deleted a total of 537 | """ 538 | 539 | When I try `wp cron event list` 540 | Then STDOUT should not contain: 541 | """ 542 | wp_cli_test_event_2 543 | """ 544 | And STDOUT should contain: 545 | """ 546 | wp_cli_test_event_1 547 | """ 548 | 549 | Scenario: A valid combination of parameters should be present 550 | When I try `wp cron event delete --due-now --all` 551 | Then STDERR should be: 552 | """ 553 | Error: Please use either --due-now or --all. 554 | """ 555 | 556 | When I try `wp cron event delete wp_cli_test_event_1 --all` 557 | Then STDERR should be: 558 | """ 559 | Error: Please either specify cron events, or use --all. 560 | """ 561 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom ruleset for WP-CLI cron-command 4 | 5 | 12 | 13 | 14 | . 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 38 | 39 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | */src/Cron_(Event_|Schedule_)?Command\.php$ 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/Cron_Command.php: -------------------------------------------------------------------------------- 1 | get_error_message() ) ); 45 | } 46 | 47 | $code = wp_remote_retrieve_response_code( $spawn ); 48 | $message = wp_remote_retrieve_response_message( $spawn ); 49 | 50 | if ( 200 === $code ) { 51 | WP_CLI::success( 'WP-Cron spawning is working as expected.' ); 52 | } else { 53 | WP_CLI::error( sprintf( 'WP-Cron spawn returned HTTP status code: %1$s %2$s', $code, $message ) ); 54 | } 55 | } 56 | 57 | /** 58 | * Spawns a request to `wp-cron.php` and return the response. 59 | * 60 | * This function is designed to mimic the functionality in `spawn_cron()` 61 | * with the addition of returning the result of the `wp_remote_post()` 62 | * request. 63 | * 64 | * @return WP_Error|array The response or WP_Error on failure. 65 | */ 66 | protected static function get_cron_spawn() { 67 | 68 | $sslverify = \WP_CLI\Utils\wp_version_compare( 4.0, '<' ); 69 | $doing_wp_cron = sprintf( '%.22F', microtime( true ) ); 70 | 71 | $cron_request_array = array( 72 | 'url' => site_url( 'wp-cron.php?doing_wp_cron=' . $doing_wp_cron ), 73 | 'key' => $doing_wp_cron, 74 | 'args' => array( 75 | 'timeout' => 3, 76 | 'blocking' => true, 77 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook. 78 | 'sslverify' => apply_filters( 'https_local_ssl_verify', $sslverify ), 79 | ), 80 | ); 81 | 82 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook. 83 | $cron_request = apply_filters( 'cron_request', $cron_request_array ); 84 | 85 | # Enforce a blocking request in case something that's hooked onto the 'cron_request' filter sets it to false 86 | $cron_request['args']['blocking'] = true; 87 | 88 | $result = wp_remote_post( $cron_request['url'], $cron_request['args'] ); 89 | 90 | return $result; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Cron_Event_Command.php: -------------------------------------------------------------------------------- 1 | ] 47 | * : Limit the output to specific object fields. 48 | * 49 | * [--=] 50 | * : Filter by one or more fields. 51 | * 52 | * [--field=] 53 | * : Prints the value of a single field for each event. 54 | * 55 | * [--format=] 56 | * : Render output in a particular format. 57 | * --- 58 | * default: table 59 | * options: 60 | * - table 61 | * - csv 62 | * - ids 63 | * - json 64 | * - count 65 | * - yaml 66 | * --- 67 | * 68 | * ## AVAILABLE FIELDS 69 | * 70 | * These fields will be displayed by default for each cron event: 71 | * * hook 72 | * * next_run_gmt 73 | * * next_run_relative 74 | * * recurrence 75 | * 76 | * These fields are optionally available: 77 | * * time 78 | * * sig 79 | * * args 80 | * * schedule 81 | * * interval 82 | * * next_run 83 | * 84 | * ## EXAMPLES 85 | * 86 | * # List scheduled cron events 87 | * $ wp cron event list 88 | * +-------------------+---------------------+---------------------+------------+ 89 | * | hook | next_run_gmt | next_run_relative | recurrence | 90 | * +-------------------+---------------------+---------------------+------------+ 91 | * | wp_version_check | 2016-05-31 22:15:13 | 11 hours 57 minutes | 12 hours | 92 | * | wp_update_plugins | 2016-05-31 22:15:13 | 11 hours 57 minutes | 12 hours | 93 | * | wp_update_themes | 2016-05-31 22:15:14 | 11 hours 57 minutes | 12 hours | 94 | * +-------------------+---------------------+---------------------+------------+ 95 | * 96 | * # List scheduled cron events in JSON 97 | * $ wp cron event list --fields=hook,next_run --format=json 98 | * [{"hook":"wp_version_check","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_plugins","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_themes","next_run":"2016-05-31 10:15:14"}] 99 | * 100 | * @subcommand list 101 | */ 102 | public function list_( $args, $assoc_args ) { 103 | $formatter = $this->get_formatter( $assoc_args ); 104 | 105 | $events = self::get_cron_events(); 106 | 107 | if ( is_wp_error( $events ) ) { 108 | $events = array(); 109 | } 110 | 111 | foreach ( $events as $key => $event ) { 112 | foreach ( $this->fields as $field ) { 113 | if ( ! empty( $assoc_args[ $field ] ) && $event->{$field} !== $assoc_args[ $field ] ) { 114 | unset( $events[ $key ] ); 115 | break; 116 | } 117 | } 118 | } 119 | 120 | if ( 'ids' === $formatter->format ) { 121 | echo implode( ' ', wp_list_pluck( $events, 'hook' ) ); 122 | } else { 123 | $formatter->display_items( $events ); 124 | } 125 | } 126 | 127 | /** 128 | * Schedules a new cron event. 129 | * 130 | * ## OPTIONS 131 | * 132 | * 133 | * : The hook name. 134 | * 135 | * [] 136 | * : A Unix timestamp or an English textual datetime description compatible with `strtotime()`. Defaults to now. 137 | * 138 | * [] 139 | * : How often the event should recur. See `wp cron schedule list` for available schedule names. Defaults to no recurrence. 140 | * 141 | * [--=] 142 | * : Arguments to pass to the hook for the event. should be a numeric key, not a string. 143 | * 144 | * ## EXAMPLES 145 | * 146 | * # Schedule a new cron event 147 | * $ wp cron event schedule cron_test 148 | * Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:19:16 GMT. 149 | * 150 | * # Schedule new cron event with hourly recurrence 151 | * $ wp cron event schedule cron_test now hourly 152 | * Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:20:32 GMT. 153 | * 154 | * # Schedule new cron event and pass arguments 155 | * $ wp cron event schedule cron_test '+1 hour' --0=first-argument --1=second-argument 156 | * Success: Scheduled event with hook 'cron_test' for 2016-05-31 11:21:35 GMT. 157 | */ 158 | public function schedule( $args, $assoc_args ) { 159 | if ( count( $assoc_args ) && count( array_filter( array_keys( $assoc_args ), 'is_string' ) ) ) { 160 | WP_CLI::warning( 'Numeric keys should be used for the hook arguments.' ); 161 | } 162 | 163 | $hook = $args[0]; 164 | $next_run = Utils\get_flag_value( $args, 1, 'now' ); 165 | $recurrence = Utils\get_flag_value( $args, 2, false ); 166 | 167 | if ( empty( $next_run ) ) { 168 | $timestamp = time(); 169 | } elseif ( is_numeric( $next_run ) ) { 170 | $timestamp = absint( $next_run ); 171 | } else { 172 | $timestamp = strtotime( $next_run ); 173 | } 174 | 175 | if ( ! $timestamp ) { 176 | WP_CLI::error( sprintf( "'%s' is not a valid datetime.", $next_run ) ); 177 | } 178 | 179 | if ( ! empty( $recurrence ) ) { 180 | 181 | $schedules = wp_get_schedules(); 182 | 183 | if ( ! isset( $schedules[ $recurrence ] ) ) { 184 | WP_CLI::error( sprintf( "'%s' is not a valid schedule name for recurrence.", $recurrence ) ); 185 | } 186 | 187 | $event = wp_schedule_event( $timestamp, $recurrence, $hook, $assoc_args ); 188 | 189 | } else { 190 | 191 | $event = wp_schedule_single_event( $timestamp, $hook, $assoc_args ); 192 | 193 | } 194 | 195 | if ( false !== $event ) { 196 | WP_CLI::success( sprintf( "Scheduled event with hook '%s' for %s GMT.", $hook, date( self::$time_format, $timestamp ) ) ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date 197 | } else { 198 | WP_CLI::error( 'Event not scheduled.' ); 199 | } 200 | } 201 | 202 | /** 203 | * Runs the next scheduled cron event for the given hook. 204 | * 205 | * ## OPTIONS 206 | * 207 | * [...] 208 | * : One or more hooks to run. 209 | * 210 | * [--due-now] 211 | * : Run all hooks due right now. 212 | * 213 | * [--exclude=] 214 | * : Comma-separated list of hooks to exclude. 215 | * 216 | * [--all] 217 | * : Run all hooks. 218 | * 219 | * ## EXAMPLES 220 | * 221 | * # Run all cron events due right now 222 | * $ wp cron event run --due-now 223 | * Executed the cron event 'cron_test_1' in 0.01s. 224 | * Executed the cron event 'cron_test_2' in 0.006s. 225 | * Success: Executed a total of 2 cron events. 226 | */ 227 | public function run( $args, $assoc_args ) { 228 | 229 | $events = self::get_selected_cron_events( $args, $assoc_args ); 230 | 231 | if ( is_wp_error( $events ) ) { 232 | WP_CLI::error( $events ); 233 | } 234 | 235 | $executed = 0; 236 | foreach ( $events as $event ) { 237 | $start = microtime( true ); 238 | $result = self::run_event( $event ); 239 | $total = round( microtime( true ) - $start, 3 ); 240 | ++$executed; 241 | WP_CLI::log( sprintf( "Executed the cron event '%s' in %ss.", $event->hook, $total ) ); 242 | } 243 | 244 | $message = ( 1 === $executed ) ? 'Executed a total of %d cron event.' : 'Executed a total of %d cron events.'; 245 | WP_CLI::success( sprintf( $message, $executed ) ); 246 | } 247 | 248 | /** 249 | * Unschedules all cron events for a given hook. 250 | * 251 | * ## OPTIONS 252 | * 253 | * 254 | * : Name of the hook for which all events should be unscheduled. 255 | * 256 | * ## EXAMPLES 257 | * 258 | * # Unschedule a cron event on given hook. 259 | * $ wp cron event unschedule cron_test 260 | * Success: Unscheduled 2 events for hook 'cron_test'. 261 | */ 262 | public function unschedule( $args, $assoc_args ) { 263 | 264 | list( $hook ) = $args; 265 | 266 | if ( Utils\wp_version_compare( '4.9.0', '<' ) ) { 267 | WP_CLI::error( 'Unscheduling events is only supported from WordPress 4.9.0 onwards.' ); 268 | } 269 | 270 | $unscheduled = wp_unschedule_hook( $hook ); 271 | 272 | if ( empty( $unscheduled ) ) { 273 | $message = 'Failed to unschedule events for hook \'%1\$s.'; 274 | 275 | // If 0 event found on hook. 276 | if ( 0 === $unscheduled ) { 277 | $message = "No events found for hook '%1\$s'."; 278 | } 279 | 280 | WP_CLI::error( sprintf( $message, $hook ) ); 281 | 282 | } else { 283 | WP_CLI::success( 284 | sprintf( 285 | 'Unscheduled %1$d %2$s for hook \'%3$s\'.', 286 | $unscheduled, 287 | Utils\pluralize( 'event', $unscheduled ), 288 | $hook 289 | ) 290 | ); 291 | } 292 | } 293 | 294 | /** 295 | * Deletes all scheduled cron events for the given hook. 296 | * 297 | * ## OPTIONS 298 | * 299 | * [...] 300 | * : One or more hooks to delete. 301 | * 302 | * [--due-now] 303 | * : Delete all hooks due right now. 304 | * 305 | * [--exclude=] 306 | * : Comma-separated list of hooks to exclude. 307 | * 308 | * [--all] 309 | * : Delete all hooks. 310 | * 311 | * ## EXAMPLES 312 | * 313 | * # Delete all scheduled cron events for the given hook 314 | * $ wp cron event delete cron_test 315 | * Success: Deleted a total of 2 cron events. 316 | */ 317 | public function delete( $args, $assoc_args ) { 318 | $events = self::get_selected_cron_events( $args, $assoc_args ); 319 | 320 | if ( is_wp_error( $events ) ) { 321 | WP_CLI::error( $events ); 322 | } 323 | 324 | $deleted = 0; 325 | foreach ( $events as $event ) { 326 | $result = self::delete_event( $event ); 327 | if ( $result ) { 328 | ++$deleted; 329 | } else { 330 | WP_CLI::warning( sprintf( "Failed to the delete the cron event '%s'.", $event->hook ) ); 331 | } 332 | } 333 | 334 | $message = sprintf( 'Deleted a total of %d %s.', $deleted, Utils\pluralize( 'cron event', $deleted ) ); 335 | WP_CLI::success( sprintf( $message, $deleted ) ); 336 | } 337 | 338 | /** 339 | * Executes an event immediately. 340 | * 341 | * @param stdClass $event The event 342 | * @return bool Whether the event was successfully executed or not. 343 | */ 344 | protected static function run_event( stdClass $event ) { 345 | 346 | if ( ! defined( 'DOING_CRON' ) ) { 347 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Using native WordPress constant. 348 | define( 'DOING_CRON', true ); 349 | } 350 | 351 | if ( false !== $event->schedule ) { 352 | wp_reschedule_event( $event->time, $event->schedule, $event->hook, $event->args ); 353 | } 354 | 355 | wp_unschedule_event( $event->time, $event->hook, $event->args ); 356 | 357 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Can't prefix dynamic hooks here, calling registered hooks. 358 | do_action_ref_array( $event->hook, $event->args ); 359 | 360 | return true; 361 | } 362 | 363 | /** 364 | * Deletes a cron event. 365 | * 366 | * @param stdClass $event The event 367 | * @return bool Whether the event was successfully deleted or not. 368 | */ 369 | protected static function delete_event( stdClass $event ) { 370 | $crons = _get_cron_array(); 371 | 372 | if ( ! isset( $crons[ $event->time ][ $event->hook ][ $event->sig ] ) ) { 373 | return false; 374 | } 375 | 376 | wp_unschedule_event( $event->time, $event->hook, $event->args ); 377 | return true; 378 | } 379 | 380 | /** 381 | * Callback function to format a cron event. 382 | * 383 | * @param stdClass $event The event. 384 | * @return stdClass The formatted event object. 385 | */ 386 | protected static function format_event( stdClass $event ) { 387 | 388 | $schedules = wp_get_schedules(); 389 | $event->recurrence = ( isset( $schedules[ $event->schedule ] ) ) ? self::interval( $event->interval ) : 'Non-repeating'; 390 | $event->next_run = get_date_from_gmt( date( 'Y-m-d H:i:s', $event->time ), self::$time_format ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date 391 | $event->next_run_gmt = date( self::$time_format, $event->time ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date 392 | $event->next_run_relative = self::interval( $event->time - time() ); 393 | 394 | return $event; 395 | } 396 | 397 | /** 398 | * Fetches an array of scheduled cron events. 399 | * 400 | * @return array|WP_Error An array of event objects, or a WP_Error object if there are no events scheduled. 401 | */ 402 | protected static function get_cron_events( $is_due_now = false ) { 403 | 404 | // wp_get_ready_cron_jobs since 5.1.0 405 | if ( $is_due_now && function_exists( 'wp_get_ready_cron_jobs' ) ) { 406 | $crons = wp_get_ready_cron_jobs(); 407 | } else { 408 | $crons = _get_cron_array(); 409 | } 410 | 411 | $events = array(); 412 | 413 | if ( empty( $crons ) && ! $is_due_now ) { 414 | return new WP_Error( 415 | 'no_events', 416 | 'You currently have no scheduled cron events.' 417 | ); 418 | } 419 | 420 | foreach ( $crons as $time => $hooks ) { 421 | 422 | // Incorrectly registered cron events can produce a string key. 423 | if ( is_string( $time ) ) { 424 | WP_CLI::warning( sprintf( 'Ignoring incorrectly registered cron event "%s".', $time ) ); 425 | continue; 426 | } 427 | 428 | foreach ( $hooks as $hook => $hook_events ) { 429 | foreach ( $hook_events as $sig => $data ) { 430 | 431 | $events[] = (object) array( 432 | 'hook' => $hook, 433 | 'time' => $time, 434 | 'sig' => $sig, 435 | 'args' => $data['args'], 436 | 'schedule' => $data['schedule'], 437 | 'interval' => Utils\get_flag_value( $data, 'interval' ), 438 | ); 439 | 440 | } 441 | } 442 | } 443 | 444 | $events = array_map( 'Cron_Event_Command::format_event', $events ); 445 | 446 | return $events; 447 | } 448 | 449 | /** 450 | * Fetches an array of scheduled cron events selected by the user. 451 | * 452 | * @param array $args A list of event names 453 | * @param array $assoc_args An associative list of CLI parameters 454 | * 455 | * @return array|WP_Error An array of objects, or a WP_Error object is there are no events scheduled. 456 | */ 457 | protected static function get_selected_cron_events( $args, $assoc_args ) { 458 | $due_now = Utils\get_flag_value( $assoc_args, 'due-now' ); 459 | $all = Utils\get_flag_value( $assoc_args, 'all' ); 460 | $exclude = Utils\get_flag_value( $assoc_args, 'exclude' ); 461 | 462 | if ( empty( $args ) && ! $due_now && ! $all ) { 463 | WP_CLI::error( 'Please specify one or more cron events, or use --due-now/--all.' ); 464 | } 465 | 466 | if ( ! empty( $args ) && $all ) { 467 | WP_CLI::error( 'Please either specify cron events, or use --all.' ); 468 | } 469 | 470 | if ( $due_now && $all ) { 471 | WP_CLI::error( 'Please use either --due-now or --all.' ); 472 | } 473 | 474 | $events = self::get_cron_events(); 475 | 476 | if ( is_wp_error( $events ) ) { 477 | return $events; 478 | } 479 | 480 | $hooks = wp_list_pluck( $events, 'hook' ); 481 | foreach ( $args as $hook ) { 482 | if ( ! in_array( $hook, $hooks, true ) ) { 483 | WP_CLI::error( sprintf( "Invalid cron event '%s'", $hook ) ); 484 | } 485 | } 486 | 487 | // Remove all excluded hooks. 488 | if ( ! empty( $exclude ) ) { 489 | $exclude = explode( ',', $exclude ); 490 | $events = array_filter( 491 | $events, 492 | function ( $event ) use ( $exclude ) { 493 | return ! in_array( $event->hook, $exclude, true ); 494 | } 495 | ); 496 | } 497 | 498 | // If --due-now is specified, take only the events that have 'now' as 499 | // their next_run_relative time. 500 | if ( $due_now ) { 501 | $due_events = array(); 502 | foreach ( $events as $event ) { 503 | if ( ! empty( $args ) && ! in_array( $event->hook, $args, true ) ) { 504 | continue; 505 | } 506 | if ( 'now' === $event->next_run_relative ) { 507 | $due_events[] = $event; 508 | } 509 | } 510 | $events = $due_events; 511 | } elseif ( ! $all ) { 512 | // If --all is not specified, take only the events that have been 513 | // given as $args. 514 | $due_events = array(); 515 | foreach ( $events as $event ) { 516 | if ( in_array( $event->hook, $args, true ) ) { 517 | $due_events[] = $event; 518 | } 519 | } 520 | $events = $due_events; 521 | } 522 | 523 | return $events; 524 | } 525 | 526 | /** 527 | * Converts a time interval into human-readable format. 528 | * 529 | * Similar to WordPress' built-in `human_time_diff()` but returns two time period chunks instead of just one. 530 | * 531 | * @param int $since An interval of time in seconds 532 | * @return string The interval in human readable format 533 | */ 534 | private static function interval( $since ) { 535 | if ( $since <= 0 ) { 536 | return 'now'; 537 | } 538 | 539 | $since = absint( $since ); 540 | 541 | // Array of time period chunks. 542 | $chunks = array( 543 | array( 60 * 60 * 24 * 365, 'year' ), 544 | array( 60 * 60 * 24 * 30, 'month' ), 545 | array( 60 * 60 * 24 * 7, 'week' ), 546 | array( 60 * 60 * 24, 'day' ), 547 | array( 60 * 60, 'hour' ), 548 | array( 60, 'minute' ), 549 | array( 1, 'second' ), 550 | ); 551 | 552 | // we only want to output two chunks of time here, eg: 553 | // x years, xx months 554 | // x days, xx hours 555 | // so there's only two bits of calculation below: 556 | 557 | // step one: the first chunk 558 | for ( $i = 0, $j = count( $chunks ); $i < $j; $i++ ) { 559 | $seconds = $chunks[ $i ][0]; 560 | $name = $chunks[ $i ][1]; 561 | 562 | // Finding the biggest chunk (if the chunk fits, break). 563 | $count = floor( $since / $seconds ); 564 | if ( floatval( 0 ) !== $count ) { 565 | break; 566 | } 567 | } 568 | 569 | // Set output var. 570 | $output = sprintf( '%d %s', $count, Utils\pluralize( $name, absint( $count ) ) ); 571 | 572 | // Step two: the second chunk. 573 | if ( $i + 1 < $j ) { 574 | $seconds2 = $chunks[ $i + 1 ][0]; 575 | $name2 = $chunks[ $i + 1 ][1]; 576 | 577 | $count2 = floor( ( $since - ( $seconds * $count ) ) / $seconds2 ); 578 | if ( floatval( 0 ) !== $count2 ) { 579 | // Add to output var. 580 | $output .= ' ' . sprintf( '%d %s', $count2, Utils\pluralize( $name2, absint( $count2 ) ) ); 581 | } 582 | } 583 | 584 | return $output; 585 | } 586 | 587 | private function get_formatter( &$assoc_args ) { 588 | return new \WP_CLI\Formatter( $assoc_args, $this->fields, 'event' ); 589 | } 590 | } 591 | -------------------------------------------------------------------------------- /src/Cron_Schedule_Command.php: -------------------------------------------------------------------------------- 1 | ] 32 | * : Limit the output to specific object fields. 33 | * 34 | * [--field=] 35 | * : Prints the value of a single field for each schedule. 36 | * 37 | * [--format=] 38 | * : Render output in a particular format. 39 | * --- 40 | * default: table 41 | * options: 42 | * - table 43 | * - csv 44 | * - ids 45 | * - json 46 | * - yaml 47 | * --- 48 | * 49 | * ## AVAILABLE FIELDS 50 | * 51 | * These fields will be displayed by default for each cron schedule: 52 | * 53 | * * name 54 | * * display 55 | * * interval 56 | * 57 | * There are no additional fields. 58 | * 59 | * ## EXAMPLES 60 | * 61 | * # List available cron schedules 62 | * $ wp cron schedule list 63 | * +------------+-------------+----------+ 64 | * | name | display | interval | 65 | * +------------+-------------+----------+ 66 | * | hourly | Once Hourly | 3600 | 67 | * | twicedaily | Twice Daily | 43200 | 68 | * | daily | Once Daily | 86400 | 69 | * +------------+-------------+----------+ 70 | * 71 | * # List id of available cron schedule 72 | * $ wp cron schedule list --fields=name --format=ids 73 | * hourly twicedaily daily 74 | * 75 | * @subcommand list 76 | */ 77 | public function list_( $args, $assoc_args ) { 78 | $formatter = $this->get_formatter( $assoc_args ); 79 | 80 | $schedules = self::get_schedules(); 81 | 82 | if ( 'ids' === $formatter->format ) { 83 | echo implode( ' ', wp_list_pluck( $schedules, 'name' ) ); 84 | } else { 85 | $formatter->display_items( $schedules ); 86 | } 87 | } 88 | 89 | /** 90 | * Callback function to format a cron schedule. 91 | * 92 | * @param array $schedule The schedule. 93 | * @param string $name The schedule name. 94 | * @return array The formatted schedule. 95 | */ 96 | protected static function format_schedule( array $schedule, $name ) { 97 | $schedule['name'] = $name; 98 | return $schedule; 99 | } 100 | 101 | /** 102 | * Return a list of the cron schedules sorted according to interval. 103 | * 104 | * @return array The array of cron schedules. Each schedule is itself an array. 105 | */ 106 | protected static function get_schedules() { 107 | $schedules = wp_get_schedules(); 108 | if ( ! empty( $schedules ) ) { 109 | uasort( $schedules, 'Cron_Schedule_Command::sort' ); 110 | $schedules = array_map( 'Cron_Schedule_Command::format_schedule', $schedules, array_keys( $schedules ) ); 111 | } 112 | return $schedules; 113 | } 114 | 115 | /** 116 | * Callback function to sort the cron schedule array by interval. 117 | * 118 | */ 119 | protected static function sort( array $a, array $b ) { 120 | return $a['interval'] - $b['interval']; 121 | } 122 | 123 | private function get_formatter( &$assoc_args ) { 124 | return new \WP_CLI\Formatter( $assoc_args, $this->fields, 'schedule' ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /wp-cli.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - cron-command.php 3 | --------------------------------------------------------------------------------