Merge pull request #23016 from woocommerce/update_action_scheduler_2_2_1

Update action scheduler to version 2.2.1
This commit is contained in:
Mike Jolley 2019-03-13 17:20:16 +00:00 committed by GitHub
commit c3abdfedc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 530 additions and 756 deletions

View File

@ -1,541 +1,38 @@
# Action Scheduler [![Build Status](https://travis-ci.org/Prospress/action-scheduler.png?branch=master)](https://travis-ci.org/Prospress/action-scheduler) [![codecov](https://codecov.io/gh/Prospress/action-scheduler/branch/master/graph/badge.svg)](https://codecov.io/gh/Prospress/action-scheduler)
# Action Scheduler - Job Queue for WordPress [![Build Status](https://travis-ci.org/Prospress/action-scheduler.png?branch=master)](https://travis-ci.org/Prospress/action-scheduler) [![codecov](https://codecov.io/gh/Prospress/action-scheduler/branch/master/graph/badge.svg)](https://codecov.io/gh/Prospress/action-scheduler)
A robust scheduling library for use in WordPress plugins.
Action Scheduler is a scalable, traceable job queue for background processing large sets of actions in WordPress. It's specially designed to be distributed in WordPress plugins.
## Overview
Action Scheduler works by triggering an action hook to run at some time in the future. Each hook can be scheduled with unique data, to allow callbacks to perform operations on that data. The hook can also be scheduled to run on one or more occassions.
Action Scheduler uses a WordPress [custom post type](http://codex.wordpress.org/Post_Types), creatively named `scheduled-action`, to store the hook name, arguments and scheduled date for an action that should be triggered at some time in the future.
Think of it like an extension to `do_action()` which adds the ability to delay and repeat a hook.
The scheduler will run every minute by attaching itself as a callback to the `'action_scheduler_run_schedule'` hook, which is scheduled using WordPress's built-in [WP-Cron](http://codex.wordpress.org/Function_Reference/wp_cron) system.
## Battle-Tested Background Processing
When triggered, Action Scheduler will check for posts of the `scheduled-action` type that have a `post_date` at or before this point in time i.e. actions scheduled to run now or at sometime in the past.
Every month, Action Scheduler processes millions of payments for [Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/), webhooks for [WooCommerce](https://wordpress.org/plugins/woocommerce/), as well as emails and other events for a range of other plugins.
### Batch Processing
It's been seen on live sites processing queues in excess of 50,000 jobs and doing resource intensive operations, like processing payments and creating orders, at a sustained rate of over 10,000 / hour without negatively impacting normal site operations.
If there are actions to be processed, Action Scheduler will stake a unique claim for a batch of 20 actions and begin processing that batch. The PHP process spawned to run the batch will then continue processing batches of 20 actions until it times out or exhausts available memory.
This is all on infrastructure and WordPress sites outside the control of the plugin author.
If your site has a large number of actions scheduled to run at the same time, Action Scheduler will process more than one batch at a time. Specifically, when the `'action_scheduler_run_schedule'` hook is triggered approximately one minute after the first batch began processing, a new PHP process will stake a new claim to a batch of actions which were not claimed by the previous process. It will then begin to process that batch.
If your plugin needs background processing, especially of large sets of tasks, Action Scheduler can help.
This will continue until all actions are processed using a maximum of 5 concurrent queues.
## Learn More
### Housekeeping
To learn more about how to Action Scheduler works, and how to use it in your plugin, check out the docs on [ActionScheduler.org](https://actionscheduler.org).
Before processing a batch, the scheduler will remove any existing claims on actions which have been sitting in a queue for more than five minutes.
There you will find:
Action Scheduler will also trash any actions which were completed more than a month ago.
If an action runs for more than 5 minutes, Action Scheduler will assume the action has timed out and will mark it as failed. However, if all callbacks attached to the action were to successfully complete sometime after that 5 minute timeout, its status would later be updated to completed.
### Record Keeping
Events for each action will be also logged in the [comments table](http://codex.wordpress.org/Database_Description#Table_Overview).
The events logged by default include when an action:
* is created
* starts
* completes
* fails
Actions can also be grouped together using a custom taxonomy named `action-group`.
## Usage
There are two ways to install Action Scheduler:
1. as a library within your plugin or theme; or
1. as a regular WordPress plugin
### Usage as a Library
To use Action Scheduler as a library:
1. include the Action Scheduler codebase
1. load the library by including the `action-scheduler.php` file
#### Including Action Scheduler Codebase
Using a [subtree in your plugin, theme or site's Git repository](https://www.atlassian.com/blog/git/alternatives-to-git-submodule-git-subtree) to include Action Scheduler is the recommended method. Composer can also be used.
To include Action Scheduler as a git subtree:
##### Step 1. Add the Repository as a Remote
```
git remote add -f subtree-action-scheduler https://github.com/Prospress/action-scheduler.git
```
Adding the subtree as a remote allows us to refer to it in short from via the name `subtree-action-scheduler`, instead of the full GitHub URL.
##### Step 2. Add the Repo as a Subtree
```
git subtree add --prefix libraries/action-scheduler subtree-action-scheduler master --squash
```
This will add the `master` branch of Action Scheduler to your repository in the folder `libraries/action-scheduler`.
You can change the `--prefix` to change where the code is included. Or change the `master` branch to a tag, like `2.1.0` to include only a stable version.
##### Step 3. Update the Subtree
To update Action Scheduler to a new version, use the commands:
```
git fetch subtree-action-scheduler master
git subtree pull --prefix libraries/action-scheduler subtree-action-scheduler master --squash
```
#### Loading Action Scheduler
To load Action Scheduler, you only need to include `action-scheduler.php` file, e.g.
```php
<?php
require_once( plugin_dir_path( __FILE__ ) . '/libraries/action-scheduler/action-scheduler.php' );
```
There is no need to call any functions or do else to initialize Action Scheduler.
When the `action-scheduler.php` file is included, Action Scheduler will register the version in that file and then load the most recent version of itself on the site. It will also load the most recent version of [all API functions](https://github.com/prospress/action-scheduler#api-functions).
#### Load Order
Action Scheduler will register its version on `'plugins_loaded'` with priority `0` - after all other plugin codebases has been loaded. Therefore **the `action-scheduler.php` file must be included before `'plugins_loaded'` priority `0`**.
It is recommended to load it _when the file including it is included_. However, if you need to load it on a hook, then the hook must occur before `'plugins_loaded'`, or you can use `'plugins_loaded'` with negative priority, like `-10`.
Action Scheduler will later initialize itself on `'init'` with priority `1`. Action Scheduler APIs should not be used until after `'init'` with priority `1`.
### Usage as a Plugin
Action Scheduler includes the necessary file headers to be used as a plugin.
To install it as a plugin:
1. Download the .zip archive of the latest [stable release](https://github.com/Prospress/action-scheduler/releases)
1. Go to the **Plugins > Add New > Upload** administration screen on your WordPress site
1. Select the archive file you just downloaded
1. Click **Install Now**
1. Click **Activate**
Or clone the Git repository into your site's `wp-content/plugins` folder.
Using Action Scheduler as a plugin can be handy for developing against newer versions, rather than having to update the subtree in your codebase. **When installed as a plugin, Action Scheduler does not provide any user interfaces for scheduling actions**. The only way to interact with Action Scheduler is via code.
## Managing Scheduled Actions
Action Scheduler has a built in administration screen for monitoring, debugging and manually triggering scheduled actions.
The administration interface is accesible through both:
1. **Tools > Scheduled Actions**
1. **WooCommerce > Status > Scheduled Actions**, when WooCommerce is installed.
![](https://cldup.com/5BA2BNB1sw.png)
Among other tasks, from the admin screen you can:
* run a pending action
* view the scheduled actions with a specific status, like the all actions which have failed or are in-progress (https://cldup.com/NNTwE88Xl8.png).
* view the log entries for a specific action to find out why it failed.
* sort scheduled actions by hook name, scheduled date, claim ID or group name.
Still have questions? Check out the [FAQ below](#faq).
## WP CLI
Action Scheduler has custom [WP CLI](http://wp-cli.org) commands available for processing actions.
For large sites, WP CLI is a much better choice for running queues of actions than the default WP Cron runner. These are some common cases where WP CLI is a better option:
* long-running tasks - Tasks that take a significant amount of time to run
* large queues - A large number of tasks will naturally take a longer time
* other plugins with extensive WP Cron usage - WP Cron's limited resources are spread across more tasks
With a regular web request, you may have to deal with script timeouts enforced by hosts, or other restraints that make it more challenging to run Action Scheduler tasks. Utilizing WP CLI to run commands directly on the server give you more freedom. This means that you typically don't have the same constraints of a normal web request.
If you choose to utilize WP CLI exclusively, you can disable the normal WP CLI queue runner by installing the [Action Scheduler - Disable Default Queue Runner](https://github.com/Prospress/action-scheduler-disable-default-runner) plugin. Note that if you do this, you **must** run Action Scheduler via WP CLI or another method, otherwise no scheduled actions will be processed.
### Commands
These are the commands available to use with Action Scheduler:
* `action-scheduler run`
Options:
* `--batch-size` - This is the number of actions to run in a single batch. The default is `100`.
* `--batches` - This is the number of batches to run. Using 0 means that batches will continue running until there are no more actions to run.
* `--hooks` - Process only actions with specific hook or hooks, like `'woocommerce_scheduled_subscription_payment'`. By default, actions with any hook will be processed. Define multiple hooks as a comma separated string (without spaces), e.g. `--hooks=woocommerce_scheduled_subscription_trial_end,woocommerce_scheduled_subscription_payment,woocommerce_scheduled_subscription_expiration`
* `--group` - Process only actions in a specific group, like `'woocommerce-memberships'`. By default, actions in any group (or no group) will be processed.
* `--force` - By default, Action Scheduler limits the number of concurrent batches that can be run at once to ensure the server does not get overwhelmed. Using the `--force` flag overrides this behavior to force the WP CLI queue to run.
The best way to get a full list of commands and their available options is to use WP CLI itself. This can be done by running `wp action-scheduler` to list all Action Scheduler commands, or by including the `--help` flag with any of the individual commands. This will provide all relevant parameters and flags for the command.
### Cautionary Note on Action Dependencies when using `--group` or `--hooks` Options
The `--group` and `--hooks` options should be used with caution if you have an implicit dependency between scheduled actions based on their schedule.
For example, consider two scheduled actions for the same subscription:
* `scheduled_payment` scheduled for `2015-11-13 00:00:00` and
* `scheduled_expiration` scheduled for `2015-11-13 00:01:00`.
Under normal conditions, Action Scheduler will ensure the `scheduled_payment` action is run before the `scheduled_expiration` action. Becuase that's how they are scheduled.
However, when using the `--hooks` option, the `scheduled_payment` and `scheduled_expiration` actions will be processed in separate queues. As a result, this dependency is not guaranteed.
For example, consider a site with both:
* 100,000 `scheduled_payment` actions, scheduled for `2015-11-13 00:00:00`
* 100 `scheduled_expiration` actions, scheduled for `2015-11-13 00:01:00`
If two queue runners are running alongside each other with each runner dedicated to just one of these hooks, the queue runner handling expiration hooks will complete the processing of the expiration hooks more quickly than the queue runner handling all the payment actions.
**Because of this, the `--group` and `--hooks` options should be used with caution to avoid processing actions with an implicit dependency based on their schedule in separate queues.**
### Improving Performance with `--group` or `--hooks`
Being able to run queues for specific hooks or groups of actions is valuable at scale. Why? Because it means you can restrict the concurrency for similar actions.
For example, let's say you have 300,000 actions queued up comprised of:
* 100,000 renewals payments
* 100,000 email notifications
* 100,000 membership status updates
Action Scheduler's default WP Cron queue runner will process them all together. e.g. when it claims a batch of actions, some may be emails, some membership updates and some renewals.
When you add concurrency to that, you can end up with issues. For example, if you have 3 queues running, they may all be attempting to process similar actions at the same time, which can lead to querying the same database tables with similar queries. Depending on the code/queries running, this can lead to database locks or other issues.
If you can batch based on each action's group, then you can improve performance by processing like actions consecutively, but still processing the full set of actions concurrently.
For example, if one queue is created to process emails, another to process membership updates, and another to process renewal payments, then the same queries won't be run at the same time, and 3 separate queues will be able to run more efficiently.
The WP CLI runner can achieve this using the `--group` option.
## API Functions
### Action Scheduler API vs. WP-Cron API
The Action Scheduler API functions are designed to mirror the WordPress [WP-Cron API functions](http://codex.wordpress.org/Category:WP-Cron_Functions).
Functions return similar values and accept similar arguments to their WP-Cron counterparts. The notable differences are:
* `as_schedule_single_action()` & `as_schedule_recurring_action()` will return the post ID of the scheduled action rather than boolean indicating whether the event was scheduled
* `as_schedule_recurring_action()` takes an interval in seconds as the recurring interval rather than an arbitrary string
* `as_schedule_single_action()` & `as_schedule_recurring_action()` can accept a `$group` parameter to group different actions for the one plugin together.
* the `wp_` prefix is substituted with `as_` and the term `event` is replaced with `action`
### API Function Availability
As mentioned in the [Usage - Load Order](#load-order) section, Action Scheduler will initialize itself on the `'init'` hook with priority `1`. While API functions are loaded prior to this and call be called, they should not be called until after `'init'` with priority `1`, because each component, like the data store, has not yet been initialized.
Do not use Action Scheduler API functions prior to `'init'` hook with priority `1`. Doing so could lead to unexpected results, like data being stored in the incorrect location.
#### Function Reference / `as_schedule_single_action()`
##### Description
Schedule an action to run one time.
##### Usage
```php
<?php as_schedule_single_action( $timestamp, $hook, $args, $group ); ?>
````
##### Parameters
- **$timestamp** (integer)(required) The Unix timestamp representing the date you want the action to run. Default: _none_.
- **$hook** (string)(required) Name of the action hook. Default: _none_.
- **$args** (array) Arguments to pass to callbacks when the hook triggers. Default: _`array()`_.
- **$group** (array) The group to assign this job to. Default: _''_.
##### Return value
(integer) the action's ID in the [posts](http://codex.wordpress.org/Database_Description#Table_Overview) table.
#### Function Reference / `as_schedule_recurring_action()`
##### Description
Schedule an action to run repeatedly with a specified interval in seconds.
##### Usage
```php
<?php as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args, $group ); ?>
````
##### Parameters
- **$timestamp** (integer)(required) The Unix timestamp representing the date you want the action to run. Default: _none_.
- **$interval_in_seconds** (integer)(required) How long to wait between runs. Default: _none_.
- **$hook** (string)(required) Name of the action hook. Default: _none_.
- **$args** (array) Arguments to pass to callbacks when the hook triggers. Default: _`array()`_.
- **$group** (array) The group to assign this job to. Default: _''_.
##### Return value
(integer) the action's ID in the [posts](http://codex.wordpress.org/Database_Description#Table_Overview) table.
#### Function Reference / `as_schedule_cron_action()`
##### Description
Schedule an action that recurs on a cron-like schedule.
##### Usage
```php
<?php as_schedule_cron_action( $timestamp, $schedule, $hook, $args, $group ); ?>
````
##### Parameters
- **$timestamp** (integer)(required) The Unix timestamp representing the date you want the action to run. Default: _none_.
- **$schedule** (string)(required) $schedule A cron-link schedule string, see http://en.wikipedia.org/wiki/Cron. Default: _none_.
- **$hook** (string)(required) Name of the action hook. Default: _none_.
- **$args** (array) Arguments to pass to callbacks when the hook triggers. Default: _`array()`_.
- **$group** (array) The group to assign this job to. Default: _''_.
##### Return value
(integer) the action's ID in the [posts](http://codex.wordpress.org/Database_Description#Table_Overview) table.
#### Function Reference / `as_unschedule_action()`
##### Description
Cancel the next occurrence of a job.
##### Usage
```php
<?php as_unschedule_action( $hook, $args, $group ); ?>
````
##### Parameters
- **$hook** (string)(required) Name of the action hook. Default: _none_.
- **$args** (array) Arguments to pass to callbacks when the hook triggers. Default: _`array()`_.
- **$group** (array) The group to assign this job to. Default: _''_.
##### Return value
(null)
#### Function Reference / `as_next_scheduled_action()`
##### Description
Returns the next timestamp for a scheduled action.
##### Usage
```php
<?php as_next_scheduled_action( $hook, $args, $group ); ?>
````
##### Parameters
- **$hook** (string)(required) Name of the action hook. Default: _none_.
- **$args** (array) Arguments to pass to callbacks when the hook triggers. Default: _`array()`_.
- **$group** (array) The group to assign this job to. Default: _''_.
##### Return value
(integer|boolean) The timestamp for the next occurrence, or false if nothing was found.
#### Function Reference / `as_get_scheduled_actions()`
##### Description
Find scheduled actions.
##### Usage
```php
<?php as_get_scheduled_actions( $args, $return_format ); ?>
````
##### Parameters
- **$args** (array) Arguments to search and filter results by. Possible arguments, with their default values:
* `'hook' => ''` - the name of the action that will be triggered
* `'args' => NULL` - the args array that will be passed with the action
* `'date' => NULL` - the scheduled date of the action. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime().
* `'date_compare' => '<=`' - operator for testing "date". accepted values are '!=', '>', '>=', '<', '<=', '='
* `'modified' => NULL` - the date the action was last updated. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime().
* `'modified_compare' => '<='` - operator for testing "modified". accepted values are '!=', '>', '>=', '<', '<=', '='
* `'group' => ''` - the group the action belongs to
* `'status' => ''` - ActionScheduler_Store::STATUS_COMPLETE or ActionScheduler_Store::STATUS_PENDING
* `'claimed' => NULL` - TRUE to find claimed actions, FALSE to find unclaimed actions, a string to find a specific claim ID
* `'per_page' => 5` - Number of results to return
* `'offset' => 0`
* `'orderby' => 'date'` - accepted values are 'hook', 'group', 'modified', or 'date'
* `'order' => 'ASC'`
- **$return_format** (string) The format in which to return the scheduled actions: 'OBJECT', 'ARRAY_A', or 'ids'. Default: _'OBJECT'_.
##### Return value
(array) Array of the actions matching the criteria specified with `$args`.
## Performance Tuning
By default, Action Scheduler will process a minimum of 1,200 actions per hour. On servers which allow long running PHP processes, this will be significantly higher as processes will be able loop over queues indefinitely.
The batch size and number of concurrent queues that may be processed simultaneously is low by default to ensure the scheduler runs on low powered servers; however, you can configure these settings to increase performance on your site.
#### Increasing Batch Size
By default, Action Scheduler will claim a batch of 20 actions. This small batch size is to minimise the risk of causing a fatal error due to memory exhaustion.
If you know the callbacks attached to your actions use very little memory, or you've tested the number of actions you can process before memory limits are exceeded, you can increase the batch size using the `'action_scheduler_queue_runner_batch_size'` filter.
For example, to increase the batch size to 100, we can use the following function:
```
<?php
function eg_increase_action_scheduler_batch_size( $batch_size ) {
return 100;
}
add_filter( 'action_scheduler_queue_runner_batch_size', 'eg_increase_action_scheduler_batch_size' );
?>
```
### Increasing Concurrent Batches
By default, Action Scheduler will run up to 5 concurrent batches of actions. This is to prevent consuming all the available connections or processes on your webserver.
However, your server may allow a large number of connection, for example, because it has a high value for Apache's `MaxClients` setting or PHP-FPM's `pm.max_children` setting.
If this is the case, you can use the `'action_scheduler_queue_runner_concurrent_batches'` filter to increase the number of conncurrent batches allowed, and therefore speed up processing large numbers of actions scheduled to be processed simultaneously.
For example, to increase the allowed number of concurrent queues to 25, we can use the following code:
```
<?php
function eg_increase_action_scheduler_concurrent_batches( $concurrent_batches ) {
return 25;
}
add_filter( 'action_scheduler_queue_runner_concurrent_batches', 'eg_increase_action_scheduler_concurrent_batches' );
?>
```
## FAQ
### Is it safe to release Action Scheduler in my plugin? Won't its functions conflict with another copy of the library?
Action Scheduler is designed to be used and released in plugins. It avoids redeclaring public API functions when more than one copy of the library is being loaded by different plugins. It will also load only the most recent version of itself (by checking registered versions after all plugins are loaded on the `'plugins_loaded'` hook).
To use it in your plugin, simply require the `action-scheduler/action-scheduler.php` file. Action Scheduler will take care of the rest.
### I don't want to use WP-Cron. Does Action Scheduler depend on WP-Cron?
By default, Action Scheduler is initiated by WP-Cron. However, it has no dependency on the WP-Cron system. You can initiate the Action Scheduler queue in other ways with just one or two lines of code.
For example, you can start a queue directly by calling:
```php
ActionScheduler::runner()->run();
```
Or trigger the `'action_scheduler_run_queue'` hook and let Action Scheduler do it for you:
```php
do_action( 'action_scheduler_run_queue' );
```
Further customization can be done by extending the `ActionScheduler_Abstract_QueueRunner` class to create a custom Queue Runner. For an example of a customized queue runner, see the [`ActionScheduler_WPCLI_QueueRunner`](https://github.com/Prospress/action-scheduler/blob/master/classes/ActionScheduler_WPCLI_QueueRunner.php), which is used when running WP CLI.
Want to create some other method for initiating Action Scheduler? [Open a new issue](https://github.com/Prospress/action-scheduler/issues/new), we'd love to help you with it.
### I don't want to use WP-Cron, ever. Does Action Scheduler replace WP-Cron?
By default, Action Scheduler is designed to work alongside WP-Cron and not change any of its behaviour. This helps avoid unexpectedly overriding WP-Cron on sites installing your plugin, which may have nothing to do with WP-Cron.
However, we can understand why you might want to replace WP-Cron completely in environments within you control, especially as it gets you the advantages of Action Scheduler. This should be possible without too much code.
You could use the `'schedule_event'` hook in WordPress to use Action Scheduler for only newly scheduled WP-Cron jobs and map the `$event` param to Action Scheduler API functions.
Alternatively, you can use a combination of the `'pre_update_option_cron'` and `'pre_option_cron'` hooks to override all new and previously scheduled WP-Cron jobs (similar to the way [Cavalcade](https://github.com/humanmade/Cavalcade) does it).
If you'd like to create a plugin to do this automatically and want to share your work with others, [open a new issue to let us know](https://github.com/Prospress/action-scheduler/issues/new), we'd love to help you with it.
### Eww gross, Custom Post Types! That's _so_ 2010. Can I use a different storage scheme?
Of course! Action Scheduler data storage is completely swappable, and always has been.
You can store scheduled actions in custom tables in the WordPress site's database. Some sites using it already are. You can actually store them anywhere for that matter, like in a remote storage service from Amazon Web Services.
To implement a custom store:
1. extend the abstract `ActionScheduler_Store` class, being careful to implement each of its methods
2. attach a callback to `'action_scheduler_store_class'` to tell Action Scheduler your class is the one which should be used to manage storage, e.g.
```
function eg_define_custom_store( $existing_storage_class ) {
return 'My_Radical_Action_Scheduler_Store';
}
add_filter( 'action_scheduler_store_class', 'eg_define_custom_store', 10, 1 );
```
Take a look at the `ActionScheduler_wpPostStore` class for an example implementation of `ActionScheduler_Store`.
If you'd like to create a plugin to do this automatically and release it publicly to help others, [open a new issue to let us know](https://github.com/Prospress/action-scheduler/issues/new), we'd love to help you with it.
> Note: we're also moving Action Scheduler itself to use [custom tables for better scalability](https://github.com/Prospress/action-scheduler/issues/77).
### Can I use a different storage scheme just for logging?
Of course! Action Scheduler's logger is completely swappable, and always has been. You can also customise where logs are stored, and the storage mechanism.
To implement a custom logger:
1. extend the abstract `ActionScheduler_Logger` class, being careful to implement each of its methods
2. attach a callback to `'action_scheduler_logger_class'` to tell Action Scheduler your class is the one which should be used to manage logging, e.g.
```
function eg_define_custom_logger( $existing_storage_class ) {
return 'My_Radical_Action_Scheduler_Logger';
}
add_filter( 'action_scheduler_logger_class', 'eg_define_custom_logger', 10, 1 );
```
Take a look at the `ActionScheduler_wpCommentLogger` class for an example implementation of `ActionScheduler_Logger`.
### I want to run Action Scheduler only on a dedicated application server in my cluster. Can I do that?
Wow, now you're really asking the tough questions. In theory, yes, this is possible. The `ActionScheduler_QueueRunner` class, which is responsible for running queues, is swappable via the `'action_scheduler_queue_runner_class'` filter.
Because of this, you can effectively customise queue running however you need. Whether that means tweaking minor things, like not using WP-Cron at all to initiate queues by overriding `ActionScheduler_QueueRunner::init()`, or completely changing how and where queues are run, by overriding `ActionScheduler_QueueRunner::run()`.
### Is Action Scheduler safe to use on my production site?
Yes, absolutely! Action Scheduler is actively used on tens of thousands of production sites already. Right now it's responsible for scheduling everything from emails to payments.
In fact, every month, Action Scheduler processes millions of payments as part of the [WooCommerce Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/) extension.
It requires no setup, and won't override any WordPress APIs (unless you want it to).
### How does Action Scheduler work on WordPress Multisite?
Action Scheduler is designed to manage the scheduled actions on a single site. It has no special handling for running queues across multiple sites in a multisite network. That said, because it's storage and Queue Runner are completely swappable, it would be possible to write multisite handling classes to use with it.
If you'd like to create a multisite plugin to do this and release it publicly to help others, [open a new issue to let us know](https://github.com/Prospress/action-scheduler/issues/new), we'd love to help you with it.
* [Usage guide](https://actionscheduler.org/usage/): instructions on installing and using Action Scheduler
* [WP CLI guide](https://actionscheduler.org/wp-cli/): instructions on running Action Scheduler at scale via WP CLI
* [API Reference](https://actionscheduler.org/api/): complete reference guide for all API functions
* [Administration Guide](https://actionscheduler.org/admin/): guide to managing scheduled actions via the administration screen
* [Guide to Background Processing at Scale](https://actionscheduler.org/perf/): instructions for running Action Scheduler at scale via the default WP Cron queue runner
## Credits
This extension is developed and maintained as a collaboration between the teams at [Prospress](http://prospress.com/) and [Flightless](https://flightless.us/).
Action Scheduler is developed and maintained by [Prospress](http://prospress.com/) in collaboration with [Flightless](https://flightless.us/).
Collaboration is cool. We'd love to work with you to improve Action Scheduler. Pull Requests welcome.
Collaboration is cool. We'd love to work with you to improve Action Scheduler. [Pull Requests](https://github.com/prospress/action-scheduler/pulls) welcome.
---

View File

@ -5,7 +5,7 @@
* Description: A robust scheduling library for use in WordPress plugins.
* Author: Prospress
* Author URI: http://prospress.com/
* Version: 2.1.0
* Version: 2.2.1
* License: GPLv3
*
* Copyright 2018 Prospress, Inc. (email : freedoms@prospress.com)
@ -25,21 +25,21 @@
*
*/
if ( ! function_exists( 'action_scheduler_register_2_dot_1_dot_0' ) ) {
if ( ! function_exists( 'action_scheduler_register_2_dot_2_dot_1' ) ) {
if ( ! class_exists( 'ActionScheduler_Versions' ) ) {
require_once( 'classes/ActionScheduler_Versions.php' );
add_action( 'plugins_loaded', array( 'ActionScheduler_Versions', 'initialize_latest_version' ), 1, 0 );
}
add_action( 'plugins_loaded', 'action_scheduler_register_2_dot_1_dot_0', 0, 0 );
add_action( 'plugins_loaded', 'action_scheduler_register_2_dot_2_dot_1', 0, 0 );
function action_scheduler_register_2_dot_1_dot_0() {
function action_scheduler_register_2_dot_2_dot_1() {
$versions = ActionScheduler_Versions::instance();
$versions->register( '2.1.0', 'action_scheduler_initialize_2_dot_1_dot_0' );
$versions->register( '2.2.1', 'action_scheduler_initialize_2_dot_2_dot_1' );
}
function action_scheduler_initialize_2_dot_1_dot_0() {
function action_scheduler_initialize_2_dot_2_dot_1() {
require_once( 'classes/ActionScheduler.php' );
ActionScheduler::init( __FILE__ );
}

View File

@ -59,7 +59,9 @@ abstract class ActionScheduler {
public static function autoload( $class ) {
$d = DIRECTORY_SEPARATOR;
if ( strpos( $class, 'ActionScheduler' ) === 0 ) {
if ( 'Deprecated' === substr( $class, -10 ) ) {
$dir = self::plugin_path('deprecated'.$d);
} elseif ( strpos( $class, 'ActionScheduler' ) === 0 ) {
$dir = self::plugin_path('classes'.$d);
} elseif ( strpos( $class, 'CronExpression' ) === 0 ) {
$dir = self::plugin_path('lib'.$d.'cron-expression'.$d);
@ -83,6 +85,11 @@ abstract class ActionScheduler {
self::$plugin_file = $plugin_file;
spl_autoload_register( array( __CLASS__, 'autoload' ) );
/**
* Fires in the early stages of Action Scheduler init hook.
*/
do_action( 'action_scheduler_pre_init' );
$store = self::store();
add_action( 'init', array( $store, 'init' ), 1, 0 );

View File

@ -3,7 +3,7 @@
/**
* Abstract class with common Queue Cleaner functionality.
*/
abstract class ActionScheduler_Abstract_QueueRunner {
abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abstract_QueueRunner_Deprecated {
/** @var ActionScheduler_QueueCleaner */
protected $cleaner;
@ -57,13 +57,16 @@ abstract class ActionScheduler_Abstract_QueueRunner {
$action = $this->store->fetch_action( $action_id );
$this->store->log_execution( $action_id );
$action->execute();
do_action( 'action_scheduler_after_execute', $action_id );
do_action( 'action_scheduler_after_execute', $action_id, $action );
$this->store->mark_complete( $action_id );
} catch ( Exception $e ) {
$this->store->mark_failure( $action_id );
do_action( 'action_scheduler_failed_execution', $action_id, $e );
}
$this->schedule_next_instance( $action );
if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) ) {
$this->schedule_next_instance( $action );
}
}
/**
@ -86,7 +89,7 @@ abstract class ActionScheduler_Abstract_QueueRunner {
* @author Jeremy Pry
*/
protected function run_cleanup() {
$this->cleaner->clean();
$this->cleaner->clean( 10 * $this->get_time_limit() );
}
/**
@ -103,22 +106,21 @@ abstract class ActionScheduler_Abstract_QueueRunner {
*
* @return int The number of seconds.
*/
protected function get_maximum_execution_time() {
protected function get_time_limit() {
// There are known hosts with a strict 60 second execution time.
if ( defined( 'WPENGINE_ACCOUNT' ) || defined( 'PANTHEON_ENVIRONMENT' ) ) {
$maximum_execution_time = 60;
} elseif ( false !== strpos( getenv( 'HOSTNAME' ), '.siteground.' ) ) {
$maximum_execution_time = 120;
} else {
$maximum_execution_time = ini_get( 'max_execution_time' );
$time_limit = 30;
// Apply deprecated filter from deprecated get_maximum_execution_time() method
if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) {
_deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' );
$time_limit = apply_filters( 'action_scheduler_maximum_execution_time', $time_limit );
}
return absint( apply_filters( 'action_scheduler_maximum_execution_time', $maximum_execution_time ) );
return absint( apply_filters( 'action_scheduler_queue_runner_time_limit', $time_limit ) );
}
/**
* Get the number of seconds a batch has run for.
* Get the number of seconds the process has been running.
*
* @return int The number of seconds.
*/
@ -146,10 +148,10 @@ abstract class ActionScheduler_Abstract_QueueRunner {
protected function time_likely_to_be_exceeded( $processed_actions ) {
$execution_time = $this->get_execution_time();
$max_execution_time = $this->get_maximum_execution_time();
$max_execution_time = $this->get_time_limit();
$time_per_action = $execution_time / $processed_actions;
$estimated_time = $execution_time + ( $time_per_action * 2 );
$likely_to_be_exceeded = $estimated_time > $this->get_maximum_execution_time();
$estimated_time = $execution_time + ( $time_per_action * 3 );
$likely_to_be_exceeded = $estimated_time > $max_execution_time;
return apply_filters( 'action_scheduler_maximum_execution_time_likely_to_be_exceeded', $likely_to_be_exceeded, $this, $processed_actions, $execution_time, $max_execution_time );
}

View File

@ -30,6 +30,7 @@ class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated {
if ( class_exists( 'WooCommerce' ) ) {
add_action( 'woocommerce_admin_status_content_action-scheduler', array( $this, 'render_admin_ui' ) );
add_action( 'woocommerce_system_status_report', array( $this, 'system_status_report' ) );
add_filter( 'woocommerce_admin_status_tabs', array( $this, 'register_system_status_tab' ) );
}
@ -37,6 +38,10 @@ class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated {
}
}
public function system_status_report() {
$table = new ActionScheduler_wcSystemStatus( ActionScheduler::store() );
$table->print();
}
/**
* Registers action-scheduler into WooCommerce > System status.

View File

@ -77,4 +77,23 @@ class ActionScheduler_Compatibility {
}
return false;
}
/**
* Attempts to raise the PHP timeout for time intensive processes.
*
* Only allows raising the existing limit and prevents lowering it. Wrapper for wc_set_time_limit(), when available.
*
* @param int The time limit in seconds.
*/
public static function raise_time_limit( $limit = 0 ) {
if ( $limit < ini_get( 'max_execution_time' ) ) {
return;
}
if ( function_exists( 'wc_set_time_limit' ) ) {
wc_set_time_limit( $limit );
} elseif ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
@set_time_limit( $limit );
}
}
}

View File

@ -31,6 +31,13 @@ class ActionScheduler_CronSchedule implements ActionScheduler_Schedule {
return true;
}
/**
* @return string
*/
public function get_recurrence() {
return strval($this->cron);
}
/**
* For PHP 5.2 compat, since DateTime objects can't be serialized
* @return array

View File

@ -36,9 +36,7 @@ class ActionScheduler_IntervalSchedule implements ActionScheduler_Schedule {
}
/**
* @param DateTime $after
*
* @return DateTime|null
* @return int
*/
public function interval_in_seconds() {
return $this->interval_in_seconds;

View File

@ -222,9 +222,16 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
*/
protected function get_recurrence( $action ) {
$recurrence = $action->get_schedule();
if ( method_exists( $recurrence, 'interval_in_seconds' ) ) {
return sprintf( __( 'Every %s', 'action-scheduler' ), self::human_interval( $recurrence->interval_in_seconds() ) );
if ( $recurrence->is_recurring() ) {
if ( method_exists( $recurrence, 'interval_in_seconds' ) ) {
return sprintf( __( 'Every %s', 'action-scheduler' ), self::human_interval( $recurrence->interval_in_seconds() ) );
}
if ( method_exists( $recurrence, 'get_recurrence' ) ) {
return sprintf( __( 'Cron %s', 'action-scheduler' ), $recurrence->get_recurrence() );
}
}
return __( 'Non-repeating', 'action-scheduler' );
}
@ -280,7 +287,7 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) {
$date = $log_entry->get_date();
$date->setTimezone( $timezone );
return sprintf( '<li><strong>%s</strong><br/>%s</li>', esc_html( $date->format( 'Y-m-d H:i:s e' ) ), esc_html( $log_entry->get_message() ) );
return sprintf( '<li><strong>%s</strong><br/>%s</li>', esc_html( $date->format( 'Y-m-d H:i:s O' ) ), esc_html( $log_entry->get_message() ) );
}
/**
@ -378,7 +385,7 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
$next_timestamp = $schedule->next()->getTimestamp();
$schedule_display_string .= $schedule->next()->format( 'Y-m-d H:i:s e' );
$schedule_display_string .= $schedule->next()->format( 'Y-m-d H:i:s O' );
$schedule_display_string .= '<br/>';
if ( gmdate( 'U' ) > $next_timestamp ) {

View File

@ -95,4 +95,4 @@ abstract class ActionScheduler_Logger {
$this->log( $action_id, __( 'action ignored', 'action-scheduler' ) );
}
}

View File

@ -18,13 +18,6 @@ class ActionScheduler_QueueCleaner {
*/
private $month_in_seconds = 2678400;
/**
* Five minutes in seconds
*
* @var int
*/
private $five_minutes = 300;
/**
* ActionScheduler_QueueCleaner constructor.
*
@ -77,8 +70,16 @@ class ActionScheduler_QueueCleaner {
}
}
public function reset_timeouts() {
$timeout = apply_filters( 'action_scheduler_timeout_period', $this->five_minutes );
/**
* Unclaim pending actions that have not been run within a given time limit.
*
* When called by ActionScheduler_Abstract_QueueRunner::run_cleanup(), the time limit passed
* as a parameter is 10x the time limit used for queue processing.
*
* @param int $time_limit The number of seconds to allow a queue to run before unclaiming its pending actions. Default 300 (5 minutes).
*/
public function reset_timeouts( $time_limit = 300 ) {
$timeout = apply_filters( 'action_scheduler_timeout_period', $time_limit );
if ( $timeout < 0 ) {
return;
}
@ -97,8 +98,17 @@ class ActionScheduler_QueueCleaner {
}
}
public function mark_failures() {
$timeout = apply_filters( 'action_scheduler_failure_period', $this->five_minutes );
/**
* Mark actions that have been running for more than a given time limit as failed, based on
* the assumption some uncatachable and unloggable fatal error occurred during processing.
*
* When called by ActionScheduler_Abstract_QueueRunner::run_cleanup(), the time limit passed
* as a parameter is 10x the time limit used for queue processing.
*
* @param int $time_limit The number of seconds to allow an action to run before it is considered to have failed. Default 300 (5 minutes).
*/
public function mark_failures( $time_limit = 300 ) {
$timeout = apply_filters( 'action_scheduler_failure_period', $time_limit );
if ( $timeout < 0 ) {
return;
}
@ -119,12 +129,13 @@ class ActionScheduler_QueueCleaner {
/**
* Do all of the cleaning actions.
*
* @param int $time_limit The number of seconds to use as the timeout and failure period. Default 300 (5 minutes).
* @author Jeremy Pry
*/
public function clean() {
public function clean( $time_limit = 300 ) {
$this->delete_old_actions();
$this->reset_timeouts();
$this->mark_failures();
$this->reset_timeouts( $time_limit );
$this->mark_failures( $time_limit );
}
/**

View File

@ -51,7 +51,7 @@ class ActionScheduler_QueueRunner extends ActionScheduler_Abstract_QueueRunner {
public function run() {
ActionScheduler_Compatibility::raise_memory_limit();
@set_time_limit( apply_filters( 'action_scheduler_queue_runner_time_limit', 600 ) );
ActionScheduler_Compatibility::raise_time_limit( $this->get_time_limit() );
do_action( 'action_scheduler_before_process_queue' );
$this->run_cleanup();
$processed_actions = 0;

View File

@ -27,6 +27,7 @@ class ActionScheduler_WPCLI_QueueRunner extends ActionScheduler_Abstract_QueueRu
*/
public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null ) {
if ( ! ( defined( 'WP_CLI' ) && WP_CLI ) ) {
/* translators: %s php class name */
throw new Exception( sprintf( __( 'The %s class can only be run within WP CLI.', 'action-scheduler' ), __CLASS__ ) );
}
@ -76,7 +77,7 @@ class ActionScheduler_WPCLI_QueueRunner extends ActionScheduler_Abstract_QueueRu
*/
protected function add_hooks() {
add_action( 'action_scheduler_before_execute', array( $this, 'before_execute' ) );
add_action( 'action_scheduler_after_execute', array( $this, 'after_execute' ) );
add_action( 'action_scheduler_after_execute', array( $this, 'after_execute' ), 10, 2 );
add_action( 'action_scheduler_failed_execution', array( $this, 'action_failed' ), 10, 2 );
}
@ -143,11 +144,16 @@ class ActionScheduler_WPCLI_QueueRunner extends ActionScheduler_Abstract_QueueRu
*
* @author Jeremy Pry
*
* @param $action_id
* @param int $action_id
* @param null|ActionScheduler_Action $action The instance of the action. Default to null for backward compatibility.
*/
public function after_execute( $action_id ) {
public function after_execute( $action_id, $action = null ) {
// backward compatibility
if ( null === $action ) {
$action = $this->store->fetch_action( $action_id );
}
/* translators: %s refers to the action ID */
WP_CLI::log( sprintf( __( 'Completed processing action %s', 'action-scheduler' ), $action_id ) );
WP_CLI::log( sprintf( __( 'Completed processing action %s with hook: %s', 'action-scheduler' ), $action_id, $action->get_hook() ) );
}
/**

View File

@ -0,0 +1,129 @@
<?php
/**
* Class ActionScheduler_wcSystemStatus
*/
class ActionScheduler_wcSystemStatus {
/**
* The active data stores
*
* @var ActionScheduler_Store
*/
protected $store;
function __construct( $store ) {
$this->store = $store;
}
/**
* Display action data, including number of actions grouped by status and the oldest & newest action in each status.
*
* Helpful to identify issues, like a clogged queue.
*/
public function print() {
$action_counts = $this->store->action_counts();
$status_labels = $this->store->get_status_labels();
$oldest_and_newest = $this->get_oldest_and_newest( array_keys( $status_labels ) );
$this->get_template( $status_labels, $action_counts, $oldest_and_newest );
}
/**
* Get oldest and newest scheduled dates for a given set of statuses.
*
* @param array $status_keys Set of statuses to find oldest & newest action for.
* @return array
*/
protected function get_oldest_and_newest( $status_keys ) {
$oldest_and_newest = array();
foreach ( $status_keys as $status ) {
$oldest_and_newest[ $status ] = array(
'oldest' => '&ndash;',
'newest' => '&ndash;',
);
if ( 'in-progress' === $status ) {
continue;
}
$oldest_and_newest[ $status ]['oldest'] = $this->get_action_status_date( $status, 'oldest' );
$oldest_and_newest[ $status ]['newest'] = $this->get_action_status_date( $status, 'newest' );
}
return $oldest_and_newest;
}
/**
* Get oldest or newest scheduled date for a given status.
*
* @param string $status Action status label/name string.
* @param string $date_type Oldest or Newest.
* @return DateTime
*/
protected function get_action_status_date( $status, $date_type = 'oldest' ) {
$order = 'oldest' === $date_type ? 'ASC' : 'DESC';
$action = $this->store->query_actions( array(
'claimed' => false,
'status' => $status,
'per_page' => 1,
'order' => $order,
) );
if ( ! empty( $action ) ) {
$date_object = $this->store->get_date( $action[0] );
$action_date = $date_object->format( 'Y-m-d H:i:s O' );
} else {
$action_date = '&ndash;';
}
return $action_date;
}
/**
* Get oldest or newest scheduled date for a given status.
*
* @param array $status_labels Set of statuses to find oldest & newest action for.
* @param array $action_counts Number of actions grouped by status.
* @param array $oldest_and_newest Date of the oldest and newest action with each status.
*/
protected function get_template( $status_labels, $action_counts, $oldest_and_newest ) {
?>
<table class="wc_status_table widefat" cellspacing="0">
<thead>
<tr>
<th colspan="5" data-export-label="Action Scheduler"><h2><?php esc_html_e( 'Action Scheduler', 'action-scheduler' ); ?><?php echo wc_help_tip( esc_html__( 'This section shows scheduled action counts.', 'action-scheduler' ) ); ?></h2></th>
</tr>
<tr>
<td><strong><?php esc_html_e( 'Action Status', 'action-scheduler' ); ?></strong></td>
<td class="help">&nbsp;</td>
<td><strong><?php esc_html_e( 'Count', 'action-scheduler' ); ?></strong></td>
<td><strong><?php esc_html_e( 'Oldest Scheduled Date', 'action-scheduler' ); ?></strong></td>
<td><strong><?php esc_html_e( 'Newest Scheduled Date', 'action-scheduler' ); ?></strong></td>
</tr>
</thead>
<tbody>
<?php
foreach ( $action_counts as $status => $count ) {
// WC uses the 3rd column for export, so we need to display more data in that (hidden when viewed as part of the table) and add an empty 2nd column.
printf(
'<tr><td>%1$s</td><td>&nbsp;</td><td>%2$s<span style="display: none;">, Oldest: %3$s, Newest: %4$s</span></td><td>%3$s</td><td>%4$s</td></tr>',
esc_html( $status_labels[ $status ] ),
number_format_i18n( $count ),
$oldest_and_newest[ $status ]['oldest'],
$oldest_and_newest[ $status ]['newest']
);
}
?>
</tbody>
</table>
<?php
}
}

View File

@ -32,7 +32,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
$post = array(
'post_type' => self::POST_TYPE,
'post_title' => $action->get_hook(),
'post_content' => wp_json_encode($action->get_args()),
'post_content' => json_encode($action->get_args()),
'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
'post_date' => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
@ -42,8 +42,10 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
protected function save_post_array( $post_array ) {
add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
$post_id = wp_insert_post($post_array);
remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
if ( is_wp_error($post_id) || empty($post_id) ) {
throw new RuntimeException(__('Unable to save action.', 'action-scheduler'));
@ -61,6 +63,41 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
return $postdata;
}
/**
* Create a (probably unique) post name for scheduled actions in a more performant manner than wp_unique_post_slug().
*
* When an action's post status is transitioned to something other than 'draft', 'pending' or 'auto-draft, like 'publish'
* or 'failed' or 'trash', WordPress will find a unique slug (stored in post_name column) using the wp_unique_post_slug()
* function. This is done to ensure URL uniqueness. The approach taken by wp_unique_post_slug() is to iterate over existing
* post_name values that match, and append a number 1 greater than the largest. This makes sense when manually creating a
* post from the Edit Post screen. It becomes a bottleneck when automatically processing thousands of actions, with a
* database containing thousands of related post_name values.
*
* WordPress 5.1 introduces the 'pre_wp_unique_post_slug' filter for plugins to address this issue.
*
* We can short-circuit WordPress's wp_unique_post_slug() approach using the 'pre_wp_unique_post_slug' filter. This
* method is available to be used as a callback on that filter. It provides a more scalable approach to generating a
* post_name/slug that is probably unique. Because Action Scheduler never actually uses the post_name field, or an
* action's slug, being probably unique is good enough.
*
* For more backstory on this issue, see:
* - https://github.com/Prospress/action-scheduler/issues/44 and
* - https://core.trac.wordpress.org/ticket/21112
*
* @param string $override_slug Short-circuit return value.
* @param string $slug The desired slug (post_name).
* @param int $post_ID Post ID.
* @param string $post_status The post status.
* @param string $post_type Post type.
* @return string
*/
public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
if ( self::POST_TYPE == $post_type ) {
$override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false );
}
return $override_slug;
}
protected function save_post_schedule( $post_id, $schedule ) {
update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
}
@ -102,7 +139,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
}
$schedule = get_post_meta( $post->ID, self::SCHEDULE_META_KEY, true );
if ( empty($schedule) ) {
if ( empty( $schedule ) || ! is_a( $schedule, 'ActionScheduler_Schedule' ) ) {
$schedule = new ActionScheduler_NullSchedule();
}
$group = wp_get_object_terms( $post->ID, self::GROUP_TAXONOMY, array('fields' => 'names') );
@ -190,7 +227,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
$args[] = self::POST_TYPE;
if ( !is_null($params['args']) ) {
$query .= " AND p.post_content=%s";
$args[] = wp_json_encode($params['args']);
$args[] = json_encode($params['args']);
}
if ( ! empty( $params['status'] ) ) {
@ -271,7 +308,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
}
if ( !is_null($query['args']) ) {
$sql .= " AND p.post_content=%s";
$sql_params[] = wp_json_encode($query['args']);
$sql_params[] = json_encode($query['args']);
}
if ( ! empty( $query['status'] ) ) {
@ -404,7 +441,9 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
throw new InvalidArgumentException(sprintf(__('Unidentified action %s', 'action-scheduler'), $action_id));
}
do_action( 'action_scheduler_canceled_action', $action_id );
add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
wp_trash_post($action_id);
remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
}
public function delete_action( $action_id ) {
@ -587,16 +626,9 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
'ID' => 'ASC',
),
'date_query' => array(
'column' => 'post_date',
array(
'compare' => '<=',
'year' => $date->format( 'Y' ),
'month' => $date->format( 'n' ),
'day' => $date->format( 'j' ),
'hour' => $date->format( 'G' ),
'minute' => $date->format( 'i' ),
'second' => $date->format( 's' ),
),
'column' => 'post_date_gmt',
'before' => $date->format( 'Y-m-d H:i' ),
'inclusive' => true,
),
'tax_query' => array(
array(
@ -716,11 +748,13 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
throw new InvalidArgumentException(sprintf(__('Unidentified action %s', 'action-scheduler'), $action_id));
}
add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
$result = wp_update_post(array(
'ID' => $action_id,
'post_status' => 'publish',
), TRUE);
remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
if ( is_wp_error($result) ) {
throw new RuntimeException($result->get_error_message());
}
@ -736,7 +770,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
* @param ActionScheduler_Action $action
*/
protected function validate_action( ActionScheduler_Action $action ) {
if ( strlen( wp_json_encode( $action->get_args() ) ) > self::$max_index_length ) {
if ( strlen( json_encode( $action->get_args() ) ) > self::$max_index_length ) {
_doing_it_wrong( 'ActionScheduler_Action::$args', sprintf( 'To ensure the action args column can be indexed, action args should not be more than %d characters when encoded as JSON. Support for strings longer than this will be removed in a future version.', self::$max_index_length ), '2.1.0' );
}
}

View File

@ -6,6 +6,6 @@
"minimum-stability": "dev",
"require": {},
"require-dev": {
"wp-cli/wp-cli": "2.1.0"
"wp-cli/wp-cli": "^1.3"
}
}

View File

@ -1,10 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "2a94d15afcb17b214630daa2d90e97fa",
"content-hash": "f4556531e7b95173d1b769b3d7350926",
"packages": [],
"packages-dev": [
{
@ -13,12 +13,12 @@
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "45501c64f5d1b6a9c53c9a9def19e141bcbf2260"
"reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/45501c64f5d1b6a9c53c9a9def19e141bcbf2260",
"reference": "45501c64f5d1b6a9c53c9a9def19e141bcbf2260",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/b17e6153cb7f33c7e44eb59578dc12eee5dc8e12",
"reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12",
"shasum": ""
},
"require": {
@ -27,9 +27,12 @@
"php": "^5.3.2 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
"phpunit/phpunit": "^4.5",
"psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0 || ^4.0"
"symfony/process": "^2.5 || ^3.0"
},
"suggest": {
"symfony/process": "This is necessary to reliably check whether openssl_x509_parse is vulnerable on older php versions, but can be ignored on PHP 5.5.6+"
},
"type": "library",
"extra": {
@ -61,7 +64,7 @@
"ssl",
"tls"
],
"time": "2018-10-31T14:14:03+00:00"
"time": "2017-03-06T11:59:08+00:00"
},
{
"name": "composer/composer",
@ -146,12 +149,12 @@
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "2b303e43d14d15cc90c8e8db4a1cdb6259f1a5c5"
"reference": "7ea669582e6396857cf6d1c0a6cd2728f4e7e383"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/2b303e43d14d15cc90c8e8db4a1cdb6259f1a5c5",
"reference": "2b303e43d14d15cc90c8e8db4a1cdb6259f1a5c5",
"url": "https://api.github.com/repos/composer/semver/zipball/7ea669582e6396857cf6d1c0a6cd2728f4e7e383",
"reference": "7ea669582e6396857cf6d1c0a6cd2728f4e7e383",
"shasum": ""
},
"require": {
@ -200,7 +203,7 @@
"validation",
"versioning"
],
"time": "2017-11-06T09:05:54+00:00"
"time": "2017-05-15T12:49:06+00:00"
},
{
"name": "composer/spdx-licenses",
@ -208,19 +211,19 @@
"source": {
"type": "git",
"url": "https://github.com/composer/spdx-licenses.git",
"reference": "5781a46078ca46330d05f46f90c42eaacba85749"
"reference": "2603a0d7ddc00a015deb576fa5297ca43dee6b1c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/spdx-licenses/zipball/5781a46078ca46330d05f46f90c42eaacba85749",
"reference": "5781a46078ca46330d05f46f90c42eaacba85749",
"url": "https://api.github.com/repos/composer/spdx-licenses/zipball/2603a0d7ddc00a015deb576fa5297ca43dee6b1c",
"reference": "2603a0d7ddc00a015deb576fa5297ca43dee6b1c",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
"phpunit/phpunit": "^4.5 || ^5.0.5",
"phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
},
"type": "library",
@ -261,7 +264,7 @@
"spdx",
"validator"
],
"time": "2018-12-26T21:53:59+00:00"
"time": "2017-04-03T19:08:52+00:00"
},
{
"name": "justinrainbow/json-schema",
@ -269,12 +272,12 @@
"source": {
"type": "git",
"url": "https://github.com/justinrainbow/json-schema.git",
"reference": "8560d4314577199ba51bf2032f02cd1315587c23"
"reference": "36ed4d935f8f5eb958dbd29e1fa5a241ec3ece4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/8560d4314577199ba51bf2032f02cd1315587c23",
"reference": "8560d4314577199ba51bf2032f02cd1315587c23",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/36ed4d935f8f5eb958dbd29e1fa5a241ec3ece4d",
"reference": "36ed4d935f8f5eb958dbd29e1fa5a241ec3ece4d",
"shasum": ""
},
"require": {
@ -283,7 +286,7 @@
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.1",
"json-schema/json-schema-test-suite": "1.2.0",
"phpunit/phpunit": "^4.8.35"
"phpunit/phpunit": "^4.8.22"
},
"bin": [
"bin/validate-json"
@ -327,7 +330,7 @@
"json",
"schema"
],
"time": "2018-02-14T22:26:30+00:00"
"time": "2017-06-23T11:43:36+00:00"
},
{
"name": "mustache/mustache",
@ -422,12 +425,12 @@
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "014d250daebff39eba15ba990eeb2a140798e77c"
"reference": "2cc4a01788191489dc7459446ba832fa79a216a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/014d250daebff39eba15ba990eeb2a140798e77c",
"reference": "014d250daebff39eba15ba990eeb2a140798e77c",
"url": "https://api.github.com/repos/php-fig/container/zipball/2cc4a01788191489dc7459446ba832fa79a216a7",
"reference": "2cc4a01788191489dc7459446ba832fa79a216a7",
"shasum": ""
},
"require": {
@ -463,7 +466,7 @@
"container-interop",
"psr"
],
"time": "2018-12-29T15:36:03+00:00"
"time": "2017-06-28T15:35:32+00:00"
},
{
"name": "psr/log",
@ -555,7 +558,6 @@
"array_column",
"column"
],
"abandoned": true,
"time": "2015-03-20T22:07:39+00:00"
},
{
@ -754,18 +756,17 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "17c5d8941eb75a03d19bc76a43757738632d87b3"
"reference": "d668d8c0502d2b485c00d107db65fdbc56c26282"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/17c5d8941eb75a03d19bc76a43757738632d87b3",
"reference": "17c5d8941eb75a03d19bc76a43757738632d87b3",
"url": "https://api.github.com/repos/symfony/config/zipball/d668d8c0502d2b485c00d107db65fdbc56c26282",
"reference": "d668d8c0502d2b485c00d107db65fdbc56c26282",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/filesystem": "~2.8|~3.0|~4.0",
"symfony/polyfill-ctype": "~1.8"
"symfony/filesystem": "~2.8|~3.0|~4.0"
},
"conflict": {
"symfony/dependency-injection": "<3.3",
@ -773,7 +774,6 @@
},
"require-dev": {
"symfony/dependency-injection": "~3.3|~4.0",
"symfony/event-dispatcher": "~3.3|~4.0",
"symfony/finder": "~3.3|~4.0",
"symfony/yaml": "~3.0|~4.0"
},
@ -810,7 +810,7 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
"time": "2019-01-01T13:45:19+00:00"
"time": "2017-08-05T17:34:46+00:00"
},
{
"name": "symfony/console",
@ -818,12 +818,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "a700b874d3692bc8342199adfb6d3b99f62cc61a"
"reference": "0e283478c2d68c9bf9cc52592ad1ef1834083a85"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/a700b874d3692bc8342199adfb6d3b99f62cc61a",
"reference": "a700b874d3692bc8342199adfb6d3b99f62cc61a",
"url": "https://api.github.com/repos/symfony/console/zipball/0e283478c2d68c9bf9cc52592ad1ef1834083a85",
"reference": "0e283478c2d68c9bf9cc52592ad1ef1834083a85",
"shasum": ""
},
"require": {
@ -832,19 +832,20 @@
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/dependency-injection": "<3.4",
"symfony/dependency-injection": "<3.3",
"symfony/process": "<3.3"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~3.3|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/dependency-injection": "~3.3|~4.0",
"symfony/event-dispatcher": "~2.8|~3.0|~4.0",
"symfony/http-kernel": "~2.8|~3.0|~4.0",
"symfony/lock": "~3.4|~4.0",
"symfony/process": "~3.3|~4.0"
},
"suggest": {
"psr/log-implementation": "For using the console logger",
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
@ -879,7 +880,7 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2019-01-04T04:42:43+00:00"
"time": "2017-08-10T07:07:17+00:00"
},
{
"name": "symfony/debug",
@ -887,12 +888,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
"reference": "26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186"
"reference": "50bda5b4b8641616d45254c6855bcd45f2f64187"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186",
"reference": "26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186",
"url": "https://api.github.com/repos/symfony/debug/zipball/50bda5b4b8641616d45254c6855bcd45f2f64187",
"reference": "50bda5b4b8641616d45254c6855bcd45f2f64187",
"shasum": ""
},
"require": {
@ -935,7 +936,7 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
"time": "2019-01-01T13:45:19+00:00"
"time": "2017-08-10T07:07:17+00:00"
},
{
"name": "symfony/dependency-injection",
@ -943,12 +944,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
"reference": "928a38b18bd632d67acbca74d0b2eed09915e83e"
"reference": "aaee88765cb21a838e8da26d6acda4ca2ae3a2ea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/928a38b18bd632d67acbca74d0b2eed09915e83e",
"reference": "928a38b18bd632d67acbca74d0b2eed09915e83e",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/aaee88765cb21a838e8da26d6acda4ca2ae3a2ea",
"reference": "aaee88765cb21a838e8da26d6acda4ca2ae3a2ea",
"shasum": ""
},
"require": {
@ -956,7 +957,7 @@
"psr/container": "^1.0"
},
"conflict": {
"symfony/config": "<3.3.7",
"symfony/config": "<3.3.1",
"symfony/finder": "<3.3",
"symfony/proxy-manager-bridge": "<3.4",
"symfony/yaml": "<3.4"
@ -1006,7 +1007,7 @@
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
"time": "2019-01-05T12:26:58+00:00"
"time": "2017-08-10T19:43:00+00:00"
},
{
"name": "symfony/event-dispatcher",
@ -1014,12 +1015,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "d1cdd46c53c264a2bd42505bd0e8ce21423bd0e2"
"reference": "cd8b015f859e6b60933324db00067c2f060b4d18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d1cdd46c53c264a2bd42505bd0e8ce21423bd0e2",
"reference": "d1cdd46c53c264a2bd42505bd0e8ce21423bd0e2",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/cd8b015f859e6b60933324db00067c2f060b4d18",
"reference": "cd8b015f859e6b60933324db00067c2f060b4d18",
"shasum": ""
},
"require": {
@ -1069,7 +1070,7 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2019-01-01T18:08:36+00:00"
"time": "2017-08-03T09:34:20+00:00"
},
{
"name": "symfony/filesystem",
@ -1077,17 +1078,16 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "c24ce3d18ccc9bb9d7e1d6ce9330fcc6061cafde"
"reference": "e4d366b620c8b6e2d4977c154f6a1d5b416db4dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/c24ce3d18ccc9bb9d7e1d6ce9330fcc6061cafde",
"reference": "c24ce3d18ccc9bb9d7e1d6ce9330fcc6061cafde",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/e4d366b620c8b6e2d4977c154f6a1d5b416db4dd",
"reference": "e4d366b620c8b6e2d4977c154f6a1d5b416db4dd",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/polyfill-ctype": "~1.8"
"php": "^5.5.9|>=7.0.8"
},
"type": "library",
"extra": {
@ -1119,7 +1119,7 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2019-01-01T13:45:19+00:00"
"time": "2017-08-03T09:34:20+00:00"
},
{
"name": "symfony/finder",
@ -1127,12 +1127,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e"
"reference": "bf0450cfe7282c5f06539c4733ba64273e91e918"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e",
"reference": "3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e",
"url": "https://api.github.com/repos/symfony/finder/zipball/bf0450cfe7282c5f06539c4733ba64273e91e918",
"reference": "bf0450cfe7282c5f06539c4733ba64273e91e918",
"shasum": ""
},
"require": {
@ -1168,65 +1168,7 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2019-01-01T13:45:19+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.10.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
"reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
},
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"time": "2018-08-06T14:22:27+00:00"
"time": "2017-08-03T09:34:20+00:00"
},
{
"name": "symfony/polyfill-mbstring",
@ -1293,12 +1235,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "0d41dd7d95ed179aed6a13393b0f4f97bfa2d25c"
"reference": "9794f948d9af3be0157185051275d78b24d68b92"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/0d41dd7d95ed179aed6a13393b0f4f97bfa2d25c",
"reference": "0d41dd7d95ed179aed6a13393b0f4f97bfa2d25c",
"url": "https://api.github.com/repos/symfony/process/zipball/9794f948d9af3be0157185051275d78b24d68b92",
"reference": "9794f948d9af3be0157185051275d78b24d68b92",
"shasum": ""
},
"require": {
@ -1334,7 +1276,7 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2019-01-02T21:24:08+00:00"
"time": "2017-08-03T09:34:20+00:00"
},
{
"name": "symfony/translation",
@ -1342,12 +1284,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "5f357063f4907cef47e5cf82fa3187fbfb700456"
"reference": "62bb068e004874bbe39624101e1aae70ca7c05cd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/5f357063f4907cef47e5cf82fa3187fbfb700456",
"reference": "5f357063f4907cef47e5cf82fa3187fbfb700456",
"url": "https://api.github.com/repos/symfony/translation/zipball/62bb068e004874bbe39624101e1aae70ca7c05cd",
"reference": "62bb068e004874bbe39624101e1aae70ca7c05cd",
"shasum": ""
},
"require": {
@ -1357,18 +1299,17 @@
"conflict": {
"symfony/config": "<2.8",
"symfony/dependency-injection": "<3.4",
"symfony/yaml": "<3.4"
"symfony/yaml": "<3.3"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.8|~3.0|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/finder": "~2.8|~3.0|~4.0",
"symfony/intl": "^2.8.18|^3.2.5|~4.0",
"symfony/yaml": "~3.4|~4.0"
"symfony/yaml": "~3.3|~4.0"
},
"suggest": {
"psr/log-implementation": "To use logging capability in translator",
"psr/log": "To use logging capability in translator",
"symfony/config": "",
"symfony/yaml": ""
},
@ -1402,7 +1343,7 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2019-01-01T13:45:19+00:00"
"time": "2017-08-03T12:04:31+00:00"
},
{
"name": "symfony/yaml",
@ -1410,23 +1351,19 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea"
"reference": "1395ddba6f65bf46cdf1d80d59223cbab8ff3ccc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/554a59a1ccbaac238a89b19c8e551a556fd0e2ea",
"reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea",
"url": "https://api.github.com/repos/symfony/yaml/zipball/1395ddba6f65bf46cdf1d80d59223cbab8ff3ccc",
"reference": "1395ddba6f65bf46cdf1d80d59223cbab8ff3ccc",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/console": "<3.4"
"php": "^5.5.9|>=7.0.8"
},
"require-dev": {
"symfony/console": "~3.4|~4.0"
"symfony/console": "~2.8|~3.0|~4.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
@ -1461,7 +1398,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2019-01-01T13:45:19+00:00"
"time": "2017-08-04T13:29:48+00:00"
},
{
"name": "wp-cli/autoload-splitter",
@ -2865,66 +2802,101 @@
},
{
"name": "wp-cli/wp-cli",
"version": "v2.1.0",
"version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/wp-cli.git",
"reference": "6dde820a8f6f183f4b6d751e5be1cd343974f333"
"reference": "4ab0d99da0ad5e6ca39453ff5c82d4f06aecb086"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/6dde820a8f6f183f4b6d751e5be1cd343974f333",
"reference": "6dde820a8f6f183f4b6d751e5be1cd343974f333",
"url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/4ab0d99da0ad5e6ca39453ff5c82d4f06aecb086",
"reference": "4ab0d99da0ad5e6ca39453ff5c82d4f06aecb086",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-readline": "*",
"composer/composer": "^1.2.0",
"composer/semver": "~1.0",
"mustache/mustache": "~2.4",
"php": ">=5.4",
"php": ">=5.3.29",
"ramsey/array_column": "~1.1",
"rmccue/requests": "~1.6",
"symfony/finder": ">2.7",
"symfony/config": "^2.7|^3.0",
"symfony/console": "^2.7|^3.0",
"symfony/debug": "^2.7|^3.0",
"symfony/dependency-injection": "^2.7|^3.0",
"symfony/event-dispatcher": "^2.7|^3.0",
"symfony/filesystem": "^2.7|^3.0",
"symfony/finder": "^2.7|^3.0",
"symfony/process": "^2.1|^3.0",
"symfony/translation": "^2.7|^3.0",
"symfony/yaml": "^2.7|^3.0",
"wp-cli/autoload-splitter": "^0.1.5",
"wp-cli/cache-command": "^1.0",
"wp-cli/checksum-command": "^1.0",
"wp-cli/config-command": "^1.0",
"wp-cli/core-command": "^1.0",
"wp-cli/cron-command": "^1.0",
"wp-cli/db-command": "^1.0",
"wp-cli/entity-command": "^1.0",
"wp-cli/eval-command": "^1.0",
"wp-cli/export-command": "^1.0",
"wp-cli/extension-command": "^1.0",
"wp-cli/import-command": "^1.0",
"wp-cli/language-command": "^1.0",
"wp-cli/media-command": "^1.0",
"wp-cli/mustangostang-spyc": "^0.6.3",
"wp-cli/php-cli-tools": "~0.11.2"
"wp-cli/package-command": "^1.0",
"wp-cli/php-cli-tools": "~0.11.2",
"wp-cli/rewrite-command": "^1.0",
"wp-cli/role-command": "^1.0",
"wp-cli/scaffold-command": "^1.0",
"wp-cli/search-replace-command": "^1.0",
"wp-cli/server-command": "^1.0",
"wp-cli/shell-command": "^1.0",
"wp-cli/super-admin-command": "^1.0",
"wp-cli/widget-command": "^1.0"
},
"require-dev": {
"roave/security-advisories": "dev-master",
"wp-cli/db-command": "^1.3 || ^2",
"wp-cli/entity-command": "^1.2 || ^2",
"wp-cli/extension-command": "^1.1 || ^2",
"wp-cli/package-command": "^1 || ^2",
"wp-cli/wp-cli-tests": "^2.0.8"
"behat/behat": "2.5.*",
"phpunit/phpunit": "3.7.*",
"roave/security-advisories": "dev-master"
},
"suggest": {
"ext-zip": "Needed to support extraction of ZIP archives when doing downloads or updates"
"psy/psysh": "Enhanced `wp shell` functionality"
},
"bin": [
"bin/wp",
"bin/wp.bat"
"bin/wp.bat",
"bin/wp"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
"autoload-splitter": {
"splitter-logic": "WP_CLI\\AutoloadSplitter",
"splitter-location": "php/WP_CLI/AutoloadSplitter.php",
"split-target-prefix-true": "autoload_commands",
"split-target-prefix-false": "autoload_framework"
}
},
"autoload": {
"psr-0": {
"WP_CLI": "php"
},
"psr-4": {
"": "php/commands/src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "WP-CLI framework",
"homepage": "https://wp-cli.org",
"description": "A command line interface for WordPress",
"homepage": "http://wp-cli.org",
"keywords": [
"cli",
"wordpress"
],
"time": "2018-12-18T17:37:51+00:00"
"time": "2017-08-08T14:28:58+00:00"
}
],
"aliases": [],

View File

@ -0,0 +1,27 @@
<?php
/**
* Abstract class with common Queue Cleaner functionality.
*/
abstract class ActionScheduler_Abstract_QueueRunner_Deprecated {
/**
* Get the maximum number of seconds a batch can run for.
*
* @deprecated 2.1.1
* @return int The number of seconds.
*/
protected function get_maximum_execution_time() {
_deprecated_function( __METHOD__, '2.1.1', 'ActionScheduler_Abstract_QueueRunner::get_time_limit()' );
$maximum_execution_time = 30;
// Apply deprecated filter
if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) {
_deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' );
$maximum_execution_time = apply_filters( 'action_scheduler_maximum_execution_time', $maximum_execution_time );
}
return absint( $maximum_execution_time );
}
}

View File

@ -206,4 +206,57 @@ class ActionScheduler_QueueRunner_Test extends ActionScheduler_UnitTestCase {
public function return_6() {
return 6;
}
public function test_store_fetch_action_failure_schedule_next_instance() {
$random = md5( rand() );
$schedule = new ActionScheduler_IntervalSchedule( as_get_datetime_object( '12 hours ago' ), DAY_IN_SECONDS );
$action = new ActionScheduler_Action( $random, array(), $schedule );
$action_id = ActionScheduler::store()->save_action( $action );
// Set up a mock store that will throw an exception when fetching actions.
$store = $this
->getMockBuilder( 'ActionScheduler_wpPostStore' )
->setMethods( array( 'fetch_action' ) )
->getMock();
$store
->method( 'fetch_action' )
->with( $action_id )
->will( $this->throwException( new Exception() ) );
// Set up a mock queue runner to verify that schedule_next_instance()
// isn't called for an undefined $action.
$runner = $this
->getMockBuilder( 'ActionScheduler_QueueRunner' )
->setConstructorArgs( array( $store ) )
->setMethods( array( 'schedule_next_instance' ) )
->getMock();
$runner
->expects( $this->never() )
->method( 'schedule_next_instance' );
$runner->run();
// Set up a mock store that will throw an exception when fetching actions.
$store2 = $this
->getMockBuilder( 'ActionScheduler_wpPostStore' )
->setMethods( array( 'fetch_action' ) )
->getMock();
$store2
->method( 'fetch_action' )
->with( $action_id )
->willReturn( null );
// Set up a mock queue runner to verify that schedule_next_instance()
// isn't called for an undefined $action.
$runner2 = $this
->getMockBuilder( 'ActionScheduler_QueueRunner' )
->setConstructorArgs( array( $store ) )
->setMethods( array( 'schedule_next_instance' ) )
->getMock();
$runner2
->expects( $this->never() )
->method( 'schedule_next_instance' );
$runner2->run();
}
}